Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/project-worker-directories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"aicodeman": minor
---

Allow `codeman agent spawn` to start workers in the current project directory or an existing directory selected with `--working-dir`, while preserving explicit case quick-start behavior. A directory launch uses `POST /api/v1/sessions` followed by the mode's start route (`/interactive` or `/shell`), and its `--json` output is `{ sessionId, workingDir, created: true }`, plus `ready` when a readiness wait ran, `readinessError` when that call failed, and `startupError` when the start failed (exit 1). Only an omitted case means "use a directory": an empty case name still goes to quick-start, which refuses it, and an empty or whitespace-only `--working-dir` is refused locally (exit 4) before any request instead of resolving to the cwd. A cwd that resolves to the home directory is refused the same way, because the server would install its workspace hooks (and an injected skill) into `~/.claude`; an explicit `--working-dir ~` is still accepted.

One change for named-case spawns too: an unknown `--mode` is now refused locally (exit 4) before any request, where it used to reach quick-start and come back as the server's 400 (exit 1).
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ The skill above drives the API with `curl` and `jq` from a preamble Codeman seed
```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 # --wait = default signal set
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)
Expand All @@ -994,7 +995,7 @@ codeman agent ack <msg-id> # remove what you hand
codeman agent rm "$SID" # any session except this one
```

Rules the commands enforce rather than document: they refuse outside a Codeman session and never guess a URL; `send` transmits printable text plus Enter only (a control byte such as `Ctrl+C` is `app_exit` in opencode — ESC exists solely as `interrupt`, which never appends Enter); `rm` refuses an empty id, an unprovable self id and a prefix match in either direction. Ids may be the 8-character prefixes `ls` prints (resolved through the list; an ambiguous prefix refuses, anything shorter than 8 characters refuses on every verb). A prompt that starts with `-` goes after `--` (`send "$SID" -- "- fix the bug"`). The echo of the prompt you sent is output too: a `--match` marker that appears verbatim in the prompt matches at once, before the worker has done anything, so the prompt asks for it in halves. A remote session whose host is asleep answers a fire-and-forget `send` with `buffered` (Codeman wakes the host and types the prompt once the pane is back) or `dropped` (over the wake buffer's cap, nothing will be typed: exit `1`). Exit codes: `0` delivered/matched/signal, `1` error, `2` timeout, `3` the worker exited or the wait ended without an answer (`delivered:false`, `ended:true`), `4` refused. `spawn` prints the id alone on stdout (prose goes to stderr), so `SID=$(…)` captures exactly the id. `--json` prints the envelope's `data` for every verb. `--until stop` on a mode without hook signals is the server's 400, passed through — the marker path (`--match`) is the answer there, exactly as for the skill.
Rules the commands enforce rather than document: they refuse outside a Codeman session and never guess a URL; `send` transmits printable text plus Enter only (a control byte such as `Ctrl+C` is `app_exit` in opencode — ESC exists solely as `interrupt`, which never appends Enter); `rm` refuses an empty id, an unprovable self id and a prefix match in either direction. Ids may be the 8-character prefixes `ls` prints (resolved through the list; an ambiguous prefix refuses, anything shorter than 8 characters refuses on every verb). A prompt that starts with `-` goes after `--` (`send "$SID" -- "- fix the bug"`). The echo of the prompt you sent is output too: a `--match` marker that appears verbatim in the prompt matches at once, before the worker has done anything, so the prompt asks for it in halves. A remote session whose host is asleep answers a fire-and-forget `send` with `buffered` (Codeman wakes the host and types the prompt once the pane is back) or `dropped` (over the wake buffer's cap, nothing will be typed: exit `1`). Exit codes: `0` delivered/matched/signal, `1` error, `2` timeout, `3` the worker exited or the wait ended without an answer (`delivered:false`, `ended:true`), `4` refused. `spawn` prints the id alone on stdout (prose goes to stderr), so `SID=$(…)` captures exactly the id. `spawn` with no case, or with `--working-dir`, starts the worker in that existing directory through `POST /api/v1/sessions` plus `POST /api/v1/sessions/:id/interactive` (or `/shell`) instead of quick-start, so no case is created and the directory is never labelled agent scratch; an empty `--working-dir` refuses (exit `4`) rather than falling back to cwd, and so does a cwd that is your home directory (Codeman's hooks would land in `~/.claude`), which needs an explicit `--working-dir ~`; if the start fails it still prints the created id and exits `1`. `--json` prints the envelope's `data` for every verb, 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. `--until stop` on a mode without hook signals is the server's 400, passed through — the marker path (`--match`) is the answer there, exactly as for the skill.

### MCP server (`codeman mcp`): drive Codeman from any MCP client

Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ request returns. The capture runs synchronous tmux calls on the server; the

## The `codeman agent` CLI (client over these endpoints)

`codeman agent ls|spawn|send|wait|read|interrupt|rm|post|inbox|ack` (`src/cli-agent.ts`) is the command-line client for the endpoints above, for agents the skill does not reach (a pi or shell session, a machine without `jq`). It adds no route: `spawn` is `POST /api/v1/quick-start` (+ `wait-output` on the mode's `capabilities.composerReadyMark` from the CLI registry, where it declares one), `send` is `POST …/input` with `clientId`+`seq` (and `wait`/`waitTimeout` for `--wait` / `--until <signals>`; `delivered:false` without `duplicate` and `wait.ended` both exit 3 — the CLI never reports a dead worker as done), `wait` is `GET …/wait` (`--until`) or `GET …/wait-output` (`--match`, `from=buffer` by default), `read` is `GET …/last-response` or `GET …/terminal?tail=`, `interrupt` is `POST …/input` with a bare `\u001b`, `rm` is `DELETE …/sessions/:id`, and the inbox verbs map one to one onto the [agent inbox](#agent-inbox) routes (`post` → `POST …/inbox`, `inbox [id] [--wait]` → `GET …/inbox[?wait=]`, own inbox by default, `ack <ids…>` → `POST …/inbox/ack`); `ls` adds an `INBOX` column from `GET /api/v1/agent-inbox/summary` and drops it when that route does not answer. A fire-and-forget `send` to a sleeping wake-on-LAN host reads the route's `buffered` (own line, exit 0) and `dropped` (exit 1: the chunk is gone). An id may be the 8-character form `ls` prints, resolved through `GET /api/v1/sessions`; anything shorter refuses before any request, the same floor as `PARENT_SESSION_ID_MIN_PREFIX`. Every call carries `X-Codeman-Parent-Session`; only `spawn`'s quick-start carries `X-Codeman-Agent-Origin: codeman-agent-cli` (the agent-scratch label must never reach a request that cannot create the case directory). Basic auth comes from `CODEMAN_PASSWORD` or the data dir's `.env`. Server-side error codes are shown verbatim (`INVALID_INPUT: until=stop …` on a hook-less mode is not hidden); exit codes are `0` ok, `1` error, `2` timeout, `3` the session exited, `4` refused by a client-side guard (for `inbox --wait`, `3` only when `GET /api/v1/sessions` no longer lists the session; a wait the server ended otherwise, on a restart or a detach, is `2`). `post` takes text that starts with `-` as it is (a markdown file often does); text that is exactly an option (`--json`, `-h`) goes after `--`. See the README section "`codeman agent`" for the guards and `test/cli-agent.test.ts` for the pinned behaviour.
`codeman agent ls|spawn|send|wait|read|interrupt|rm|post|inbox|ack` (`src/cli-agent.ts`) is the command-line client for the endpoints above, for agents the skill does not reach (a pi or shell session, a machine without `jq`). It adds no route: `spawn <case>` is `POST /api/v1/quick-start`, and `spawn` with no case (the caller's cwd) or with `--working-dir <dir>` (an existing local directory; combining it with a case, or an empty or whitespace-only value, refuses with exit 4 before any request; so does a cwd that resolves to the home directory, since the server would install its hooks there, so HOME needs an explicit `--working-dir ~`) is `POST /api/v1/sessions` with `workingDir`, `mode`, `name` and `parentSessionId` followed by `POST /api/v1/sessions/:id/interactive` or `/shell` (the mode's `capabilities.startMode`), never retried; either form then runs `wait-output` on the mode's `capabilities.composerReadyMark` from the CLI registry where it declares one. `send` is `POST …/input` with `clientId`+`seq` (and `wait`/`waitTimeout` for `--wait` / `--until <signals>`; `delivered:false` without `duplicate` and `wait.ended` both exit 3 — the CLI never reports a dead worker as done), `wait` is `GET …/wait` (`--until`) or `GET …/wait-output` (`--match`, `from=buffer` by default), `read` is `GET …/last-response` or `GET …/terminal?tail=`, `interrupt` is `POST …/input` with a bare `\u001b`, `rm` is `DELETE …/sessions/:id`, and the inbox verbs map one to one onto the [agent inbox](#agent-inbox) routes (`post` → `POST …/inbox`, `inbox [id] [--wait]` → `GET …/inbox[?wait=]`, own inbox by default, `ack <ids…>` → `POST …/inbox/ack`); `ls` adds an `INBOX` column from `GET /api/v1/agent-inbox/summary` and drops it when that route does not answer. A fire-and-forget `send` to a sleeping wake-on-LAN host reads the route's `buffered` (own line, exit 0) and `dropped` (exit 1: the chunk is gone). An id may be the 8-character form `ls` prints, resolved through `GET /api/v1/sessions`; anything shorter refuses before any request, the same floor as `PARENT_SESSION_ID_MIN_PREFIX`. Every call carries `X-Codeman-Parent-Session`; only `spawn`'s quick-start carries `X-Codeman-Agent-Origin: codeman-agent-cli` (the agent-scratch label must never reach a request that cannot create the case directory). Basic auth comes from `CODEMAN_PASSWORD` or the data dir's `.env`. `--json` prints the envelope'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 (with `ready: false`, exit 1, the created id kept so the caller can inspect it). An unknown `--mode` refuses (exit 4) before any request, for a named case too. Server-side error codes are shown verbatim (`INVALID_INPUT: until=stop …` on a hook-less mode is not hidden); exit codes are `0` ok, `1` error, `2` timeout, `3` the session exited, `4` refused by a client-side guard (for `inbox --wait`, `3` only when `GET /api/v1/sessions` no longer lists the session; a wait the server ended otherwise, on a restart or a detach, is `2`). `post` takes text that starts with `-` as it is (a markdown file often does); text that is exactly an option (`--json`, `-h`) goes after `--`. See the README section "`codeman agent`" for the guards and `test/cli-agent.test.ts` for the pinned behaviour.

`codeman mcp` (`src/mcp-server.ts`) is a Model Context Protocol server on stdio over the same endpoints, for MCP clients: `list_sessions` (`GET /api/v1/sessions`), `start_session` (`POST /api/v1/quick-start` with `X-Codeman-Agent-Origin: codeman-mcp`, then `wait-output` on the mode's ready mark), `send_prompt` (`POST …/input` with `wait`/`waitTimeout`), `wait_for_session` (`GET …/wait` or `…/wait-output`), `read_output` (`GET …/last-response` or `…/terminal?tail=`), `interrupt_session` and `delete_session`. It adds no route. The URL comes from `CODEMAN_API_URL` or `--url`, never a guess; prompting, interrupting and deleting are limited to sessions the process started (or the calling session's own children) unless it runs with `--allow-any-session`. See the README section "MCP server" and `test/mcp-server.test.ts`.

Expand Down
20 changes: 19 additions & 1 deletion docs/wiki/Driving-Codeman-From-An-Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ act outside a Codeman session. One line in a case's `AGENTS.md` is enough: *"oth
```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)
Expand All @@ -53,6 +54,20 @@ codeman agent ack <msg-id> # remove what you hand
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, and an empty case
name (`spawn "$CASE"` with `CASE` unset) is refused rather than read as "no case",
as is an empty `--working-dir` (exit 4, before any request), 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
Expand All @@ -68,7 +83,10 @@ codeman agent rm "$SID" # any session except t
**Keep agent inbox across restarts** is on (see [Settings Reference](Settings-Reference)),
and goes when its session is deleted.
- **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`.
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.

## The MCP server

Expand Down
10 changes: 10 additions & 0 deletions plugins/codeman/skills/codeman/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ verbs over the same endpoints, with the guards below (no control bytes, no self-
no guessed URL) enforced in code, and it is also your own way through when this
machine has no `jq` for the preamble below.

For an existing project, use one command:
`codeman agent spawn --working-dir /path/to/project --mode codex`. With no case and
no `--working-dir`, `spawn` uses cwd. Give editing workers separate worktrees. A
positional case keeps quick-start behavior and cannot be combined with
`--working-dir`; an empty `--working-dir` (`"$WT"` unset) refuses with exit 4
rather than falling back to cwd, and a cwd that is HOME refuses too (hooks would
land in `~/.claude`; pass `--working-dir ~` to mean it). Directory launches keep parent lineage and never
mark the project as agent scratch. If startup fails or is ambiguous, the CLI
reports the exact created ID: inspect it before retrying or cleaning up.

## 0. Guard and bootstrap

If `CODEMAN_MUX` is not `1`, **stop and say so**. Do not guess an API URL; a server
Expand Down
2 changes: 1 addition & 1 deletion plugins/codeman/skills/codeman/reference/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ own head: the worker enforcing the cap is the one who has to be told about it.

| Job | Channel |
| --- | --- |
| spawn a worker, create its case | HTTP `quick-start` (the only path) |
| spawn a worker, create its case | HTTP `quick-start` (the only path that creates a case); an existing directory is `POST sessions` + `interactive` (`codeman agent spawn --working-dir`) |
| readiness, incl. the trust dialog | HTTP, Flow 1 (a message cannot answer a dialog) |
| deliver a task to a READY claude worker | **messaging** (preferred) or HTTP input |
| steer a BUSY claude worker mid-turn | **messaging** (read between the worker's tool calls; the HTTP path can only type into the composer, where text waits for the turn to end) |
Expand Down
2 changes: 1 addition & 1 deletion plugins/codeman/skills/codeman/reference/verbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ wrong directory.** `quick-start` with a new `caseName` does not find your repo:
|-------------------|------|------------------------------|
| a fresh scratch dir (throwaway experiments) | `POST /api/v1/quick-start {"caseName":"scratch-1","mode":"claude"}` with a **new** case name | Codeman creates the directory and **writes hooks**: `stop` and `blocked` fire, send-and-wait is trustworthy |
| a linked case (a real repo in the linked-cases registry) | same call with the linked name | **hooks installed at session create**, so `stop` fires here too. Not guaranteed: the operator can turn it off. Check |
| any other absolute path, e.g. a git worktree you made | `POST /api/v1/sessions {"workingDir":"/abs/path","mode":"claude"}` then `POST /api/v1/sessions/:id/interactive` | same: **hooks installed at session create**, subject to the same setting. Check |
| any other absolute path, e.g. a git worktree you made | `POST /api/v1/sessions {"workingDir":"/abs/path","mode":"claude"}` then `POST /api/v1/sessions/:id/interactive` (what `codeman agent spawn --working-dir /abs/path` sends) | same: **hooks installed at session create**, subject to the same setting. Check |

Read `.data.casePath` back from the `quick-start` response and check it is where you
meant. `caseName` accepts letters, digits, `-` and `_` only, and it resolves through
Expand Down
10 changes: 10 additions & 0 deletions skills/codeman/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ verbs over the same endpoints, with the guards below (no control bytes, no self-
no guessed URL) enforced in code, and it is also your own way through when this
machine has no `jq` for the preamble below.

For an existing project, use one command:
`codeman agent spawn --working-dir /path/to/project --mode codex`. With no case and
no `--working-dir`, `spawn` uses cwd. Give editing workers separate worktrees. A
positional case keeps quick-start behavior and cannot be combined with
`--working-dir`; an empty `--working-dir` (`"$WT"` unset) refuses with exit 4
rather than falling back to cwd, and a cwd that is HOME refuses too (hooks would
land in `~/.claude`; pass `--working-dir ~` to mean it). Directory launches keep parent lineage and never
mark the project as agent scratch. If startup fails or is ambiguous, the CLI
reports the exact created ID: inspect it before retrying or cleaning up.

## 0. Guard and bootstrap

If `CODEMAN_MUX` is not `1`, **stop and say so**. Do not guess an API URL; a server
Expand Down
Loading
Loading