Agents and the CLI
How to create, configure, and deploy an agent — and how agents act in sandboxes and sessions, how cost is tracked, and how to install and use the ch CLI.
Agents are templates, not processes
In CodeHerder an agent is a reusable configuration template — a command to run, arguments, environment variables, a persona description, and a set of required capabilities (such as which AI model tier to use). An agent is not a long-lived process, and it does not accumulate state across tasks.
When the engine assigns a task to an agent, it:
- Creates a sandbox for the task — an isolated git worktree on a device, cut from the repository’s default branch.
- Spawns a fresh, clean-context process (a session) inside that sandbox, feeding it only the task brief, the codebase, workspace memory, and the agent persona.
- Lets the session run to completion for that stage.
- If the task advances to the next stage, it spawns a new session — again with no shared transcript from the previous session.
This model keeps per-session context windows small and costs predictable. Each session is billed independently.
Sandboxes and sessions
A sandbox is the durable per-task execution context. It has a dedicated branch (e.g. session/abc123) and a lifecycle:
provision → ready → active → completing → done (or abandoned)
A session is one activation within a sandbox — one clean-context process doing one stage of the work. Several sessions may happen inside a single sandbox over the task’s lifetime (one per stage).
You can list a task’s sandboxes and their state with:
ch sandbox list <taskId>
ch sandbox list <taskId> --limit N --cursor <token>
ch sandbox show <sandboxId>
ch sandbox list defaults to 50 rows, up to 200 with --limit — see Paging through long lists for how --cursor works.
Cost per session
Cost is tracked per session and reported in USD. For a full breakdown — including CLI commands, time windows, per-agent and per-model splits, and the web cost view — see Understanding costs.
Installing the CLI
The ch binary is the primary interface for agents and human operators alike. Install instructions are on the Install CLI page in your workspace sidebar (under Set up).
Once installed, configure it with your API key:
export CH_TOKEN=<your-key>
export CH_WORKSPACE_ID=<your-workspace-id>
Verify with:
ch whoami
To keep the binary current, see Updating the CLI. For the full credential reference — including named profiles and credential precedence — see Credentials and profiles. For the conventions every command shares — discoverable help, short-form aliases, JSON output, and how to supply text — see Using the ch CLI.
Creating an agent
An agent is a workspace member of kind “agent”. You can create one from the CLI, from the web app, or by copying an existing agent template from another workspace.
From the CLI
Create one with:
ch agent create --display-name "Builder" --cap model:opus --cap model:sonnet
--cap declares the skill labels this agent carries — used to rank this agent higher when a task specifies matching capabilities (a soft preference, never a hard block). Pass --cap multiple times to declare more than one:
ch agent create --display-name "Go coder" --cap model:sonnet --cap go
--role sets a free-form label for the agent’s role, such as ic or leader. It defaults to ic if you omit it. This is the CLI equivalent of the Role field in the web app’s New agent panel, below:
ch agent create --display-name "Team lead" --role leader --cap model:opus
Add --mint-key to receive an API key for the agent at creation time:
ch agent create --display-name "Builder" --cap model:opus --cap model:sonnet --mint-key
The API key is printed once and not stored by CodeHerder — copy it immediately. You use it to authenticate as this agent on a device.
The command prints an agent ID. Note it — you need it for the configuration and assignment steps below.
From the web app
On the Set up → Agents page, click New agent:
| Field | Notes |
|---|---|
| Display name | Required. |
| Role | Free-form, such as ic or leader. Defaults to ic if left blank — same default as the CLI’s --role. |
| Capabilities | The same skill labels as --cap on the CLI. Enter them separated by commas or spaces, e.g. model:sonnet, go. |
| Mint a one-time API key for this agent | A checkbox, on by default. Leave it checked to get a key immediately — the key is shown exactly once right after creation and CodeHerder never stores it, so copy it before dismissing the panel. Uncheck it if you don’t need a key yet; you can mint one later (see Minting API keys). |
Either way — CLI or web — creating an agent does not set a launch config. The new agent cannot run any task until you add one; see Configuring the agent template below.
Copying an existing agent
If a workspace you’re already a member of has an agent set up the way you want, you can copy it instead of starting from scratch. On the Set up → Agents page, click Copy existing.
The panel lists agent templates from your other workspaces — the ones where you’re a direct member, not workspaces you only see through group inheritance. Pick one, optionally give the copy its own display name, and submit.
Copying carries over:
- role and capabilities,
- the launch config — command/args, working directory, required capabilities, persona, and system prompt, if the source agent has one set up,
- environment variable keys, and any secret references (an env var bound to a workspace secret keeps pointing at that same secret).
It does not copy secret values: plain (non-secret) environment variable values are left blank on the copy, so you re-enter them afterward. Copying also does not mint an API key — add one later the same way as any agent created without one (see Minting API keys).
After the copy completes, you land on the new agent’s detail page, where you can fill in its launch config and any environment values the copy left blank.
Configuring the agent template
Before an agent can run tasks, it needs a launch config — the settings the device uses to start the coding-agent CLI for each stage. Inspect the current config with:
ch agent config show <agent-id>
Set or update the config with:
ch agent config set <agent-id> \
--harness claude \
--cap model:opus --cap model:sonnet \
[--arg <argument>] \
[--env KEY=VALUE] \
[--credential-ref ENV_VAR=<secretId>] \
[--persona "A senior Go engineer focused on correctness"] \
[--system-prompt "Always write tests first."] \
[--workdir /home/user/repos]
Choosing a harness
The harness selects which coding-agent CLI CodeHerder launches on the device. Use --harness with one of:
| Harness | CLI launched |
|---|---|
claude |
claude (default) |
codex |
codex |
cursor |
cursor-agent |
opencode |
opencode |
pi |
pi |
When you set --harness, CodeHerder derives the launch command automatically — you do not need to provide --command. The selected CLI must be installed on the device that runs the agent.
The five harnesses aren’t interchangeable in every respect — see Choosing the coding-agent CLI your agents run for what actually changes (placement, cost tracking, conversation continuity on rework, and device readiness checks) when you switch.
Flag reference
Each flag accepted by config set:
| Flag | Purpose |
|---|---|
--harness |
Coding-agent CLI to launch (claude, codex, cursor, opencode, pi) — derives --command when omitted |
--command |
Advanced override: absolute path or binary name the device runs; required only when --harness is omitted |
--cap |
Stage-gate capability label — the engine only runs a stage on this config if its caps fully cover what the stage requires; repeat for multiple |
--arg |
Extra argument passed to the command — repeat for multiple |
--env KEY=VALUE |
Environment variable set on each launch — repeat for multiple |
--credential-ref ENV_VAR=<secretId> |
Bind a workspace secret to an env var name — repeat for multiple; the value is resolved and injected only when a session spawns, never passed through the CLI. See Secrets |
--persona |
Short description of this agent’s role, included in each task brief |
--system-prompt |
Instruction prepended to every session; inline text, or --from-file <path> (or - for stdin) to read it from a file |
--workdir |
Working directory for the launched process |
--allowed-model |
Model this config is allowed to run — repeat for multiple; see Which model your agents run |
config set replaces the stored config, not merges it. Command, arguments, environment variables, capabilities, and working directory are always reset to exactly what you provide on that call — omitted means cleared, not “left as is.” For example, if a config currently has two --arg values and you run config set again without any --arg, both are dropped and the config ends up with none. Include every value you want to keep. Harness, persona, system prompt, Credential refs, and Allowed models are the exception: if you omit --harness, --persona, --system-prompt, --credential-ref, or --allowed-model, those fields retain their current values.
For guidance on what to put in each field and how they affect sessions, see Shaping how an agent works — personas and system prompts.
Only the human who created the agent, workspace owners, and workspace admins can call config set.
For an API key or other credential you don’t want to hard-code in --env, create a workspace secret and bind it to an env var with --credential-ref instead — see Secrets for creating a secret and adding a Credential ref.
Running more than one launch config
An agent can carry an ordered list of launch configs — each with its own harness, capabilities, environment, and credentials. This lets one agent run different coding-agent CLIs for different stages, or hold a primary config and a fallback.
config show and config set always target the top config (position 1, the first one in the list). To manage multiple configs, use the dedicated subcommands from the ch CLI:
ch agent config list <agent-id> # show all configs in order
ch agent config add <agent-id> --harness codex --cap model:sonnet # add a new config at the next position
ch agent config reorder <agent-id> <configId> <configId> ... # reorder by listing IDs
ch agent config enable <agent-id> <configId> # make a config selectable again
ch agent config disable <agent-id> <configId> # skip a config without deleting it
ch agent config delete <agent-id> <configId> # delete a config (alias: rm)
You can also manage the full list of configs from the web app: the Agent templates panel on the agent detail page lists every config in order and lets you add, edit, reorder (drag or move up/down), enable/disable, and remove configs — the same set of operations available through the CLI above.
How selection works: when CodeHerder places a task stage, it picks each agent’s first enabled config whose capabilities cover what the stage requires. Ordering and enabling controls which config — and which harness and credentials — actually runs. To switch an agent between configurations, reorder or enable/disable; to take a config temporarily out of rotation, disable it rather than deleting it.
Reordering or disabling configs never overrides a device-wide pause from an exhausted AI subscription window — selection doesn’t look at live usage, so switching configs on the same paused device won’t get work moving there again. See AI usage limits.
Capabilities and routing
For the complete picture across agents, launch configs, stages, and tasks, see Capabilities.
Capabilities are free-form string labels — you define them to match your team’s conventions. Common examples: model:sonnet, model:opus, go, frontend.
They serve two distinct purposes with different rules:
Agent skills — a soft preference, never a block. The --cap labels you declare at creation time (ch agent create --cap model:sonnet) are the agent’s skill set. When the engine places an unassigned task, it ranks available agents by how much their skill set overlaps with the task’s required capabilities — more overlap means higher priority. A partial or even zero overlap never blocks placement: if no agent is a perfect match, the task still runs on the best available agent.
Stage requirements — a hard gate. Workflow stages can declare which capabilities an agent config must carry before the engine will run that stage on it. These requirements are set on the config with --cap (in ch agent config set). Remember that config set replaces the config’s capability list rather than adding to it, so any capability you want the config to keep afterward has to be named again on that same call:
ch agent config set <agentId> --harness claude --cap model:opus --cap model:sonnet
The engine runs a stage only on a config whose capabilities fully cover what that stage requires. If no config in your workspace carries the needed capability, the stage is unstaffable — the task queues and never starts. The web app’s Capability gap callout on the Set up → Agents page gives an early heads-up by checking agent templates’ capability labels, not launch configs directly — a useful early signal, but not a substitute for checking your launch configs, since the two are set independently. See Monitoring your agents for how to read that callout.
By default, the built-in feature, story, and bug workflow needs both the opus and sonnet tiers — its planning, review, and verify stages require model:opus, while its build and merge stages require model:sonnet (see How work flows for what each stage does). A launch config carrying only one tier can staff part of the pipeline but not all of it, so a task can stall partway through even though it started moving fine. Give a single config both tiers, as in the example above, or run two configs — one per tier — see Running more than one launch config above.
To close a gap, add the missing capability to an existing config, keeping every capability you already had:
ch agent config set <agentId> --harness claude --cap <existing-cap> --cap <missing-cap>
Or create a new config that carries it — see Creating an agent above, then set the config’s caps with ch agent config set. For the Capability gap callout and how to read the Agents page, see Monitoring your agents. For how to diagnose an unstaffable task, see Why isn’t my task moving?.
Putting an agent on a device
Agents run on devices — machines running ch device-server. Three steps deploy an agent to a device.
Throughout this section, <agent-id> also accepts the agent’s display name, and <device-id> also accepts the device’s name — its hostname, unless you’ve set one — see Referring to things on the command line for the full rule.
Step 1 — propose the assignment:
ch agent assign <agent-id> <device-id>
This creates a pending assignment. The agent is not yet active on the device.
Step 2 — approve the assignment:
The device owner or a workspace owner approves it:
ch agent approve <agent-id> <device-id>
approve mints a one-time per-agent-device key, printed once in the output, along with a ready-to-run command that stores it in the device’s keyring. A device that’s already connected receives the key as part of approving. Running the keyring command is still good practice — it means the key survives a later restart — but it isn’t required; a device without a stored copy gets what it needs when the agent’s first session starts. Store the printed key securely regardless — it’s how the device authenticates sessions for this agent.
If the agent’s top launch config references a device-side secret (an env value of the form @secret:<key> — see Secrets on a device), add --secret <key> once per referenced key so the approval covers them too:
ch agent approve <agent-id> <device-id> --secret <key>
The set of keys you pass must match the config’s @secret: references exactly, or the approval is rejected.
Step 3 — make sure the device server is running:
ch device-server
Approving takes effect immediately on a device that’s already connected — its assignment list and its new key both update in place, with nothing to restart. If the device server isn’t running yet, start it with the command above. If the device happened to be offline when you approved, it picks up the assignment as soon as it reconnects. (ch agent approve still prints a reminder to restart ch device-server — that’s safe to run, just no longer necessary.)
Find device IDs under Set up → Devices in the sidebar. For the first-time setup flow — registering a device and walking through these steps end to end — see the Quickstart. For ongoing device management, see Managing your devices.
Rotating an agent’s device key
The per-agent-device key from Step 2 doesn’t have to last forever. Mint a fresh one whenever you suspect the current key has leaked, after you re-provision or wipe the device, or just as routine security hygiene:
ch agent rotate-device-key <agent-id> <device-id>
This mints a new key and revokes the previous one in the same step — the old key stops authenticating immediately, so there’s no window where both are valid. The new key is printed once, the same as at approval time: copy it now. The command also prints a ready-to-run command that stores the new key in the target device’s keyring — run that command on the device.
If the target device’s server is already running and connected to CodeHerder, it picks up the new key automatically — you don’t need to do anything else. If the device is offline when you rotate, run the printed command on it once it’s back online, so the device has the new key ready before its next session starts.
You can rotate a key if you’re the agent’s operator (the human who created it) or a workspace owner or admin.
This is a per-(agent, device) credential, separate from the device’s own device token — rotating one has no effect on the other.
Core CLI verbs
Tasks
ch task create --title "My task" --type story --parent <epicId>
ch task list [--status code] [--created-by me]
ch task show <taskId>
ch task status <taskId> <stage> # move to named pipeline stage
ch task status <taskId> done # direct status transition
ch task comment <taskId> --from-file notes.md
ch task field <taskId> acceptance --from-file criteria.md
For the full ch task list filter set — status, priority, type, limit, and more — see Finding and tracking your work.
Dependencies and blockers
ch task depends-on <taskId> <upstreamId>
ch task block <taskId> "Waiting on upstream API"
ch task unblock <blockerId>
Sessions and agents
ch sandbox list <taskId>
ch agent create --display-name "Builder" --cap model:opus --cap model:sonnet [--role <role>] [--mint-key]
ch agent config show <agent-id>
ch agent config set <agent-id> --harness <h> --cap <cap> [--command <path>] [--arg ...] [--env K=V ...]
ch agent config list <agent-id>
ch agent config add <agent-id> --harness <h> [--cap <cap>] [--arg ...] [--env K=V ...]
ch agent config reorder <agent-id> <configId> ...
ch agent config enable <agent-id> <configId>
ch agent config disable <agent-id> <configId>
ch agent config delete <agent-id> <configId>
ch agent assign <agent-id> <device-id>
ch agent approve <agent-id> <device-id>
ch agent rotate-device-key <agent-id> <device-id>
Workspace memory
ch memory list
ch memory show <slug|id>
ch memory write --slug <slug> --title "<title>" --from-file learning.md
Messages
ch msg send <name-or-workspace> --from-file message.md
ch msg inbox --unread
The current-task default (CH_TASK_ID)
Most of the commands above take a task ID as their first argument — and copying a 36-character UUID around gets old fast. CH_TASK_ID is the task-level equivalent of CH_WORKSPACE_ID (see Credentials and profiles): set it once, and task-scoped commands pick it up automatically.
Set it for the rest of your shell session:
export CH_TASK_ID=<taskId>
Or set it for a single command inline:
CH_TASK_ID=<taskId> ch task show
With CH_TASK_ID exported, you can drive a task’s whole lifecycle without naming it again:
export CH_TASK_ID=<taskId>
ch task show
ch task comment --from-file handoff.md
ch task status code
Task-scoped ch task verbs (show, status, comment, field, watch, blockers, deps, and most others) and ch sandbox list all default their task argument from CH_TASK_ID when you omit it. An explicit task ID on the command line always overrides CH_TASK_ID.
Two commands don’t follow the default:
ch task block <taskId> "<reason>"always needs an explicit task ID — because its first argument after the ID is a free-text reason, there’s no safe way to tell “task ID, then reason” apart from “reason only” if the ID were optional.ch task commentonly defaults fromCH_TASK_IDwhen the body is supplied with--from-file. If you pass the comment body as a plain argument, the task ID must be explicit too.
Auto-staffing
Five of the six built-in task types — initiative, epic, feature, bug, and story — are eligible for auto-staffing: when an unassigned task of one of these types is created, the engine assigns a suitable idle agent and begins work automatically. This means you can create a story, set its acceptance criteria, and the engine will route it to an agent without any further human action.
The task type is not auto-staffed. Tasks are the lightweight, human-started to-do type — the kind you create for yourself or direct to a specific person — so the engine leaves them for a human to assign from the web app.
Monitoring your fleet
Once your agents are set up and running, you’ll want to know whether they’re healthy and picking up work. Monitoring your agents covers the web Agents page (the template list and its columns, the capability-gap warning), pausing an agent with Disable/Enable, and the CLI commands for workload snapshots (ch agent workload) and per-agent event feeds (ch activity agent <agentId>).
