feat(lanes): auto-advance a codex lane's stage from its rollout detection
A codex session emits no hooks and rarely calls `ccam stage` on its own, so its card sat at `idle` forever while the amber "detected" badge was the only thing that ever moved. `codex-ingest.js` now advances the declared `stage` from its own written detection — the one place in CCAM where an inference moves the blue `current` ring, and only for `codex` lanes. It stays honest: gated on `recordDetection` having written (so forward-only, declared-wins and the detection TTL are inherited unchanged), passes NO evidence (the node renders `passed-no-evidence` amber, never `done`), and no template's last node carries detect rules, so a lane can never be auto-declared `done`. `setStage` grows a `keepDetection` option — its sole caller — so the detection columns survive the advance and the card keeps showing which command moved it instead of looking like an agent's own claim. Two levers then get the evidence auto-advance cannot supply: - `codex-nudge.js` now nudges on a stage held 2+ min with no evidence, instead of on a detection sitting ahead of the declared stage — after auto-advance the old condition can never fire, since the two now match. - `ccam lanes agents install` on a codex lane writes the marker-delimited `<!-- ccam:stage:start -->` block into the lane's own AGENTS.md — the one instruction channel Codex reads — instead of copying agent templates into `.claude/agents/`, which it never loads. Appended, user prose preserved, reinstall replaces exactly that block. Verified with a real `codex exec` run: stage moved to `watching-pr` with `evidence: null` and the detection signal intact. 1364 server tests pass.
This commit is contained in:
+57
-20
@@ -73,11 +73,14 @@ Codex does not emit Claude Code hooks (sessions, agent events, tool-execution tr
|
||||
**These are not bugs; they reflect Codex's architecture.** The stage you declare via `ccam stage` (or that the driving agent calls itself) still works; liveness is tracked by the lane's tmux pane (if the stack is up) and `dashboard_runs` table (recording every invocation). Cost tracking for Codex runs would require parsing Codex's own logs, a separate design not yet implemented.
|
||||
|
||||
**Stage detection is not in that list.** It does work for a codex lane, from
|
||||
Codex's own on-disk rollout journals rather than from hooks — see "Stage
|
||||
detection" below. What a codex lane cannot get for free is a *declared* stage:
|
||||
the run console never pre-fills a Claude slash command into a codex pane (Codex
|
||||
Codex's own on-disk rollout journals rather than from hooks, and unlike a claude
|
||||
lane a written detection there also **advances the lane's declared `stage`** —
|
||||
see "Stage detection" below. What a codex lane cannot get for free is
|
||||
*evidence*: an auto-advanced node renders `passed-no-evidence` (amber), never
|
||||
`done`, because only the session itself can say what it actually finished. The
|
||||
run console never pre-fills a Claude slash command into a codex pane (Codex
|
||||
would read `/ccam:ship-feature-lane` as prose), so a codex session declares
|
||||
stages only when something tells it to. Two things do:
|
||||
evidence only when something tells it to. Three things do:
|
||||
|
||||
- **An opening prompt checklist.** The Run Setup panel pre-fills a codex pane's
|
||||
first prompt with a plain-text list of `ccam stage <stage> --tool codex`
|
||||
@@ -86,14 +89,20 @@ stages only when something tells it to. Two things do:
|
||||
the same up-front priming a Claude lane gets from its pre-filled
|
||||
`/ccam:ship-feature-lane` skill command, just as plain text since Codex has
|
||||
no slash commands. See `LaneConsolePane.tsx`'s `promptPrefill`.
|
||||
- **A stale-detection nudge.** `server/lib/codex-nudge.js` sweeps every 60s
|
||||
- **An `AGENTS.md` block.** `ccam lanes agents install` on a codex lane writes
|
||||
a marker-delimited `<!-- ccam:stage:start -->` section into
|
||||
`<lane cwd>/AGENTS.md` — the one instruction channel Codex actually reads,
|
||||
since it never loads `.claude/agents/`. Appended, never overwriting your own
|
||||
prose, and a reinstall replaces exactly that block. (On a `claude` lane the
|
||||
same command still copies the two agent templates instead.)
|
||||
- **A missing-evidence nudge.** `server/lib/codex-nudge.js` sweeps every 60s
|
||||
(`DASHBOARD_CODEX_NUDGE_MS` to change, `0` to disable) for a codex lane whose
|
||||
fresh, non-stale `detected_stage` has sat ahead of its declared `stage` for
|
||||
at least two minutes, and types a one-line `[ccam] ... run: ccam stage ...`
|
||||
reminder straight into that lane's live tmux pane (`tmux.sendText`, skipped
|
||||
if the pane is at a bare shell prompt). It never writes `stage` or
|
||||
`detected_stage` itself — declared-wins and inferred-never-`done` are
|
||||
untouched — it only prods the session into making the real declaration.
|
||||
current stage has been held for at least two minutes with no evidence
|
||||
recorded, and types a one-line `[ccam] ... run: ccam stage ... --evidence
|
||||
"..."` reminder straight into that lane's live tmux pane (`tmux.sendText`,
|
||||
skipped if the pane is at a bare shell prompt). It never writes `stage`,
|
||||
`stages` or `detected_stage` itself — the evidence that results is a real
|
||||
declaration the session made.
|
||||
|
||||
## Destructive lane actions
|
||||
|
||||
@@ -852,9 +861,25 @@ shape a Claude hook delivers:
|
||||
| anything else (messages, reasoning, web search, tool OUTPUT) | ignored |
|
||||
|
||||
Everything past that point is shared: the same templates, the same
|
||||
forward-only/declared-wins rules, the same amber-never-green boundary. Two
|
||||
forward-only/declared-wins rules, the same amber-never-green boundary. Three
|
||||
codex-only details:
|
||||
|
||||
- **A written detection also advances the lane's declared `stage`.** This is
|
||||
the one place in CCAM where an inference moves the blue `current` ring, and
|
||||
it exists because nothing else can: a codex session has no hook and rarely
|
||||
calls `ccam stage` on its own, so without it the card sits at `idle` forever
|
||||
while the amber badge is the only thing that ever moves. It is gated on
|
||||
`recordDetection` having *written*, so it inherits that function's
|
||||
forward-only, declared-wins and TTL guards unchanged — a detection behind the
|
||||
declared stage still advances nothing. The `setStage` call passes **no
|
||||
evidence**, so every node it moves through renders `passed-no-evidence`
|
||||
(amber), never `done`: a detection sees a tool call, never its outcome. And
|
||||
it passes `keepDetection: true`, the sole exception to `setStage`'s clearing
|
||||
of the detection columns — here the detection is the *reason* for the
|
||||
declaration, so the card keeps showing which command caused it rather than
|
||||
looking like a claim the agent made. The last node of every template carries
|
||||
no `detect` rules, so this can never auto-declare a lane `done`.
|
||||
|
||||
- Only bytes appended since the last pass are read. On the first sight of a
|
||||
journal only its recent tail is read, and nothing at all when that file is
|
||||
already idle — replaying a thread that finished days ago would paint stages
|
||||
@@ -869,14 +894,15 @@ codex-only details:
|
||||
`provisioning` or `failed`). It writes nothing else: not `stage`, not
|
||||
`notes`, not `slot`/`ports`.
|
||||
|
||||
**A codex lane's *declared* stage is a separate matter.** CCAM cannot inject an
|
||||
instruction into a CLI it merely spawns, so a codex session declares a stage
|
||||
only if something tells it to — an `AGENTS.md` in the repository (this repo has
|
||||
one) is the only lever. A codex lane in a repo without one shows detected stages
|
||||
and no declarations. Note that from inside a codex session `ccam stage` needs to
|
||||
know which lane it is: a pane the dashboard started carries `CCAM_LANE_ID`, and
|
||||
anywhere else `--tool codex` disambiguates (see "Which lane a CLI command
|
||||
addresses" below).
|
||||
**A codex lane's *evidence* is a separate matter.** Auto-advance moves the ring
|
||||
but can never supply evidence, so every node a codex lane passes through stays
|
||||
amber until the session itself runs `ccam stage <node> --evidence "..."`. CCAM
|
||||
cannot inject an instruction into a CLI it merely spawns, so an `AGENTS.md` in
|
||||
the working copy is the only lever — `ccam lanes agents install` writes the
|
||||
marker-delimited block for you (see "Installing the QC/gate agents"). Note that
|
||||
from inside a codex session `ccam stage` needs to know which lane it is: a pane
|
||||
the dashboard started carries `CCAM_LANE_ID`, and anywhere else `--tool codex`
|
||||
disambiguates (see "Which lane a CLI command addresses" below).
|
||||
|
||||
### The evidence boundary
|
||||
|
||||
@@ -1413,6 +1439,17 @@ ccam lanes agents install
|
||||
|
||||
Writes both agent definitions (from the repo's own `agents/`, the same files the plugin ships) into `<lane>/.claude/agents/` and adds that directory to the lane's local `.git/info/exclude` (never the tracked `.gitignore` — this is a per-clone runtime concern, not an app-repo change). Idempotent and never automatic, the same shape as `ccam lanes proof-link` — a session installs the agents explicitly, once, before a lane's first run through Stage 6/7 (or after a CCAM upgrade ships updated templates — reinstalling overwrites, it doesn't merge).
|
||||
|
||||
**On a `codex` lane the same command installs something else.** Codex never
|
||||
reads `.claude/agents/`, so copying agent definitions there would be dead
|
||||
files. Instead it writes the stage-declaration block into
|
||||
`<lane>/AGENTS.md` — the project doc Codex *does* load from the working
|
||||
directory — delimited by `<!-- ccam:stage:start -->` / `<!-- ccam:stage:end -->`
|
||||
so a reinstall replaces exactly that block and nothing else. The file is
|
||||
appended to when it already exists (your own prose is preserved) and created
|
||||
when it does not; `installed` comes back as `["AGENTS.md"]`. This is the lever
|
||||
that gets a codex session to supply the *evidence* auto-advance cannot — see
|
||||
"Stage detection".
|
||||
|
||||
**Credentials are not embedded.** Unlike Shipyard's original per-lane agent generation, these templates carry no QA account credentials — this repo has no seeded-QA-account system yet. `qc-local` degrades gracefully: if it hits a login page with no credentials block present, it reports `LOCAL-QC: FAIL — login required, no seed-account mechanism configured for this profile` rather than guessing.
|
||||
|
||||
**`ticketer`, `dev-qc`, and `pr-reviewer`** are not ported yet — `ticketer` is invoked by Stage 9 (skipped), and `dev-qc` by Stage 13's dev-QC half (unavailable); `pr-reviewer` isn't referenced anywhere in this skill's text.
|
||||
|
||||
+3
-1
@@ -106,7 +106,9 @@ root holds all five (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`,
|
||||
`.claude/agents/` — the highest-priority agent source — which is what makes the
|
||||
bare `subagent_type: qc-local` in the pipeline skill resolve there. One source
|
||||
file, two ways to reach it; the skill names the bare form first and falls back
|
||||
to the `ccam:` form.
|
||||
to the `ccam:` form. On a `codex` lane the same command installs the
|
||||
`<!-- ccam:stage:start -->` block in the lane's `AGENTS.md` instead — Codex
|
||||
never reads `.claude/agents/`.
|
||||
|
||||
Because the plugin ships the hooks itself, `npm run install-hooks` is not needed
|
||||
for plugin users — and must not be run alongside it. Events carry no id, so two
|
||||
|
||||
Reference in New Issue
Block a user