Files
Codeman/docs/wiki/Driving-Codeman-From-An-Agent.md
T
Codeman maintainer f3cb7696f0 docs: publish docs/wiki as the user manual, with a sync workflow
30 pages covering install, concepts, the dashboard, the agent CLIs, unattended
runs, remote and Docker cases, security and the HTTP API, plus a sidebar and a
footer. The wiki repo has no CI and no review, so docs/wiki is the source of
truth and .github/workflows/wiki-sync.yml mirrors it on every push to master.

The workflow refuses to mirror when docs/wiki is missing or holds no pages,
because it deletes before it copies and would otherwise publish the deletion of
every page. The footer carries a {{VERSION}} placeholder stamped at publish
time rather than a hand-written version, which went stale on every release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:20:43 +02:00

10 KiB

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.

Two routes. Start with the skill.

The agent skill

A Claude Code skill that teaches the agent the whole API, so you ask in plain English instead of pasting endpoint documentation into prompts.

Install it

How Command Scope
Skills CLI npx skills add Ark0N/Codeman --skill codeman -g Global, any skills-aware agent.
Bundled CLI codeman skill install Global, at ~/.claude/skills/codeman.
Bundled CLI codeman skill install --case <name> One case.
Web UI App Settings → Agents & CLIs → Claude → Agent Skill Injects into each case when a Claude session is created. Off by default.

codeman skill uninstall [--case <name>] reverses the CLI installs, and never touches a skills/codeman you wrote yourself.

Then just ask

You say What happens
"What sessions are running right now?" Lists them with name, mode, and status. Read-only.
"Start a shell worker on the myapp case, run the test suite, tell me if it passes." Spawns, waits on a completion marker, reads the exit code, cleans up.
"Spin up 3 workers for lint, typecheck and tests, run them in parallel, report failures." One session per task, all started first, then gathered as each finishes.
"Have a claude worker summarize src/session.ts, then close it." Spawns, runs the readiness ladder, sends and waits, reads the answer, deletes the session.
"Watch session w4 and tell me if it gets stuck on a permission prompt." Blocks on the blocked signal and surfaces the question to you.

Sessions the agent creates get deleted when it is done. You can watch the tabs appear and disappear in the dashboard while it works.

What it will and will not do

  • It self-gates. Outside a Codeman session it refuses to act and does not guess an API URL, so a global install costs an unrelated Claude Code session nothing.
  • Unprompted, it may only spawn sessions, prompt them, and delete ones it created in that conversation, by exact id, behind a guard that refuses to delete the agent's own session.
  • It will not answer another session's permission prompt on your behalf. It surfaces the question instead.
  • Deleting a case (which erases a real directory of your code), bulk kills, respawn, Ralph, cron, orchestrator, and settings writes all require you to ask, naming the target.

Turning the setting back off does not remove already-injected copies, because a create-time sweep would yank the skill out from under other live sessions sharing that directory. Remove them per case with codeman skill uninstall --case <name>.

The skill ships with the verb index always loaded, plus on-demand references for the verbs, worked multi-worker recipes, endpoint tables, and cross-session messaging.

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 is single line and 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. Embedded newlines are stripped rather than rejected, so "echo A\necho B\r" runs the joined echo Aecho B. One line per call.
  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 sessions emit stop and blocked. They come from Claude Code hooks. Shell and the external CLIs accept only idle, working, and exit; asking for stop explicitly there 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; prompt it there and the wait resolves on idle in about two seconds looking exactly like a finished turn, while your text sits stuck in the dialog.

Recipes

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 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:

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

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 arc 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.