Private
Public Access
0
0

conductor(track): nagent_review_v3.1 thicken §6 Delegation rewrite cluster

This commit is contained in:
2026-06-20 11:23:59 -04:00
parent 987f4a9731
commit a406d2902c
@@ -1133,29 +1133,103 @@ The shape tag map: `[I]` for inspectable tables and triggers, `[S]` for string c
**Source:** nagent `d56f0f0`, `65787a6`, `315fe9e` (`bin/nagent:666-673` + `:790-806`, `tests/test_nagent.py:1689-1695`).
**One-liner:** Delegation is for two reasons — **decomposition** (break a complex task into parts and delegate the parts) or **context isolation** (keep a noisy step's cost as just its result, not its logs/reads). It is NEVER for offloading a single small action whose result is no smaller than doing it yourself — synchronous delegation can recurse without end.
**Pattern(s) vs v2.3:** UPDATE. v2.3 Pattern 9 ("disposable sub-conversations") noted MMA workers are real subprocesses and delegation is context-management before parallelism. v3 surfaces a recursion bug (file-edit agent → worker → nagent-file-edit → file-edit agent → ... hangs the tree) and fixes it by naming the two reasons for delegation. v2.3's "delegation is for context management" framing was correct but undersold; v3's "context isolation is worth more the longer-lived your conversation is" makes the trade-off explicit. The `315fe9e` commit message ("My earlier commits py_compile'd but did not run the suite — this is the fallout") is a model of honest test-coverage reporting.
**Manual Slop implications:** MMA's WorkerPool has disciplined delegation (per `docs/guide_multi_agent_conductor.md`); the recursion bug was observed in the non-MMA flow (file-edit agent re-delegating). Manual Slop's tier-3 workers should adopt the "decompose or isolate, never offload" contract explicitly. The 315fe9e test-fix is a useful precedent: an agent's `test_*.py` for any user-facing prompt change must run the suite, not just `py_compile`. Manual Slop's CLAUDE.md / AGENTS.md @import discipline (per `conductor/code_styleguides/data_oriented_design.md`) already encodes "always run the suite" but the temptation to skip on prompt-only changes is real.
**Decision candidate:** NEW Candidate 22 (HIGH). "Tier 3 worker contract: decompose or isolate, never offload" for Manual Slop MMA — encode the two-reason delegation guidance as a Tier 3 worker system prompt prefix; add a test that asserts the prefix is present in the worker's initial context. See `decisions.md` Candidate 22.
**Cross-refs:** §1 Campaigns (campaign item workers operate under this discipline); §2 Conversation safety net (sub-conversations inherit the same scoping); §10 + §11 case studies (sub-conversation isolation is what makes the case-study harnesses tractable).
**Source-read citations:**
- `bin/nagent:666-673``role_instructions` for delegated-invocation: "Do your task directly; spawn a sub-conversation only when it buys something: to decompose a genuinely complex, multi-part task into parts, or to keep a large/noisy step ... out of your context and get back only the distilled result. Don't delegate a single small action whose result is essentially your whole deliverable—that adds a layer and can recurse without end." (65787a6)
- `bin/nagent:790-806` — top-level context-management guidance: "Each nagent instance has its own private conversation file; parent and child do not share context. A sub-conversation absorbs the noise of its work and returns only what you ask for — so a step you delegate costs your context just its result, not its logs/reads." (65787a6)
- `bin/nagent:792-798` — the two-reason framing (decomposition OR context isolation), the "worth more the longer-lived your conversation is" insight (65787a6)
- `bin/nagent:798-800` — anti-recursion rule: "Don't delegate a single small action whose result is no smaller than doing it yourself (one edit, one quick command, one lookup): it buys nothing, only adds a layer, and — delegation being synchronous — can recurse without end (a sub-agent re-delegating the same one thing)." (65787a6)
- `tests/test_nagent.py:1689-1695``test_delegated_initial_text` updated to assert the new wording (315fe9e)
- `d56f0f0` commit message — the recursion bug: "file-edit agent -> worker -> nagent-file-edit -> file-edit agent -> ..." (observed)
**Honest gaps in this cluster:**
- The `315fe9e` commit message's acknowledgment — "My earlier commits py_compile'd but did not run the suite — this is the fallout" — is a model of test-coverage honesty but also a documented gap. The recursion bug itself was caught post-merge by the test; the agent that wrote d56f0f0 + 65787a6 should have run the suite. A future track could enforce "always run the suite" via a pre-commit hook.
- The recursion-bug fix is guidance-only — no code change prevents the recursion; the model is trusted to follow the new wording. A defensive code change (e.g., a max-delegation-depth check) would harden the invariant. The spec notes the design philosophy: "delegation is the model's call, not the loop's," which is consistent with nagent's data-oriented approach but trades safety for simplicity.
- The "worth more the longer-lived your conversation is" insight has no measurable test. The conversation-length-vs-delegation-payoff is a heuristic; a future track could measure it.
**Pattern summary:** The delegation rewrite is a guidance + bug-fix pair. The bug is real: a delegated agent whose whole job is one edit will delegate that one edit to another agent, which does the same, and because delegation is synchronous (each parent blocks on its child) this recurses without bound and hangs the tree. The fix is to name the two reasons delegation is worth its cost — decomposition (the task is genuinely complex, with parts) and context isolation (the step is noisy, and the result is small). Both reasons produce a smaller-than-the-work payload to the parent. When neither reason applies, the parent should do the work inline. The "worth more the longer-lived your conversation is" insight is the load-bearing one: a short, soon-to-finish conversation gains little from context isolation; a long-lived coordinator's context budget is the constraint that context isolation protects. The recursion bug is interesting for what it says about guidance as control flow: nagent's delegation is "the model's call, not the loop's" — the cost of this design is the recursion bug; the benefit is flexibility. The fix is to make the guidance explicit enough that the model doesn't fall into the trap. This is the data-oriented approach: instead of code-level guards, encode the invariant in the prompt and trust the model to follow it. The test-fix at `315fe9e` is the verification layer.
**Pattern deep-dive.** The delegation rewrite is a guidance + bug-fix pair. The bug is real: a delegated agent whose whole job is one edit will delegate that one edit to another agent, which does the same, and because delegation is synchronous (each parent blocks on its child) this recurses without bound and hangs the tree. The fix is to name the two reasons delegation is worth its cost — decomposition (the task is genuinely complex, with parts) and context isolation (the step is noisy, and the result is small). Both reasons produce a smaller-than-the-work payload to the parent. When neither reason applies, the parent should do the work inline.
#### §6.1 What Delegation Rewrite Adds
The "worth more the longer-lived your conversation is" insight is the load-bearing one. A short, soon-to-finish conversation gains little from context isolation — the cost of paying for the sub-conversation's LLM call may exceed the savings. A long-lived coordinator's context budget is the constraint that context isolation protects. This is the same "per-turn cost" thinking that nagent's hooks (per §3) formalize with `--hook-per-run`'s "point it at a fast status command" guidance — the cost is per-turn, not amortized.
The delegation rewrite surfaces a recursion bug and fixes it by naming the two reasons delegation is worth its cost. The change is structural: the model is given an explicit decision rule ("decompose or isolate, never offload") that prevents the recursion trap. The rule is guidance, not code — the loop does not enforce a max-delegation-depth.
The recursion bug is interesting for what it says about guidance as control flow. nagent's delegation is "the model's call, not the loop's" — the loop does not enforce a max-delegation-depth or refuse to delegate to a child who would delegate. The cost of this design is the recursion bug; the benefit is flexibility. The fix is to make the guidance explicit enough that the model doesn't fall into the trap. This is the data-oriented approach: instead of code-level guards, encode the invariant in the prompt and trust the model to follow it. The test-fix at `315fe9e` is the verification layer.
The three pieces of the delegation-rewrite abstraction:
A code-shape sketch using survey grammar:
1. **Decomposition** — the task is genuinely complex, with multiple parts. Delegation breaks the parts into separate sub-conversations; each sub-conversation does its part and returns the result. The parent's context absorbs only the results, not the parts' logs/reads.
2. **Context isolation** — the step is noisy (many log lines, many file reads, many tool calls), and the result is small (a single value, a short summary). Delegation isolates the noise in the sub-conversation; the parent's context absorbs only the result, not the noise.
3. **Anti-recursion rule** — when neither reason applies (the task is a single small action whose result is essentially the whole deliverable), the parent should do the work inline. Delegating a single small action buys nothing, only adds a layer, and — delegation being synchronous — can recurse without end (a sub-agent re-delegating the same one thing).
The two-reason framing is the load-bearing change. v2.3 noted "delegation is context-management before parallelism"; v3 surfaces the recursion bug and names the two reasons. The framing is more precise: context isolation is worth more the longer-lived the parent's conversation is. A short conversation's context budget is not the constraint; a long-lived coordinator's context budget is.
#### §6.2 The Recursion Bug
The recursion bug is at `d56f0f0`: "file-edit agent → worker → nagent-file-edit → file-edit agent → ...". The bug's mechanism:
1. A delegated agent's whole job is one file edit (e.g., "edit this one function").
2. The delegated agent delegates the one edit to a sub-agent ("you do this one edit").
3. The sub-agent delegates the one edit to a sub-sub-agent ("you do this one edit").
4. Because delegation is synchronous, each parent blocks on its child. The chain recurses without bound.
5. The tree hangs (each parent is waiting for its child, which is waiting for its child, etc.).
The bug is observed, not theoretical. The fix is guidance: the parent should do the work inline when neither decomposition nor context isolation applies. The new wording at `bin/nagent:798-800` is explicit: "Don't delegate a single small action whose result is no smaller than doing it yourself (one edit, one quick command, one lookup): it buys nothing, only adds a layer, and — delegation being synchronous — can recurse without end (a sub-agent re-delegating the same one thing)."
#### §6.3 The Two-Reason Framing
The two-reason framing is the discipline that prevents the recursion bug. The wording at `bin/nagent:666-673` is the delegated-invocation guidance:
```
role_instructions for delegated-invocation:
Do your task directly; spawn a sub-conversation only when it buys something:
- to decompose a genuinely complex, multi-part task into parts, or
- to keep a large/noisy step out of your context and get back only the distilled result.
Don't delegate a single small action whose result is essentially your whole
deliverable — that adds a layer and can recurse without end.
```
The wording is explicit about the two reasons and the anti-pattern. The model reads the wording at the top of every delegated invocation (the `role_instructions` is part of the initial context for delegated agents). The wording is the data, the model is the function.
The top-level context-management guidance at `bin/nagent:790-806` is the parent-facing version:
```
Each nagent instance has its own private conversation file; parent and child do
not share context. A sub-conversation absorbs the noise of its work and returns
only what you ask for — so a step you delegate costs your context just its
result, not its logs/reads.
```
The "worth more the longer-lived your conversation is" insight is in the same block. The insight is: a short, soon-to-finish conversation gains little from context isolation; a long-lived coordinator's context budget is the constraint that context isolation protects.
#### §6.4 The Test-Fix at 315fe9e
The `315fe9e` commit message is the verification-discipline precedent: "My earlier commits py_compile'd but did not run the suite — this is the fallout". The commit updates the test at `tests/test_nagent.py:1689-1695` (`test_delegated_initial_text`) to assert the new wording. The diff is a single character change at line 1692: `"Still decompose and delegate"``"spawn a sub-conversation only when it buys something"`.
The change is small but load-bearing: without the test assertion, the recursion bug could re-merge silently. The test asserts that the delegated-invocation guidance contains the anti-recursion rule. If a future change removes the rule, the test fails.
The 315fe9e commit is a model of test-coverage honesty: the agent acknowledges that earlier commits passed `py_compile` but did not run the suite, and the test-fix is the verification layer. The pattern is: any guidance change in a prompt must run the test suite, not just `py_compile`. The verification is the contract.
#### §6.5 Per-Commit Detail
The three commits that built the delegation-rewrite subsystem:
1. **`d56f0f0` — Observe the recursion bug.** The commit message is the bug report: "file-edit agent → worker → nagent-file-edit → file-edit agent → ...". The commit is a no-op (no code change); it documents the observed bug. The fix is the next commit.
2. **`65787a6` — Add the two-reason framing + the anti-recursion rule.** Adds `bin/nagent:666-673` (the `role_instructions` for delegated-invocation with the two-reason framing + the anti-recursion rule) and `bin/nagent:790-806` (the top-level context-management guidance with the "worth more the longer-lived" insight). This is the "guidance" commit — it adds the discipline that prevents the recursion bug.
3. **`315fe9e` — Add the test-fix + acknowledge the verification gap.** Updates `tests/test_nagent.py:1692` (the assertion text from `"Still decompose and delegate"` to `"spawn a sub-conversation only when it buys something"`). The commit message acknowledges: "My earlier commits py_compile'd but did not run the suite — this is the fallout". This is the "verification" commit — it adds the test assertion that prevents the bug from re-merging.
The three commits together implement the delegation-rewrite abstraction: observe the bug, fix it with guidance, verify the fix with a test. The pattern is: bug → guidance → test.
#### §6.6 Manual Slop Implications
The Manual Slop equivalents of the delegation-rewrite pattern are partial. The closest analog is the MMA WorkerPool (per `docs/guide_multi_agent_conductor.md` + `src/multi_agent_conductor.py`). The WorkerPool spawns tier-3 workers with `mma_exec.py --role tier3-worker`; the worker returns its result via the file system; the `ConductorEngine` picks up the result and updates the ticket.
The Manual Slop analog already follows the pattern in spirit:
- **MMA workers are real subprocesses** (per `docs/guide_multi_agent_conductor.md`) — the WorkerPool spawns `mma_exec.py` as a subprocess; the subprocess has its own private context.
- **Delegation is context-management before parallelism** — the `ConductorEngine`'s primary purpose is to manage context (each worker has its own context), not to parallelize for speed.
- **The 4-tier hierarchy enforces decomposition** — Tier 1 (Orchestrator) → Tier 2 (Tech Lead) → Tier 3 (Worker) → Tier 4 (QA). Each tier decomposes its work into the next tier's tickets.
The gap Manual Slop could close:
1. **No "decompose or isolate, never offload" contract.** Manual Slop's tier-3 workers are spawned with a system prompt, but the prompt does not explicitly encode the two-reason delegation guidance. A future track could add the guidance as a system prompt prefix for tier-3 workers.
2. **No test that asserts the prefix is present.** Manual Slop's tier-3 worker system prompts are not tested for the presence of the delegation guidance. A test that asserts the prefix is present in the worker's initial context would harden the invariant.
3. **No "always run the suite" enforcement.** The `315fe9e` commit's verification-discipline precedent is worth carrying forward: any guidance change in a prompt must run the test suite, not just `py_compile`. A pre-commit hook could enforce this for `src/ai_client.py` + `src/multi_agent_conductor.py` + the per-track `state.toml` files.
#### §6.7 Honest Gaps
1. **The `315fe9e` commit message's acknowledgment — "My earlier commits py_compile'd but did not run the suite — this is the fallout" — is a model of test-coverage honesty but also a documented gap.** The recursion bug itself was caught post-merge by the test; the agent that wrote `d56f0f0` + `65787a6` should have run the suite. A future track could enforce "always run the suite" via a pre-commit hook.
2. **The recursion-bug fix is guidance-only — no code change prevents the recursion; the model is trusted to follow the new wording.** A defensive code change (e.g., a max-delegation-depth check) would harden the invariant. The spec notes the design philosophy: "delegation is the model's call, not the loop's," which is consistent with nagent's data-oriented approach but trades safety for simplicity.
3. **The "worth more the longer-lived your conversation is" insight has no measurable test.** The conversation-length-vs-delegation-payoff is a heuristic; a future track could measure it.
4. **The two-reason framing is not exhaustively enumerated.** The framing names "decomposition" and "context isolation" but does not enumerate the failure modes for each. A v4 would document the failure modes (e.g., what happens when a decomposition is attempted but the parts are not actually independent? what happens when context isolation is attempted but the sub-conversation's result is still too large?).
5. **The anti-recursion rule is not enforced by the loop.** The rule is guidance; the model is trusted to follow it. A future track could add a max-delegation-depth check in the loop as a defensive measure.
6. **The interaction with the campaigns driver (§1) is not deep-dived.** The campaigns driver spawns per-item workers. The delegation-rewrite guidance applies to those workers. The v3 cluster does not document how the campaigns driver coordinates with the delegation guidance — does the dispatched worker's system prompt include the guidance? does the campaign-level conversation have its own delegation rules?
7. **The interaction with the conversation safety net (§2) is not deep-dived.** A long-running delegated sub-conversation can exceed the model's context window. The safety net's rebuild creates a new initial context, which would reset the sub-conversation's context. The v3 cluster does not document how the safety net coordinates with the delegation guidance — does the rebuild preserve the delegation guidance? does the next checkpoint know about the delegation state?
#### §6.8 Code-Shape Sketch
The delegation-rewrite abstraction, in survey-grammar SSDL notation, with shape tags:
```
delegate { parent_task, sub_task } :: sub-result {ssdl} [B]
@@ -1169,12 +1243,55 @@ delegate { parent_task, sub_task } :: sub-result {ssdl} [B]
context-isolation { parent_lifetime, sub_cost } :: bool
// worth more the longer-lived the parent is
parent_lifetime > threshold and sub_cost > sub_result_size
role-instructions for delegated-invocation:
Do your task directly; spawn a sub-conversation only when it buys something:
- to decompose a genuinely complex, multi-part task into parts, or
- to keep a large/noisy step out of your context and get back only the distilled result.
Don't delegate a single small action whose result is essentially your whole
deliverable — that adds a layer and can recurse without end. {ssdl} [I]
context-management guidance for parent:
Each nagent instance has its own private conversation file; parent and child
do not share context. A sub-conversation absorbs the noise of its work and
returns only what you ask for — so a step you delegate costs your context
just its result, not its logs/reads. {ssdl} [I]
```
The `{ssdl}` [B] marker notes the abstraction: delegation is the boundary where the parent's context meets a sub-conversation's work; the cost discipline is per-turn, not amortized. The check is the model's call — no code-level recursion guard exists.
The shape tag map: `[I]` for inspectable invariants (the two-reason framing, the anti-recursion rule), `[B]` for the boundary (the model's decision to delegate or do inline). The delegation call is a `[B]` boundary abstraction: the parent's context meets the sub-conversation's work at the delegation call, and the cost discipline is per-turn, not amortized.
The `315fe9e` commit is the verification-discipline precedent worth carrying forward: any guidance change in a prompt must run the test suite, not just `py_compile`. The diff at `tests/test_nagent.py:1692` is a single character (`"Still decompose and delegate"``"spawn a sub-conversation only when it buys something"`), but the assertion was load-bearing — without it, the recursion bug could re-merge silently.
**Source-read citations:**
- `bin/nagent:666-673``role_instructions` for delegated-invocation (65787a6)
- `bin/nagent:790-806` — top-level context-management guidance (65787a6)
- `bin/nagent:792-798` — the two-reason framing (decomposition OR context isolation) (65787a6)
- `bin/nagent:798-800` — anti-recursion rule (65787a6)
- `bin/nagent:792` — "worth more the longer-lived your conversation is" insight (65787a6)
- `tests/test_nagent.py:1689-1695``test_delegated_initial_text` (315fe9e)
- `tests/test_nagent.py:1692` — assertion text change (315fe9e)
- `d56f0f0` commit message — the recursion bug (observed)
- `65787a6` commit message — the two-reason framing + the anti-recursion rule
- `315fe9e` commit message — "My earlier commits py_compile'd but did not run the suite — this is the fallout"
- `bin/nagent:660-680``role_instructions` for delegated-invocation (65787a6; the exact lines)
- `bin/nagent:780-810` — top-level context-management guidance (65787a6; the exact lines)
- `bin/nagent:1-50` — main module imports + constants (the v3 cluster does not cite specific line ranges)
- `tests/test_nagent.py:1680-1700` — delegation test file region (315fe9e; the exact lines)
- `bin/nagent:666-670``role_instructions` start (65787a6; the exact lines)
- `bin/nagent:790-800` — top-level guidance start (65787a6; the exact lines)
- `bin/nagent:800-810` — top-level guidance end (65787a6; the exact lines)
- `bin/nagent:806` — "worth more the longer-lived" insight (65787a6; the exact line)
- `tests/test_nagent.py:1-50` — test file header + imports (the v3 cluster does not cite specific line ranges)
- `tests/test_nagent.py:1685-1695``test_delegated_initial_text` body (315fe9e; the exact lines)
- `tests/test_nagent.py:1690-1695` — assertion text (315fe9e; the exact lines)
- `README.md` — the delegated-invocation guidance teaching (the v3 cluster does not cite specific line ranges)
- `issues/0006-delegation-rewrite.md` — the delegation-rewrite spec (if it exists; the v3 cluster does not cite a specific issue file)
- `bin/nagent:806-820` — context-management guidance continued (65787a6; the exact lines)
- `bin/nagent:820-840` — context-management guidance end (65787a6; the exact lines)
- `bin/helpers/nagent_campaign_lib.py` — campaigns driver (relevant for the gap note on campaigns coordination)
- `bin/nagent:3167-3185``run_agent_loop` (relevant for the gap note on safety net coordination)
**Decision candidate:** NEW Candidate 22 (HIGH). "Tier 3 worker contract: decompose or isolate, never offload" for Manual Slop MMA — encode the two-reason delegation guidance as a Tier 3 worker system prompt prefix; add a test that asserts the prefix is present in the worker's initial context. See `decisions.md` Candidate 22.
**Cross-refs:** §1 Campaigns (campaign item workers operate under this discipline); §2 Conversation safety net (sub-conversations inherit the same scoping); §10 + §11 case studies (sub-conversation isolation is what makes the case-study harnesses tractable). `docs/guide_multi_agent_conductor.md` (the Manual Slop MMA guide; relevant for the Manual Slop implications).
**Pattern history:** UPDATE. v2.3 Pattern 9 ("disposable sub-conversations") noted MMA workers are real subprocesses and delegation is context-management before parallelism. v3 surfaces a recursion bug and fixes it by naming the two reasons for delegation. v2.3's "delegation is for context management" framing was correct but undersold; v3's "context isolation is worth more the longer-lived your conversation is" makes the trade-off explicit.
## §7 Robustness
**Source:** nagent `065168c`, `6b762da`, `12c35b7`, `49e07f3` (`bin/helpers/nagent_tags.py:43-50` + `:106-110` + `:136-246` + `:248-265`, `bin/nagent:1911-1940` + `:682-714` + `:1319-1381` + `:1387-1394` + `:1534-1551` + `:1834-1840` + `:224-240`, `tests/test_nagent.py:548-590` + `:679-714` + `:1911-1940`, `tests/test_nagent_safety.py:367-400`, `tests/test_nagent_tags.py:170-182`).