mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
Every place that called the Codeman Agent Skill setting off by default now says it is on by default since 1.50.0, where to turn it off, and that turning it off still removes no copy already written (`codeman skill uninstall --case <name>` does). The README, the wiki's Codeman Skill page and the FAQ say an agent inside Codeman usually has the skill without an install step, and that the copy ignores itself in git. Also written down: an install whose App Settings were saved on an earlier version stored the old default and stays off until switched on; with the Claude Code plugin installed, Claude Code lists the skill twice (both work); and, in the invariants, the self-ignoring `.gitignore` rules and the measurement that Claude Code 2.1.319 never runs `git check-ignore` on a skill folder. The agent control plan keeps its original default-off decision and gains a dated note for the flip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
331 lines
18 KiB
Markdown
331 lines
18 KiB
Markdown
# Driving Codeman From An Agent
|
|
|
|
Everything the dashboard does is HTTP, so an agent can do it too. This page is for the case
|
|
that makes Codeman interesting: **Claude Code running inside a Codeman session, spawning and
|
|
supervising other sessions.**
|
|
|
|
Three routes. Start with the skill, whichever CLI the agent is. Where the skill does not
|
|
reach (a pi or shell session), use the `codeman agent` commands. Raw HTTP is there for
|
|
everything else.
|
|
|
|
## The agent skill
|
|
|
|
A skill in the `SKILL.md` format that Claude Code, Codex, Gemini CLI, OpenCode, Grok, OMP
|
|
and most other agent CLIs read. It teaches the agent the whole API, so you ask in plain
|
|
English instead of pasting endpoint documentation into prompts:
|
|
|
|
Since 1.50.0 Codeman puts it into every new session's folder by default (App Settings →
|
|
Agents & CLIs → **Codeman Agent Skill**), so an agent inside Codeman usually has it already.
|
|
For pi, or a copy that does not depend on that setting:
|
|
|
|
```bash
|
|
codeman skill install # or: npx skills add Ark0N/Codeman --skill codeman -g
|
|
```
|
|
|
|
Then ask the agent in a Codeman session something like *"use codeman to start 5 claude
|
|
workers, one per TODO item"*. It starts the workers, hands each one its task, gathers the
|
|
answers and deletes the sessions it created.
|
|
|
|
**[The Codeman Skill](Codeman-Skill)** has the rest: recordings of real runs, every install
|
|
route (Claude Code plugin, skills CLI, per case, the web UI setting), prompts that work as
|
|
written, what it will and will not do on its own, and the notes for Codex. It drives
|
|
DeepSeek Harness workers the same way it drives Claude ones (`spawn_workers alpha
|
|
beta:deepseek` is a mixed fleet in one call), since those are the two modes with real
|
|
completion signals.
|
|
|
|
## The `codeman agent` commands
|
|
|
|
The skill drives the API with `curl` and `jq`. Where it does not reach (a pi or shell
|
|
session, a machine without `jq`), the agent runs in the same environment, so `codeman agent`
|
|
packages the same verbs as shell commands. It is a thin client over the endpoints in
|
|
[the manual path](#the-manual-path), so auth and ownership apply unchanged, and it refuses to
|
|
act outside a Codeman session. One line in a case's `AGENTS.md` is enough: *"other sessions:
|
|
`codeman agent --help`"*.
|
|
|
|
```bash
|
|
codeman agent ls # sessions; * marks this one
|
|
SID=$(codeman agent spawn scratch-1 --mode claude) # quick-start + wait for the composer where the mode has a ready mark
|
|
SID=$(codeman agent spawn --working-dir ~/src/app --mode codex) # no case: an existing directory (cwd by default)
|
|
codeman agent send "$SID" 'review src/, then say DONE' --until stop,exit --timeout 300000
|
|
codeman agent read "$SID" # last answer (as the server reads it for that mode)
|
|
codeman agent read "$SID" --tail 3000 # terminal tail, ANSI stripped (every mode)
|
|
codeman agent inspect "$SID" --max-bytes 8192 --json # experimental, opt-in: recent evidence + opaque cursor
|
|
codeman agent stage "$SID" --message-file /tmp/prompt.txt --json # experimental, opt-in: no Enter; local Codex composer only
|
|
codeman agent submit "$SID" --stage-id "$STAGE_ID" --confirm-sha "$SHA256" --json # experimental, opt-in: one conditional Enter
|
|
codeman agent send "$SID" 'run the tests, then print WORKDONE followed by _4711' # hook-less modes: the marker in halves …
|
|
codeman agent wait "$SID" --match WORKDONE_4711 # … and the wait on the joined form
|
|
codeman agent interrupt "$SID" # a bare ESC, conversation intact
|
|
codeman agent post "$SID" 'context for later, not a prompt' # stored in its inbox, not typed into the pane
|
|
codeman agent inbox --wait --timeout 300000 # your own inbox: blocks while nothing is new
|
|
codeman agent ack <msg-id> # remove what you handled (read never removes)
|
|
codeman agent rm "$SID" # any session except this one
|
|
```
|
|
|
|
- **`spawn [case]`** uses the current directory when no case is supplied. Use
|
|
`codeman agent spawn --working-dir /path/to/project --mode codex` for another existing
|
|
local directory. A case and `--working-dir` cannot be combined. An empty case name
|
|
(`spawn "$CASE"` with `CASE` unset) is never read as "no case": it goes to quick-start,
|
|
which refuses it (exit 1). An empty `--working-dir` is refused before any request
|
|
(exit 4), so it never falls back to cwd.
|
|
A cwd that is your home directory is refused the same way, because Codeman's hooks would
|
|
land in `~/.claude`; pass `--working-dir ~` to start a worker there on purpose.
|
|
Explicit case names retain quick-start behavior (creating a scratch case if missing).
|
|
A directory launch is `POST /api/v1/sessions` followed by
|
|
`POST /api/v1/sessions/:id/interactive` (or `/shell`); it preserves the project and
|
|
parent lineage, and never labels it as agent scratch. Choose a separate worktree for
|
|
each worker that will edit code. If startup fails or its response is lost, the CLI
|
|
reports the exact created session ID and exits `1`; inspect that session before
|
|
deciding what to do next.
|
|
- **Ids** may be the 8-character form `ls` prints. Anything shorter refuses, and so does an
|
|
ambiguous prefix.
|
|
- **`send`** takes ONE quoted argument of printable text and presses Enter. A prompt that
|
|
starts with `-` goes after `--`: `codeman agent send "$SID" -- "- fix the bug"`.
|
|
- **Markers** follow [the split-marker trick](#the-split-marker-trick): the echo of your own
|
|
prompt is output too, so ask for the marker in halves and wait on the joined form.
|
|
- **`post`, `inbox`, `ack`** are the agent inbox: a message for another session that starts
|
|
no turn. `post` stores ONE quoted argument (multi-line is fine, so `"$(cat task.md)"` works)
|
|
and types nothing into the pane. `inbox` reads this session's mail, or peeks at another's
|
|
with an id, and removes nothing; `ack <id>…` removes what you handled. `inbox --wait`
|
|
blocks while nothing is new, so mail you read but did not ack does not wake it again.
|
|
`ls` shows each session's mail in an INBOX column. Mail lives in memory unless
|
|
**Keep agent inbox across restarts** is on (see [Settings Reference](Settings-Reference)),
|
|
and goes when its session is deleted.
|
|
- **`inspect`, `stage`, `submit`** are experimental and opt-in: they answer `403 FORBIDDEN`
|
|
naming the setting until **Experimental agent coordination** is on (see
|
|
[Settings Reference](Settings-Reference); `agentCoordinationEnabled`, synced, off by
|
|
default), and they may change in any release. `inspect` returns bounded evidence with an
|
|
opaque cursor. `stage` types one printable UTF8 line without trailing whitespace into a
|
|
local Codex composer, with no Enter, and returns a receipt that expires after two minutes
|
|
once the pane shows exactly that text; it refuses slash (`/`) and shell (`!`) commands
|
|
and a message whose last word starts with `@` or `$` (those open a Codex popup that Enter
|
|
would accept), so send commands with `send`. `submit` sends one Enter only if the
|
|
composer still shows exactly that text, and never replays it. `send --no-enter` alone has no receipt. Details:
|
|
[API reference](https://github.com/Ark0N/Codeman/blob/master/docs/api-reference.md#experimental-bounded-agent-inspection-and-guarded-input).
|
|
- **Exit codes** are the same for every verb: `0` done, `1` error, `2` timeout, `3` the
|
|
worker exited, `4` refused. `--json` prints the response's `data`, except a directory
|
|
`spawn`, which prints `{ sessionId, workingDir, created: true }`, plus `ready` when a
|
|
readiness wait ran, `readinessError` when that call failed, and `startupError` when the
|
|
start failed.
|
|
|
|
## Reading saved Codex conversations
|
|
|
|
`codeman history read` is the one command here that needs no server and no Codeman
|
|
session: it reads Codex's own saved rollouts (`$CODEX_HOME/sessions`, else
|
|
`~/.codex/sessions`) from disk and writes nothing.
|
|
|
|
```bash
|
|
codeman history read --session-id 019fc604 # one conversation, by id or 8+ character prefix
|
|
codeman history read --root-session-id <uuid> --topic auth # it and every sub-agent or fork under it
|
|
codeman history read --file /path/to/rollout.jsonl --format text
|
|
```
|
|
|
|
- **Filters** keep whole user/assistant turns: `--topic` is a literal, case-insensitive
|
|
match, and `--after`/`--before` are inclusive UTC dates.
|
|
- **Forks and sub-agents**: a child's copy of its parent's history is dropped, so every
|
|
record belongs to the thread that wrote it.
|
|
- **`complete: false`** in the JSON means the evidence is partial: a store file that could
|
|
not be attributed, a compressed `.jsonl.zst` rollout (not read), a record a running
|
|
Codex is still writing, an ancestor not found.
|
|
- **Budgets** fail closed and are raisable: `--metadata-bytes` (attributing the store,
|
|
default 1 GiB), `--max-entries` (65536), `--scan-bytes` (the selected transcripts,
|
|
16 MiB) and `--max-bytes` (output, 8192). `--json` and `--format text` together are an
|
|
error.
|
|
|
|
The full reference ships with the skill as
|
|
[`reference/history.md`](https://github.com/Ark0N/Codeman/blob/master/skills/codeman/reference/history.md).
|
|
|
|
## The MCP server
|
|
|
|
`codeman mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server, so any MCP
|
|
client (Claude Code, Claude Desktop, Codex, Gemini CLI, Cursor) can drive Codeman, from
|
|
inside a Codeman session or from an assistant on your desktop. It is a client of the same
|
|
HTTP API, with seven tools: `list_sessions`, `start_session`, `send_prompt`,
|
|
`wait_for_session`, `read_output`, `interrupt_session` and `delete_session`.
|
|
|
|
```bash
|
|
# Claude Code
|
|
claude mcp add codeman -e CODEMAN_API_URL=https://127.0.0.1:3000 -- codeman mcp
|
|
```
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"codeman": {
|
|
"command": "codeman",
|
|
"args": ["mcp"],
|
|
"env": { "CODEMAN_API_URL": "https://127.0.0.1:3000" }
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
```toml
|
|
# Codex (~/.codex/config.toml): raise the 60 s default tool timeout for long waits
|
|
[mcp_servers.codeman]
|
|
command = "codeman"
|
|
args = ["mcp"]
|
|
env = { CODEMAN_API_URL = "https://127.0.0.1:3000" }
|
|
env_vars = ["CODEMAN_MUX", "CODEMAN_SESSION_ID"] # Codex passes MCP servers a minimal env; these let it know the Codeman session it runs in
|
|
tool_timeout_sec = 120
|
|
```
|
|
|
|
The JSON form is what Claude Desktop, Cursor and Gemini CLI take. Add `CODEMAN_PASSWORD` to
|
|
`env` when the server has a password.
|
|
|
|
- **It never guesses a server.** Without `CODEMAN_API_URL` (or `--url`) every call says how
|
|
to configure it.
|
|
- **Only its own sessions.** Prompting, interrupting and deleting are limited to sessions it
|
|
started; listing, reading and waiting work on everything. `codeman mcp
|
|
--allow-any-session` lifts that, for an assistant that should drive your existing tabs.
|
|
- **Waits are short by default** (45 s, at most 600 s), so they fit a client's tool
|
|
timeout. A `timed_out` answer means call again.
|
|
|
|
## The manual path
|
|
|
|
The same operations as raw HTTP, for a CI bot, a shell script, or an agent without skill
|
|
support.
|
|
|
|
### Detect that you are inside Codeman
|
|
|
|
These are set in every managed session. Read them rather than hardcoding anything:
|
|
|
|
| Variable | Meaning |
|
|
| -------------------------- | ------------------------------------------------------------------------ |
|
|
| `CODEMAN_MUX=1` | You are in a managed tmux session. Never `tmux kill-session`, `pkill claude`, or `pkill tmux`: you will kill yourself or a sibling. |
|
|
| `CODEMAN_API_URL` | Base URL, with the correct scheme. |
|
|
| `CODEMAN_SESSION_ID` | Your own session id. Use it to avoid acting on yourself. |
|
|
| `CODEMAN_HOOK_SECRET_FILE` | Path to the hook secret. |
|
|
|
|
### Rules of the road
|
|
|
|
Read these before writing any code. Each one has cost somebody an afternoon.
|
|
|
|
1. **Input must end with `\r`.** Enter fires only when the payload contains a carriage
|
|
return. Without it the text sits unsubmitted on the prompt, the request still succeeds,
|
|
and a combined wait burns its full timeout on a turn that never started. With
|
|
`useMux`, embedded newlines are kept: the text goes in as one paste and the Enter after
|
|
it submits all of it, so `"echo A\necho B\r"` runs both commands in a shell. The check
|
|
that re-presses a stranded Enter cannot reliably recognise a multi-line paste, so a
|
|
long multi-line prompt to a just-started agent is still safer in a file it is told to
|
|
read.
|
|
2. **Make input idempotent.** Send a stable `clientId` and a monotonic per-session `seq`. The
|
|
server deduplicates, so a retry after a dropped connection cannot double-deliver.
|
|
3. **Auth.** With `CODEMAN_PASSWORD` set, use HTTP Basic or the session cookie. A missing
|
|
`Origin` is allowed, so plain curl works. A `401` replies with the bare string
|
|
`Unauthorized`, **not** the JSON envelope, so piping it into `jq` throws a parse error
|
|
instead of showing the failure. Check the status before parsing.
|
|
4. **Envelope.** Most endpoints return `{ "success": true, "data": ... }`. A few legacy GETs
|
|
return bare bodies, so handle both: `body.data ?? body`.
|
|
5. **Wait instead of polling, and a timeout is not an error.** The wait endpoints answer
|
|
`200` with `wait.timedOut: true`. Loop over short waits rather than one long call, because
|
|
tunnels cut idle connections.
|
|
6. **Only `claude` and `deepseek` sessions emit `stop`; `blocked` also fires for `codex`.**
|
|
Claude's come from Claude Code hooks, DeepSeek's from the harness reporting its state to
|
|
Codeman, and Codex's `blocked` from Codeman reading its approval, question and trust
|
|
dialogs off the screen (a wait that asks for `blocked` while such a dialog is already up
|
|
answers at once). Shell and the other external CLIs accept only `idle`, `working`, and
|
|
`exit`; asking for `stop` explicitly there (codex included) is a `400`, while omitting
|
|
`until` is always safe. On a shell session
|
|
`idle` fires **once at startup and never again**, so synchronize hook-less sessions with an
|
|
output marker instead.
|
|
7. **Nothing reports "ready", so wait for it explicitly.** A new session answers
|
|
`{"signal":"exit","immediate":true}` until its PID exists, and that means *not started*,
|
|
not *crashed*. A Claude worker in a fresh case then sits on the CLI's trust dialog, whose
|
|
highlighted option is `No, exit`: prompt it there and your Enter quits the CLI. Codeman
|
|
holds `idle` while the dialog is on screen, but that is not a readiness signal either.
|
|
|
|
### Recipes
|
|
|
|
```bash
|
|
API="${CODEMAN_API_URL:-http://localhost:3000}"
|
|
# Add -u admin:"$CODEMAN_PASSWORD" if a password is set, and -k on an HTTPS install.
|
|
|
|
# What is running
|
|
curl -s "$API/api/sessions" | jq '.data[] | {id, name, mode, status}'
|
|
|
|
# Spawn a worker in a case
|
|
curl -s -X POST "$API/api/quick-start" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"caseName":"myapp","mode":"shell"}' | jq
|
|
|
|
# Send a prompt (note the \r)
|
|
curl -s -X POST "$API/api/sessions/$ID/input" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"input":"run the tests\r","clientId":"my-agent","seq":1}' | jq
|
|
|
|
# Send and block until the turn finishes (registers the wait BEFORE writing)
|
|
curl -s -X POST "$API/api/sessions/$ID/input" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"input":"summarize src/session.ts\r","wait":["stop"],"waitTimeout":120000}' | jq
|
|
|
|
# Or wait for a marker in the output, which works on shell sessions too
|
|
curl -s "$API/api/sessions/$ID/wait-output?contains=DONE_17909&from=buffer" | jq
|
|
|
|
# Read the last answer as clean text (claude, codex, deepseek sessions)
|
|
curl -s "$API/api/sessions/$ID/last-response" | jq -r '.data.text'
|
|
|
|
# Or read the terminal back
|
|
curl -s "$API/api/sessions/$ID/terminal?tail=4000" | jq -r '.data.output'
|
|
|
|
# Clean up, by exact id
|
|
curl -s -X DELETE "$API/api/sessions/$ID" | jq
|
|
```
|
|
|
|
Use `POST /api/quick-start` rather than `POST /api/sessions` when a case might be remote:
|
|
the plain create endpoint validates the working directory locally and has no case concept.
|
|
|
|
### The split-marker trick
|
|
|
|
For hook-less sessions, synchronize on a marker in the output. The catch: your own
|
|
keystrokes echo into the output stream, so an unsplit marker matches **before the command
|
|
has run**.
|
|
|
|
Split it so the typed line never contains the string you are waiting for:
|
|
|
|
```bash
|
|
M=DONE; R=17909
|
|
# typed: echo ${M}_${R} → output contains DONE_17909, the typed line does not
|
|
```
|
|
|
|
Make it unique per call, because tmux repaints replay old screen text.
|
|
|
|
### Reading output
|
|
|
|
For `claude`, `codex` and `deepseek` sessions, read the answer from the transcript rather
|
|
than the screen: `GET /api/sessions/:id/last-response` returns the last reply as clean text
|
|
with no TUI frames or repaint noise. Poll it briefly rather than reading once, because the
|
|
transcript lands slightly after the `stop` signal, so a read immediately after send-and-wait
|
|
returns often comes back empty.
|
|
|
|
For everything else, use `terminal?tail=`, not `/output`. The latter's text field is empty for every tmux-backed
|
|
session, which is every interactive session. `tail` counts **bytes**, and what comes back is
|
|
terminal data with ANSI sequences included.
|
|
|
|
## Fan-out, and why it needs care
|
|
|
|
Wait signals are **edge triggered with no history**. A signal that fires with no waiter
|
|
registered is unobservable afterwards.
|
|
|
|
So a fan-out must register its waits before or as it dispatches: use send-and-wait per
|
|
worker, or latched output markers. Dispatching all the workers and then waiting on them one
|
|
at a time loses the signals of everyone who finished early.
|
|
|
|
Send-and-wait registers the waiter **before** the write for the same reason. A separate POST
|
|
followed by a wait races, and reports the previous turn's state.
|
|
|
|
## Lineage
|
|
|
|
A create request can name the session that spawned it, through a body field or a header, and
|
|
the dashboard then draws a lineage line from parent to child. The skill sets it automatically.
|
|
|
|
It is resolved rather than trusted: an unresolvable parent is dropped silently rather than
|
|
failing the spawn, because a cosmetic field must never break a worker.
|
|
|
|
## Read next
|
|
|
|
- [The Codeman Skill](Codeman-Skill) - the skill in action, its install routes and its limits.
|
|
- [HTTP API](HTTP-API) - the endpoint map and the envelope.
|
|
- [Hooks And Integrations](Hooks-And-Integrations) - events flowing the other way.
|
|
- [Watching Agents Work](Watching-Agents-Work) - seeing the fan-out in the UI.
|
|
- [`skills/codeman/SKILL.md`](https://github.com/Ark0N/Codeman/blob/master/skills/codeman/SKILL.md) - the skill itself.
|