# terok User Guide

> [!WARNING]
> This documentation was written by an AI agent and might be inaccurate.

Complete guide to installing, configuring, and using terok.

> [!WARNING]
> Terok is in **alpha** development phase. It is under active development and until version
> 1.0.0 is released, APIs, internals, and security boundaries may change
> without deprecation notice. Not recommended for production deployment.

## Table of Contents

- [Installation](#installation)
- [Runtime Locations](#runtime-locations)
- [Global Configuration](#global-configuration)
- [From Zero to First Run](#from-zero-to-first-run)
- [Authentication](#authentication)
- [Headless Agent Runs (Unattended)](#headless-agent-runs-unattended)
- [Task Lifecycle Hooks](#task-lifecycle-hooks)
- [Image Management](#image-management)
- [Project Management](#project-management)
- [GPU Passthrough](#gpu-passthrough)
- [Tips](#tips)
- [FAQ](#faq)

---

## Installation

### Recommended: pipx

```bash
pipx install terok
```

### Alternative: pip

```bash
pip install terok
```

After install, the `terok` command is on `$PATH`.  Run it with no arguments
on a TTY to launch the TUI; pass a subcommand to drive the CLI.

### Global Flags

| Flag | Description |
|------|-------------|
| `--no-emoji` | Replace emojis with text labels (e.g. `[gate]` instead of emoji) |

### Subsystem Surfaces

terok exposes two surfaces over the sibling subsystems (`terok-executor`, `terok-sandbox`):

| Surface | Identity | Config | When to use |
|---------|----------|--------|-------------|
| `terok <X>` (incl. `terok executor *`, `terok sandbox *`, `terok vault`, `terok ssh`, `terok gate`) | `(project, task)` for terok-native verbs; container id or `project/task` for forwarded per-container verbs | terok's resolved cfg | Anything tied to your terok install |
| Standalone `terok-executor` / `terok-sandbox` | container id | sibling defaults / `TEROK_CONFIG_FILE` override | Raw, no-terok-context operations (testing, scripting, custom orchestration) |

Every `terok <X>` invocation uses terok's configured state — same sandbox install, same vault, same auth, same image cache.  For raw operation, install the sibling separately:

```bash
pipx install terok-executor       # standalone executor
terok-executor show-config         # raw cfg (no terok context)
TEROK_CONFIG_FILE=/path/to/cfg.yml terok-executor run claude .  # explicit override
```

#### Identity forms on per-container verbs

`terok executor stop` (and future `exec` / `logs` / `state` / `login`) accept either form:

```bash
terok executor stop <container-id>        # raw form
terok executor stop <project>/<task>      # slash form — resolved via terok's task store
```

Same dual-form precedent as `git push origin master` vs `origin/master`.  `terok task <verb> p t` and `terok task <verb> p/t` are accepted interchangeably for the same reason.

#### Diffing the effective config

To verify that `terok executor *` operates on terok's configured state:

```bash
terok executor show-config              # cfg terok would pass down
terok-executor show-config              # cfg standalone executor would read
diff <(terok executor show-config) <(terok-executor show-config)
```

For fields with a representation in the shared `config.yml` schema that sandbox/executor own, the two outputs match.  Runtime-only ambient context (added by orchestrators) may differ.

### Shell Completion

Tab completion is powered by [argcomplete](https://github.com/kislyuk/argcomplete).

**Recommended: auto-install to your shell's completion directory**

```bash
# Auto-detect shell from $SHELL and install
terok completions install

# Or specify the shell explicitly
terok completions install --shell bash
```

Install locations (auto-loaded, no RC file edits needed):

| Shell | Path |
|-------|------|
| bash  | `~/.local/share/bash-completion/completions/terok` |
| zsh   | `~/.local/share/zsh/site-functions/_terok` |
| fish  | `~/.config/fish/completions/terok.fish` |

**Alternative: print raw completion script**

```bash
terok completions bash   # Print bash completion script to stdout
terok completions zsh    # Print zsh completion script to stdout
terok completions fish   # Print fish completion script to stdout
```

Run `terok config` to check whether completions are detected as installed.

---

## Runtime Locations

### Config/Projects

| Install Type | Path |
|--------------|------|
| Root | `/etc/terok/projects` |
| User | `~/.config/terok/projects` |
| Override | `TEROK_CONFIG_DIR=/path/to/config` |

### State (writable: tasks, build, gate)

| Install Type | Path |
|--------------|------|
| Root | `/var/lib/terok` |
| User | `${XDG_DATA_HOME:-~/.local/share}/terok` |
| Override | `TEROK_STATE_DIR=/path/to/state` |

---

## Global Configuration

The tool looks for a global config file in this order (first found wins):

1. `${XDG_CONFIG_HOME:-~/.config}/terok/config.yml` (user override)
2. `sys.prefix/etc/terok/config.yml` (pip/venv installs)
3. `/etc/terok/config.yml` (system default)

### Example Config

Copy from `examples/terok-config.yml`:
```bash
mkdir -p ~/.config/terok
cp examples/terok-config.yml ~/.config/terok/config.yml
```

### Minimum Settings

Every project needs these fields in its `project.yml`:

```yaml
project:
  name: myproj
  security_class: gatekeeping   # default — or "online" for direct push

image:
  base_image: docker.io/library/ubuntu:24.04

git:
  upstream_url: https://github.com/yourorg/yourrepo.git   # optional
```

`git.upstream_url` is optional.  Projects without one start their task
containers with an empty workspace — useful for local experiments.
See [Gate and upstream combinations](#gate-and-upstream-combinations).

### Nice-to-Have Settings

These can be set in `project.yml` (per-project) or `config.yml` (global default):

```yaml
git:
  human_name: "Your Name"
  human_email: "your@email.com"
  default_branch: main
  authorship: agent-human    # or: human-agent, human, agent
```

### Gate and upstream combinations

The host-side git gate (mirror) and the project's remote `upstream_url`
are independent knobs.  Four combinations:

| `gate.enabled` | `upstream_url` | Behaviour |
|---|---|---|
| `true` (default) | set | Host mirrors upstream; container clones from the mirror.  Default. |
| `true` | absent | Host initialises a remoteless bare gate; the container still gets a remote to push to.  Local-only scratch projects. |
| `false` | set | Host never touches the remote; the container fetches directly from upstream.  Useful when the host has no path to the remote (firewall, corporate proxy) but the container does. |
| `false` | absent | No git plumbing at all; container workspace starts empty. |

When `upstream_url` is absent, `security_class` collapses — `online` and
`gatekeeping` describe the same act, since there is nothing to push
beyond the gate.  Both values are accepted; they behave identically.

`gatekeeping + gate.enabled: false` is rejected at load time —
gatekeeping *is* the gate-enforced mode, so disabling the gate in that
mode is incoherent.

```yaml
# Host blocks outbound SSH to github, but the container's network
# allowlist reaches github via its own rules.
project:
  name: cp2k
  security_class: online
git:
  upstream_url: git@github.com:user/cp2k.git
gate:
  enabled: false
```

<!-- markdownlint-disable MD046 -->
!!! info "Auto-deduction from host git config"

    If `human_name` and `human_email` are not set, terok deduces them from
    the host's `git config user.name` and `git config user.email`.
<!-- markdownlint-enable MD046 -->

---

## From Zero to First Run

The quickest way to manage projects is through the TUI — run `terok` after
install.  The steps below show the equivalent CLI workflow.

### Prerequisites

- Podman installed and working
- OpenSSH client tools (ssh, ssh-keygen) for private Git over SSH
- tmux (optional, for the TUI under tmux and persistent container sessions)

### Step 1: Create Project Directory

```bash
mkdir -p ~/.config/terok/projects/myproj
```

### Step 2: Create project.yml

```yaml
# ~/.config/terok/projects/myproj/project.yml
project:
  name: myproj
  security_class: gatekeeping   # default — or "online" for direct push

image:
  base_image: docker.io/library/ubuntu:24.04
  user_snippet_file: user.dockerinclude  # optional

git:
  upstream_url: git@github.com:yourorg/yourrepo.git  # optional; omit for local-only
  default_branch: main
  # authorship: human-agent  # optional: author = human, committer = agent

# gate:
#   enabled: false          # optional; skip the host-side gate mirror
                            #           (container fetches upstream directly)
```

See [Gate and upstream combinations](#gate-and-upstream-combinations)
for the four cells of the (`gate.enabled`, `upstream_url`) matrix —
scratch projects, firewalled hosts, and the defaults for each.

### Step 3: (Optional) Image Snippet

Create `~/.config/terok/projects/myproj/user.dockerinclude`:
```dockerfile
RUN apt-get update && apt-get install -y ripgrep jq && rm -rf /var/lib/apt/lists/*
```

This text is pasted near the end of your project image (L2) Dockerfile.

### Step 4: Generate Dockerfiles

```bash
terok project generate myproj
```

### Step 5: Build Images

```bash
# Build only L2 project images (fast, reuses existing L0/L1 layers)
terok project build myproj

# Refresh just the agent-install layers (cache bust from the AGENT_CACHE_BUST point)
terok project build myproj --refresh-agents

# Rebuild from L0 (no cache) (includes base image pull and system packages)
terok project build myproj --full-rebuild

# Pick which agents get baked into L1 for this build (one-shot override)
terok project build myproj --agents claude,codex
terok project build myproj --agents all

# Optional: build a dev image from L0 as well
terok project build myproj --dev
```

Build modes:
- **Default** (`build`): Only rebuilds L2 project images, reuses existing L0/L1. Use for project config changes.
- **`--refresh-agents`**: Rebuilds L0+L1+L2 and cache-busts the per-agent install layers, leaving the system-package layer intact. Use when an agent CLI has a new release.
- **`--full-rebuild`**: Rebuilds L0+L1+L2 with `--no-cache --pull=always`. Use when the base image or system packages need updating.
- **`--agents <list>|all`**: One-shot override of the agent selection for this build. Does not modify `project.yml`.

### Choosing which agents to bake in

The L1 (agent) image can be built with a subset of the roster instead of "everything". The selection flows from (narrowest wins):

1. **Per-build CLI override** — `terok project build --agents claude,codex` (above).
2. **Per-project default** — `project.yml`:
   ```yaml
   image:
     agents: "claude,codex,gh"   # or "all"
   ```
3. **Global default** — `~/.config/terok/config.yml`:
   ```yaml
   image:
     agents: "all"   # default for every project that doesn't set its own
   ```

Different selections produce different L1 image tags (`terok-l1-cli:<base>-claude-codex`, `...-gh-glab`, ...) so multiple selections can coexist in the local image store. The OCI label `ai.terok.agents` on each L1 image records the exact selection for introspection; inside the container the same list is in `/etc/terok/installed.env` and the `hilfe` banner filters its output to match.

Transitive dependencies are expanded automatically — picking `blablador` or `kisski` also pulls in `opencode`.

### Step 6: Initialize SSH (for private repos)

```bash
terok project ssh-init myproj
```

Mints an ed25519 keypair into the vault credential database (no on-disk
key files in the project directory).  The public key is printed on
success — register it as a deploy key on your Git host.

### Step 7: Create and Run a Task

Authenticate each provider the task will use *before* starting the task —
see [Authentication](#authentication) for commands and for why late auth
does not reach already-running containers.

```bash
# Create a new task and attach into its shell (default on a TTY).
# Equivalent to: task run + waiting for ready + terok login — one command.
terok task run myproj

# --no-attach keeps the old behaviour: start the container and print the
# `terok login` instructions instead of exec'ing into it.  Scripts piping
# terok's output get --no-attach automatically (non-TTY = non-interactive).
terok task run myproj --no-attach

# If the project's image hasn't been built yet, task run prompts on a TTY
# ("Build now? [Y/n]") and runs `project build` inline; scripts exit with
# a hint pointing at the command.

# List tasks
terok task list myproj
```

#### Additional Task Operations

```bash
# Rename a task
terok task rename myproj v9krt fix-auth-bug

# Follow up on a completed/failed headless task with a new prompt
terok task followup myproj v9krt -p "Now add tests for the fix"

# View formatted container logs
terok task logs myproj v9krt              # Latest logs
terok task logs myproj v9krt -f           # Follow live output
terok task logs myproj v9krt --tail 50    # Last 50 lines
terok task logs myproj v9krt --raw        # Raw podman output

# Stop or restart a task
terok task stop myproj v9krt
terok task restart myproj v9krt

# Delete a task
terok task delete myproj v9krt

# View archived (deleted) tasks and their logs
terok task archive list myproj
terok task archive logs myproj 20260305T143000Z
```

### Step 8: Log into a Running Container

```bash
# Open a shell in a running task container (persistent tmux session)
terok login myproj v9krt

# Any unambiguous prefix works — terok resolves it against the live task list
terok login myproj v9k
terok login myproj v9
```

This opens a tmux session inside the container. The session persists across
disconnects — re-running `terok login` reattaches to the same session.
Interactive shells show `hilfe --kurz` on entry; run `hilfe` inside the
container for the fuller in-container help.

`login` and the other per-task verbs (`task stop`, `task restart`,
`task delete`, `task logs`, `task followup`, `task rename`) all accept an
unambiguous prefix in place of the full task ID.

#### From the TUI

Press `l` on any running task to open a login session. The TUI picks the best
method automatically:

| Environment | What happens |
|---|---|
| Inside tmux | Opens new tmux window (TUI stays visible) |
| Desktop (GNOME/KDE) | Opens new terminal window |
| Plain terminal | Suspends TUI, opens shell, resumes on exit |
| Web (`terok-web` / `textual serve`) | CLI login is disabled — there is no host terminal; an error notification points at toad mode. Run a task in **toad** mode for an in-browser session. |

#### Running the TUI under tmux (recommended)

```bash
terok tui --tmux
```

This wraps the TUI in a managed tmux session with a blue status bar showing
keyboard shortcuts. Login sessions open as additional tmux windows — press
`^b n`/`^b p` to switch between TUI and container shells.

To launch the TUI in tmux by default without passing `--tmux` every time, set
it in your config:

```yaml
tui:
  default_tmux: true
```

`--no-tmux` overrides the config setting for a single launch.

When tmux mode is active, terok **attaches to the shared `terok` session** if
one is already running, so a second `terok tui --tmux` reconnects to your
existing TUI instead of failing on the duplicate session name. Pass
`--new-session` to opt out and start a separate, tmux-named session alongside
it.

#### tmux Quick Reference

| Context | Prefix | Status bar color | Common keys |
|---|---|---|---|
| Host tmux | `^b` | Blue | `^b n/p` switch windows, `^b d` detach |
| Container tmux | `^a` | Green | `^a n/p` switch windows, `^a c` new window |

The container's tmux prefix (`^a`) is different from the host's (`^b`) to avoid
conflicts. The container status bar shows `host: ^b` as a reminder.

---

## Authentication

Before running tasks, authenticate each agent or tool you plan to use.
Credentials are stored host-wide in the vault and shared across every
project and task by default.

```bash
# Host-wide auth — one login per provider, usable by every project
terok auth claude
terok auth gh
terok auth codex

# Project-scoped escape hatch — uses the project's L2 image for the auth
# container; the credential still lands provider-scoped in the vault
terok auth claude --project myproj

# Interactive menu — pick one or more providers in sequence
terok auth
```

Each provider offers the modes its vendor supports — OAuth / interactive
login (launches an auth container), API key (paste, no container needed),
or both with a chooser prompt.

On a headless host (no local browser), Codex's OAuth login can't open the
usual browser callback. Use the device-code flow instead — it shows a URL
and a short code to enter on another device:

```bash
terok auth codex --device-auth
```

### Container snapshot: auth must exist before `task run`

Agent env vars (and the phantom tokens that route each container's
requests through the vault) are **baked at container creation time**.
Two consequences follow from this:

- **Missing auth is permanent for that container.**  If you start a
  task without having authenticated provider X, the container has no
  env var for X — and it never will, even if you `terok auth X` later.
  The credential exists in the vault, but the running container has no
  phantom token pointing at it.  To pick up the new credential, start
  a fresh task; existing ones stay blind.
- **Refreshing an existing auth works transparently.**  If provider X
  *was* authenticated when the task started, replacing the credential
  (new OAuth token, rotated API key) needs no restart — the container's
  phantom token resolves through the vault to whatever the current
  credential is at request time.

Run `terok sickbay <project> <task>` to see a per-task auth report.
Missing phantom env vars show up as warnings ("not set") and flag exactly
which providers were unauthenticated when the container was created.

### Vault passphrase backend

The credentials DB itself is SQLCipher-encrypted; the passphrase
travels through a resolver chain (session-unlock file → systemd-creds
→ keyring → `credentials.passphrase` plaintext → interactive prompt).
`terok vault unlock` writes to the session tier, `terok vault lock`
clears it, and `terok-sandbox setup` picks the persistent tier at
install time.  To move the passphrase between backends —
**retrieve → lock --forget → reseed** — see the
["Changing tiers" recipe](https://github.com/terok-ai/terok-sandbox/blob/master/docs/credentials-encryption.md)
in terok-sandbox.

Pressing PANIC also clears the session-unlock file so a
freshly-spawned supervisor can't auto-resume from that tier; persistent
tiers stay so the panic is reversible.

---

## Headless Agent Runs (Unattended)

Run any supported agent headlessly in a container — no interactive session needed.
Useful for CI/CD pipelines, batch tasks, or scripted workflows.

Supported providers: `claude`, `codex`, `copilot`, `vibe`, `blablador`, `opencode`.

### Basic Usage

```bash
# Run with a prompt (uses default provider — claude unless configured otherwise)
terok task run myproj "Fix the authentication bug in login.py"

# Override model and set a timeout
terok task run myproj "Add unit tests for utils.py" --model opus --timeout 3600

# Detach immediately (don't stream output)
terok task run myproj "Refactor the database layer" --no-follow

# Use a specific agent
terok task run myproj "Fix the auth bug" --agent codex
terok task run myproj "Add tests" --agent copilot
```

The command creates a new task, starts a container, runs the agent with the given
prompt, and streams the output. When the agent finishes, the task is marked as
completed and a diff summary is printed.

### Default Agent

The agent is resolved in this order:
1. `--agent` flag (if given)
2. `default_agent` in project config (`project.yml`)
3. `default_agent` in global config (`config.yml`)
4. `claude` (ultimate fallback)

```yaml
# Set per-project default in project.yml (top-level key)
default_agent: codex

# Set global default in config.yml
default_agent: claude
```

### Provider Feature Matrix

| Feature | claude | codex | copilot | vibe | blablador | opencode |
|---------|--------|-------|---------|------|-----------|----------|
| `--model` | Yes | Yes | Yes | Yes (`--agent`) | No | Yes |
| Session resume | Yes | No | No | Yes | Yes | Yes |
| Structured log output | Yes | No | No | No | No | No |

### Per-Provider Config Values

Config keys like `model` and `timeout` can be set per-provider
using a dict syntax.  A flat value applies to all providers:

```yaml
# Flat value — same for all providers
model: sonnet
```

A dict maps each provider to its own value, with `_default` as fallback:

```yaml
# Per-provider values
model:
  claude: opus
  codex: codex-mini
  vibe: mistral-small
  _default: fast
timeout: 1800  # flat values still work
```

Providers not listed in the dict (and without `_default`) use their own built-in
default.  CLI flags (`--model`, `--timeout`) always override config values.
Features a provider doesn't support produce a warning but don't block the run.

### Permission Mode (Unrestricted / Restricted)

By default, terok starts agents in **unrestricted** mode — all safety prompts
are auto-approved so the agent can work fully autonomously.  You can switch to
**restricted** mode, which launches the agent with its vendor-default permission
settings (the agent will ask for confirmation before dangerous operations like
file writes or shell commands).

#### CLI flags

```bash
# Run restricted (agent uses vendor defaults — asks before dangerous ops)
terok task run myproj "Fix the bug" --restricted

# Explicitly unrestricted (default behavior)
terok task run myproj "Fix the bug" --unrestricted
```

The flags are mutually exclusive.  When neither is given, the value comes from
config (see below), defaulting to unrestricted.

#### Config

Like other config keys, `unrestricted` lives inside the `agent:` section
and follows the resolution stack:
global config → project config → CLI flag.

```yaml
# In project.yml or global config
agent:
  # Flat value — same for all providers
  unrestricted: false  # all agents start restricted
```

Per-provider dict syntax is supported:

```yaml
agent:
  # Per-provider values
  unrestricted:
    claude: true
    codex: false
    _default: true
```

Providers not listed in the dict (and without `_default`) default to
unrestricted (`true`).

#### What each mode does per agent

| Provider | Unrestricted mechanism | Restricted (vendor default) |
|----------|-----------------------|---------------------------|
| claude | `/etc/claude-code/managed-settings.json` → `"defaultMode": "bypassPermissions"` | Normal interactive prompts |
| codex | `--yolo` (injected by the shell wrapper) | Sandboxed with approval prompts |
| copilot | `COPILOT_ALLOW_ALL=true` | Tool confirmation prompts |
| vibe | `VIBE_BYPASS_TOOL_PERMISSIONS=true` | Approval prompts |
| opencode / blablador | `OPENCODE_PERMISSION='{"*":"allow"}'` | Default permission policy |

#### Checking the current mode

```bash
terok task status myproj v9krt
# Output includes: Permissions: unrestricted
```

The TUI task detail panel also shows the permission mode.

### Native Claude Agents and MCPs

Sub-agents, skills, and MCP servers are managed natively by Claude — terok
does not own a sub-agent abstraction of its own and does not inject any
`--agents` flag. Drop your definitions into the shared Claude config mount
(`terok agents dir claude` prints its path) and Claude discovers them on its
own, exactly as it would outside a container:

| What | Where |
|------|-------|
| Global agents | `~/.claude/agents/` |
| Global MCPs | `~/.claude/settings.json` (`mcpServers` section) |
| Project agents | `<workspace>/.claude/agents/` |
| Project MCPs | `<workspace>/.claude/settings.json` |

Any extra flags you pass to the in-container `claude` command are forwarded
straight through, so per-invocation customization needs no terok support.

Run `terok config` to see the actual paths on your system.

---

## Agent Instructions

terok provides layered agent instructions that describe the container environment to AI agents. Instructions are delivered automatically — no setup required.

### How It Works

Every task container receives instructions explaining the workspace layout, available tools, sudo access, git workflow, and conventions. Two independent layers control what a task receives:

1. **YAML `instructions` key** — controls the inheritance chain via config stack. Uses `_inherit` in list form to splice the bundled default at that position. Absent = bundled default.
2. **Standalone `instructions.md` file** in the project root — always appended at the end of whatever the YAML chain resolved. Purely additive. If empty or absent, nothing is appended.

- **Claude**: injected via `--append-system-prompt` (system-level context)
- **Codex**: loaded from `/home/dev/.terok/instructions.md` via `-c model_instructions_file=...`
  in the wrapper
- **Other providers**: prepended to the task prompt (headless `terok task run`)

### Scenarios

| YAML `instructions` | File | Result |
|---|---|---|
| absent | absent | bundled default |
| absent | has content | bundled default + file |
| `["_inherit"]` | has content | bundled default + file (same, explicit) |
| `["_inherit", "extra"]` | has content | bundled default + extra + file |
| `["custom only"]` | absent | custom only (no default) |
| `["custom only"]` | has content | custom only + file |
| `[]` | has content | file only |
| `"flat string"` | has content | flat string + file |

### Customizing Instructions

#### Option 1: Standalone file (recommended for most users)

Create `instructions.md` in your project root with project-specific notes. These are appended to the bundled default automatically:

```markdown
# Project Notes
This project uses Poetry. Run `make check` before committing.
```

#### Option 2: YAML config

Set the `instructions` key in your project's `agent:` config:

```yaml
# project.yml — flat string (replaces default)
agent:
  instructions: |
    You are in a Podman container. This project uses Poetry.
    Run `make check` before committing.
```

Per-provider instructions:

```yaml
agent:
  instructions:
    claude: |
      Use Claude-specific conventions...
    codex: |
      Use Codex-specific conventions...
    _default: |
      Generic instructions for all providers.
```

Extend (rather than replace) parent instructions using `_inherit`:

```yaml
# Project config — appends to global/default instructions
agent:
  instructions:
    - _inherit
    - |
      ## Project-specific additions
      This project uses Poetry for dependency management.
      Run `make check` before committing.
```

To suppress defaults entirely, use an empty list:

```yaml
agent:
  instructions: []
```

### CLI Flag

Override all config-stack instructions with a file:

```bash
terok task run myproj "Fix the bug" --instructions path/to/instructions.md
```

### TUI

The project details panel shows an **Instruct:** badge with three states:

- **default** (dim) — no custom instructions
- **custom + inherited** (green) — has custom content with defaults included
- **custom only** (cyan) — has custom content, defaults overridden

Available actions from the project details screen:

- **Shift+I** — edit project `instructions.md` in `$EDITOR`
- **t** — toggle instructions inheritance (include/exclude bundled defaults)
- **v** — view fully resolved instructions as a task would receive them

Command palette (`Ctrl+P`) actions:

- **Edit Global Instructions** — edit the global `instructions.md` in `$EDITOR`
- **Show Default Instructions** — view the bundled default instructions (read-only)

### Debugging

Resolved instructions are always written to `<tasks_root>/<task_id>/agent-config/instructions.md` on the host for inspection.

---

## Task Lifecycle Hooks

Hooks run user-configured shell commands on the **host** at key points during
a task container's lifecycle.  They are useful for port forwarding, notifications,
logging, or custom setup/teardown.

### Hook points

| Hook | When | Use case |
|------|------|----------|
| `pre_start` | Before the container is created | Validate prerequisites, set up host resources |
| `post_start` | After the container is running | Start sidecars, register with service discovery |
| `post_ready` | After the application is ready (CLI ready marker / toad serving) | Port forwarding, open browser, notify |
| `post_stop` | After the container stops | Clean up port forwards, notify, archive logs |

### Configuration

Hooks can be set globally (all projects) or per-project:

```yaml
# ~/.config/terok/config.yml (global)
run:
  hooks:
    post_ready: ~/.config/terok/hooks/on-ready.sh
    post_stop: ~/.config/terok/hooks/on-stop.sh
```

```yaml
# project.yml (per-project, overrides global)
run:
  hooks:
    post_ready: ./hooks/on-ready.sh
```

### Environment variables

Hook commands receive task context via environment variables:

| Variable | Description | Example |
|----------|-------------|---------|
| `TEROK_HOOK` | Hook name | `post_ready` |
| `TEROK_PROJECT_NAME` | Project name | `myproject` |
| `TEROK_TASK_ID` | Task number | `3` |
| `TEROK_TASK_MODE` | Task mode | `cli`, `toad`, `run` |
| `TEROK_CONTAINER_NAME` | Podman container name | `myproject-toad-3` |
| `TEROK_WEB_PORT` | Web port (toad only) | `7861` |
| `TEROK_TASK_DIR` | Host-side task directory | `/home/user/.local/share/terok/tasks/myproject/3` |

### Hook tracking and sickbay

Hooks are tracked in task metadata (`hooks_fired` list).  If a task stops
without its `post_stop` hook running (e.g. after a crash or host reboot),
`terok sickbay` detects the inconsistency:

```bash
terok sickbay                    # check all projects
terok sickbay myproject          # check one project
terok sickbay myproject 3        # check one task
terok sickbay --fix              # auto-reconcile (run missed hooks)
terok sickbay --system           # host-wide checks only; skip the per-container walk (fast)
```

### Example: task lifecycle logging

See `examples/hooks/task-notify.sh` for a simple example that logs
lifecycle events with task context to stderr.

---

## Image Management

Manage terok container images (L0/L1/L2 layers) with the `image` subcommand.

```bash
# List all terok images with sizes
terok image list

# List images for a specific project
terok image list myproj

# Remove orphaned and dangling terok images
terok image cleanup

# Preview what would be removed without actually deleting
terok image cleanup --dry-run
```

---

## Project Management

### Deleting a Project

Remove a project and all its associated data (tasks, containers, images):

```bash
# Delete with confirmation prompt
terok project delete myproj

# Skip confirmation
terok project delete myproj --force
```

### Deriving a Project

Create a new project from an existing one (shared infrastructure, fresh agent config):

```bash
terok project derive myproj myproj-v2
```

### OpenCode Config Import

Import an OpenCode config file into the shared mount:

```bash
terok config import-opencode /path/to/opencode.json
```

---

## Container Timezone

Task containers follow the host's timezone by default — no configuration
needed. Override per-project when you need a fixed zone (for example, to
pin CI to UTC regardless of where the runner lives):

```yaml
run:
  timezone: UTC            # or Europe/Prague, America/Los_Angeles, …
```

The value is any IANA zone name; it is resolved inside the container
against the image's `tzdata`. Omitting `run.timezone` (or setting it to
an empty value) falls back to host-follow — terok reads the host's
`$TZ`, then `/etc/timezone`, then the `/etc/localtime` symlink — and
leaves `TZ` unset if none of those resolve.

---

## GPU Passthrough

GPU passthrough is a per-project opt-in feature (disabled by default).

### Enable in project.yml

```yaml
run:
  gpus: all   # or true
```

### Requirements

- NVIDIA drivers installed on host
- `nvidia-container-toolkit` with Podman integration
- A CUDA/NVIDIA-capable base image (e.g., NVIDIA HPC SDK or CUDA)

Set the base image in `project.yml`:
```yaml
image:
  base_image: nvcr.io/nvidia/nvhpc:25.9-devel-cuda13.0-ubuntu24.04
```

When enabled, terok adds:
- `--device nvidia.com/gpu=all`
- `NVIDIA_VISIBLE_DEVICES=all`
- `NVIDIA_DRIVER_CAPABILITIES=all`

---

## Running Containers Inside Your Container

Projects that run `podman` or `docker` inside their terok container (for example, projects developing container tooling, or those needing `fuse-overlayfs`) need the outer container launched with two extra flags. Declare this once in `project.yml`:

```yaml
run:
  nested_containers: true
```

When set, terok appends to the outer `podman run`:

- `--security-opt label=nested` — the SELinux type that confines the outer container but permits nested container operations (devpts mount, rootless overlay setup). This is *not* `label=disable` — SELinux stays enforced.
- `--device /dev/fuse` — required by rootless podman's `fuse-overlayfs` storage driver.

Verify inside the container:

```console
$ podman run alpine echo hello
hello
```

### Requirements

- Podman ≥ v4.5.0 on the **host** (introduced `label=nested`, April 2023 — every current distro ships 4.5+).
- A base image with podman preinstalled; the bundled `online-podman` / `gatekeeping-podman` presets point at `quay.io/podman/stable:latest` (Fedora-based, rootless-ready).
- On SELinux-enforcing hosts: `container-selinux` package (usually already installed on Fedora/RHEL).

If the image doesn't have podman preinstalled, `nested_containers: true` still sets the capabilities — your project's user snippet can install the runtime:

```yaml
image:
  base_image: fedora:44
  user_snippet_inline: RUN dnf install -y podman fuse-overlayfs
run:
  nested_containers: true
```

---

## Tips

- **Show resolved paths:** `terok config`
- **Where credentials live:** `~/.local/share/terok/credentials` (or `/var/lib/terok/credentials` if root, or as configured under `credentials.dir`)
- **Shared directories:** See [shared-dirs.md](shared-dirs.md)
- **Security modes:** See [git-gate-and-security-modes.md](git-gate-and-security-modes.md)
- **Copying text from the terminal:** TUI and tmux can intercept mouse
  events, preventing normal text selection from reaching the clipboard.
  Hold **Shift** while selecting, then **Shift+Ctrl+C** to copy.

---

## FAQ

### Where are templates and scripts stored?

Loaded from Python package resources bundled with the wheel (under `terok/resources/`). The application never reads from `/usr/share`.
