Skip to content

feat(agent): COD-549 launch workers in existing project directories - #708

Open
aakhter wants to merge 3 commits into
Ark0N:masterfrom
aakhter:pr/COD-549-project-workers
Open

aakhter wants to merge 3 commits into
Ark0N:masterfrom
aakhter:pr/COD-549-project-workers

Conversation

@aakhter

@aakhter aakhter commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

codeman agent spawn requires a case name even when a worker should use an existing project. Allow an omitted case to use cwd and add --working-dir for another existing local directory. Explicit case names keep their current quick-start behavior.

Project launches validate the mode and directory, create and start the session through the existing APIs, and preserve parent lineage and server ownership checks. Real project directories receive no agent-scratch label. Rejected or ambiguous startup returns the exact created ID, one valid JSON document when requested, and an error exit without retrying. Includes CLI/skill documentation, the synchronized plugin mirror, and a minor changeset.

Validation:

  • 98 focused CLI, project-launch and plugin tests passed, including both startup error-output regressions.
  • Typecheck, lint, formatting, lockfile, plugin parity and diff checks passed.
  • Independent review confirmed the session-create response shape, mode-specific start endpoint and unchanged named-case behavior.

Paths must exist on the CLI host; normal server path and ownership validation still apply. This contribution is independent of the readiness-transport fix and offline-history commands.

@Ark0N

Ark0N commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Thanks for this, aakhter. This PR lets codeman agent spawn start a worker in the current directory, or in an existing directory given with --working-dir, through POST /api/v1/sessions plus the mode's start route, while named cases keep using quick-start.

I ran it against a real in-process server as well as your tests: claude and shell workers land in the given directory with the parent lineage intact and a started pane, no agent-case marker is written, and a CLI that is not installed is refused before any session exists. Typecheck, lint, format and the agent CLI, MCP, skill and plugin tests all pass.

A few small things:

  1. src/cli-agent.ts:542 and :544: options.caseName is tested for truthiness, so codeman agent spawn "" (for example spawn "$CASE" with $CASE unset) now launches a worker in the caller's cwd and exits 0. On master that request reached quick-start with caseName: "" and the schema refused it. Please branch on options.caseName !== undefined in both places so only an omitted case means "use cwd", and add a test for the empty string.
  2. Docs that still describe the old mapping: docs/api-reference.md:594 says spawn is POST /api/v1/quick-start and that --json prints the envelope's data; the codeman agent block in README.md (line 984) shows only spawn scratch-1; and the @fileoverview in src/cli-agent.ts (line 5) lists the endpoints the client uses. One sentence each covering POST /api/v1/sessions, POST /api/v1/sessions/:id/interactive|shell and the directory-launch JSON shape is enough.
  3. test/cli-agent-project-spawn.test.ts:38: every new test sets ready: false, but --ready is the default. Please add one test with ready: true on claude that pins the order create, /interactive, then wait-output on the created id, and one with json: false for a failed start (id alone on stdout, message on stderr, exit 1).

Nits:

  • src/cli-agent.ts:541: the unknown-mode check now also refuses named-case spawns locally (exit 4 instead of the server's 400 and exit 1). That is fine by itself, and it deserves a line in the changeset, since the description says named cases are unchanged.
  • src/cli-agent.ts:570: POST /api/sessions always answers { session: { id } }, so the created?.id fallback never runs against a real server, and the three tests that feed ok({ id: OTHER }) exercise only that fallback. Dropping it and using the real shape in the fakes keeps the tests aligned with the route.
  • skills/codeman/SKILL.md:44 (and the plugin mirror): the new paragraph's first line is about 170 characters and the rest is hard-wrapped. A reflow of both copies keeps them identical.

No need to push again for these: I will apply them on top when I merge, unless you would rather push them yourself first.

…ment the directory launch

`codeman agent spawn ""` (for example `spawn "$CASE"` with CASE unset)
used to launch a worker in the caller's cwd. Both case checks now test
`caseName !== undefined`, so an empty name reaches quick-start and is
refused by its schema as before.

Drop the `{ id }` fallback on the create answer: `POST /api/sessions`
answers `{ session: { id } }`, and the test fakes now use that shape.

Tests: an empty case name (unit and through the real argument parser
and HTTP transport), the claude `--ready` order create, /interactive,
wait-output on the created id, and a non-JSON failed start (id alone on
stdout, reason on stderr, exit 1).

Docs: the spawn mapping and directory-launch JSON shape in
docs/api-reference.md, the README and wiki `codeman agent` sections, the
cli-agent @fileoverview and the skill reference tables; reflow the new
SKILL.md paragraph (plugin mirror synced); the changeset notes the
local exit-4 refusal of an unknown mode for named cases.
@aakhter

aakhter commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and for testing it against a real server. I pushed all of it (c5ffea0), so there is nothing left for you to apply at merge.

  1. Empty case: both checks now use options.caseName !== undefined, so spawn "" goes to quick-start with caseName: "" and the schema refuses it, as on master. Two unit tests cover it (the empty name alone, and the empty name with --working-dir, which is refused like any case plus --working-dir). Two more tests run spawn "" and a bare spawn through the real commander parser and HTTP transport against a local server, which checks that commander really passes "" through.
  2. Docs: docs/api-reference.md, the README codeman agent block and its rules paragraph, and the @fileoverview now describe POST /api/v1/sessions followed by /interactive or /shell, plus the directory-launch --json shape { sessionId, workingDir, created: true, ready } (with startupError on a failed start). I made the same update in the wiki page and in the skill's reference/verbs.md and reference/messaging.md tables, which still said quick-start was the only spawn path.
  3. Tests: a ready: true claude test checks the order create, /interactive, then wait-output on the created id, and a json: false failed-start test checks that stdout has only the id, the reason goes to stderr, the exit code is 1 and no readiness wait runs.

Nits: the changeset now says an unknown --mode is refused locally with exit 4 for named cases too. I removed the created?.id fallback, the fakes now use { session: { id } }, and a new test checks that a bare { id } answer counts as a failure and starts nothing. I reflowed the SKILL.md paragraph and resynced the plugin mirror (sync-plugin.mjs --check is clean).

Typecheck, lint, format and the agent CLI, MCP, skill and plugin tests (201) all pass.

@Ark0N

Ark0N commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Thanks for the quick and thorough follow-up, aakhter. With c5ffea0, codeman agent spawn starts a worker in the current directory or in an existing directory given with --working-dir, through POST /api/v1/sessions plus the mode's start route, while named cases keep using quick-start. I checked every point from the first round and all of them landed: the empty case name now reaches quick-start, the docs and skill tables describe the directory launch, and the ready: true and json: false tests pin the order and the output. Typecheck, lint, format, the plugin mirror and the agent CLI, MCP, skill and plugin tests all pass.

One small thing is left, the flag version of the empty-case issue:

  1. src/cli-agent.ts:548: options.workingDir ?? process.cwd() only falls back on undefined, and commander passes --working-dir "" through as an empty string, which resolve() turns into the cwd. So codeman agent spawn --working-dir "$WT" --mode codex with $WT empty (a git worktree add that failed, say) starts an editing worker in the caller's own checkout and exits 0, the opposite of the separate-worktree advice in SKILL.md. Please refuse an empty (or whitespace-only) --working-dir with exit 4 before any request, with one unit test (agentSpawn(t.d, { ...base, workingDir: '' }) returns EXIT.refused with no calls) and one through the real parser (runSpawn(['--working-dir', '']) sends nothing).

Nit:

  • src/cli-agent.ts:379: the emitJson comment says the --json output is never a reshaped copy of the envelope's data, but a directory spawn now prints { sessionId, workingDir, created: true, ready }. One clause naming that exception keeps the comment true.

No need to push again for these: I will apply them on top when I merge, unless you would rather push them yourself first.

`codeman agent spawn --working-dir "$WT"` with WT empty (a failed
`git worktree add`, say) resolved '' to the caller's cwd and started an
editing worker in the caller's own checkout. An empty or
whitespace-only --working-dir now refuses with exit 4 before any
request.

The emitJson comment names the directory-spawn exception to "the
envelope's data": `{ sessionId, workingDir, created: true, ready }`.

Tests: unit (`''`, spaces, tab: exit 4, no calls) and through the real
argument parser and HTTP transport (nothing sent, exit 4).

Docs: README, api-reference, wiki, SKILL.md (plugin mirror synced) and
the changeset note the refusal.
@aakhter

aakhter commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. I pushed both so nothing is left to apply at merge:

  1. An empty or whitespace-only --working-dir now refuses with exit 4 before any request. Tests: agentSpawn with '', spaces and a tab returns EXIT.refused with no calls, and runSpawn(['--working-dir', '']) (and ' ') sends nothing and exits 4. README, api-reference, wiki, SKILL.md and the changeset mention it.

Nit: the emitJson comment now names the directory-spawn exception, { sessionId, workingDir, created: true, ready } (plus startupError on a failed start).

@Ark0N

Ark0N commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Thanks for the second follow-up, aakhter. With b26234d, codeman agent spawn starts a worker in the current directory, or in an existing directory given with --working-dir, through POST /api/v1/sessions plus the mode's start route, while named cases keep using quick-start.

Both points from the last round landed: an empty or whitespace-only --working-dir refuses with exit 4 before any request, and the emitJson comment names the directory-spawn exception. I also ran the directory launch against a real in-process server: shell and claude workers start in the given directory with the parent lineage and a live pane, and no agent-case marker is written. Typecheck, lint, format, frontend syntax, the plugin mirror check and the agent CLI, MCP, skill and plugin tests (202) pass, and CI is green.

One small thing is left:

  1. src/cli-agent.ts:558: a bare codeman agent spawn run from a session whose cwd is $HOME installs Codeman's hooks into ~/.claude/settings.local.json (and, with agent skill injection on, the skill into ~/.claude/skills, the user-level location every Claude session loads). POST /api/sessions skips workspace hooks when the caller names no workingDir to avoid exactly that (src/web/routes/session-routes.ts:1268-1274: the cwd fallback is $HOME under installer-created services), but the CLI resolves its own cwd and always sends it as an explicit workingDir, so the server cannot tell the two apart. I reproduced it against a real server: chdir to HOME, agentSpawn with no case and no flag, exit 0, and the settings file appears with the hooks block. A session created without a workingDir runs in the server's cwd, so its agent reaches this without any cd. Please refuse (exit 4, before any request) when the directory came from cwd and its realpath equals the realpath of os.homedir(), with a message to pass --working-dir ~ explicitly, plus one unit test. An explicit --working-dir ~ stays allowed, the same as POST /api/sessions with workingDir set to HOME today.

Nit:

  • docs/api-reference.md:594 lists readinessError in the directory-spawn --json shape while the README, the wiki page and the changeset do not, and ready itself is absent with --no-ready or for a mode without a ready mark (JSON.stringify drops undefined). One clause in each place ("ready when a readiness wait ran, readinessError when that call failed") keeps them exact.

No need to push again for these: I will apply them on top when I merge, unless you would rather push them yourself first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants