Skip to content

Commit 81d71c1

Browse files
authored
feat(issue-60): delegate quorum slot coding to external agents (#68)
Adds Mode C coding delegation, --delegate flag on /nf:quick, and routing-policy.cjs with Tier 0 PresetPolicy + Tier 1 RiverPolicy bandit layer.
1 parent 1a33a68 commit 81d71c1

29 files changed

Lines changed: 3657 additions & 10 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,7 @@ bin/.planning/
150150
# Session artifacts from cycle2 simulations (route to os.tmpdir() instead)
151151
.planning/formal/cycle2-simulations/
152152
.planning/quorum/slot-results/
153+
154+
# Routing policy runtime state (learned bandit data)
155+
.nf-routing-rewards.jsonl
156+
.nf-river-state.json

.planning/STATE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ See: .planning/PROJECT.md
1818
| 379 | Issue #46: Add formal model staleness detection via content hashing | 2026-04-05 | 7ae0fe01 | Needs Review | [379-issue-46-add-formal-model-staleness-dete](./quick/379-issue-46-add-formal-model-staleness-dete/) |
1919
| 375 | issue-47-agents-skip-formal-modeling | 2026-04-05 | d174cae7 | Verified | [375-issue-47-agents-skip-formal-modeling](./quick/375-issue-47-agents-skip-formal-modeling/) |
2020
| 380 | Improve nForma reporting for feature usefulness and bugs caught | 2026-04-07 | 0e3bb9ee | Verified | [380-improve-nforma-reporting-for-feature-use](./quick/380-improve-nforma-reporting-for-feature-use/) |
21+
| 380 | Delegate quorum slot coding to external agents via quorum-slot-dispatch.cjs | 2026-04-06 | f56c7d43 | Verified | [380-delegate-quorum-slot-coding-to-external-](./quick/380-delegate-quorum-slot-coding-to-external-/) |
22+
| 381 | Add --delegate flag to nf:quick for full task delegation via Mode C dispatch | 2026-04-07 | e77c29eb | Verified | [381-add-delegate-flag-to-nf-quick-for-full-t](./quick/381-add-delegate-flag-to-nf-quick-for-full-t/) |
23+
| 382 | Implement Tier 0 policy interface and Tier 1 River bandit layer for routing authority ladder in selectSlot | 2026-04-07 | 1fc88449 | Verified | [382-implement-tier-0-policy-interface-and-ti](./quick/382-implement-tier-0-policy-interface-and-ti/) |
24+
| 383 | Wire task-intake routing into PresetPolicy, update nf:quick command metadata with delegate flag, add delegation docs | 2026-04-07 | 40ef3ab3 | Verified | [383-wire-task-intake-routing-into-presetpoli](./quick/383-wire-task-intake-routing-into-presetpoli/) |
2125

2226
## Session Log
2327

@@ -26,3 +30,7 @@ See: .planning/PROJECT.md
2630
- 2026-04-04: STATE.md regenerated by /gsd:health --repair
2731
- 2026-04-05: Completed quick task 379 - Issue #46: Add formal model staleness detection via content hashing
2832
- 2026-04-07: Completed quick task 380 - Improve nForma reporting for feature usefulness and bugs caught
33+
- 2026-04-06 - Completed quick task 380: Delegate quorum slot coding to external agents via quorum-slot-dispatch.cjs
34+
- 2026-04-07 - Completed quick task 381: Add --delegate flag to nf:quick for full task delegation via Mode C dispatch
35+
- 2026-04-07 - Completed quick task 382: Implement Tier 0 policy interface and Tier 1 River bandit layer for routing authority ladder in selectSlot
36+
- 2026-04-07 - Completed quick task 383: Wire task-intake routing into PresetPolicy, update command metadata, add delegation docs

.planning/formal/requirements.json

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"aggregated_at": "2026-04-06T07:17:59.088Z",
3-
"content_hash": "6de002472c2b7872",
2+
"aggregated_at": "2026-04-07T09:24:58.362Z",
3+
"content_hash": "535327adb9754ce0",
44
"requirements": [
55
{
66
"id": "ACT-01",
@@ -1433,6 +1433,30 @@
14331433
"milestone": "quick-374"
14341434
}
14351435
},
1436+
{
1437+
"id": "DISP-07",
1438+
"text": "Quorum dispatcher Mode C delegates structured coding tasks to external agent CLI slots (codex, gemini, opencode, copilot) via coding-task-router.cjs, with status-to-verdict mapping (SUCCESS->APPROVE, PARTIAL->FLAG, FAILED->REJECT, UNKNOWN->FLAG) for integration into the quorum voting path",
1439+
"category": "Quorum & Dispatch",
1440+
"phase": "unknown",
1441+
"status": "Complete",
1442+
"background": "Quick task 380 implemented coding task delegation as the foundational layer for Issue #60. Previously quorum slots only handled review/voting (Mode A for questions, Mode B for execution review). Mode C enables external agents to receive and execute actual coding work with structured result collection.",
1443+
"provenance": {
1444+
"source_file": ".planning/quick/380-delegate-quorum-slot-coding-to-external-/380-PLAN.md",
1445+
"milestone": "quick-380"
1446+
}
1447+
},
1448+
{
1449+
"id": "DISP-08",
1450+
"text": "/nf:quick --delegate {slot-name} flag enables full task delegation to named external agent CLI slots via Mode C dispatch, bypassing local planning and execution while preserving scope contract tracking and result recording in STATE.md.",
1451+
"category": "Quorum & Dispatch",
1452+
"phase": "unknown",
1453+
"status": "Complete",
1454+
"background": "Quick task 381 adds the --delegate flag to the /nf:quick workflow, providing a user-facing interface to Mode C dispatch (established in DISP-07). The flag validates the target slot against providers.json, ensures it is a subprocess provider with file access, and routes the task through coding-task-router.cjs for execution by the external agent.",
1455+
"provenance": {
1456+
"source_file": ".planning/quick/381-add-delegate-flag-to-nf-quick-for-full-t/381-PLAN.md",
1457+
"milestone": "quick-381"
1458+
}
1459+
},
14361460
{
14371461
"id": "DIST-01",
14381462
"text": "The npm `@next` dist-tag SHALL never point to a version older than `@latest`. After every stable publish to `@latest`, the `@next` dist-tag MUST be aligned to the same or a newer version.",
@@ -4100,6 +4124,18 @@
41004124
".planning/formal/tla/TaskClassification.tla"
41014125
]
41024126
},
4127+
{
4128+
"id": "ROUTE-05",
4129+
"text": "selectSlot delegates to pluggable 3-tier policy interface enabling learned slot routing via PresetPolicy (Tier 0) and RiverPolicy (Tier 1) contextual bandit with confidence gates.",
4130+
"category": "Token Optimization & Model Routing",
4131+
"phase": "unknown",
4132+
"status": "Complete",
4133+
"background": "Quick task 382 implements a policy layer beneath selectSlot that enables the system to learn which agent slot performs best for each task type, starting with static preset selection and progressing to evidence-based routing through the River contextual bandit algorithm.",
4134+
"provenance": {
4135+
"source_file": ".planning/quick/382-implement-tier-0-policy-interface-and-ti/382-PLAN.md",
4136+
"milestone": "quick-382"
4137+
}
4138+
},
41034139
{
41044140
"id": "RSN-01",
41054141
"text": "Hazard model applies FMEA (Severity x Occurrence x Detection = RPN) to L2 operational model states and transitions",
@@ -6141,5 +6177,5 @@
61416177
"schema_version": "1",
61426178
"source": ".planning/REQUIREMENTS.md",
61436179
"last_sync": "2026-03-31T09:45:23.686Z",
6144-
"frozen_at": "2026-04-06T07:17:59.088Z"
6180+
"frozen_at": "2026-04-07T09:24:58.362Z"
61456181
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
---
2+
phase: quick-380
3+
plan: 01
4+
type: execute
5+
wave: 1
6+
depends_on: []
7+
files_modified:
8+
- bin/quorum-slot-dispatch.cjs
9+
- bin/quorum-slot-dispatch.test.cjs
10+
- bin/coding-task-router.cjs
11+
- bin/coding-task-router.test.cjs
12+
autonomous: true
13+
requirements: [INTENT-01]
14+
formal_artifacts: none
15+
16+
must_haves:
17+
truths:
18+
- "quorum-slot-dispatch.cjs accepts --mode C and builds a coding delegation prompt"
19+
- "coding-task-router.cjs routes a task description to a named provider slot and returns structured output"
20+
- "Mock agent CLI (echo-based) can receive a coding task via stdin and return structured result"
21+
- "Existing Mode A and Mode B dispatch paths are unaffected by the changes"
22+
artifacts:
23+
- path: "bin/quorum-slot-dispatch.cjs"
24+
provides: "Mode C prompt builder and dispatch path"
25+
contains: "buildModeCPrompt"
26+
- path: "bin/coding-task-router.cjs"
27+
provides: "Coding task router with pluggable slot selection"
28+
exports: ["routeCodingTask", "buildCodingPrompt", "parseCodingResult"]
29+
- path: "bin/coding-task-router.test.cjs"
30+
provides: "Tests for coding task router including mock CLI delegation"
31+
min_lines: 80
32+
key_links:
33+
- from: "bin/coding-task-router.cjs"
34+
to: "bin/call-quorum-slot.cjs"
35+
via: "child_process.spawn for CLI delegation"
36+
pattern: "call-quorum-slot\\.cjs"
37+
- from: "bin/quorum-slot-dispatch.cjs"
38+
to: "bin/coding-task-router.cjs"
39+
via: "require and mode C branch"
40+
pattern: "coding-task-router"
41+
consumers:
42+
- artifact: "bin/coding-task-router.cjs"
43+
consumed_by: "bin/quorum-slot-dispatch.cjs"
44+
integration: "Mode C branch in main() delegates to router"
45+
verify_pattern: "coding-task-router"
46+
---
47+
48+
<objective>
49+
Add coding task delegation to the quorum slot dispatch system, enabling external agent CLIs
50+
(codex, gemini, opencode, copilot) to receive and execute coding tasks -- not just review tasks.
51+
52+
Purpose: This is the foundational layer for Issue #60. Currently quorum slots only handle
53+
review/voting (Mode A: questions, Mode B: execution review). This adds Mode C: coding delegation,
54+
where a task description is sent to an external CLI agent to perform actual coding work, with
55+
structured result collection.
56+
57+
Output: New coding-task-router.cjs module, Mode C support in quorum-slot-dispatch.cjs, and tests.
58+
</objective>
59+
60+
<execution_context>
61+
@./.claude/nf/workflows/execute-plan.md
62+
@./.claude/nf/templates/summary.md
63+
</execution_context>
64+
65+
<context>
66+
@.planning/STATE.md
67+
@bin/quorum-slot-dispatch.cjs
68+
@bin/call-quorum-slot.cjs
69+
@bin/providers.json
70+
</context>
71+
72+
<tasks>
73+
74+
<task type="auto">
75+
<name>Task 1: Create coding-task-router.cjs with prompt builder, result parser, and routing logic</name>
76+
<files>bin/coding-task-router.cjs, bin/coding-task-router.test.cjs</files>
77+
<action>
78+
Create `bin/coding-task-router.cjs` -- a new module that handles coding task delegation to external agent CLIs.
79+
80+
The module must export these pure functions (testable without subprocess):
81+
82+
1. `buildCodingPrompt({ task, repoDir, files, constraints, context })` -- constructs a coding task prompt:
83+
- `task` (string): description of what to implement/fix/refactor
84+
- `repoDir` (string): absolute path to the repository
85+
- `files` (string[]): files the agent should focus on
86+
- `constraints` (string[]): optional constraints (e.g., "do not modify tests", "use CommonJS")
87+
- `context` (string): optional additional context (e.g., error output, prior attempt)
88+
- Returns a structured prompt string with clear sections: TASK, REPOSITORY, FILES, CONSTRAINTS, CONTEXT
89+
- Include instruction block telling the agent to output a structured result with: status (SUCCESS|PARTIAL|FAILED), files_modified (list), summary (what was done), diff_preview (optional abbreviated diff)
90+
91+
2. `parseCodingResult(rawOutput)` -- extracts structured result from CLI output:
92+
- Looks for `status:`, `files_modified:`, `summary:`, `diff_preview:` fields in output
93+
- Returns `{ status, filesModified, summary, diffPreview, rawOutput }` or null on parse failure
94+
- Fail-open: if parsing fails, returns `{ status: 'UNKNOWN', filesModified: [], summary: rawOutput.slice(0, 500), diffPreview: null, rawOutput }`
95+
96+
3. `routeCodingTask({ task, slot, repoDir, files, constraints, context, timeout })` -- orchestrates delegation:
97+
- Builds the coding prompt via buildCodingPrompt()
98+
- Spawns `call-quorum-slot.cjs --slot <slot> --timeout <timeout> --cwd <repoDir>` with prompt on stdin
99+
- Parses the raw output via parseCodingResult()
100+
- Returns `{ slot, status, filesModified, summary, diffPreview, latencyMs, rawOutput }`
101+
- On subprocess failure: returns `{ slot, status: 'UNAVAIL', error_type: classifyDispatchError(output), ... }`
102+
103+
4. `selectSlot(taskType, providers)` -- simple slot selection (pluggable policy placeholder):
104+
- For now: returns the first subprocess-type provider with `has_file_access: true`
105+
- This is the hook point for future Q-learning routing (out of scope for this task)
106+
- `taskType` is a string like "implement", "fix", "refactor", "test"
107+
- Returns provider name string or null
108+
109+
Follow existing coding patterns:
110+
- CommonJS (`'use strict'`, `require`, `module.exports`)
111+
- Fail-open error handling (try/catch with stderr logging, never crash)
112+
- Use `classifyDispatchError` from quorum-slot-dispatch.cjs for error classification
113+
- Use `path.join(__dirname, 'call-quorum-slot.cjs')` for subprocess path resolution
114+
- CLI entry point guarded by `if (require.main === module)` with arg parsing:
115+
`node coding-task-router.cjs --slot <name> --task <text> [--files <comma-separated>] [--timeout <ms>] [--cwd <dir>]`
116+
117+
Also create `bin/coding-task-router.test.cjs` with tests using `node:test` and `node:assert`:
118+
- buildCodingPrompt produces prompt containing all sections (TASK, REPOSITORY, FILES, CONSTRAINTS)
119+
- buildCodingPrompt with no optional fields still produces valid prompt
120+
- parseCodingResult extracts status/files_modified/summary from well-formed output
121+
- parseCodingResult handles malformed output gracefully (fail-open)
122+
- selectSlot returns first file-access subprocess provider
123+
- selectSlot returns null when no suitable providers exist
124+
- Module exports all expected functions
125+
</action>
126+
<verify>
127+
Run `node --test bin/coding-task-router.test.cjs` -- all tests pass.
128+
Run `node -e "const m = require('./bin/coding-task-router.cjs'); console.log(Object.keys(m))"` -- exports buildCodingPrompt, parseCodingResult, routeCodingTask, selectSlot.
129+
</verify>
130+
<done>
131+
coding-task-router.cjs exports 4 functions, all tests pass, prompt builder produces structured coding prompts,
132+
result parser handles both well-formed and malformed output, selectSlot picks first file-access subprocess provider.
133+
</done>
134+
</task>
135+
136+
<task type="auto">
137+
<name>Task 2: Add Mode C coding delegation path to quorum-slot-dispatch.cjs</name>
138+
<files>bin/quorum-slot-dispatch.cjs, bin/quorum-slot-dispatch.test.cjs</files>
139+
<action>
140+
Extend `bin/quorum-slot-dispatch.cjs` to support `--mode C` for coding task delegation.
141+
142+
1. Add `buildModeCPrompt` function (exported for testability):
143+
- Signature: `buildModeCPrompt({ repoDir, task, files, constraints, context })`
144+
- Delegates to `require('./coding-task-router.cjs').buildCodingPrompt()` internally
145+
- This keeps the prompt-building pattern consistent with buildModeAPrompt/buildModeBPrompt
146+
- Returns the constructed prompt string
147+
148+
2. Add Mode C branch in the `buildPromptForProvider` closure (around line 1519):
149+
- When `mode === 'C'`, call `buildModeCPrompt({ repoDir, task: question, files: filesArg, constraints: constraintsArg, context: reviewContext })`
150+
- Add new CLI args: `--files <comma-separated>`, `--constraints <comma-separated>`
151+
- Parse these in the arg parsing section at top of main()
152+
153+
3. Add Mode C result parsing in the post-dispatch section (around line 1785):
154+
- When mode is C and not UNAVAIL, use `require('./coding-task-router.cjs').parseCodingResult(output)`
155+
- Emit result block with coding-specific fields: add `coding_result` to emitResultBlock when mode is C
156+
- The verdict for Mode C should be derived from parseCodingResult status: SUCCESS -> APPROVE, PARTIAL -> FLAG, FAILED -> REJECT, UNKNOWN -> FLAG
157+
158+
4. Export `buildModeCPrompt` from module.exports (add to the existing exports object at line ~1881)
159+
160+
5. Add tests to `bin/quorum-slot-dispatch.test.cjs`:
161+
- `buildModeCPrompt` is exported as a function
162+
- `buildModeCPrompt` produces prompt containing TASK, REPOSITORY sections
163+
- `buildModeCPrompt` with files array includes FILES section
164+
- Existing Mode A and Mode B tests continue to pass (regression check)
165+
166+
IMPORTANT: Do NOT modify the existing buildModeAPrompt, buildModeBPrompt, parseVerdict, or any other existing function behavior. Mode C is purely additive -- new code path, no changes to existing paths. The `buildPromptForProvider` closure's existing `if (mode === 'B')` and default (Mode A) branches must remain untouched.
167+
168+
Guard against re-inlining: buildModeCPrompt MUST delegate to coding-task-router.cjs's buildCodingPrompt, not re-implement the prompt construction inline.
169+
</action>
170+
<verify>
171+
Run `node --test bin/quorum-slot-dispatch.test.cjs` -- all existing + new tests pass.
172+
Run `node -e "const m = require('./bin/quorum-slot-dispatch.cjs'); console.log(typeof m.buildModeCPrompt)"` -- prints "function".
173+
Run `grep 'coding-task-router' bin/quorum-slot-dispatch.cjs` -- confirms import exists (no re-inlining).
174+
Run `grep 'buildModeCPrompt' bin/quorum-slot-dispatch.cjs` -- confirms export exists.
175+
</verify>
176+
<done>
177+
quorum-slot-dispatch.cjs accepts --mode C, delegates prompt building to coding-task-router.cjs,
178+
parses coding results, maps status to verdict, exports buildModeCPrompt. All existing Mode A/B
179+
tests pass unchanged. New Mode C tests pass.
180+
</done>
181+
</task>
182+
183+
<task type="auto">
184+
<name>Task 3: Add mock CLI integration test for end-to-end coding delegation</name>
185+
<files>bin/coding-task-router.test.cjs</files>
186+
<action>
187+
Add an integration-level test to `bin/coding-task-router.test.cjs` that validates the full
188+
coding delegation pipeline using a mock agent CLI.
189+
190+
1. Create an inline mock agent script (written to a temp file during test setup):
191+
- The mock reads stdin, extracts the TASK section from the prompt
192+
- Outputs a well-formed coding result:
193+
```
194+
status: SUCCESS
195+
files_modified: [src/example.js]
196+
summary: Implemented the requested feature
197+
diff_preview: |
198+
+function newFeature() { return true; }
199+
```
200+
- The mock script is a simple Node.js script that reads stdin and writes to stdout
201+
202+
2. Test `routeCodingTask` by:
203+
- Using `child_process.spawnSync` to run `coding-task-router.cjs --slot mock --task "add a function" --cwd /tmp`
204+
- BUT since routeCodingTask calls call-quorum-slot.cjs which needs providers.json, instead:
205+
- Test the pure function pipeline: buildCodingPrompt -> (mock output) -> parseCodingResult
206+
- Verify the full round-trip: prompt contains task, parsed result has correct status/summary
207+
208+
3. Test error handling path:
209+
- parseCodingResult with empty string returns fail-open result
210+
- parseCodingResult with output containing only "status: FAILED\nsummary: compilation error" returns FAILED status
211+
212+
4. Clean up temp files in test teardown.
213+
214+
Note: Full subprocess integration test (actually spawning call-quorum-slot.cjs) is deferred
215+
since it requires a live provider. The pure-function round-trip test validates the contract.
216+
</action>
217+
<verify>
218+
Run `node --test bin/coding-task-router.test.cjs` -- all tests pass including integration tests.
219+
Verify test count: `node --test bin/coding-task-router.test.cjs 2>&1 | grep -c 'ok'` shows >= 8 passing tests.
220+
</verify>
221+
<done>
222+
Integration tests validate the full prompt-build -> parse round-trip with mock data.
223+
Error handling paths tested. All tests pass. The coding delegation contract is verified
224+
end-to-end at the function level.
225+
</done>
226+
</task>
227+
228+
</tasks>
229+
230+
<verification>
231+
1. `node --test bin/coding-task-router.test.cjs` -- all tests pass
232+
2. `node --test bin/quorum-slot-dispatch.test.cjs` -- all tests pass (existing + new)
233+
3. `grep 'buildModeCPrompt' bin/quorum-slot-dispatch.cjs` -- export exists
234+
4. `grep 'coding-task-router' bin/quorum-slot-dispatch.cjs` -- import exists (no re-inlining)
235+
5. `node -e "const m = require('./bin/coding-task-router.cjs'); console.log(typeof m.routeCodingTask, typeof m.buildCodingPrompt, typeof m.parseCodingResult, typeof m.selectSlot)"` -- all "function"
236+
6. `npm run test:ci` -- full test suite passes (no regressions)
237+
</verification>
238+
239+
<success_criteria>
240+
- coding-task-router.cjs exists with 4 exported functions (buildCodingPrompt, parseCodingResult, routeCodingTask, selectSlot)
241+
- quorum-slot-dispatch.cjs supports --mode C and exports buildModeCPrompt
242+
- Mode C prompt contains structured TASK/REPOSITORY/FILES/CONSTRAINTS sections
243+
- Mode C result parsing maps SUCCESS->APPROVE, PARTIAL->FLAG, FAILED->REJECT
244+
- All existing Mode A/B tests pass unchanged (zero regressions)
245+
- New tests cover prompt construction, result parsing, slot selection, and round-trip integration
246+
</success_criteria>
247+
248+
<output>
249+
After completion, create `.planning/quick/380-delegate-quorum-slot-coding-to-external-/380-SUMMARY.md`
250+
</output>

0 commit comments

Comments
 (0)