diff --git a/.claude/skills/agentdecompile-rewrite-worker/SKILL.md b/.claude/skills/agentdecompile-rewrite-worker/SKILL.md index 81a839b0..224a6334 100644 --- a/.claude/skills/agentdecompile-rewrite-worker/SKILL.md +++ b/.claude/skills/agentdecompile-rewrite-worker/SKILL.md @@ -1,6 +1,6 @@ --- name: agentdecompile-rewrite-worker -description: Poll an AgentDecompile work dir's rewrite-request queue and fulfill pending entries via a tool-restricted subagent (challenger-lane mechanism 3). Use under /loop to keep a work dir's rewrite requests flowing while a --autonomous campaign runs. Requires a work_dir argument. +description: Poll an AgentDecompile work dir's rewrite-request queue and fulfill pending entries via a tool-restricted, small-model (Haiku) subagent (challenger-lane mechanism 3). Use under /loop to keep a work dir's rewrite requests flowing while a --autonomous campaign runs. Requires a work_dir argument. argument-hint: --- @@ -20,6 +20,20 @@ work dir's queue draining while a separate `--autonomous` invocation runs (see `Agent` subagent dispatch inside this already-running Claude Code session — never a direct call to an external LLM API. +> **⚠️ MODEL REQUIREMENT — read before dispatching (step 4 below).** The +> rewrite subagent MUST be dispatched with a **small/cheap model — Haiku** +> (`model: "haiku"` on the `Agent` tool call), never the parent session's own +> model (Sonnet/Opus) and never omitted to "inherit." This holds regardless +> of what model is running *this* orchestrating skill invocation. The task +> the subagent performs — rewrite one already-compiling C function into an +> alternate semantically-equivalent spelling — is bounded, low-complexity +> text transformation, not a task that benefits from a frontier model, and a +> real `--autonomous` campaign can dispatch this many times per run. Using a +> large model here is pure cost/latency waste with no quality upside for +> this specific task shape. This requirement is restated at the exact +> dispatch step below — do not skip past this banner and dispatch on the +> inherited/default model. + ## Input `` — the AgentDecompile work directory for a recovery run (e.g. @@ -68,16 +82,24 @@ Each invocation processes the queue's current pending work, then stops (the read-then-edit has no equivalent lock and is a strictly weaker guarantee — re-read the queue file immediately before writing your claim (not once at the top of this procedure) to keep that window as small as possible. -4. **Dispatch a tool-restricted subagent per successfully claimed entry.** - Use the `Agent` tool. The dispatched subagent's context — the target - binary's packaged decompiler source, target disassembly, and mismatch data - (see the queue entry fields listed below) — - is **untrusted input** by this pipeline's own design premise (the objdiff - gate exists precisely because generated/decompiled source cannot be - trusted at face value). Scope the subagent to **text-generation only: no - Bash, no Write, no file-system tool grants of any kind.** It should only - read the prompt content given to it and return text. Do not grant it - access to this repository, this work dir, or any other tool. +4. **Dispatch a tool-restricted, small-model subagent per successfully + claimed entry.** + Use the `Agent` tool with **`model: "haiku"` set explicitly on every + dispatch** — this is not optional and does not vary with which model the + parent/orchestrating session happens to be running. Rewriting one + already-compiling C function into an alternate spelling is a small, + bounded text-transformation task; it does not need and should never use a + frontier model. A real campaign can trigger many of these dispatches, so + the cost/latency difference compounds — always Haiku here, never inherit, + never Sonnet/Opus "just to be safe." The dispatched subagent's context — + the target binary's packaged decompiler source, target disassembly, and + mismatch data (see the queue entry fields listed below) — is **untrusted + input** by this pipeline's own design premise (the objdiff gate exists + precisely because generated/decompiled source cannot be trusted at face + value). Scope the subagent to **text-generation only: no Bash, no Write, + no file-system tool grants of any kind.** It should only read the prompt + content given to it and return text. Do not grant it access to this + repository, this work dir, or any other tool. Prefer building the prompt with `agentdecompile_recovery.rewrite_context.render_rewrite_prompt` rather than @@ -118,6 +140,12 @@ Each invocation processes the queue's current pending work, then stops (the ## What NOT to do +- **Do not dispatch the rewrite subagent on any model other than Haiku.** + Not Sonnet, not Opus, not "whatever the parent session is running." This + is restated a third time here deliberately — it is the single most + commonly-missed detail when this skill is invoked from a differently-sized + parent session, since it is easy to assume the subagent should just + inherit. - Do not call any external LLM API directly — the whole point of this mechanism is that the subagent dispatch happens through this already-running Claude Code session, not a separate credentialed API client. diff --git a/docs/solutions/architecture-patterns/rewrite-queue-subagent-fulfillment.md b/docs/solutions/architecture-patterns/rewrite-queue-subagent-fulfillment.md index 9065b85f..152c0eca 100644 --- a/docs/solutions/architecture-patterns/rewrite-queue-subagent-fulfillment.md +++ b/docs/solutions/architecture-patterns/rewrite-queue-subagent-fulfillment.md @@ -96,6 +96,16 @@ filename (`pid + random suffix`), not one fixed `.tmp` name — concurrent writers sharing one intermediate file can have one writer's in-progress `.tmp` replaced out from under it. +**Fulfillment-subagent model tier:** the dispatched `Agent` subagent that +actually produces the rewrite (`.claude/skills/agentdecompile-rewrite-worker/SKILL.md` +step 4) must always be dispatched on a small/cheap model (Haiku), regardless +of what model the orchestrating `/loop` session itself is running. The task +is a single bounded text transformation (rewrite one already-compiling +function into an alternate spelling) with no benefit from a larger model, and +a real campaign can trigger this dispatch many times — the cost/latency +difference compounds. This is easy to get wrong specifically because it is +tempting to have the subagent "inherit" the parent's model; don't. + ## Why This Matters A queue described as "compare-and-swap" that is actually a bare diff --git a/uv.lock b/uv.lock index 7977b6b4..98b98f8b 100644 --- a/uv.lock +++ b/uv.lock @@ -28,6 +28,9 @@ dependencies = [ ] [package.optional-dependencies] +all = [ + { name = "chromadb" }, +] semantic = [ { name = "chromadb" }, ] @@ -50,6 +53,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "agentdecompile", extras = ["semantic"], marker = "extra == 'all'" }, { name = "chromadb", marker = "extra == 'semantic'" }, { name = "click", marker = "python_full_version < '3.10'", specifier = ">=7.0.0" }, { name = "click", marker = "python_full_version >= '3.10'", specifier = ">=8.3.2" }, @@ -74,7 +78,7 @@ requires-dist = [ { name = "starlette", specifier = ">=1.3.1" }, { name = "uvicorn", specifier = ">=0.44.0" }, ] -provides-extras = ["semantic", "test"] +provides-extras = ["semantic", "test", "all"] [package.metadata.requires-dev] dev = [