mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
feat(agent): make coordination routes opt-in and address review
Gate inspect, stage and submit behind the synced agentCoordinationEnabled setting (default OFF, App Settings > Cross-session features, zh-CN strings). While off, all three routes answer 403 FORBIDDEN naming the setting, checked before session lookup; the CLI verbs print it verbatim. Remember the attributed Codex rollout per session and re-verify only its head on later calls, rescanning only date directories not older than it for a /new conflict. The first walk reads 1 KiB prefixes first, so a store of 2,100+ unrelated rollouts still attributes. Answer unexpected inspection errors with 422 OPERATION_FAILED and log the session id and error code only. Read the guarded composer glyph and footer from the CLI registry: codex workDetect.promptGlyph and a new workDetect.composerFooterLine capability, compiled through compileVersionRegex. Codex footer effort words are one shared list with modelDetect.screenLine. Fix the wiki API reference link, frame the versioning policy entry as a case of the experimental rule, join the CLAUDE.md Agent CLI paragraph, and update the changeset, skill reference and architecture notes.
This commit is contained in:
1 parent
1a2c3be0ea
commit
c78635a3d3
32 files changed
+848
-124
No files matched your search
+22
-3
@@ -593,11 +593,20 @@ request returns. The capture runs synchronous tmux calls on the server; the
|
||||
|
||||
**Experimental:** `codeman agent inspect`, `stage` and `submit`, and their
|
||||
`/api/v1/sessions/:id/inspect|stage|submit` routes (including `/api` aliases),
|
||||
are excluded from the stable CLI/HTTP contract. Their commands, flags, paths,
|
||||
are labeled experimental, so the versioning policy's rule for experimental
|
||||
surfaces applies (`docs/versioning-policy.md`, "Experimental agent
|
||||
coordination"): they are not part of the stable contract. Their commands, flags, paths,
|
||||
payloads and behavior may change or be removed in a minor or patch release.
|
||||
Pin a compatible Codeman version when automating them. Authentication and
|
||||
session ownership checks still apply.
|
||||
|
||||
**Opt-in:** all three routes answer `403 FORBIDDEN` while the synced
|
||||
`agentCoordinationEnabled` setting is off (the default; App Settings,
|
||||
Cross-session features, **Experimental agent coordination**), with a message
|
||||
naming it. The check runs before session lookup and body validation, and reads
|
||||
the saved setting on every request, so turning it on needs no restart.
|
||||
`codeman agent inspect|stage|submit` print that refusal verbatim (exit 1).
|
||||
|
||||
These authenticated routes use the standard envelope and session ownership checks.
|
||||
`/api/v1` aliases `/api`; an inaccessible session returns `404 NOT_FOUND` before
|
||||
any transcript, pane or receipt lookup.
|
||||
@@ -617,7 +626,14 @@ keeping UTF8/JSONL boundaries. Evidence bytes exclude response metadata and JSON
|
||||
escaping. Record scans cap at 256 KiB plus bounded identity anchors; attribution
|
||||
separately caps at 8192 directory entries, eight directory levels, 8 MiB of
|
||||
negative-filter prefix reads and 1 MiB of candidate metadata reads (including
|
||||
final identity verification). Unreachable workspace/home/metadata directories
|
||||
final identity verification). The negative filter reads 1 KiB of each rollout
|
||||
first and 4 KiB only when that window holds no complete `originator` field. Once
|
||||
a rollout is attributed, later calls re-verify only that file (same file identity,
|
||||
same strict metadata) and rescan only the `sessions/YYYY/MM/DD` directories not
|
||||
older than its own, for a second candidate such as a codex `/new`; a failed check
|
||||
falls back to one full walk. So the caps bound the first call, not every call.
|
||||
An unexpected read error is `422 OPERATION_FAILED` (logged with the session id
|
||||
and error code only), not a retryable `409`. Unreachable workspace/home/metadata directories
|
||||
are probed with a bounded past-cap probe before canonical reads and remain
|
||||
unattributed. A full incremental page can be continued with its cursor; only a
|
||||
first pending record too large for the budget reports an output-budget block.
|
||||
@@ -643,7 +659,10 @@ filesystem path is returned as cursor/identity metadata; evidence retains the
|
||||
owner's native record content, which can itself contain paths.
|
||||
|
||||
Guarded input initially requires a local characterized Codex composer, one
|
||||
unwrapped visible line, a recognized footer and a cursor at its exact end.
|
||||
unwrapped visible line, a recognized footer and a cursor at its exact end. The
|
||||
composer glyph and footer are the CLI registry's
|
||||
`capabilities.workDetect.promptGlyph` and `composerFooterLine`; a CLI that
|
||||
declares no `composerFooterLine` is refused.
|
||||
Unknown/working/modal/shell/remote/container composers fail closed. Stage accepts
|
||||
at most 8192 UTF8 bytes, no C0/C1/DEL controls, multiline text, or trailing
|
||||
whitespace (the existing mux transport trims it). Before typing, it checks the
|
||||
|
||||
@@ -111,7 +111,7 @@ Further detail: ⚠️ An ADOPTED container may back SEVERAL cases at different
|
||||
|
||||
⚠️ `external`, `hooks` and `altScreen` are three INDEPENDENT capabilities on purpose; deriving one from another shipped the `until=stop`-hangs-on-shell bug.
|
||||
|
||||
⚠️ Four capability fields carry a REGEX from config (`discovery.version.regex`, `capabilities.workDetect.workingLine`, `capabilities.workDetect.watchingLine` and `capabilities.workDetect.awaitingLine`) and all four must compile through `compileVersionRegex()`, which caps length and refuses nested quantifiers; `workingLine` is the one that runs on the PTY hot path.
|
||||
⚠️ Several capability fields carry a REGEX from config (`discovery.version.regex`, `capabilities.workDetect.workingLine`, `watchingLine`, `awaitingLine`, `blockedLine`, `composerFooterLine` and `capabilities.modelDetect.screenLine`) and all of them must compile through `compileVersionRegex()`, which caps length and refuses nested quantifiers; `workingLine` is the one that runs on the PTY hot path.
|
||||
|
||||
⚠️ **`param` is TWO namespaces.** `launch.params` keys, `env.configSetenv[].fromParam` and `capabilities.privilegedParams[].param` all name a LAUNCH PARAM; the legacy `<Mode>Config` wire field is a separate namespace, bridged only by `launch.legacyConfigAliases`. Getting `privilegedParams[].param` wrong is SILENT — it is the multi-user bypass clamp's only handle on a CLI's privilege switch, and a wrong name clamps nothing with no load error and no failing test — so `schema.ts` rejects an entry naming a param it never declared. Codex is the entry where the two names differ (`bypassApprovals` vs `dangerouslyBypassApprovals`) and therefore the one that catches a regression.
|
||||
|
||||
@@ -1264,8 +1264,15 @@ Its CLI knowledge is a GENERATED block (`npm run generate:cli-catalog`, markers
|
||||
## Agent inspection and guarded submission
|
||||
|
||||
These three operations are experimental across both CLI and HTTP, including the
|
||||
`/api/v1` paths and unversioned aliases. See `docs/versioning-policy.md` for the
|
||||
explicit stability exception; do not infer stability from the URL prefix.
|
||||
`/api/v1` paths and unversioned aliases: a case of the "labeled experimental" rule
|
||||
in `docs/versioning-policy.md`, so do not infer stability from the URL prefix.
|
||||
|
||||
⚠️ They are opt-in. All three routes answer `403 FORBIDDEN` with a message naming
|
||||
`agentCoordinationEnabled` (synced, default OFF, App Settings, Cross-session
|
||||
features) until it is on, the `cliManagementEnabled` / `mcpSyncEnabled` shape. The
|
||||
gate runs first, before session lookup or body validation, and reads
|
||||
`settings.json` fresh on every request, so a toggle applies without a restart.
|
||||
`codeman agent inspect|stage|submit` pass the 403 through verbatim.
|
||||
|
||||
`agent-session-routes.ts` is registered by `WebServer` with the other route
|
||||
modules. Ownership is checked before transcript/pane/receipt lookup. Inspection
|
||||
@@ -1275,8 +1282,24 @@ complete UTF8 JSONL records. A full page advances its cursor without claiming a
|
||||
blocked record. Opaque cursors bind caller/session/lifecycle/file identity; pane
|
||||
geometry is excluded so changing devices does not invalidate transcript paging.
|
||||
|
||||
Staging requires a fresh, known local Codex composer/caret capture. The footer
|
||||
uses the shared `CODEX_REASONING_EFFORTS` vocabulary. Before any write, available
|
||||
⚠️ Codex never prunes `~/.codex/sessions`, so attribution must not cost a store
|
||||
walk per call. The first call walks (negative-filter prefixes read 1 KiB first,
|
||||
4 KiB only when that window holds no complete `originator` field; non-UUID file
|
||||
names are skipped unread), then the attributed `{path, dev:ino}` and its strict
|
||||
metadata are remembered per session (`attributionMemo`, bounded LRU). A later call
|
||||
re-verifies only that file's head, then scans just the date directories not older
|
||||
than its own (`sessions/YYYY/MM/DD`, compared numerically; any non-numeric name is
|
||||
never pruned) for a second candidate, which is how a codex `/new` or a new owned
|
||||
rollout beside a configured resume is still caught. A failed check drops the memo
|
||||
and falls back to one full walk. Non-`InspectionConflict` errors are `422
|
||||
OPERATION_FAILED` (never a retryable `409`) and are logged with the session id and
|
||||
error code only, never the path.
|
||||
|
||||
Staging requires a fresh, known composer/caret capture. The composer glyph and
|
||||
footer are registry data (`capabilities.workDetect.promptGlyph` and
|
||||
`composerFooterLine`, compiled through `compileVersionRegex()`), and only a CLI
|
||||
declaring `composerFooterLine` takes guarded input (codex today). Codex's footer
|
||||
words come from the same `CODEX_EFFORT_WORDS` as its `modelDetect.screenLine`. Before any write, available
|
||||
columns are calculated from actual captured geometry and the rendered prefix,
|
||||
including indentation; reaching the last column or wrapping is refused. Stage
|
||||
cancels older verifier retries and sends no Enter. Receipts contain hashes and
|
||||
|
||||
+16
-3
@@ -47,9 +47,10 @@ interface CliEntry {
|
||||
env: CliEnv; // exports, tmux setenv keys, the env-override allowlist
|
||||
capabilities: CliCapabilities; // what every call site reads instead of the id
|
||||
// .workDetect?: { promptGlyph, workingLine, watchingLine?, watchingLines?, awaitingLine?,
|
||||
// blockedLine?, blockedLines? }
|
||||
// blockedLine?, blockedLines?, composerFooterLine? }
|
||||
// (how this CLI's pane shows work, work it started in the background, a turn
|
||||
// that ended waiting for workers it will resume from, and a dialog it is blocked on)
|
||||
// that ended waiting for workers it will resume from, a dialog it is blocked on,
|
||||
// and the rows under its idle composer that guarded input relies on)
|
||||
// .modelDetect?: { screenLine, screenLines? }
|
||||
// (where this CLI's own chrome names the model it runs: SessionState.displayModel)
|
||||
// .launchDefaults?: { [launchParam]: settingsKey }
|
||||
@@ -63,7 +64,7 @@ interface CliEntry {
|
||||
|
||||
### Regexes that come from config
|
||||
|
||||
Six capability fields carry a regular expression an override file can set: `discovery.version.regex`, `capabilities.workDetect.workingLine`, `capabilities.workDetect.watchingLine`, `capabilities.workDetect.awaitingLine`, `capabilities.workDetect.blockedLine` and `capabilities.modelDetect.screenLine`. All six go through `compileVersionRegex()`, which caps the source at 200 characters, refuses the nested-quantifier shapes that cause catastrophic backtracking, and returns `null` rather than throwing so every caller degrades instead of crashing.
|
||||
Seven capability fields carry a regular expression an override file can set: `discovery.version.regex`, `capabilities.workDetect.workingLine`, `capabilities.workDetect.watchingLine`, `capabilities.workDetect.awaitingLine`, `capabilities.workDetect.blockedLine`, `capabilities.workDetect.composerFooterLine` and `capabilities.modelDetect.screenLine`. All seven go through `compileVersionRegex()`, which caps the source at 200 characters, refuses the nested-quantifier shapes that cause catastrophic backtracking, and returns `null` rather than throwing so every caller degrades instead of crashing.
|
||||
|
||||
`workingLine` is the one that matters most, because it is compiled once per session and then run against every accumulated PTY chunk and every pane capture. A nested quantifier there is a ReDoS against the event loop for the whole server, not just that session. The guard therefore runs in two places, and neither is redundant: `schema.ts` rejects the entry at LOAD time so a bad pattern never reaches a session, and `_workingLinePattern()` in `session.ts` compiles through the same helper so the runtime cannot end up with a pattern the schema would have refused.
|
||||
|
||||
@@ -124,6 +125,18 @@ below, open PERMISSION items, which a watching label never acknowledges), so a f
|
||||
costs a wrong badge and cannot silence an alert. Before giving a CLI idle signals and a
|
||||
pattern, make sure its row is one the agent cannot write.
|
||||
|
||||
### The idle composer guarded input reads (`composerFooterLine`)
|
||||
|
||||
The experimental guarded `stage`/`submit` routes (`src/agent-staging.ts`, opt-in through
|
||||
`agentCoordinationEnabled`) type into a composer only after proving, from a fresh capture,
|
||||
that the newest `promptGlyph` row is the CLI's own composer at rest, with the caret at the
|
||||
exact end of its text. `composerFooterLine` is the second half of that proof: every
|
||||
non-blank row below the composer (at most three) must match it, one row at a time. A CLI
|
||||
that declares none never takes guarded input; only codex declares one, built from the same
|
||||
effort-word list as its `modelDetect.screenLine` (`CODEX_EFFORT_WORDS` in `stock.ts`), so a
|
||||
codex footer change is a one-place edit. Like `blockedLine`, the rows it sees are near the
|
||||
foot of the screen, so a pattern must anchor on chrome only that CLI draws there.
|
||||
|
||||
### Dialogs a CLI blocks on (`blockedLine`)
|
||||
|
||||
`blockedLine` is the screen-read source of the `blocked` state for a CLI with no hook to
|
||||
|
||||
+16
-11
@@ -64,17 +64,22 @@ These may change in a **MINOR** (or even PATCH) release without a MAJOR bump:
|
||||
|
||||
### Experimental agent coordination
|
||||
|
||||
`codeman agent inspect`, `codeman agent stage` and `codeman agent submit` are
|
||||
experimental. The matching `GET /api/v1/sessions/:id/inspect`,
|
||||
`POST /api/v1/sessions/:id/stage` and `POST /api/v1/sessions/:id/submit` routes,
|
||||
including their unversioned `/api` aliases, share this designation. This is an
|
||||
explicit exception to both the CLI and HTTP stability promises above: these
|
||||
commands, flags, endpoint paths, payloads and behavior may change or be removed
|
||||
in a MINOR or PATCH release without a stable-surface deprecation period. Pin a
|
||||
compatible Codeman version when automating them. Existing authentication,
|
||||
ownership and shared response-envelope rules still apply. Other agent commands
|
||||
and API routes retain their existing stability guarantees. Promotion to stable
|
||||
requires an explicit documented decision.
|
||||
`codeman agent inspect`, `codeman agent stage` and `codeman agent submit`, and
|
||||
the matching `GET /api/v1/sessions/:id/inspect`, `POST /api/v1/sessions/:id/stage`
|
||||
and `POST /api/v1/sessions/:id/submit` routes (with their `/api` aliases, since
|
||||
every `/api` route is also served under `/api/v1`), are labeled experimental, so
|
||||
item 3 above applies to them: their commands, flags, endpoint paths, payloads and
|
||||
behavior may change or be removed in a MINOR or PATCH release. They are also
|
||||
opt-in: the routes answer `403 FORBIDDEN` with a message naming the synced
|
||||
`agentCoordinationEnabled` setting (default OFF) until it is turned on, so
|
||||
automation cannot come to depend on them without choosing to. Pin a compatible
|
||||
Codeman version when automating them. Existing authentication, ownership and
|
||||
shared response-envelope rules still apply. The session behavior underneath them
|
||||
(input revision tracking in the session's write paths, the SubmitVerifier
|
||||
re-press going through the shared write boundary) runs for every session and is
|
||||
covered like any other stable behavior. Other agent commands and API routes keep
|
||||
their existing guarantees. Promotion to stable requires an explicit documented
|
||||
decision.
|
||||
|
||||
## Deprecation policy
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ SID=$(codeman agent spawn scratch-1 --mode claude) # quick-start + wait f
|
||||
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)
|
||||
codeman agent inspect "$SID" --max-bytes 8192 --json # recent evidence + opaque cursor (pane snapshots are labeled)
|
||||
codeman agent stage "$SID" --message-file /tmp/prompt.txt --json # no Enter; known local Codex composer only
|
||||
codeman agent submit "$SID" --stage-id "$STAGE_ID" --confirm-sha "$SHA256" --json # one conditional Enter
|
||||
codeman agent inspect "$SID" --max-bytes 8192 --json # experimental, opt-in: recent evidence + opaque cursor
|
||||
codeman agent stage "$SID" --message-file /tmp/prompt.txt --json # experimental, opt-in: no Enter; local Codex composer only
|
||||
codeman agent submit "$SID" --stage-id "$STAGE_ID" --confirm-sha "$SHA256" --json # experimental, opt-in: one conditional Enter
|
||||
codeman agent send "$SID" 'run the tests, then print WORKDONE followed by _4711' # hook-less modes: the marker in halves …
|
||||
codeman agent wait "$SID" --match WORKDONE_4711 # … and the wait on the joined form
|
||||
codeman agent interrupt "$SID" # a bare ESC, conversation intact
|
||||
@@ -70,6 +70,15 @@ codeman agent rm "$SID" # any session except t
|
||||
`ls` shows each session's mail in an INBOX column. Mail lives in memory unless
|
||||
**Keep agent inbox across restarts** is on (see [Settings Reference](Settings-Reference)),
|
||||
and goes when its session is deleted.
|
||||
- **`inspect`, `stage`, `submit`** are experimental and opt-in: they answer `403 FORBIDDEN`
|
||||
naming the setting until **Experimental agent coordination** is on (see
|
||||
[Settings Reference](Settings-Reference); `agentCoordinationEnabled`, synced, off by
|
||||
default), and they may change in any release. `inspect` returns bounded evidence with an
|
||||
opaque cursor. `stage` types one printable UTF8 line without trailing whitespace into a
|
||||
local Codex composer, with no Enter, and returns a receipt that expires after two minutes;
|
||||
`submit` sends one Enter only if the composer still shows exactly that text, and never
|
||||
replays it. `send --no-enter` alone has no receipt. Details:
|
||||
[API reference](https://github.com/Ark0N/Codeman/blob/master/docs/api-reference.md#experimental-bounded-agent-inspection-and-guarded-input).
|
||||
- **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`.
|
||||
|
||||
@@ -267,5 +276,3 @@ failing the spawn, because a cosmetic field must never break a worker.
|
||||
- [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.
|
||||
|
||||
Bounded inspection and guarded stage/submit details: [API reference](../api-reference.md#bounded-agent-inspection-and-guarded-input). Stage accepts one printable UTF8 line without trailing whitespace; receipts expire after two minutes. Submit refuses changed/unprovable composers and never automatically replays Enter. `send --no-enter` alone has no receipt.
|
||||
@@ -106,6 +106,14 @@ memory and a restart empties it; switching it off deletes the file and keeps wha
|
||||
memory. Messages expire 12 hours after they were posted either way. See
|
||||
[Driving Codeman From An Agent](Driving-Codeman-From-An-Agent#the-codeman-agent-commands).
|
||||
|
||||
**Experimental agent coordination** (under **Cross-session features**, synced, off by
|
||||
default) turns on the `codeman agent inspect`, `stage` and `submit` commands: bounded,
|
||||
paged reads of another session's output, and typing a prompt into a local Codex composer
|
||||
that is sent with one Enter only after Codeman checks it is unchanged. Off, their HTTP
|
||||
routes answer `403 FORBIDDEN` naming the setting (`agentCoordinationEnabled`). They are
|
||||
experimental and may change in any release. See
|
||||
[Driving Codeman From An Agent](Driving-Codeman-From-An-Agent#the-codeman-agent-commands).
|
||||
|
||||
### Appearance
|
||||
|
||||
| Setting | Notes |
|
||||
|
||||
Reference in new issue
Block a user