From a18b16739bb6fcd8c78884377c09dce147a34f55 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:32:34 -0700 Subject: [PATCH 1/6] chore(porch): 1709 init air --- .../status.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml diff --git a/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml new file mode 100644 index 000000000..423e2ec3e --- /dev/null +++ b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml @@ -0,0 +1,14 @@ +id: '1709' +title: arch-save-accept-a-next-task-i +protocol: air +phase: implement +plan_phases: [] +current_plan_phase: null +gates: + pr: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-09-19T14:32:33.742Z' +updated_at: '2026-09-19T14:32:33.743Z' From dff523431f1b461a5648890444358086127bd69f Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:36:57 -0700 Subject: [PATCH 2/6] [Air #1709] feat: /arch-save takes a next task, /arch-init starts on it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An owner who already knows what the refreshed session should do first had nowhere to put that instruction: /arch-save saved, cleared and re-inited, and the architect came back oriented but idle, waiting to be told again. /arch-save now takes the next task as free text. Argument resolution runs `afx whoami` first (unconditionally now — the split compares the first token against the resolved name), then: empty $ARGUMENTS means no next task; a first token matching the whoami name is the name, with the remainder as the task; any other leading token makes the whole of $ARGUMENTS the task. /arch-save and /arch-save are unchanged. The task is written verbatim into the state file's banner as a NEXT TASK line, directly after the intentional-clear line. New text replaces an existing line; no text preserves one, since a surviving line means the previous cycle never resumed to pick it up. /arch-init reports it in the orient block and starts on it as the first action of the resumed session, then deletes the line and logs the pickup so a second re-init cannot re-run it. The standard limits are unchanged: a next task naming a gate, merge, release or Tower restart produces a prepared request for the word, not the act — a saved instruction is an instruction, not a pre-spent approval. All four copies of each skill stay byte-identical; the new test guards that for arch-init too, which spec-1134 does not (it compares only .claude). Co-Authored-By: Claude Opus 5 --- .claude/skills/arch-init/SKILL.md | 38 +++- .claude/skills/arch-save/SKILL.md | 64 ++++++- .codex/skills/arch-init/SKILL.md | 38 +++- .codex/skills/arch-save/SKILL.md | 64 ++++++- .../.claude/skills/arch-init/SKILL.md | 38 +++- .../.claude/skills/arch-save/SKILL.md | 64 ++++++- .../.codex/skills/arch-init/SKILL.md | 38 +++- .../.codex/skills/arch-save/SKILL.md | 64 ++++++- codev/state/air-1709_thread.md | 50 ++++++ .../__tests__/air-1709-next-task.test.ts | 166 ++++++++++++++++++ 10 files changed, 552 insertions(+), 72 deletions(-) create mode 100644 codev/state/air-1709_thread.md create mode 100644 packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts diff --git a/.claude/skills/arch-init/SKILL.md b/.claude/skills/arch-init/SKILL.md index fb6a11cc7..a90936ad5 100644 --- a/.claude/skills/arch-init/SKILL.md +++ b/.claude/skills/arch-init/SKILL.md @@ -46,12 +46,29 @@ name in a multi-architect workspace). (`codev/state/*.md`); never commit them. Builder `*_thread.md` files are the opposite: versioned, shipping with each builder PR. -3. **Confirm identity + orient, then follow the state file.** In one tight - block, report: who you now are (name + one-line role from the banner, if - present), the file you read, and the current-state / open-loops summary - from the most recent dated section (or the file's leading content if it - has no dated sections). Then carry out whatever the state file says to do - on resume. Do not invent a new agenda — resume the one the state file +3. **Confirm identity + orient.** In one tight block, report: who you now are + (name + one-line role from the banner, if present), the file you read, and + the current-state / open-loops summary from the most recent dated section + (or the file's leading content if it has no dated sections). If the banner + carries a `NEXT TASK` line, report it too, as + `Next task from the owner at save time: `. + +4. **Start on the next task, if the banner carries one.** It is the **first + action of the resumed session**, ahead of the general resume agenda — the + owner wrote it at save time precisely so it would not have to be typed + again once you came back. Begin it without waiting for a further prompt. + - It carries the owner's authority the way any owner message does, **with + the standard limits unchanged**. A next task never by itself approves a + porch gate, merges a PR, cuts a release, restarts Tower, or performs any + other act that needs a per-occasion word. If the next task *is* such an + act, prepare it and ask for the word live: a saved instruction is an + instruction, not a pre-spent approval. + - **Once you have started, delete the `NEXT TASK` line from the banner** + and record the pickup as a log entry (`picked up next task: `). + A second re-init, or the next `/arch-save`, must not re-run it. + +5. **Then follow the state file.** Carry out whatever it says to do on + resume. Do not invent a new agenda — resume the one the state file describes. ## Saving your state (and knowing when to `/clear`) @@ -118,9 +135,12 @@ Do not repeat it, and do not prompt for it at any other time. **`/arch-save` packages this whole loop**, and is the preferred path when the owner directs a refresh: it stops your monitors, writes the pruned state file, clears, and schedules `/arch-init` to bring you back — in that order, which is -the part that matters. The save discipline above is what it performs at its -step 3, so this section remains the source of truth for *how to write the -file*; `/arch-save` is the source of truth for *the sequence*. The manual path +the part that matters. It also accepts a **next task** as free text +(`/arch-save file and spawn that issue`), which it writes into the banner as a +`NEXT TASK` line for step 4 above to pick up. The save discipline above is +what it performs at its step 3, so this section remains the source of truth +for *how to write the file*; `/arch-save` is the source of truth for *the +sequence*. The manual path (save → human clears → `/arch-init`) stays valid and is the fallback when Tower is unavailable. diff --git a/.claude/skills/arch-save/SKILL.md b/.claude/skills/arch-save/SKILL.md index 7f50280de..e531d7809 100644 --- a/.claude/skills/arch-save/SKILL.md +++ b/.claude/skills/arch-save/SKILL.md @@ -1,7 +1,7 @@ --- name: arch-save description: Save an architect's state, clear its context, and re-init automatically — the packaged save→clear→re-init refresh cycle. Use when the owner directs a context refresh, or says "/arch-save", "save and clear", "refresh your context". Runs on the owner's direction; an architect does not invoke it autonomously mid-task. Counterpart to /arch-init, which recovers the state this writes. -argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +argument-hint: "[name] [next task] (e.g. main; or: file and spawn that issue)" --- # /arch-save — save state, clear, and come back as yourself @@ -9,7 +9,18 @@ argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" Long sessions accumulate stale context. This is the deliberate cure: you choose the moment, you choose what survives, and a fresh session resumes from what you wrote. -`$ARGUMENTS` is the architect name (e.g. `main`). Omit it to auto-detect. +`$ARGUMENTS` carries the architect name, a **next task** for the session that comes back, +or both: + +``` +/arch-save save, clear, re-init, then wait +/arch-save file and spawn that issue … and start on that first, unprompted +/arch-save main file and spawn that issue explicit name, then the next task +``` + +The next task is how an owner who already knows what the refreshed session should do first +says so once, at the moment they direct the refresh, instead of watching for the re-init to +land and typing it again. ## When NOT to run this @@ -28,20 +39,36 @@ action. A mid-task snapshot resumes into confusion. Do these in order. **The order is the feature** — step 3 must precede step 4, because the context that knows what to write is the one about to be destroyed. -### 1. Resolve your name +### 1. Resolve your name, then split off the next task -If `$ARGUMENTS` is non-empty, that is your name. Otherwise run `afx whoami` and use the -reported `name` when `type: architect`. +**Always run `afx whoami` first**, whatever `$ARGUMENTS` holds — the split below cannot +tell a name from a task until it knows your name. The name rules are unchanged: +- `type: architect` → the reported `name` is yours. - `type: builder` → **STOP.** This terminal is a builder. Report the mismatch. -- Non-zero exit → **STOP** and ask which architect you are. Do **not** guess, and do not - default to `main` — writing another architect's state file is the exact failure - `/arch-init` exists to prevent (#1094). +- Non-zero exit → you have no name yet. A single-token `$ARGUMENTS` that validates as a + name is still accepted as one; anything else → **STOP** and ask which architect you are. + Do **not** guess, and do not default to `main` — writing another architect's state file + is the exact failure `/arch-init` exists to prevent (#1094). **Validate the name before building any path**: `[a-z][a-z0-9-]*`, at most 64 characters. Reject slashes, `..`, uppercase, spaces. Never interpolate an unvalidated name into `codev/state/.md`. +Then split `$ARGUMENTS`: + +1. `$ARGUMENTS` is empty → name from `afx whoami`, and there is no next task. +2. The **first whitespace-separated token** equals the whoami name (or, when whoami could + not resolve one, is a single token that validates as a name) → that token is the name, + and the **remainder** is the next-task text, which may be empty. +3. Otherwise → the **whole** of `$ARGUMENTS` is the next-task text, and the name comes + from `afx whoami`. + +So `/arch-save` and `/arch-save main` behave exactly as they always have. The one +ambiguity — a next task whose first word happens to be your own architect name — is +accepted rather than worked around: lead with the explicit name +(`/arch-save main main is stalled, look at it`) to disambiguate. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. @@ -75,6 +102,21 @@ first (`cp codev/state/.md codev/state/..bak.md`) is cheap insurance **Content guardrails.** No secrets — tokens, keys, credentials. No transcript dumps, no raw tool output. Only: current focus, open loops, and what a fresh session needs to resume. +**Write the next task into the banner**, if step 1 produced one. Add a single line to the +banner block, directly after the `⭐ THIS /clear IS INTENTIONAL` line: + +``` +# ⏭ NEXT TASK (owner-directed at save, 2026-09-19T02:10Z): file and spawn that issue +``` + +- **Verbatim.** Collapse newlines to spaces; do not paraphrase, expand, or "helpfully" + plan it. Those are the owner's words, and the context writing them down is the one about + to be destroyed — this is the last moment at which a paraphrase can be caught. +- **New text replaces** any existing NEXT TASK line. +- **No new text preserves an existing one.** A NEXT TASK line still in the banner means the + previous cycle never came back to pick it up; dropping it silently would lose an owner + instruction that nothing else is holding. + Use the template at the end of this document. ### 4. Clear @@ -160,12 +202,16 @@ literal text on the front of the next message never executes. The structure below comes from a live run of this cycle. Every element earns its place; keep them all, including a `MONITORS:` line even when the answer is "none armed" — an -omitted monitor list is indistinguishable from a forgotten one. +omitted monitor list is indistinguishable from a forgotten one. The `NEXT TASK` line is the +single exception: it appears only when there is one. ``` # architect — state (vNN, ~HH:MM UTC — , DELIBERATE /clear cycle) # ⭐ THIS /clear IS INTENTIONAL (owner-directed context refresh). On re-init: normal # /arch-init flow, then: +# ⏭ NEXT TASK (owner-directed at save, ): +# — OPTIONAL; present only when the save carried one. /arch-init starts here before the +# agenda below, then deletes this line and logs the pickup. # 1. MONITORS: — watch target, # cadence, alert pattern. Self-test once before trusting alerts. ("none armed" is a # valid and complete answer.) diff --git a/.codex/skills/arch-init/SKILL.md b/.codex/skills/arch-init/SKILL.md index fb6a11cc7..a90936ad5 100644 --- a/.codex/skills/arch-init/SKILL.md +++ b/.codex/skills/arch-init/SKILL.md @@ -46,12 +46,29 @@ name in a multi-architect workspace). (`codev/state/*.md`); never commit them. Builder `*_thread.md` files are the opposite: versioned, shipping with each builder PR. -3. **Confirm identity + orient, then follow the state file.** In one tight - block, report: who you now are (name + one-line role from the banner, if - present), the file you read, and the current-state / open-loops summary - from the most recent dated section (or the file's leading content if it - has no dated sections). Then carry out whatever the state file says to do - on resume. Do not invent a new agenda — resume the one the state file +3. **Confirm identity + orient.** In one tight block, report: who you now are + (name + one-line role from the banner, if present), the file you read, and + the current-state / open-loops summary from the most recent dated section + (or the file's leading content if it has no dated sections). If the banner + carries a `NEXT TASK` line, report it too, as + `Next task from the owner at save time: `. + +4. **Start on the next task, if the banner carries one.** It is the **first + action of the resumed session**, ahead of the general resume agenda — the + owner wrote it at save time precisely so it would not have to be typed + again once you came back. Begin it without waiting for a further prompt. + - It carries the owner's authority the way any owner message does, **with + the standard limits unchanged**. A next task never by itself approves a + porch gate, merges a PR, cuts a release, restarts Tower, or performs any + other act that needs a per-occasion word. If the next task *is* such an + act, prepare it and ask for the word live: a saved instruction is an + instruction, not a pre-spent approval. + - **Once you have started, delete the `NEXT TASK` line from the banner** + and record the pickup as a log entry (`picked up next task: `). + A second re-init, or the next `/arch-save`, must not re-run it. + +5. **Then follow the state file.** Carry out whatever it says to do on + resume. Do not invent a new agenda — resume the one the state file describes. ## Saving your state (and knowing when to `/clear`) @@ -118,9 +135,12 @@ Do not repeat it, and do not prompt for it at any other time. **`/arch-save` packages this whole loop**, and is the preferred path when the owner directs a refresh: it stops your monitors, writes the pruned state file, clears, and schedules `/arch-init` to bring you back — in that order, which is -the part that matters. The save discipline above is what it performs at its -step 3, so this section remains the source of truth for *how to write the -file*; `/arch-save` is the source of truth for *the sequence*. The manual path +the part that matters. It also accepts a **next task** as free text +(`/arch-save file and spawn that issue`), which it writes into the banner as a +`NEXT TASK` line for step 4 above to pick up. The save discipline above is +what it performs at its step 3, so this section remains the source of truth +for *how to write the file*; `/arch-save` is the source of truth for *the +sequence*. The manual path (save → human clears → `/arch-init`) stays valid and is the fallback when Tower is unavailable. diff --git a/.codex/skills/arch-save/SKILL.md b/.codex/skills/arch-save/SKILL.md index 7f50280de..e531d7809 100644 --- a/.codex/skills/arch-save/SKILL.md +++ b/.codex/skills/arch-save/SKILL.md @@ -1,7 +1,7 @@ --- name: arch-save description: Save an architect's state, clear its context, and re-init automatically — the packaged save→clear→re-init refresh cycle. Use when the owner directs a context refresh, or says "/arch-save", "save and clear", "refresh your context". Runs on the owner's direction; an architect does not invoke it autonomously mid-task. Counterpart to /arch-init, which recovers the state this writes. -argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +argument-hint: "[name] [next task] (e.g. main; or: file and spawn that issue)" --- # /arch-save — save state, clear, and come back as yourself @@ -9,7 +9,18 @@ argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" Long sessions accumulate stale context. This is the deliberate cure: you choose the moment, you choose what survives, and a fresh session resumes from what you wrote. -`$ARGUMENTS` is the architect name (e.g. `main`). Omit it to auto-detect. +`$ARGUMENTS` carries the architect name, a **next task** for the session that comes back, +or both: + +``` +/arch-save save, clear, re-init, then wait +/arch-save file and spawn that issue … and start on that first, unprompted +/arch-save main file and spawn that issue explicit name, then the next task +``` + +The next task is how an owner who already knows what the refreshed session should do first +says so once, at the moment they direct the refresh, instead of watching for the re-init to +land and typing it again. ## When NOT to run this @@ -28,20 +39,36 @@ action. A mid-task snapshot resumes into confusion. Do these in order. **The order is the feature** — step 3 must precede step 4, because the context that knows what to write is the one about to be destroyed. -### 1. Resolve your name +### 1. Resolve your name, then split off the next task -If `$ARGUMENTS` is non-empty, that is your name. Otherwise run `afx whoami` and use the -reported `name` when `type: architect`. +**Always run `afx whoami` first**, whatever `$ARGUMENTS` holds — the split below cannot +tell a name from a task until it knows your name. The name rules are unchanged: +- `type: architect` → the reported `name` is yours. - `type: builder` → **STOP.** This terminal is a builder. Report the mismatch. -- Non-zero exit → **STOP** and ask which architect you are. Do **not** guess, and do not - default to `main` — writing another architect's state file is the exact failure - `/arch-init` exists to prevent (#1094). +- Non-zero exit → you have no name yet. A single-token `$ARGUMENTS` that validates as a + name is still accepted as one; anything else → **STOP** and ask which architect you are. + Do **not** guess, and do not default to `main` — writing another architect's state file + is the exact failure `/arch-init` exists to prevent (#1094). **Validate the name before building any path**: `[a-z][a-z0-9-]*`, at most 64 characters. Reject slashes, `..`, uppercase, spaces. Never interpolate an unvalidated name into `codev/state/.md`. +Then split `$ARGUMENTS`: + +1. `$ARGUMENTS` is empty → name from `afx whoami`, and there is no next task. +2. The **first whitespace-separated token** equals the whoami name (or, when whoami could + not resolve one, is a single token that validates as a name) → that token is the name, + and the **remainder** is the next-task text, which may be empty. +3. Otherwise → the **whole** of `$ARGUMENTS` is the next-task text, and the name comes + from `afx whoami`. + +So `/arch-save` and `/arch-save main` behave exactly as they always have. The one +ambiguity — a next task whose first word happens to be your own architect name — is +accepted rather than worked around: lead with the explicit name +(`/arch-save main main is stalled, look at it`) to disambiguate. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. @@ -75,6 +102,21 @@ first (`cp codev/state/.md codev/state/..bak.md`) is cheap insurance **Content guardrails.** No secrets — tokens, keys, credentials. No transcript dumps, no raw tool output. Only: current focus, open loops, and what a fresh session needs to resume. +**Write the next task into the banner**, if step 1 produced one. Add a single line to the +banner block, directly after the `⭐ THIS /clear IS INTENTIONAL` line: + +``` +# ⏭ NEXT TASK (owner-directed at save, 2026-09-19T02:10Z): file and spawn that issue +``` + +- **Verbatim.** Collapse newlines to spaces; do not paraphrase, expand, or "helpfully" + plan it. Those are the owner's words, and the context writing them down is the one about + to be destroyed — this is the last moment at which a paraphrase can be caught. +- **New text replaces** any existing NEXT TASK line. +- **No new text preserves an existing one.** A NEXT TASK line still in the banner means the + previous cycle never came back to pick it up; dropping it silently would lose an owner + instruction that nothing else is holding. + Use the template at the end of this document. ### 4. Clear @@ -160,12 +202,16 @@ literal text on the front of the next message never executes. The structure below comes from a live run of this cycle. Every element earns its place; keep them all, including a `MONITORS:` line even when the answer is "none armed" — an -omitted monitor list is indistinguishable from a forgotten one. +omitted monitor list is indistinguishable from a forgotten one. The `NEXT TASK` line is the +single exception: it appears only when there is one. ``` # architect — state (vNN, ~HH:MM UTC — , DELIBERATE /clear cycle) # ⭐ THIS /clear IS INTENTIONAL (owner-directed context refresh). On re-init: normal # /arch-init flow, then: +# ⏭ NEXT TASK (owner-directed at save, ): +# — OPTIONAL; present only when the save carried one. /arch-init starts here before the +# agenda below, then deletes this line and logs the pickup. # 1. MONITORS: — watch target, # cadence, alert pattern. Self-test once before trusting alerts. ("none armed" is a # valid and complete answer.) diff --git a/codev-skeleton/.claude/skills/arch-init/SKILL.md b/codev-skeleton/.claude/skills/arch-init/SKILL.md index fb6a11cc7..a90936ad5 100644 --- a/codev-skeleton/.claude/skills/arch-init/SKILL.md +++ b/codev-skeleton/.claude/skills/arch-init/SKILL.md @@ -46,12 +46,29 @@ name in a multi-architect workspace). (`codev/state/*.md`); never commit them. Builder `*_thread.md` files are the opposite: versioned, shipping with each builder PR. -3. **Confirm identity + orient, then follow the state file.** In one tight - block, report: who you now are (name + one-line role from the banner, if - present), the file you read, and the current-state / open-loops summary - from the most recent dated section (or the file's leading content if it - has no dated sections). Then carry out whatever the state file says to do - on resume. Do not invent a new agenda — resume the one the state file +3. **Confirm identity + orient.** In one tight block, report: who you now are + (name + one-line role from the banner, if present), the file you read, and + the current-state / open-loops summary from the most recent dated section + (or the file's leading content if it has no dated sections). If the banner + carries a `NEXT TASK` line, report it too, as + `Next task from the owner at save time: `. + +4. **Start on the next task, if the banner carries one.** It is the **first + action of the resumed session**, ahead of the general resume agenda — the + owner wrote it at save time precisely so it would not have to be typed + again once you came back. Begin it without waiting for a further prompt. + - It carries the owner's authority the way any owner message does, **with + the standard limits unchanged**. A next task never by itself approves a + porch gate, merges a PR, cuts a release, restarts Tower, or performs any + other act that needs a per-occasion word. If the next task *is* such an + act, prepare it and ask for the word live: a saved instruction is an + instruction, not a pre-spent approval. + - **Once you have started, delete the `NEXT TASK` line from the banner** + and record the pickup as a log entry (`picked up next task: `). + A second re-init, or the next `/arch-save`, must not re-run it. + +5. **Then follow the state file.** Carry out whatever it says to do on + resume. Do not invent a new agenda — resume the one the state file describes. ## Saving your state (and knowing when to `/clear`) @@ -118,9 +135,12 @@ Do not repeat it, and do not prompt for it at any other time. **`/arch-save` packages this whole loop**, and is the preferred path when the owner directs a refresh: it stops your monitors, writes the pruned state file, clears, and schedules `/arch-init` to bring you back — in that order, which is -the part that matters. The save discipline above is what it performs at its -step 3, so this section remains the source of truth for *how to write the -file*; `/arch-save` is the source of truth for *the sequence*. The manual path +the part that matters. It also accepts a **next task** as free text +(`/arch-save file and spawn that issue`), which it writes into the banner as a +`NEXT TASK` line for step 4 above to pick up. The save discipline above is +what it performs at its step 3, so this section remains the source of truth +for *how to write the file*; `/arch-save` is the source of truth for *the +sequence*. The manual path (save → human clears → `/arch-init`) stays valid and is the fallback when Tower is unavailable. diff --git a/codev-skeleton/.claude/skills/arch-save/SKILL.md b/codev-skeleton/.claude/skills/arch-save/SKILL.md index 7f50280de..e531d7809 100644 --- a/codev-skeleton/.claude/skills/arch-save/SKILL.md +++ b/codev-skeleton/.claude/skills/arch-save/SKILL.md @@ -1,7 +1,7 @@ --- name: arch-save description: Save an architect's state, clear its context, and re-init automatically — the packaged save→clear→re-init refresh cycle. Use when the owner directs a context refresh, or says "/arch-save", "save and clear", "refresh your context". Runs on the owner's direction; an architect does not invoke it autonomously mid-task. Counterpart to /arch-init, which recovers the state this writes. -argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +argument-hint: "[name] [next task] (e.g. main; or: file and spawn that issue)" --- # /arch-save — save state, clear, and come back as yourself @@ -9,7 +9,18 @@ argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" Long sessions accumulate stale context. This is the deliberate cure: you choose the moment, you choose what survives, and a fresh session resumes from what you wrote. -`$ARGUMENTS` is the architect name (e.g. `main`). Omit it to auto-detect. +`$ARGUMENTS` carries the architect name, a **next task** for the session that comes back, +or both: + +``` +/arch-save save, clear, re-init, then wait +/arch-save file and spawn that issue … and start on that first, unprompted +/arch-save main file and spawn that issue explicit name, then the next task +``` + +The next task is how an owner who already knows what the refreshed session should do first +says so once, at the moment they direct the refresh, instead of watching for the re-init to +land and typing it again. ## When NOT to run this @@ -28,20 +39,36 @@ action. A mid-task snapshot resumes into confusion. Do these in order. **The order is the feature** — step 3 must precede step 4, because the context that knows what to write is the one about to be destroyed. -### 1. Resolve your name +### 1. Resolve your name, then split off the next task -If `$ARGUMENTS` is non-empty, that is your name. Otherwise run `afx whoami` and use the -reported `name` when `type: architect`. +**Always run `afx whoami` first**, whatever `$ARGUMENTS` holds — the split below cannot +tell a name from a task until it knows your name. The name rules are unchanged: +- `type: architect` → the reported `name` is yours. - `type: builder` → **STOP.** This terminal is a builder. Report the mismatch. -- Non-zero exit → **STOP** and ask which architect you are. Do **not** guess, and do not - default to `main` — writing another architect's state file is the exact failure - `/arch-init` exists to prevent (#1094). +- Non-zero exit → you have no name yet. A single-token `$ARGUMENTS` that validates as a + name is still accepted as one; anything else → **STOP** and ask which architect you are. + Do **not** guess, and do not default to `main` — writing another architect's state file + is the exact failure `/arch-init` exists to prevent (#1094). **Validate the name before building any path**: `[a-z][a-z0-9-]*`, at most 64 characters. Reject slashes, `..`, uppercase, spaces. Never interpolate an unvalidated name into `codev/state/.md`. +Then split `$ARGUMENTS`: + +1. `$ARGUMENTS` is empty → name from `afx whoami`, and there is no next task. +2. The **first whitespace-separated token** equals the whoami name (or, when whoami could + not resolve one, is a single token that validates as a name) → that token is the name, + and the **remainder** is the next-task text, which may be empty. +3. Otherwise → the **whole** of `$ARGUMENTS` is the next-task text, and the name comes + from `afx whoami`. + +So `/arch-save` and `/arch-save main` behave exactly as they always have. The one +ambiguity — a next task whose first word happens to be your own architect name — is +accepted rather than worked around: lead with the explicit name +(`/arch-save main main is stalled, look at it`) to disambiguate. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. @@ -75,6 +102,21 @@ first (`cp codev/state/.md codev/state/..bak.md`) is cheap insurance **Content guardrails.** No secrets — tokens, keys, credentials. No transcript dumps, no raw tool output. Only: current focus, open loops, and what a fresh session needs to resume. +**Write the next task into the banner**, if step 1 produced one. Add a single line to the +banner block, directly after the `⭐ THIS /clear IS INTENTIONAL` line: + +``` +# ⏭ NEXT TASK (owner-directed at save, 2026-09-19T02:10Z): file and spawn that issue +``` + +- **Verbatim.** Collapse newlines to spaces; do not paraphrase, expand, or "helpfully" + plan it. Those are the owner's words, and the context writing them down is the one about + to be destroyed — this is the last moment at which a paraphrase can be caught. +- **New text replaces** any existing NEXT TASK line. +- **No new text preserves an existing one.** A NEXT TASK line still in the banner means the + previous cycle never came back to pick it up; dropping it silently would lose an owner + instruction that nothing else is holding. + Use the template at the end of this document. ### 4. Clear @@ -160,12 +202,16 @@ literal text on the front of the next message never executes. The structure below comes from a live run of this cycle. Every element earns its place; keep them all, including a `MONITORS:` line even when the answer is "none armed" — an -omitted monitor list is indistinguishable from a forgotten one. +omitted monitor list is indistinguishable from a forgotten one. The `NEXT TASK` line is the +single exception: it appears only when there is one. ``` # architect — state (vNN, ~HH:MM UTC — , DELIBERATE /clear cycle) # ⭐ THIS /clear IS INTENTIONAL (owner-directed context refresh). On re-init: normal # /arch-init flow, then: +# ⏭ NEXT TASK (owner-directed at save, ): +# — OPTIONAL; present only when the save carried one. /arch-init starts here before the +# agenda below, then deletes this line and logs the pickup. # 1. MONITORS: — watch target, # cadence, alert pattern. Self-test once before trusting alerts. ("none armed" is a # valid and complete answer.) diff --git a/codev-skeleton/.codex/skills/arch-init/SKILL.md b/codev-skeleton/.codex/skills/arch-init/SKILL.md index fb6a11cc7..a90936ad5 100644 --- a/codev-skeleton/.codex/skills/arch-init/SKILL.md +++ b/codev-skeleton/.codex/skills/arch-init/SKILL.md @@ -46,12 +46,29 @@ name in a multi-architect workspace). (`codev/state/*.md`); never commit them. Builder `*_thread.md` files are the opposite: versioned, shipping with each builder PR. -3. **Confirm identity + orient, then follow the state file.** In one tight - block, report: who you now are (name + one-line role from the banner, if - present), the file you read, and the current-state / open-loops summary - from the most recent dated section (or the file's leading content if it - has no dated sections). Then carry out whatever the state file says to do - on resume. Do not invent a new agenda — resume the one the state file +3. **Confirm identity + orient.** In one tight block, report: who you now are + (name + one-line role from the banner, if present), the file you read, and + the current-state / open-loops summary from the most recent dated section + (or the file's leading content if it has no dated sections). If the banner + carries a `NEXT TASK` line, report it too, as + `Next task from the owner at save time: `. + +4. **Start on the next task, if the banner carries one.** It is the **first + action of the resumed session**, ahead of the general resume agenda — the + owner wrote it at save time precisely so it would not have to be typed + again once you came back. Begin it without waiting for a further prompt. + - It carries the owner's authority the way any owner message does, **with + the standard limits unchanged**. A next task never by itself approves a + porch gate, merges a PR, cuts a release, restarts Tower, or performs any + other act that needs a per-occasion word. If the next task *is* such an + act, prepare it and ask for the word live: a saved instruction is an + instruction, not a pre-spent approval. + - **Once you have started, delete the `NEXT TASK` line from the banner** + and record the pickup as a log entry (`picked up next task: `). + A second re-init, or the next `/arch-save`, must not re-run it. + +5. **Then follow the state file.** Carry out whatever it says to do on + resume. Do not invent a new agenda — resume the one the state file describes. ## Saving your state (and knowing when to `/clear`) @@ -118,9 +135,12 @@ Do not repeat it, and do not prompt for it at any other time. **`/arch-save` packages this whole loop**, and is the preferred path when the owner directs a refresh: it stops your monitors, writes the pruned state file, clears, and schedules `/arch-init` to bring you back — in that order, which is -the part that matters. The save discipline above is what it performs at its -step 3, so this section remains the source of truth for *how to write the -file*; `/arch-save` is the source of truth for *the sequence*. The manual path +the part that matters. It also accepts a **next task** as free text +(`/arch-save file and spawn that issue`), which it writes into the banner as a +`NEXT TASK` line for step 4 above to pick up. The save discipline above is +what it performs at its step 3, so this section remains the source of truth +for *how to write the file*; `/arch-save` is the source of truth for *the +sequence*. The manual path (save → human clears → `/arch-init`) stays valid and is the fallback when Tower is unavailable. diff --git a/codev-skeleton/.codex/skills/arch-save/SKILL.md b/codev-skeleton/.codex/skills/arch-save/SKILL.md index 7f50280de..e531d7809 100644 --- a/codev-skeleton/.codex/skills/arch-save/SKILL.md +++ b/codev-skeleton/.codex/skills/arch-save/SKILL.md @@ -1,7 +1,7 @@ --- name: arch-save description: Save an architect's state, clear its context, and re-init automatically — the packaged save→clear→re-init refresh cycle. Use when the owner directs a context refresh, or says "/arch-save", "save and clear", "refresh your context". Runs on the owner's direction; an architect does not invoke it autonomously mid-task. Counterpart to /arch-init, which recovers the state this writes. -argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +argument-hint: "[name] [next task] (e.g. main; or: file and spawn that issue)" --- # /arch-save — save state, clear, and come back as yourself @@ -9,7 +9,18 @@ argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" Long sessions accumulate stale context. This is the deliberate cure: you choose the moment, you choose what survives, and a fresh session resumes from what you wrote. -`$ARGUMENTS` is the architect name (e.g. `main`). Omit it to auto-detect. +`$ARGUMENTS` carries the architect name, a **next task** for the session that comes back, +or both: + +``` +/arch-save save, clear, re-init, then wait +/arch-save file and spawn that issue … and start on that first, unprompted +/arch-save main file and spawn that issue explicit name, then the next task +``` + +The next task is how an owner who already knows what the refreshed session should do first +says so once, at the moment they direct the refresh, instead of watching for the re-init to +land and typing it again. ## When NOT to run this @@ -28,20 +39,36 @@ action. A mid-task snapshot resumes into confusion. Do these in order. **The order is the feature** — step 3 must precede step 4, because the context that knows what to write is the one about to be destroyed. -### 1. Resolve your name +### 1. Resolve your name, then split off the next task -If `$ARGUMENTS` is non-empty, that is your name. Otherwise run `afx whoami` and use the -reported `name` when `type: architect`. +**Always run `afx whoami` first**, whatever `$ARGUMENTS` holds — the split below cannot +tell a name from a task until it knows your name. The name rules are unchanged: +- `type: architect` → the reported `name` is yours. - `type: builder` → **STOP.** This terminal is a builder. Report the mismatch. -- Non-zero exit → **STOP** and ask which architect you are. Do **not** guess, and do not - default to `main` — writing another architect's state file is the exact failure - `/arch-init` exists to prevent (#1094). +- Non-zero exit → you have no name yet. A single-token `$ARGUMENTS` that validates as a + name is still accepted as one; anything else → **STOP** and ask which architect you are. + Do **not** guess, and do not default to `main` — writing another architect's state file + is the exact failure `/arch-init` exists to prevent (#1094). **Validate the name before building any path**: `[a-z][a-z0-9-]*`, at most 64 characters. Reject slashes, `..`, uppercase, spaces. Never interpolate an unvalidated name into `codev/state/.md`. +Then split `$ARGUMENTS`: + +1. `$ARGUMENTS` is empty → name from `afx whoami`, and there is no next task. +2. The **first whitespace-separated token** equals the whoami name (or, when whoami could + not resolve one, is a single token that validates as a name) → that token is the name, + and the **remainder** is the next-task text, which may be empty. +3. Otherwise → the **whole** of `$ARGUMENTS` is the next-task text, and the name comes + from `afx whoami`. + +So `/arch-save` and `/arch-save main` behave exactly as they always have. The one +ambiguity — a next task whose first word happens to be your own architect name — is +accepted rather than worked around: lead with the explicit name +(`/arch-save main main is stalled, look at it`) to disambiguate. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. @@ -75,6 +102,21 @@ first (`cp codev/state/.md codev/state/..bak.md`) is cheap insurance **Content guardrails.** No secrets — tokens, keys, credentials. No transcript dumps, no raw tool output. Only: current focus, open loops, and what a fresh session needs to resume. +**Write the next task into the banner**, if step 1 produced one. Add a single line to the +banner block, directly after the `⭐ THIS /clear IS INTENTIONAL` line: + +``` +# ⏭ NEXT TASK (owner-directed at save, 2026-09-19T02:10Z): file and spawn that issue +``` + +- **Verbatim.** Collapse newlines to spaces; do not paraphrase, expand, or "helpfully" + plan it. Those are the owner's words, and the context writing them down is the one about + to be destroyed — this is the last moment at which a paraphrase can be caught. +- **New text replaces** any existing NEXT TASK line. +- **No new text preserves an existing one.** A NEXT TASK line still in the banner means the + previous cycle never came back to pick it up; dropping it silently would lose an owner + instruction that nothing else is holding. + Use the template at the end of this document. ### 4. Clear @@ -160,12 +202,16 @@ literal text on the front of the next message never executes. The structure below comes from a live run of this cycle. Every element earns its place; keep them all, including a `MONITORS:` line even when the answer is "none armed" — an -omitted monitor list is indistinguishable from a forgotten one. +omitted monitor list is indistinguishable from a forgotten one. The `NEXT TASK` line is the +single exception: it appears only when there is one. ``` # architect — state (vNN, ~HH:MM UTC — , DELIBERATE /clear cycle) # ⭐ THIS /clear IS INTENTIONAL (owner-directed context refresh). On re-init: normal # /arch-init flow, then: +# ⏭ NEXT TASK (owner-directed at save, ): +# — OPTIONAL; present only when the save carried one. /arch-init starts here before the +# agenda below, then deletes this line and logs the pickup. # 1. MONITORS: — watch target, # cadence, alert pattern. Self-test once before trusting alerts. ("none armed" is a # valid and complete answer.) diff --git a/codev/state/air-1709_thread.md b/codev/state/air-1709_thread.md new file mode 100644 index 000000000..efff2cc9b --- /dev/null +++ b/codev/state/air-1709_thread.md @@ -0,0 +1,50 @@ +# air-1709 — `/arch-save` next task → `/arch-init` starts on it + +Issue #1709. AIR protocol, strict mode. Docs-only feature: both halves of the change are +skill documents, so the shipped SKILL.md text *is* the implementation. + +## 2026-09-19 — implement + +**Surveyed first.** Grepped both trees for prose describing the `/arch-save` cycle. Hits +outside the two skills were all incidental references, not descriptions of the argument +contract or the banner: + +- `builder-refresh/SKILL.md` — "Counterpart to the architect's /arch-save" in frontmatter only. +- `codev/resources/commands/agent-farm.md:514`, `packages/sdk/src/tower-client.ts:797`, + `agent-farm/commands/reset/{constants,self}.ts` — all about the *delayed send* and the + clear/re-entry ordering, none about arguments. Left alone. +- `codev/roles/architect.md` (identical to `codev-skeleton/roles/architect.md`) — **does not + mention `/arch-save` at all**. The issue named it as a candidate; it needed no change. +- `codev/specs|plans|reviews/1307-*` — historical artifacts of the original cycle. Not + living docs; left alone. + +**Changes.** `arch-save`: `argument-hint`, the intro (three invocation forms), step 1 +rewritten as "resolve your name, then split off the next task", a next-task block at the end +of step 3, and the optional `⏭ NEXT TASK` line in the state-block template. `arch-init`: +old step 3 split into 3 (orient, now naming the next task) / 4 (start on it) / 5 (follow the +state file), plus one sentence in "Saving your state" so that section does not contradict +step 4. + +Two decisions worth recording: + +1. **`afx whoami` now runs unconditionally**, where before it ran only when `$ARGUMENTS` was + empty. The split in rule 2 compares the first token *against the whoami name*, so the name + has to be known before the arguments can be parsed. Consequence: the non-zero-exit branch + could no longer be an unconditional STOP, or `/arch-save main` would break whenever whoami + was down. Written as: no name yet → a single validating token is still accepted as one, + anything else stops and asks. That preserves both the old back-compat path and the + never-default-to-`main` guard (#1094). +2. **`⏭ NEXT TASK` goes in the banner, not a dated log entry.** `/arch-init` reads the banner + plus the most recent dated section; the banner is the half that is unambiguously read + first, and it is where the existing `⭐ THIS /clear IS INTENTIONAL` resume instructions + already live. + +**Tests.** `packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts`. Four-copy +parity for *both* skills — repeated for `arch-init` on purpose, since +`spec-1134-arch-init-skill.test.ts` compares only `instance/.claude` against +`skeleton/.claude` and would pass with a stale `.codex` copy. Content assertions pin each +acceptance criterion: the three invocation forms, the three split rules, back-compat, the +verbatim rule, replace-vs-preserve, the template line, the orient label, orient→next→state +ordering, the not-a-pre-spent-approval limits, and the delete + log-the-pickup rule. + +Worktree had no `node_modules` on spawn; ran `pnpm install --frozen-lockfile` before testing. diff --git a/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts new file mode 100644 index 000000000..ba9db1003 --- /dev/null +++ b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts @@ -0,0 +1,166 @@ +/** + * AIR #1709 — `/arch-save ` → `/arch-init` picks it up. + * + * `/arch-save` gains a free-text next task that survives the clear in the state + * file's banner, and `/arch-init` starts on it as the first action of the + * resumed session. Both halves are documents, so the shipped SKILL.md text is + * the testable artifact: these assertions pin the statements the feature is + * made of, and the four-copy parity that keeps adopters from getting a stale + * half of the pair. + * + * The parity guard is repeated here for `/arch-init` deliberately — + * `spec-1134-arch-init-skill.test.ts` compares only instance/.claude against + * skeleton/.claude, so a `.codex` copy left behind passes it silently. + */ + +import { describe, expect, it } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..'); + +const copies = (skill: string) => ({ + 'instance/.claude': path.join(repoRoot, '.claude', 'skills', skill, 'SKILL.md'), + 'instance/.codex': path.join(repoRoot, '.codex', 'skills', skill, 'SKILL.md'), + 'skeleton/.claude': path.join(repoRoot, 'codev-skeleton', '.claude', 'skills', skill, 'SKILL.md'), + 'skeleton/.codex': path.join(repoRoot, 'codev-skeleton', '.codex', 'skills', skill, 'SKILL.md'), +}); + +const ARCH_SAVE = copies('arch-save'); +const ARCH_INIT = copies('arch-init'); + +const save = () => fs.readFileSync(ARCH_SAVE['skeleton/.claude'], 'utf-8'); +const init = () => fs.readFileSync(ARCH_INIT['skeleton/.claude'], 'utf-8'); + +describe('AIR 1709 — both skills stay byte-identical across all four copies', () => { + it.each([ + ['arch-save', ARCH_SAVE], + ['arch-init', ARCH_INIT], + ] as const)('%s', (_skill, files) => { + const [first, ...rest] = Object.values(files).map(f => fs.readFileSync(f, 'utf-8')); + for (const other of rest) { + expect(other).toBe(first); + } + }); +}); + +describe('AIR 1709 — /arch-save accepts a next task', () => { + it('advertises the next task in the argument hint', () => { + expect(save()).toMatch(/^argument-hint: .*next task/m); + }); + + it('shows all three invocation forms', () => { + const t = save(); + expect(t).toMatch(/^\/arch-save\s+save, clear, re-init, then wait$/m); + expect(t).toMatch(/^\/arch-save file and spawn that issue\s+…/m); + expect(t).toMatch(/^\/arch-save main file and spawn that issue\s+explicit name/m); + }); + + it('runs afx whoami first, whatever the arguments hold', () => { + // The split cannot tell a leading name from a leading task word until the + // name is known, so whoami stops being conditional on empty $ARGUMENTS. + expect(save()).toMatch(/\*\*Always run `afx whoami` first\*\*, whatever `\$ARGUMENTS` holds/); + }); + + it('keeps the name-resolution guards (builder stop, no defaulting to main, validation)', () => { + const t = save(); + expect(t).toMatch(/`type: builder` → \*\*STOP\.\*\*/); + expect(t).toMatch(/do not\s+default to `main`/); + expect(t).toContain('[a-z][a-z0-9-]*'); + expect(t).toMatch(/at most 64 characters/); + }); + + it('carries the three-rule argument split', () => { + const t = save(); + expect(t).toMatch(/`\$ARGUMENTS` is empty → name from `afx whoami`, and there is no next task/); + expect(t).toMatch(/\*\*first whitespace-separated token\*\* equals the whoami name/); + expect(t).toMatch(/the \*\*remainder\*\* is the next-task text/); + expect(t).toMatch(/Otherwise → the \*\*whole\*\* of `\$ARGUMENTS` is the next-task text/); + }); + + it('states the back-compat property and documents the one ambiguity', () => { + const t = save(); + expect(t).toMatch(/`\/arch-save` and `\/arch-save main` behave exactly as they always have/); + expect(t).toMatch(/a next task whose first word happens to be your own architect name/); + }); +}); + +describe('AIR 1709 — /arch-save persists the next task in the banner', () => { + it('writes the NEXT TASK line into step 3, after the intentional-clear line', () => { + const t = save(); + expect(t).toMatch(/directly after the `⭐ THIS \/clear IS INTENTIONAL` line/); + expect(t).toMatch(/# ⏭ NEXT TASK \(owner-directed at save, .+\): /); + }); + + it('requires the owner’s words verbatim', () => { + const t = save(); + expect(t).toMatch(/\*\*Verbatim\.\*\*/); + expect(t).toMatch(/Collapse newlines to spaces; do not paraphrase/); + }); + + it('replaces an existing line with new text, and preserves it when there is none', () => { + const t = save(); + expect(t).toMatch(/\*\*New text replaces\*\* any existing NEXT TASK line/); + expect(t).toMatch(/\*\*No new text preserves an existing one\.\*\*/); + // The why must travel with the rule: a surviving line means the previous + // cycle never resumed, so dropping it loses an owner instruction. + expect(t).toMatch(/previous cycle never came back to pick it up/); + }); + + it('shows the line in the state-block template, marked optional', () => { + const template = save().slice(save().indexOf('## State block template')); + expect(template).toMatch(/# ⏭ NEXT TASK \(owner-directed at save, \): /); + expect(template).toMatch(/OPTIONAL; present only when the save carried one/); + }); + + it('still writes the state file before the clear', () => { + // Unchanged ordering property, re-pinned because step 3 grew. + const t = save(); + expect(t.indexOf('### 3. Write the pruned state file')).toBeLessThan(t.indexOf('### 4. Clear')); + expect(t).toMatch(/context that knows what to write is the one about to be destroyed/); + }); +}); + +describe('AIR 1709 — /arch-init picks the next task up', () => { + it('reports it in the orient block under a fixed label', () => { + expect(init()).toContain('Next task from the owner at save time: '); + }); + + it('starts on it first, ahead of the general resume agenda', () => { + const t = init(); + expect(t).toMatch(/\*\*first\n\s+action of the resumed session\*\*, ahead of the general resume agenda/); + expect(t).toMatch(/Begin it without waiting for a further prompt/); + }); + + it('orders orient → next task → follow the state file', () => { + const t = init(); + const orient = t.indexOf('**Confirm identity + orient.**'); + const nextTask = t.indexOf('**Start on the next task, if the banner carries one.**'); + const followState = t.indexOf('**Then follow the state file.**'); + expect(orient).toBeGreaterThanOrEqual(0); + expect(nextTask).toBeGreaterThan(orient); + expect(followState).toBeGreaterThan(nextTask); + }); + + it('is not a pre-spent approval for gates, merges, releases or Tower restarts', () => { + const t = init(); + expect(t).toMatch(/never by itself approves a\s+porch gate, merges a PR, cuts a release, restarts Tower/); + expect(t).toMatch(/prepare it and ask for the word live/); + expect(t).toMatch(/a saved instruction is an\s+instruction, not a pre-spent approval/); + }); + + it('deletes the line and logs the pickup so it cannot run twice', () => { + const t = init(); + expect(t).toMatch(/\*\*Once you have started, delete the `NEXT TASK` line from the banner\*\*/); + expect(t).toContain('picked up next task: '); + expect(t).toMatch(/A second re-init, or the next `\/arch-save`, must not re-run it/); + }); + + it('tells the save-side section that /arch-save can carry one', () => { + // The "Saving your state" section describes the cycle too; leaving it + // silent about the next task contradicts step 4 above it. + const t = init(); + const section = t.slice(t.indexOf('## Saving your state')); + expect(section).toMatch(/accepts a \*\*next task\*\* as free text/); + }); +}); From 8aac0be8607988e1f257e2331fc672d6137cd41c Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:37:50 -0700 Subject: [PATCH 3/6] chore(porch): 1709 pr phase-transition --- .../projects/1709-arch-save-accept-a-next-task-i/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml index 423e2ec3e..93a3495aa 100644 --- a/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml +++ b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml @@ -1,7 +1,7 @@ id: '1709' title: arch-save-accept-a-next-task-i protocol: air -phase: implement +phase: pr plan_phases: [] current_plan_phase: null gates: @@ -11,4 +11,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-09-19T14:32:33.742Z' -updated_at: '2026-09-19T14:32:33.743Z' +updated_at: '2026-09-19T14:37:50.798Z' From 2e01e35d610ab02e8b28fe9393b49aae88f85d13 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:43:13 -0700 Subject: [PATCH 4/6] [Air #1709] docs: close two gaps CMAP found in the next-task split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three reviewers approved; claude raised four points, three of which are cheap and additive. - The next task survives the "which architect are you?" stop. whoami down plus multi-token arguments falls through both split rules to STOP, which was the right outcome but left it unsaid that the task text is still in hand — an implementer could reasonably make the owner retype it. - /arch-save and /arch-init now resolve a name by different rules, and /arch-save says so. /arch-init takes no next task, so any argument there is unambiguously a name and overrides whoami outright; here the first token is weighed against whoami instead. Notes that a whoami reporting the wrong architect cannot be overridden by argument alone, and to stop rather than save into an unverified name. - Test nit: one regex pinned a hard line wrap and would have failed on a pure reflow with no semantic change. - Recorded that spec-1307 already covers four-copy parity for arch-save; the duplicate stays so both skills of one feature fail together. Claude's remaining finding — that the explicit-name override is narrower than before — is the issue's prescribed rules 3 and 4, so it goes to the architect rather than into this commit. Co-Authored-By: Claude Opus 5 --- .claude/skills/arch-save/SKILL.md | 9 +++++ .codex/skills/arch-save/SKILL.md | 9 +++++ .../.claude/skills/arch-save/SKILL.md | 9 +++++ .../.codex/skills/arch-save/SKILL.md | 9 +++++ codev/state/air-1709_thread.md | 39 +++++++++++++++++++ .../__tests__/air-1709-next-task.test.ts | 21 +++++++++- 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/.claude/skills/arch-save/SKILL.md b/.claude/skills/arch-save/SKILL.md index e531d7809..bb9b29ec2 100644 --- a/.claude/skills/arch-save/SKILL.md +++ b/.claude/skills/arch-save/SKILL.md @@ -69,6 +69,15 @@ ambiguity — a next task whose first word happens to be your own architect name accepted rather than worked around: lead with the explicit name (`/arch-save main main is stalled, look at it`) to disambiguate. +**If you had to ask which architect you are, the next task still stands.** The answer +supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. + +**This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: +`/arch-init` takes no next task, so any non-empty argument there is unambiguously a name +and overrides whoami outright. Here the first token is weighed *against* whoami, which +means a whoami that reports the wrong architect cannot be overridden by argument alone — +if you suspect that, stop and raise it rather than saving into a name you did not verify. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. diff --git a/.codex/skills/arch-save/SKILL.md b/.codex/skills/arch-save/SKILL.md index e531d7809..bb9b29ec2 100644 --- a/.codex/skills/arch-save/SKILL.md +++ b/.codex/skills/arch-save/SKILL.md @@ -69,6 +69,15 @@ ambiguity — a next task whose first word happens to be your own architect name accepted rather than worked around: lead with the explicit name (`/arch-save main main is stalled, look at it`) to disambiguate. +**If you had to ask which architect you are, the next task still stands.** The answer +supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. + +**This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: +`/arch-init` takes no next task, so any non-empty argument there is unambiguously a name +and overrides whoami outright. Here the first token is weighed *against* whoami, which +means a whoami that reports the wrong architect cannot be overridden by argument alone — +if you suspect that, stop and raise it rather than saving into a name you did not verify. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. diff --git a/codev-skeleton/.claude/skills/arch-save/SKILL.md b/codev-skeleton/.claude/skills/arch-save/SKILL.md index e531d7809..bb9b29ec2 100644 --- a/codev-skeleton/.claude/skills/arch-save/SKILL.md +++ b/codev-skeleton/.claude/skills/arch-save/SKILL.md @@ -69,6 +69,15 @@ ambiguity — a next task whose first word happens to be your own architect name accepted rather than worked around: lead with the explicit name (`/arch-save main main is stalled, look at it`) to disambiguate. +**If you had to ask which architect you are, the next task still stands.** The answer +supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. + +**This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: +`/arch-init` takes no next task, so any non-empty argument there is unambiguously a name +and overrides whoami outright. Here the first token is weighed *against* whoami, which +means a whoami that reports the wrong architect cannot be overridden by argument alone — +if you suspect that, stop and raise it rather than saving into a name you did not verify. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. diff --git a/codev-skeleton/.codex/skills/arch-save/SKILL.md b/codev-skeleton/.codex/skills/arch-save/SKILL.md index e531d7809..bb9b29ec2 100644 --- a/codev-skeleton/.codex/skills/arch-save/SKILL.md +++ b/codev-skeleton/.codex/skills/arch-save/SKILL.md @@ -69,6 +69,15 @@ ambiguity — a next task whose first word happens to be your own architect name accepted rather than worked around: lead with the explicit name (`/arch-save main main is stalled, look at it`) to disambiguate. +**If you had to ask which architect you are, the next task still stands.** The answer +supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. + +**This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: +`/arch-init` takes no next task, so any non-empty argument there is unambiguously a name +and overrides whoami outright. Here the first token is weighed *against* whoami, which +means a whoami that reports the wrong architect cannot be overridden by argument alone — +if you suspect that, stop and raise it rather than saving into a name you did not verify. + ### 2. Stop your own monitors Enumerate every monitor, watcher or background task you armed, and stop it. diff --git a/codev/state/air-1709_thread.md b/codev/state/air-1709_thread.md index efff2cc9b..8c7f3010f 100644 --- a/codev/state/air-1709_thread.md +++ b/codev/state/air-1709_thread.md @@ -48,3 +48,42 @@ verbatim rule, replace-vs-preserve, the template line, the orient label, orient ordering, the not-a-pre-spent-approval limits, and the delete + log-the-pickup rule. Worktree had no `node_modules` on spawn; ran `pnpm install --frozen-lockfile` before testing. + +## 2026-09-19 — pr + CMAP + +PR #1710. CMAP all three: **gemini=APPROVE, codex=APPROVE, claude=APPROVE**, all HIGH +confidence. (Note for the architect: the agy/gemini lane produced a clean VERDICT block on +a `--type pr` review here — the known "agy consult broken for `--type`" issue did not +reproduce.) + +Gemini and codex found nothing. Claude approved with four observations; I verified each +against the files before acting. + +**Applied** (additive doc/test only, no behavior change): + +- *Next task survives the STOP branch.* whoami down + multi-token `$ARGUMENTS` falls through + rule 2 (not a single token) and rule 3 (no name from whoami) to "stop and ask". The outcome + was right but the doc never said the task text is still in hand, so an implementer could + reasonably make the owner retype it. One sentence added. +- *The pair is asymmetric, and now says so.* `/arch-init` takes no next task, so any argument + there is unambiguously a name and overrides whoami outright; `/arch-save` weighs the first + token *against* whoami. `arch-init`'s frontmatter still advertises "an explicit name + argument overrides" and is still correct for itself — the gap was that `arch-save` did not + acknowledge the difference. One paragraph added there. +- *Test nit:* `/\*\*first\n\s+action of the resumed session\*\*/` pinned a hard line wrap; a + pure reflow would have failed it with no semantic change. Relaxed to `\s+`. +- *Parity duplication:* confirmed — `spec-1307-arch-save-skill.test.ts:41` already asserts + four-copy parity for `arch-save`. Kept the duplicate so both skills of one feature fail + together, and said so in the file header. + +**Referred to the architect, not applied:** claude's first finding. The explicit-name +override is narrower than before — previously *any* non-empty `$ARGUMENTS` was the name, now +only a first token equal to whoami's (or a lone validating token when whoami fails). The +uncovered case is whoami answering with the *wrong* architect: `/arch-save tower` from a +session whoami calls `main` writes to `main`'s file, and the argument cannot override it. +That is the #1094 failure class, and identity misreport is a scar this repo has taken. + +It is also exactly what the issue prescribed (rules 3 and 4), so it is a baked decision and +not mine to reverse. Claude suggested an additive guard costing one sentence: *first token +validates as a name, differs from whoami's, and `codev/state/.md` exists → stop and +ask.* Flagged via `afx send`; the architect's call. diff --git a/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts index ba9db1003..6a1126745 100644 --- a/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts +++ b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts @@ -10,7 +10,9 @@ * * The parity guard is repeated here for `/arch-init` deliberately — * `spec-1134-arch-init-skill.test.ts` compares only instance/.claude against - * skeleton/.claude, so a `.codex` copy left behind passes it silently. + * skeleton/.claude, so a `.codex` copy left behind passes it silently. The + * `/arch-save` half of that check duplicates `spec-1307-arch-save-skill.test.ts` + * and is kept only so the two skills of one feature fail together. */ import { describe, expect, it } from 'vitest'; @@ -83,6 +85,21 @@ describe('AIR 1709 — /arch-save accepts a next task', () => { expect(t).toMatch(/`\/arch-save` and `\/arch-save main` behave exactly as they always have/); expect(t).toMatch(/a next task whose first word happens to be your own architect name/); }); + + it('keeps the next task when it has to stop and ask for the name', () => { + // whoami down + multi-token arguments lands on the STOP branch. The text is + // already in hand; making the owner retype it defeats the feature. + expect(save()).toMatch(/If you had to ask which architect you are, the next task still stands/); + }); + + it('acknowledges that /arch-init resolves a name by different rules', () => { + // /arch-init takes no next task, so any argument there is unambiguously a + // name and overrides whoami outright. Here the first token is weighed + // *against* whoami — the pair is asymmetric, and the doc says so. + const t = save(); + expect(t).toMatch(/This is not how `\/arch-init` resolves a name/); + expect(t).toMatch(/a whoami that reports the wrong architect cannot be overridden by argument alone/); + }); }); describe('AIR 1709 — /arch-save persists the next task in the banner', () => { @@ -128,7 +145,7 @@ describe('AIR 1709 — /arch-init picks the next task up', () => { it('starts on it first, ahead of the general resume agenda', () => { const t = init(); - expect(t).toMatch(/\*\*first\n\s+action of the resumed session\*\*, ahead of the general resume agenda/); + expect(t).toMatch(/\*\*first\s+action of the resumed session\*\*, ahead of the general resume agenda/); expect(t).toMatch(/Begin it without waiting for a further prompt/); }); From 6cc0bfe14cd024b4aa27da7786ca7c3268526f77 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:44:35 -0700 Subject: [PATCH 5/6] [Air #1709] feat: stop when the leading token names a different architect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architect's ruling on the CMAP finding that the explicit-name override had narrowed: add the guard rather than leave the rules as prescribed. In arch-save step 1, after the split — if the first token validates as a name, differs from the name whoami reported, and codev/state/.md exists, STOP and ask which architect you are, writing nothing. whoami can be wrong (#1094), and without this rule 3 buries a real name-override inside next-task text and saves to whoami's file. The existence check keeps it narrow: a task that merely opens with a word shaped like a name passes through untouched unless a state file for that name actually exists, so the only cost is one clarification when the first word is a live sibling's name. Rewriting the paragraph's closing sentence invalidated an assertion from the previous commit; re-pinned to the new wording, which now points at the guard. Co-Authored-By: Claude Opus 5 --- .claude/skills/arch-save/SKILL.md | 12 +++++++--- .codex/skills/arch-save/SKILL.md | 12 +++++++--- .../.claude/skills/arch-save/SKILL.md | 12 +++++++--- .../.codex/skills/arch-save/SKILL.md | 12 +++++++--- codev/state/air-1709_thread.md | 23 +++++++++++++++++++ .../__tests__/air-1709-next-task.test.ts | 13 ++++++++++- 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/.claude/skills/arch-save/SKILL.md b/.claude/skills/arch-save/SKILL.md index bb9b29ec2..793e0bfc6 100644 --- a/.claude/skills/arch-save/SKILL.md +++ b/.claude/skills/arch-save/SKILL.md @@ -72,11 +72,17 @@ accepted rather than worked around: lead with the explicit name **If you had to ask which architect you are, the next task still stands.** The answer supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. +**Guard, applied after the split: a leading token that names a *different* architect stops +you.** If the first token validates as a name, differs from the name whoami reported, and +`codev/state/.md` exists → **STOP and ask which architect you are. Write nothing.** +whoami can be wrong (#1094), and rule 3 would otherwise bury a real name-override inside +next-task text and save to whoami's file; a task that merely opens with a sibling's name is +the only case this costs, and it costs one clarification. + **This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: `/arch-init` takes no next task, so any non-empty argument there is unambiguously a name -and overrides whoami outright. Here the first token is weighed *against* whoami, which -means a whoami that reports the wrong architect cannot be overridden by argument alone — -if you suspect that, stop and raise it rather than saving into a name you did not verify. +and overrides whoami outright. Here the first token is weighed *against* whoami, which is +why the guard above exists — never save into a name you did not verify. ### 2. Stop your own monitors diff --git a/.codex/skills/arch-save/SKILL.md b/.codex/skills/arch-save/SKILL.md index bb9b29ec2..793e0bfc6 100644 --- a/.codex/skills/arch-save/SKILL.md +++ b/.codex/skills/arch-save/SKILL.md @@ -72,11 +72,17 @@ accepted rather than worked around: lead with the explicit name **If you had to ask which architect you are, the next task still stands.** The answer supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. +**Guard, applied after the split: a leading token that names a *different* architect stops +you.** If the first token validates as a name, differs from the name whoami reported, and +`codev/state/.md` exists → **STOP and ask which architect you are. Write nothing.** +whoami can be wrong (#1094), and rule 3 would otherwise bury a real name-override inside +next-task text and save to whoami's file; a task that merely opens with a sibling's name is +the only case this costs, and it costs one clarification. + **This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: `/arch-init` takes no next task, so any non-empty argument there is unambiguously a name -and overrides whoami outright. Here the first token is weighed *against* whoami, which -means a whoami that reports the wrong architect cannot be overridden by argument alone — -if you suspect that, stop and raise it rather than saving into a name you did not verify. +and overrides whoami outright. Here the first token is weighed *against* whoami, which is +why the guard above exists — never save into a name you did not verify. ### 2. Stop your own monitors diff --git a/codev-skeleton/.claude/skills/arch-save/SKILL.md b/codev-skeleton/.claude/skills/arch-save/SKILL.md index bb9b29ec2..793e0bfc6 100644 --- a/codev-skeleton/.claude/skills/arch-save/SKILL.md +++ b/codev-skeleton/.claude/skills/arch-save/SKILL.md @@ -72,11 +72,17 @@ accepted rather than worked around: lead with the explicit name **If you had to ask which architect you are, the next task still stands.** The answer supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. +**Guard, applied after the split: a leading token that names a *different* architect stops +you.** If the first token validates as a name, differs from the name whoami reported, and +`codev/state/.md` exists → **STOP and ask which architect you are. Write nothing.** +whoami can be wrong (#1094), and rule 3 would otherwise bury a real name-override inside +next-task text and save to whoami's file; a task that merely opens with a sibling's name is +the only case this costs, and it costs one clarification. + **This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: `/arch-init` takes no next task, so any non-empty argument there is unambiguously a name -and overrides whoami outright. Here the first token is weighed *against* whoami, which -means a whoami that reports the wrong architect cannot be overridden by argument alone — -if you suspect that, stop and raise it rather than saving into a name you did not verify. +and overrides whoami outright. Here the first token is weighed *against* whoami, which is +why the guard above exists — never save into a name you did not verify. ### 2. Stop your own monitors diff --git a/codev-skeleton/.codex/skills/arch-save/SKILL.md b/codev-skeleton/.codex/skills/arch-save/SKILL.md index bb9b29ec2..793e0bfc6 100644 --- a/codev-skeleton/.codex/skills/arch-save/SKILL.md +++ b/codev-skeleton/.codex/skills/arch-save/SKILL.md @@ -72,11 +72,17 @@ accepted rather than worked around: lead with the explicit name **If you had to ask which architect you are, the next task still stands.** The answer supplies the name; `$ARGUMENTS` already supplied the task. Do not make the owner retype it. +**Guard, applied after the split: a leading token that names a *different* architect stops +you.** If the first token validates as a name, differs from the name whoami reported, and +`codev/state/.md` exists → **STOP and ask which architect you are. Write nothing.** +whoami can be wrong (#1094), and rule 3 would otherwise bury a real name-override inside +next-task text and save to whoami's file; a task that merely opens with a sibling's name is +the only case this costs, and it costs one clarification. + **This is not how `/arch-init` resolves a name**, and the asymmetry is deliberate: `/arch-init` takes no next task, so any non-empty argument there is unambiguously a name -and overrides whoami outright. Here the first token is weighed *against* whoami, which -means a whoami that reports the wrong architect cannot be overridden by argument alone — -if you suspect that, stop and raise it rather than saving into a name you did not verify. +and overrides whoami outright. Here the first token is weighed *against* whoami, which is +why the guard above exists — never save into a name you did not verify. ### 2. Stop your own monitors diff --git a/codev/state/air-1709_thread.md b/codev/state/air-1709_thread.md index 8c7f3010f..bf4c6d871 100644 --- a/codev/state/air-1709_thread.md +++ b/codev/state/air-1709_thread.md @@ -87,3 +87,26 @@ It is also exactly what the issue prescribed (rules 3 and 4), so it is a baked d not mine to reverse. Claude suggested an additive guard costing one sentence: *first token validates as a name, differs from whoami's, and `codev/state/.md` exists → stop and ask.* Flagged via `afx send`; the architect's call. + +## 2026-09-19 — architect ruling: guard added + +Architect (main) ruled on the referred finding: **add the guard.** Implemented as directed, +no other changes. + +In `arch-save` step 1, after the split: first token validates as a name **and** differs from +whoami's **and** `codev/state/.md` exists → STOP and ask which architect you are, +write nothing. The "why" travels with it — whoami can be wrong (#1094), rule 3 would +otherwise bury a real name-override inside next-task text and save to whoami's file, and the +only case this costs is a task whose first word is a sibling's name, which costs one +clarification. + +The existence check is what keeps the guard narrow: an ordinary task opening with a word that +merely *looks* like a name (`main is stalled, look at it` would hit it, but `merge 1660` or +`review Amr's PR` would not) only stops when a state file for that name actually exists. + +One knock-on: rewriting that paragraph's closing sentence invalidated an assertion added in +the previous commit ("a whoami that reports the wrong architect cannot be overridden by +argument alone"), which the new text replaces with a pointer to the guard. Re-pinned to the +new wording — caught by the test run, not by inspection, which is the test doing its job. + +All 8 copies (4 arch-save + 4 arch-init) verified identical by md5. diff --git a/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts index 6a1126745..a2bed3e5e 100644 --- a/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts +++ b/packages/codev/src/agent-farm/__tests__/air-1709-next-task.test.ts @@ -92,13 +92,24 @@ describe('AIR 1709 — /arch-save accepts a next task', () => { expect(save()).toMatch(/If you had to ask which architect you are, the next task still stands/); }); + it('stops when the leading token names a different, existing architect', () => { + // The architect's ruling on the CMAP finding: rule 3 would otherwise bury a + // real name-override inside next-task text and save to whoami's file, and + // whoami can be wrong (#1094). The cost is one clarification for a task that + // merely opens with a sibling's name. + const t = save(); + expect(t).toMatch(/differs from the name whoami reported, and\s*\n?`codev\/state\/\.md` exists/); + expect(t).toMatch(/\*\*STOP and ask which architect you are\. Write nothing\.\*\*/); + expect(t).toMatch(/whoami can be wrong \(#1094\)/); + }); + it('acknowledges that /arch-init resolves a name by different rules', () => { // /arch-init takes no next task, so any argument there is unambiguously a // name and overrides whoami outright. Here the first token is weighed // *against* whoami — the pair is asymmetric, and the doc says so. const t = save(); expect(t).toMatch(/This is not how `\/arch-init` resolves a name/); - expect(t).toMatch(/a whoami that reports the wrong architect cannot be overridden by argument alone/); + expect(t).toMatch(/the first token is weighed \*against\* whoami, which is\s+why the guard above exists/); }); }); From 67dae03d41173471b3de86d51c2949c5d008dd1d Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Sat, 19 Sep 2026 07:46:31 -0700 Subject: [PATCH 6/6] chore(porch): 1709 pr gate-requested --- .../projects/1709-arch-save-accept-a-next-task-i/status.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml index 93a3495aa..7a46ecd41 100644 --- a/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml +++ b/codev/projects/1709-arch-save-accept-a-next-task-i/status.yaml @@ -7,8 +7,10 @@ current_plan_phase: null gates: pr: status: pending + requested_at: '2026-09-19T14:46:31.530Z' iteration: 1 build_complete: false history: [] started_at: '2026-09-19T14:32:33.742Z' -updated_at: '2026-09-19T14:37:50.798Z' +updated_at: '2026-09-19T14:46:31.531Z' +pr_ready_for_human: true