| name | codeman |
| description | Drive Codeman, the session manager this agent is running inside, over its HTTP API: list sessions, start worker sessions, send them prompts, block until they finish (wait / wait-output / send-and-wait), read their output, and clean up; where available, message claude workers directly (Claude Code cross-session messaging). Use when asked to orchestrate or parallelize work across Codeman sessions, watch another session, or start and manage workers. Only usable inside a Codeman-managed session (CODEMAN_MUX=1); refuse to act otherwise. |
Driving Codeman from inside a session
You are an agent running inside a Codeman-managed terminal session. Codeman is the
server that spawned you; its HTTP API can start, prompt, watch, and delete other
sessions.
Read as far as your job needs and no further. §0 is the bootstrap, run once. §1 is
the whole fast path: spawn N workers, task them, collect answers. If §1 covers your
job, run it and stop there. The sections after it are for jobs it does not cover, and
reading them to be thorough is the main reason a ten-second run takes minutes. §2 is the
verb table when your job is a different one. §3 and §4 are the rules; §6 is setup and
credentials, which you only need when something 401s.
Everything else loads on demand, and is meant to be opened at one section, not read
through: the verbs in detail (the old §5) in reference/verbs.md,
worked multi-worker flows in reference/recipes.md, endpoint
tables and a symptom gallery in reference/endpoints.md, and
direct messaging to claude workers in reference/messaging.md.
0. Guard and bootstrap
If CODEMAN_MUX is not 1, stop and say so. Do not guess an API URL; a server
you are not part of is not yours to drive.
⚠️ Your shell state does not survive between tool calls. Each Bash call starts a
fresh shell, so $API, $SELF, the CURL array and delete_session are all gone by
the next call, and $$ is a different pid. The filesystem does survive, so write
the preamble to a file once and source it afterwards, rather than re-pasting a
hundred-odd lines at the top of every call (a half-re-pasted preamble used to be the
single most likely way to break a run).
Codeman seeds the preamble file for you when it spawns a claude session (server
1.18.3+), so the bootstrap is usually nothing at all: these are the two lines every
later call opens with, and your first REAL call performs them anyway:
. "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
[ "${CODEMAN_PREAMBLE:-}" = 1.19.0 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
⚠️ Never spend a Bash call on this check alone. §1's block opens with this same
loader, so when §1 is the job, start there: the check rides the spawn call for free,
and a standalone "preamble OK" call buys nothing while costing a full model turn
(measured live: a lone check plus the deliberation around it added ~6 s to a 28 s
two-worker run). §0 is done the moment any job call passes its opening check. Only
when a call reports missing or stale, run the full block below once — and run it
verbatim: paste it as-is, never re-type it, trim it, or "extract the parts you
need". A hand-assembled
preamble is the documented failure mode of this skill: one live run rebuilt it
"minimally" and lost the X-Codeman-Parent-Session header (every worker spawned with
no lineage arc in the web UI) and the fast-path functions (the spawn fell back to a
serial quick-start loop plus pid polls), turning a ten-second job into a fifty-second
one. If your harness directs temporary files into a scratchpad directory, that
directive covers task scratch, not this file: it is a per-session cache that every
later call re-sources by this exact path, so keep the path below. If you must relocate
it anyway, copy the block's content byte-for-byte unchanged and source your path in
every later call instead.
test "${CODEMAN_MUX:-}" = 1 || { echo "Not inside a Codeman-managed session; refusing to act."; exit 1; }
: "${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}" "${HOME:?HOME not set}"
PRE="${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh"
mkdir -p "$(dirname "$PRE")"
grep -qs '^CODEMAN_PREAMBLE=1.19.0$' "$PRE" || (umask 077; cat > "$PRE" <<'PREAMBLE'
API="${CODEMAN_API_URL:?CODEMAN_API_URL not set; refusing to guess}"
SELF="${CODEMAN_SESSION_ID:?CODEMAN_SESSION_ID not set}"
ENV_FILE="${CODEMAN_HOOK_SECRET_FILE:+${CODEMAN_HOOK_SECRET_FILE%hook-secret}.env}"
() { sed -n | -1 | sed \'\'; }
[ -z ] && [ -n ] && [ -f ];
CODEMAN_USERNAME=$(envval CODEMAN_USERNAME)
CODEMAN_PASSWORD=$(envval CODEMAN_PASSWORD)
AUTH=(); [ -n ] && AUTH=(-u )
CURL=(curl -sk -H )
CID=codeman-agent-1
() {
=
[ -n ] || { ; 1; }
[ -ge 8 ] || { ; 1; }
*) ; 1 ;;
*) ; 1 ;;
-X DELETE
}
() {
-G \
--data-urlencode --data-urlencode \
--data-urlencode | jq -r
}
() {
name= mode= q sid r
q=$( -X POST -H \
-d )
sid=$(jq -r <<<)
[ -n ] || { jq -c <<< >&2; 1; }
[ = claude ] || { ; 0; }
=$(jq -r <<<)
grep -qs || {
>&2
delete_session >/dev/null; 1; }
r=$(_composer_up 5000)
[ != ];
-G \
--data-urlencode --data-urlencode --data-urlencode \
| jq -e >/dev/null;
-X POST -H \
-d >/dev/null
r=$(_composer_up 45000)
[ = ] || { >&2
delete_session >/dev/null; 1; }
}
() {
d n i=0
[ -gt 0 ] || { >&2; 1; }
[ -z ] || { >&2; 1; }
d=$( -d ) || 1
n ; ( spawn_worker > ) & i=$((i+));
i=0; n ; ; i=$((i+));
-rf
}
() {
sid= p= = body r
body=$(jq -nc --arg p --arg c --argjson s \
)
r=$( -X POST \
-H --data-binary )
jq -e <<< >/dev/null 2>&1;
-X POST -H \
-d >/dev/null
r=$( -X POST \
-H --data-binary )
}
() {
t= prev=
_ $( 1 15);
t=$( | jq -r )
[ -n ] && [ != ] && { ; 0; }
1
[ -n ] && { ; 0; }
1
}
CODEMAN_PREAMBLE=1.19.0
PREAMBLE
)
. ; [ = 1.19.0 ] || { ; 1; }
Every later Bash call that touches the API starts with the same two loader lines from
the top of this section.
Why it is built this way, all of it load-bearing:
- It still fails closed. A missing or truncated file means
delete_session is
undefined, and an undefined function is "command not found", which deletes nothing.
⚠️ This argument covers accidents, NOT a hostile file: a complete attacker-written
preamble can define delete_session and set the stamp, and sourcing executes it. What
defends against that is the path choice in the next bullet, not this one. Never
hand-roll a DELETE of your own, which is the one thing that would route around this.
- The version stamp is the LAST line, and the write condition greps for it. That one
choice covers staleness and truncation together: an old skill version's file and a
half-written one both fail the grep and are rewritten in place, so neither costs you a
round trip to diagnose and
rm. The older [ -s "$PRE" ] condition could not tell a
complete file from a half-written one and left both to the post-source guard, which can
only refuse, not repair. That guard stays as the fail-closed backstop: if the rewrite
itself is cut short, CODEMAN_PREAMBLE is unset and the call stops.
- Not
/tmp. On a shared machine /tmp is world-writable, so another local user
can pre-create the exact path you are about to . and have their code run as you.
$HOME-derived paths are not world-writable, and the file is written 0600 anyway.
The file holds the credential-recovery code, not a recovered password.
- Never put
$$ in a clientId. It changes per call, so the "resend the identical
request" loop in §5.3 would stop being a duplicate and would retype the prompt,
submitting the turn twice. Use the fixed literal $CID.
- Only real environment variables (
CODEMAN_*, HOME) survive, which is why the
preamble rebuilds $API and $SELF from them on every source rather than baking
them in.
If a call comes back as unparseable text instead of JSON, that is almost always a
plain-text 401: see §6 and the symptom gallery.
1. The fast path: N workers, one Bash call
If the job is "spawn N claude workers, give them tasks, collect the answers", this
block is the whole thing. Run it, report, and stop reading. §2 onward is for jobs this
does not cover; you are not being careless by not reading them.
Fill in the case names and the prompts, then run it as your FIRST Bash call: no
standalone preamble check before it (line one below IS that check), and no
reconnaissance. ls ~/codeman-cases answers nothing this block needs: invented
fresh names need no lookup, and spawn_worker refuses a name that already exists
rather than silently reusing it. Everything below is spawn_workers / sendwait /
last_text / delete_session from the §0 preamble, so there is nothing to assemble
and no per-call body to hand-build.
. "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null
[ "${CODEMAN_PREAMBLE:-}" = 1.19.0 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
N=(alpha beta)
T=('reply with one line: the absolute path of your working directory'
'reply with one line: your model name')
S=(); while read -r _ s; do S+=("$s"); done < <(spawn_workers "${N[@]}")
for i in "${!N[@]}"; do [ -n "${S[$i]:-}" ] || FAIL=1; done
[ -z "${FAIL:-}" ] || { echo "a spawn failed (stderr says why; §5.1): deleting the siblings"
for s in "${S[@]}"; do [ -n "$s" ] && delete_session "$s" >/dev/null; done; exit 1; }
D=$(mktemp -d) || { for s in ""; delete_session >/dev/null; ; 1; }
i ; sendwait > & ;
i ;
jq -ce --arg n \
\
||
; last_text ||
i ;
jq -e >/dev/null 2>&1
delete_session >/dev/null
; -rf
Measured against a live 1.18.0 server: two cold workers spawned and ready in 6.3 s,
both turns dispatched and both answers read in 4.0 s more. If your run takes minutes,
the time went into deliberation, not the API. The four things that actually cost time:
- Spawning serially. One worker per Bash call is one model turn per worker.
& plus
wait, as above, makes N workers cost about what one costs.
- Reconnaissance turns before the spawn. A standalone preamble check, an
ls ~/codeman-cases, a list_sessions "to see what is there": each is a whole
model turn spent learning something this block already handles (line one performs
the preamble check, invented names need no listing, and spawn_worker refuses
collisions). A live two-worker run spent ~12 s of its 28 s total on exactly two
such turns; the API work in between was under 10 s.
- Re-deriving the happy path from §5.1 + §5.2 + §5.3 + §5.10. That is what the
preamble functions exist to end. Compose them; do not rebuild them. The tells that
you are rebuilding anyway: a
for loop around quick-start, a poll on .data.pid,
a bespoke ready() or spawn() of your own. Each is a worse copy of a function
already sitting in your preamble; the live run that wrote them spawned serially,
polled pid for nothing, and shipped its workers without lineage.
- Verifying what is already checked for you. Two verifications specifically are not
worth a call here, because
spawn_worker carries them: the hooks check (it refuses a
name that resolved to a hook-less directory with one local grep, so a worker it hands
back always has a working stop and sendwait is trustworthy), and the pid poll,
which is dead weight because wait-output already blocks on the composer.
Four things this block leans on, each one link away, no detour needed to run it:
- Those case names must be fresh scratch names: they create
~/codeman-cases/<name>, not your repo. A name that already means something (a
linked case, a pre-existing directory) is refused by spawn_worker rather than
silently reused. Spawning where the work actually is (a linked case, a git worktree)
is a different call, and picking the wrong one is the costliest mistake in this
skill: §5.1. Those workspaces do get hooks now, unless the operator disabled it.
sendwait supplies the \r, picks a fresh seq, and self-heals a stranded Enter.
A prompt without the \r is never submitted (§3), a reused seq is silently
swallowed as an already-applied duplicate, and an Enter eaten by an Ink repaint
strands the prompt on the composer until a bare \r follows: all three are reasons
to let sendwait build the call rather than hand-rolling it.
- Each
sendwait costs that worker one billed turn, as does every prompt you send it.
- Deleting the sessions does not remove the case directories: §5.14.
2. What do you want to do?
One row per job. Acting on this table alone is correct; the §5 links are the detail.
| I want to | Call | Detail |
|---|
| start a worker where the work is | POST /api/v1/quick-start {"caseName":…}, which creates ~/codeman-cases/<name> unless the name is already a case. Any other path (a git worktree): POST /api/v1/sessions {"workingDir":…} then POST /api/v1/sessions/:id/interactive. Both install hooks by default, so expect full signals in either, and verify rather than assume. N workers means N worktrees | §5.1 |
| know a new worker can accept a prompt | GET .../wait-output?match=shift+tab&from=buffer (urlencode the +) | §5.2 |
| deliver a task and know when it finished | POST .../input with "input":"…\r", clientId, seq, "wait":true. Resolves on stop, so it is trustworthy only where the workspace has hooks (claude mode; installed by default, but the operator can disable it and remote sessions never get them). Costs the worker one billed turn | §5.3 |
| know a hook-less worker finished | it has no stop, and wait:true there resolves on flapping idle without erroring: make it print a split, unique marker and wait-output on that instead | §5.5 |
| read the answer | GET .../last-response, polled (claude/codex only; empty for the other modes) | §5.4 |
| know if it is alive | GET .../wait?until=exit&timeout=1000: an immediate signal:"exit" means dead. status and pid both lie | §5.6 |
| know if it is stuck | GET .../active-tools and are structured and free; two samples are the crude fallback |
3. Rules digest
Ten one-liners. Each breaks something concrete; the reason is one link away.
- End every input with
\r or Enter is never sent and the text sits unsubmitted
(§5.3).
- Never branch on
.data.status. It reads idle mid-turn and idle on a dead
worker (§5.6).
- Split your markers. Your typed command echoes into the output stream, so an
unsplit marker matches before the command runs
(§5.5).
- Match single space-free tokens against TUI output. A TUI positions words with
cursor moves, so multi-word matches are unreliable there
(§5.2).
- A wait timeout is a 200, not an error. Loop over short waits; the clamp and the
applied
wait.timeoutMs are in
endpoints.md.
- Signals are edge-triggered with no history. Register the waiter before the
event can happen; a
stop that fires with no waiter is unobservable afterwards
(§5.10).
- Never delete without
delete_session. The server lets a session delete itself
(§4).
- One in-flight wait per worker. The per-session waiter cap is 16 and abandoned
waits count against it (§5.10).
- Every message you send a worker costs it a billed turn, including a readiness
ping and an interrupted turn (§5.7).
- Never answer another session's dialog. Approving a permission prompt you did
not raise authorizes an action the user never saw (§4).
4. Safety rules
You are yourself a session on this server, and the API has no undo.
- Never act on your own session, and know that
delete_session is the ONLY guard.
The server has no self-protection: a session that DELETEs its own id succeeds and
dies silently (verified live). Always delete through delete_session "$SID" from
§0; never write a bare curl -X DELETE and never reintroduce the
is_self … || curl -X DELETE … shape. That older form failed open: with the
function undefined (a missing or truncated preamble file, see §0) bash returns 127,
the || branch fires, and the delete runs with no self-check at all. Wrapping the
request inside the guard is what makes a lost preamble delete nothing instead of
deleting you. Apply the same prefix-both-directions reasoning before any kill,
respawn, or input call you write by hand.
- Mutating calls you may make unprompted (this is an allowlist):
POST /api/v1/quick-start; POST /api/v1/sessions + POST /api/v1/sessions/:id/interactive
(or /shell) for a directory the user's own task named; POST /api/v1/sessions/:id/input;
and DELETE /api/v1/sessions/:id only for a session you created in this
conversation, by exact id. Keep a list of the ids you create. Everything else
mutating needs the user to have asked for it.
- Never call these unless the user explicitly asked, naming the target:
DELETE /api/cases/:name recursively deletes a real directory of the user's
code from disk. One wrong case name destroys work that was never yours.
DELETE /api/sessions (no id) is a bulk kill of every session, the user's
real work included. DELETE /api/subagents/:agentId kills one background agent;
DELETE /api/subagents (no id) does not kill anything, it clears the watcher's
map and timers, which blinds every subagent surface in the UI until they are
rediscovered. Neither is yours to call.
- respawn / ralph / orchestrator / cron mutations: respawn runs
/clear (wipes a
conversation), orchestrator state is a single global slot, cron jobs outlive you.
PUT /api/settings, POST /api/system/update: global UI settings; server restart.
POST /api/approvals/:id/answer. It types a digit, an Esc or free text into
whichever session raised the prompt. Approving another session's permission
dialog authorizes a tool call the user never saw, from a session that is not
yours. Answer only a prompt raised by a worker you created, and only when the
user asked you to.
The per-verb detail lives in reference/verbs.md, loaded on demand
so it is not paid for on every skill load. Section numbers and anchors are unchanged, so
a §5.4 reference still resolves. §1 already covers the common job without any of
these; open the one row you actually hit.
| Open | When |
|---|
| 5.1 Where to spawn | the work is not a fresh scratch case: a linked case, a git worktree, any path that already existed. Hooks are absent there, which silently breaks send-and-wait. The costliest mistake in this skill |
| 5.2 Readiness | a worker never drew its composer, or you need the trust-dialog ladder by hand |
| 5.3 Send a task and wait | the sendwait body, its signals, and the duplicate-resend loop |
| 5.4 Read the answer | last_text came back empty, or the mode is not claude/codex |
| 5.5 Markers for hook-less workers | the worker has no stop hook: synchronize on a split, unique printed marker |
| 5.6 Alive and stuck | is it dead or just slow? status and pid both lie |
| 5.7 Interrupt without destroying | a runaway worker you want to stop but keep |
| 5.8 Usage limits | a worker halted on a subscription limit |
| 5.9 Big input via the workspace | the prompt is larger than one composer line |
| 5.10 Fan out | many workers at once: waiter caps, and why signals are edge-triggered |
| 5.11 List and find yourself | enumerate sessions, or match $SELF by prefix |
| 5.12 Read My Mind | read or record what the user wants for a case |
| 5.13 Messaging claude workers | ListAgents / SendMessage instead of the HTTP path |
| 5.14 Clean up |
6. Setup and auth
You need this section only when the API answers something jq cannot parse, or when
you are on a server old enough to lack the wait endpoints. Endpoint-level detail lives
in endpoints.md.
Credentials
Auth is active only when the server has CODEMAN_PASSWORD (or is in multi-user mode).
Your session has usually inherited that password already, which is why the §0
preamble tries $CODEMAN_PASSWORD first: Codeman does not strip it. buildClaudeEnv()
(src/session-cli-builder.ts) spreads the server's entire process.env into the
session and deletes only COLORTERM and CLAUDECODE, and the tmux spawn path applies
no denylist either. On a stock password-protected install (install.sh writes the
password into the systemd unit or launchd plist, so the server process carries it) the
value is simply in your environment.
It is not guaranteed, though, which is what the fallbacks are for. A tmux pane
inherits the tmux server's environment, and that server can predate the password;
and the data dir's .env is only ever read by the codeman CLI itself, never loaded
into the web server's environment.
Fallback 1, in the §0 preamble already: the data dir's .env, the same file
codeman attach reads. It is hand-authored; nothing ever writes it.
Fallback 2, for a stock install where the supervisor definition is the only copy on
disk. Append this to the preamble file (before its version-stamp line) and re-source:
if [ -z "${CODEMAN_PASSWORD:-}" ]; then
UNIT="$HOME/.config/systemd/user/codeman-web.service"
PLIST="$HOME/Library/LaunchAgents/com.codeman.web.plist"
if [ -f "$UNIT" ]; then
CODEMAN_PASSWORD=$(sed -n 's/^Environment="CODEMAN_PASSWORD=\(.*\)"$/\1/p' "$UNIT" | head -1 | sed 's/\\\(["\\]\)/\1/g')
elif [ -f "$PLIST" ]; then
CODEMAN_PASSWORD=$(awk '/<key>CODEMAN_PASSWORD<\/key>/{getline; print}' "$PLIST" | sed -n 's/.*<string>\(.*\)<\/string>.*/\1/p' \
| sed -e 's/</</g' -e 's/>/>/g' -e 's/&/\&/g')
fi
fi
⚠️ A 401 is plain text, not the JSON envelope, so on a password-protected server
every jq in these recipes dies with jq: parse error instead of showing
UNAUTHORIZED. If that happens, check the status with -w '%{http_code}'; if it is
401 and no fallback found a credential, stop and tell the user you need
credentials. The same is true of the guards that run before any handler: the Host
allowlist (403 Forbidden: host not allowed), the Origin/CSRF guard, and the auth
rate limiter's 429 all answer in plain text. The hook-secret bypass covers only
/api/hook-event and /api/status-telemetry, never session control.
In multi-user mode accounts live in users.json and the credential is a real user's
name and password. A recovered CODEMAN_PASSWORD still often works: bootstrapInitialAdmin()
(user-store.ts:417-427) creates the FIRST admin from CODEMAN_USERNAME/CODEMAN_PASSWORD
on first boot when no users exist, so on a stock multi-user install that pair usually IS
a valid admin login until someone changes it. Try it once; if it fails, ask the user
rather than retrying (ten failures rate-limit the address).
Server version
The wait endpoints first ship in Codeman 1.13.0, but do not gate on the version
number: a dev build can serve them while reporting an older version. Probe instead.
GET .../wait on a real session id answering 404 with an .error starting Route
means the server predates them (fall back to polling GET .../terminal?tail= and say
so). Session ... not found means your session id is wrong, not the server.
Where the API is unreachable
- Remote-SSH cases do not export
CODEMAN_MUX/CODEMAN_API_URL into the session,
so the §0 guard fails closed and you refuse to act. That is correct behavior, not a
bug to work around.
- Inside a Docker case, a loopback-bound server is unreachable from the container,
and
CODEMAN_DOCKER_BRIDGE_HOOKS=1 does not fix it: that opens a hooks-only
listener, so hook events flow but /api/v1/* stays refused. Report it rather than
retrying; making it reachable is an operator decision.
Everything else (endpoint tables, per-mode signal table, error codes, capacity limits,
Docker/remote caveats): reference/endpoints.md. Fan-out
orchestration and blocked-worker handling: reference/recipes.md.