Examples
Worked transcripts of every flow — cold start, the orchestrator loop, the worker side, fan-out, and the rails you will hit.
Every flow below ran against a real deck (2026-09-01, v0.1.3 tree) and behaved
exactly as shown. Routes and params are canonical in AGENTS.md; this file is
the doing companion. $M is the CLI (mandeck on PATH, or fetch it:
curl -so /tmp/mandeck http://127.0.0.1:7717/cli/mandeck && chmod +x /tmp/mandeck).
0. Cold start — you know only a port
curl -s http://127.0.0.1:7717/v1/health
# {"ok":true,"app":"mandeck","docs":"/agents.md","state":"dev.mandeck.native", ...}
# └── read this └── token lives at
# ~/Library/Application Support/<state>/cli-token
curl -s http://127.0.0.1:7717/agents.md # the full contract, port-corrected
TOKEN=$(cat "$HOME/Library/Application Support/dev.mandeck.native/cli-token")
1. The orchestrator loop — spawn, wait, report
JOB=$(mandeck job spawn claude-code --dir ~/myrepo -p "fix the failing test" \
--check-after 30 | cut -f1) # job-id \t session \t pane
mandeck job wait "$JOB" --timeout 1200 # blocks; exit 0=done 2=failed 3=waiting-you 4=timeout
case $? in
0) mandeck job report "$JOB" ;; # transcript + screen — summarize this to your user
3) mandeck screen "$JOB" 30 # it asked a question — read it,
mandeck job send "$JOB" "yes, use bun" ;; # answer it, wait again
4) mandeck screen "$JOB" 30 ;; # still running — honest timeout, look before deciding
esac
Rules that make this work (all enforced, all verified):
done/failedcome ONLY from explicit signals. Silence, an idle prompt,live:false, unfocus, pane close — none of them finish a job.- Mentioning
[mandeck job-done]mid-sentence does NOT finish a job; the token must start its own line. - Set
--check-afterlonger than your wait timeout, or the follow-up contract reminds you mid-wait (by design;job reportsettles it).
2. The worker side — you are the spawned agent
Every API-spawned pane exports MANDECK_PANE (your identity) and MANDECK_JOB
(the job you owe). Finish honestly:
mandeck job done # no id needed — $MANDECK_JOB is read
mandeck job fail # the work errored; say so, don't fake done
echo "[mandeck job-done]" # equivalent: the token alone on its own line
Hand work onward and the follow-up contract makes you own it: spawn with
--spawner "$MANDECK_PANE" (the CLI sends it automatically) and the deck will
type a [mandeck follow-up] reminder into YOUR pane if you never check the
result.
3. Fan out — one task, every repo
mandeck fanout claude-code "bump deps and run tests" ~/api ~/web ~/cli
# one pane per repo, all in one tab, each with the follow-up contract
mandeck waiting # which panes need an answer
--force spawns even where a live pane already holds that agent+cwd; without
it you get already live plus the existing session to reuse (409).
4. MCP instead of shell
{ "mcpServers": { "mandeck": { "command": "node",
"args": ["/path/to/mandeck-mcp.mjs"] } } }
Fetch the server from a running deck: GET /cli/mandeck-mcp.mjs. 23 tools,
1:1 with the routes — deck_spawn, deck_job_wait, deck_job_report,
deck_screen, deck_send, deck_key, deck_events, … Same token file, same
loopback-only reach.
5. Safety rails you will hit (by design)
mandeck job spawn shell # ✗ job spawn requires --dir (no default $HOME/$PWD)
mandeck job spawn shell --dir ~/x # second time: ✗ 409 "already live" + existing session
mandeck job done # outside a tasked pane: ✗ usage (no $MANDECK_JOB)
curl -X POST .../v1/jobs/<done-job> -d '{"state":"failed"}' # ✗ 409 "job already done"
6. Testing without touching the live deck
./scripts/devtest.sh up # own state dir, own port (7729), own ~/.mandeck
MANDECK_API_PORT=7729 MANDECK_STATE_DIR=dev.mandeck.native.devtest mandeck ps
./scripts/devtest.sh down # quits and wipes its state
The served /agents.md on a test instance rewrites its examples to the test
port, so nothing you copy-paste from it reaches the live deck.