Enforce inline drafter & handle no-op returns

Clarify and tighten agent and workflow protocols: make the drafter run inline (do not spawn ck:drafter subagents) and require full-domain decompositions presented in a single message for approval. Add a hard rule to task-builder: never return silently — each dispatch must perform at least one real tool call and emit a TASK RESULT with COMPLETE / PARTIAL / BLOCKED. Introduce silent-return / no-op detection and recovery across make and make-parallel: classify empty bodies / zero tool calls / auto-removed worktrees as no-ops, log dead-ends, retry once inline in the parent session, and mark/block or escalate if the inline retry also fails; add a two-no-op circuit-breaker to fallback to inline processing for the rest of the wave. Update sketch and ship flows to write kits inline, auto-advance on explicit approval, and adjust related guidance in the cavekit-writing skill. These changes reduce subagent/worktree failure modes and make failures observable and recoverable.
This commit is contained in:
Julius Brussee
2026-04-18 23:21:19 +02:00
parent 9aa1905f7d
commit 2b950c819b
7 changed files with 86 additions and 23 deletions
+8 -5
View File
@@ -7,6 +7,8 @@ tools: [Read, Write, Edit, Grep, Glob, Bash]
You are a cavekit drafter for Cavekit. Your primary function is to collaboratively design and then write domain-specific kits that serve as the single source of truth for all downstream work.
> **Dispatch policy:** `/ck:sketch` and `/ck:ship` run the drafter playbook **inline in the parent session**. They do NOT spawn this file as a subagent. Treat this document as a reference playbook the parent session reads and executes directly. If you are reading this as a dispatched subagent, the caller has violated the current protocol — stop, emit a `TASK RESULT: BLOCKED` with `Reason: drafter must run inline per commands/sketch.md`, and return.
## Core Principles
- Kits drive the development process. Code is derived from them and can be rebuilt whenever the kits are updated.
@@ -42,12 +44,13 @@ Before settling on a domain decomposition:
- Lead with your recommended approach and explain why
- Consider: coupling, complexity, parallelizability, testability
### 4. Present Design Incrementally
### 4. Present Full Design in One Message
Walk through each domain section by section:
- Present scope, requirements, acceptance criteria, cross-references
- Get approval per section before moving to the next
- Be ready to revise based on feedback
Present the entire domain decomposition in a single message and ask for approval once:
- For each domain: scope, key requirements with acceptance criteria, cross-references/dependencies
- End with one approval question covering the whole decomposition
- If the user requests changes, revise and re-present the full updated decomposition in one message
- Do NOT walk domains one-by-one across multiple turns
### 5. Design for Isolation
+6
View File
@@ -7,6 +7,12 @@ tools: [All tools]
You are a task builder for Cavekit. You implement exactly ONE task, validate it, commit it, and stop.
**HARD RULE — never return silently.** Every dispatch MUST produce at minimum:
1. At least one real tool call (Read of the build site / cavekit file counts as the floor).
2. A `TASK RESULT:` block in your final message with Status set to `COMPLETE`, `PARTIAL`, or `BLOCKED`.
Returning with zero tool calls, no `TASK RESULT`, or an empty message is a protocol violation. If you cannot make progress — worktree is empty, build site missing, task already done, inputs malformed, environment broken — you MUST still emit `TASK RESULT` with `Status: BLOCKED` and the reason in the Issues field. Never just "finish." The orchestrator treats a silent return as a harness failure and will retry/BLOCK you; that wastes budget.
**Caveman Mode:** If your dispatch prompt includes `CAVEMAN MODE: ON`, apply caveman-speak ONLY to your final status report prose (e.g. the "Issues" narrative, wave log entries). Drop articles, filler, pleasantries — keep technical terms exact. Do NOT compress: (a) your internal reasoning or thinking, (b) tool calls or tool arguments, (c) code, (d) git commit messages, (e) structured output fields (TASK RESULT keys and their values). Think and invoke tools in normal format — compression applies to prose output only. Compressing reasoning or tool calls corrupts dispatch; treat this as a hard rule.
## Input
+1
View File
@@ -44,6 +44,7 @@ When you reach the **"Execute based on frontier size"** section:
- Dispatch them in a single assistant message with multiple `Agent` tool calls.
- Use the dispatch template from `commands/make.md` verbatim (with `isolation: "worktree"` included).
- Apply the **Harness error recovery** rule if any packet returns a harness-level failure: retry that packet once sequentially; mark BLOCKED if it errors a second time.
- Apply the **Silent-return / no-op detection** rule from `commands/make.md`: if a packet returns with 0 tool calls, empty body, no `TASK RESULT`, or an auto-removed empty worktree, do NOT treat it as progress and do NOT attempt worktree merge/cleanup. Log it, write a dead-end entry, retry once **inline in the parent session** (not as another subagent), and BLOCK if the inline retry also produces no commits. If 2 no-op returns occur in the same wave, trip the circuit breaker and finish the wave inline.
Post-wave cleanup follows the `TB_ISOLATION=worktree` branch in `commands/make.md`:
1. `git merge <branch> --no-edit`
+27
View File
@@ -254,6 +254,32 @@ Once the setup script completes (outputs the ralph prompt), you run the executio
**Harness error recovery** (parallel mode, `MAX_PARALLEL>1`): if an Agent call returns `[Tool result missing due to internal error]`, no `agentId`, or otherwise reports a harness-level failure with no body, do NOT try to merge or clean up a worktree for it — there is none. Re-dispatch that packet **once sequentially** (on its own in a fresh message), then proceed. If the retry also errors, log the packet's tasks as BLOCKED with the harness error and move on. Do not retry a third time.
**Silent-return / no-op detection** (applies to BOTH modes, any `MAX_PARALLEL`): classify each returned agent result before attempting merge or cleanup. Treat a return as a **no-op** if ANY of the following is true:
- The agent body is empty, whitespace-only, or "No response".
- The body contains no `TASK RESULT:` block.
- The harness reports zero tool calls for the agent (visible in the Agent tool result metadata; also implied if the worktree has no new commits and no modified files).
- The worktree was auto-removed with no branch commits (Claude Code auto-cleans worktrees with zero changes — this is the tell).
A no-op return is **not** the same as a BLOCKED result. Do NOT treat a no-op as progress and do NOT attempt a worktree merge/cleanup sequence on it (worktree is already gone). Handle it as follows:
1. Log a concrete line in wave status:
```
T-{ID}: {title} — NO-OP (0 tool calls / empty body / auto-removed worktree). Treating as BLOCKED.
```
2. Append an entry to `context/impl/dead-ends.md`:
```markdown
## DE-noop-T-{ID}: task-builder returned no-op
**Task:** T-{ID}
**Approach:** Dispatched ck:task-builder (model={EXECUTION_MODEL}, isolation={TB_ISOLATION}).
**Result:** Agent returned with 0 tool calls / no TASK RESULT / empty body.
**Recommendation:** Retry once inline in the parent session with explicit task + cavekit paths pasted into context; if that also produces no progress, escalate to the user.
```
3. Retry the packet **at most once**, and when you retry:
- Drop to inline execution for the retry (parent session implements directly, no subagent, no worktree). This removes the subagent/worktree dimension from the failure mode.
- Paste the full task entry, cavekit requirements, and acceptance criteria into the parent's own context before starting. Do not re-dispatch an identical prompt — identical prompts produce identical no-ops.
4. If the inline retry also produces zero file changes and zero commits, mark the task BLOCKED in the build site (`cavekit-tools mark-complete` is NOT used for BLOCKED — leave status unchanged; record the blocker in `impl/impl-*.md` under Issues Found) and move on. Do not loop.
5. If team mode is active, release the claim with `cavekit team release T-XXX --note "no-op return from task-builder"` and stop the heartbeat — otherwise the claim lingers and blocks teammates.
---
5. **After wave completes**:
@@ -376,6 +402,7 @@ Then output the completion promise from the ralph prompt.
- **3 consecutive test failures on same task** → mark BLOCKED, document in dead-ends.md, skip
- **Merge conflict unresolvable** → clean up remaining worktrees (`git worktree remove <path> --force` for each), stop the wave, report which branches conflict
- **All remaining tasks blocked** → report the dependency chain and stop
- **2 consecutive no-op returns from any task-builder dispatch in the same wave** → stop dispatching subagents for the rest of this wave. Finish the remaining packets inline in the parent session. Log: `[ck:make] task-builder no-op circuit breaker tripped — inline fallback engaged for wave {N}.` This prevents burning the iteration budget on an agent that keeps returning empty.
## Critical Rules
+1 -1
View File
@@ -37,7 +37,7 @@ Stop and wait.
## Phase 1: Streamlined Sketch
No interactive Q&A. No approach proposals. No incremental presentation. Dispatch a `ck:drafter` subagent with `model: "{REASONING_MODEL}"`.
No interactive Q&A. No approach proposals. No incremental presentation. **Write kits inline in the parent session** — do NOT dispatch a `ck:drafter` subagent. The parent uses the drafter playbook (`agents/drafter.md`) as a reference and performs the work directly with Read/Write/Edit.
### 1a: Ensure Directories
+42 -16
View File
@@ -285,17 +285,32 @@ Example:
> **Approach C: Five fine-grained domains**
> Pros: Maximum parallelism. Cons: Over-decomposed, too many cross-references for this scope.
#### 3g: Present Design Incrementally
#### 3g: Present Full Design for Approval (single message)
Once the user picks an approach, present the design **section by section**. Scale each section to its complexity — a few sentences if straightforward, more detail if nuanced.
Once the user picks an approach, present the **entire domain decomposition in one message** and ask for approval once. Do NOT walk through domains one-by-one across multiple turns — the user sees the whole picture at once and can react to any part of it.
For each proposed domain:
1. Present scope and boundaries
2. Present key requirements with acceptance criteria
3. Present cross-references and dependencies
4. Ask: "Does this look right so far?"
Structure the single message as:
Only move to the next domain when the current one is approved. Be ready to revise.
```
## Proposed Domains ({count})
### 1. {domain-name}
**Scope:** {one or two sentences}
**Key requirements:** {25 bullets with testable acceptance criteria}
**Depends on:** {other domains, or "none"}
### 2. {domain-name}
...
### Cross-references
{brief map of how domains interact}
Approve this decomposition, or tell me what to change?
```
Scale each domain block to its complexity — a few bullets is fine for straightforward domains, more detail only if genuinely nuanced. Keep the whole message scannable.
If the user requests changes, revise and re-present the full updated decomposition in one message. Only proceed to Step 4 once the user approves.
**Design for isolation:** Each domain should:
- Have one clear purpose
@@ -335,7 +350,13 @@ Analyze the input and decompose into logical domains. Each domain should be:
**Only after user approves the design**, generate cavekit files.
Do NOT perform the actual cavekit writing inline in the parent thread. Dispatch a `ck:drafter` subagent with `model: "{REASONING_MODEL}"` to write the files, then review the result in the parent thread.
**Write kits inline in the parent thread.** Do NOT dispatch a `ck:drafter` subagent — the parent session performs the cavekit writing directly, following the structure in `agents/drafter.md` as a reference guide. This removes the subagent-dispatch failure mode (silent returns, worktree churn) and keeps the kit-writing step observable.
Follow the drafter playbook inline:
1. Read the agreed decomposition from the approved design.
2. For each domain, write `context/kits/cavekit-{domain}.md` directly with Write/Edit.
3. Self-review each file against the quality rules below before moving on.
4. Write `context/kits/cavekit-overview.md`.
For each domain, create `context/kits/cavekit-{domain}.md`:
@@ -482,13 +503,18 @@ Agent tool (subagent_type: "ck:cavekit-reviewer", model: "{REASONING_MODEL}"):
## Step 9: User Review Gate
After the review loop passes, ask the user to review the written kits:
After the review loop passes, present the Draft Report (Step 10 format) in a single message and ask for approval **with an explicit auto-advance offer**:
> "Kits written and validated. Files are in `context/kits/`. Please review them and let me know if you want to make any changes before we move to the Architect phase."
> "Kits written and validated. Files are in `context/kits/`. Reply **approve** (or `/ck:map`) to auto-advance to the Architect phase, or tell me what to change."
Wait for the user's response. If they request changes, make them and re-run Step 8. Only proceed once the user approves.
Response handling:
- **Approve / yes / looks good / ship it / ok / `/ck:map`** → treat as full approval. Skip waiting for a second confirmation. Immediately invoke `/ck:map` in the same turn and proceed into the Architect phase.
- **Change requests** → apply changes, re-run Step 8, then return here. Do not auto-advance until the user approves.
- **Ambiguous** → ask once for clarification; do not advance.
## Step 10: Report and Transition
The single approval in 3g + the single approval here are the only two user gates. Do not introduce extra per-domain or per-file checks.
## Step 10: Draft Report Format
```markdown
## Draft Report
@@ -505,10 +531,10 @@ Wait for the user's response. If they request changes, make them and re-run Step
- {anything that couldn't be fully specified}
### Next Step
Run `/ck:map` to generate the build site from these kits.
Reply **approve** to auto-advance to `/ck:map`, or request changes.
```
Present the report. When the user is ready, transition to `/ck:map`.
This is the payload shown in Step 9 — not a separate stage.
---
@@ -518,6 +544,6 @@ Present the report. When the user is ready, transition to `/ck:map`.
- **Multiple choice preferred** — easier to answer than open-ended when possible
- **YAGNI ruthlessly** — remove unnecessary features from all kits
- **Explore alternatives** — always propose 2-3 approaches before settling
- **Incremental validation** — present design section by section, get approval before moving on
- **Single-message domain approval** — present the full decomposition in one message and approve once; don't walk domains one-by-one
- **Design for isolation** — each domain has one purpose, clear interfaces, independently testable
- **No cavekit generation before design approval** — the design conversation IS the value
+1 -1
View File
@@ -310,7 +310,7 @@ The Draft phase (`/ck:sketch`) now embeds brainstorming principles directly. Whe
1. **Explore project context** — check existing files, docs, commits before asking questions
2. **Ask clarifying questions one at a time** — understand purpose, constraints, success criteria
3. **Propose 2-3 domain decomposition approaches** — with tradeoffs and a recommendation
4. **Present the design incrementally** — section by section, get approval per domain
4. **Present the full decomposition in one message** — all domains together, approve once; not per-domain walk-through
5. **Generate kits only after design approval** — formalize with acceptance criteria
6. **Cavekit review loop** — automated reviewer checks quality, up to 3 iterations
7. **User review gate** — explicit approval before transitioning to Architect phase