mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
Lineage lines used to draw one glowing dashed bezier per parent/child pair, hanging below the tab strip, for every family at once. With a parent on row 3 of a wrapped strip and ten children below it, the curves crossed every lower row's tab names and ran through the terminal text. - One rounded orthogonal tree per spawning tab: every route starts at the parent, so siblings share a trunk. 1.5px, solid, 1px dark outline; only a working child's branch is dashed. - Routes run only through the gaps between tab rows, joined by a spine left of every row (computeLineageTree/computeLineageRows in constants.js), so they never cross a tab or reach the terminal. The vertical rail gets the same tree on its existing left track. - Only the selected tab's family is drawn (what it spawned, plus its parent and siblings). Selection redraws, and the strip's size transitionend redraws once more, since the active tab widens for ~150ms after the selection redraw. - The routing room is reserved by .session-tabs.lineage-tree, keyed on whether any lineage exists, never on the selection, so a tab switch never resizes the header or the PTY. - Colours stay per spawning tab, now claimed in strip order for every family so selection order never decides who gets the skin blue. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
203 lines
11 KiB
Markdown
203 lines
11 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.**
|
|
|
|
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. |
|
|
| Claude Code plugin | `/plugin marketplace add Ark0N/Codeman`, then `/plugin install codeman@codeman` | Global, through Claude Code's plugin manager. `/plugin update codeman` follows releases. Pick this or `codeman skill install`, not both, or the skill is listed twice (`codeman` and `codeman:codeman`). |
|
|
| 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. 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 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` and `deepseek` sessions emit `stop` and `blocked`.** Claude's come from
|
|
Claude Code hooks, DeepSeek's from the harness reporting its state to Codeman. Shell and
|
|
the other 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
|
|
|
|
```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
|
|
|
|
- [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.
|