@nforma.ai/nforma 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +1024 -0
- package/agents/qgsd-codebase-mapper.md +764 -0
- package/agents/qgsd-debugger.md +1201 -0
- package/agents/qgsd-executor.md +472 -0
- package/agents/qgsd-integration-checker.md +443 -0
- package/agents/qgsd-phase-researcher.md +502 -0
- package/agents/qgsd-plan-checker.md +643 -0
- package/agents/qgsd-planner.md +1182 -0
- package/agents/qgsd-project-researcher.md +621 -0
- package/agents/qgsd-quorum-orchestrator.md +628 -0
- package/agents/qgsd-quorum-slot-worker.md +41 -0
- package/agents/qgsd-quorum-synthesizer.md +133 -0
- package/agents/qgsd-quorum-test-worker.md +37 -0
- package/agents/qgsd-quorum-worker.md +161 -0
- package/agents/qgsd-research-synthesizer.md +239 -0
- package/agents/qgsd-roadmapper.md +660 -0
- package/agents/qgsd-verifier.md +628 -0
- package/bin/accept-debug-invariant.cjs +165 -0
- package/bin/account-manager.cjs +719 -0
- package/bin/aggregate-requirements.cjs +466 -0
- package/bin/analyze-assumptions.cjs +757 -0
- package/bin/analyze-state-space.cjs +921 -0
- package/bin/attribute-trace-divergence.cjs +150 -0
- package/bin/auth-drivers/gh-cli.cjs +93 -0
- package/bin/auth-drivers/index.cjs +46 -0
- package/bin/auth-drivers/pool.cjs +67 -0
- package/bin/auth-drivers/simple.cjs +95 -0
- package/bin/autoClosePtoF.cjs +110 -0
- package/bin/blessed-terminal.cjs +350 -0
- package/bin/build-phase-index.cjs +472 -0
- package/bin/call-quorum-slot.cjs +541 -0
- package/bin/ccr-secure-config.cjs +99 -0
- package/bin/ccr-secure-start.cjs +83 -0
- package/bin/check-bundled-sdks.cjs +177 -0
- package/bin/check-coverage-guard.cjs +112 -0
- package/bin/check-liveness-fairness.cjs +95 -0
- package/bin/check-mcp-health.cjs +123 -0
- package/bin/check-provider-health.cjs +395 -0
- package/bin/check-results-exit.cjs +24 -0
- package/bin/check-spec-sync.cjs +360 -0
- package/bin/check-trace-redaction.cjs +271 -0
- package/bin/check-trace-schema-drift.cjs +99 -0
- package/bin/compareDrift.cjs +21 -0
- package/bin/conformance-schema.cjs +12 -0
- package/bin/count-scenarios.cjs +420 -0
- package/bin/debt-dedup.cjs +144 -0
- package/bin/debt-ledger.cjs +61 -0
- package/bin/debt-retention.cjs +76 -0
- package/bin/debt-state-machine.cjs +80 -0
- package/bin/detect-coverage-gaps.cjs +204 -0
- package/bin/detect-project-intent.cjs +362 -0
- package/bin/export-prism-constants.cjs +164 -0
- package/bin/extract-annotations.cjs +633 -0
- package/bin/extractFormalExpected.cjs +104 -0
- package/bin/fingerprint-drift.cjs +24 -0
- package/bin/fingerprint-issue.cjs +46 -0
- package/bin/formal-core.cjs +519 -0
- package/bin/formal-ref-linker.cjs +141 -0
- package/bin/formal-test-sync.cjs +788 -0
- package/bin/generate-formal-specs.cjs +588 -0
- package/bin/generate-petri-net.cjs +397 -0
- package/bin/generate-phase-spec.cjs +249 -0
- package/bin/generate-proposed-changes.cjs +194 -0
- package/bin/generate-tla-cfg.cjs +122 -0
- package/bin/generate-traceability-matrix.cjs +701 -0
- package/bin/generate-triage-bundle.cjs +300 -0
- package/bin/gh-account-rotate.cjs +34 -0
- package/bin/initialize-model-registry.cjs +105 -0
- package/bin/install-formal-tools.cjs +382 -0
- package/bin/install.js +2424 -0
- package/bin/isNumericThreshold.cjs +34 -0
- package/bin/issue-classifier.cjs +151 -0
- package/bin/levenshtein.cjs +74 -0
- package/bin/lint-formal-models.cjs +580 -0
- package/bin/load-baseline-requirements.cjs +275 -0
- package/bin/manage-agents-core.cjs +815 -0
- package/bin/migrate-formal-dir.cjs +172 -0
- package/bin/migrate-planning.cjs +206 -0
- package/bin/migrate-to-slots.cjs +255 -0
- package/bin/nForma.cjs +2726 -0
- package/bin/observe-config.cjs +353 -0
- package/bin/observe-debt-writer.cjs +140 -0
- package/bin/observe-handler-grafana.cjs +128 -0
- package/bin/observe-handler-internal.cjs +301 -0
- package/bin/observe-handler-logstash.cjs +153 -0
- package/bin/observe-handler-prometheus.cjs +185 -0
- package/bin/observe-handlers.cjs +436 -0
- package/bin/observe-registry.cjs +131 -0
- package/bin/observe-render.cjs +168 -0
- package/bin/planning-paths.cjs +167 -0
- package/bin/polyrepo.cjs +560 -0
- package/bin/prism-priority.cjs +153 -0
- package/bin/probe-quorum-slots.cjs +167 -0
- package/bin/promote-model.cjs +225 -0
- package/bin/propose-debug-invariants.cjs +165 -0
- package/bin/providers.json +392 -0
- package/bin/pty-proxy.py +129 -0
- package/bin/qgsd-solve.cjs +2477 -0
- package/bin/quorum-consensus-gate.cjs +238 -0
- package/bin/quorum-formal-context.cjs +183 -0
- package/bin/quorum-slot-dispatch.cjs +934 -0
- package/bin/read-policy.cjs +60 -0
- package/bin/requirement-map.cjs +63 -0
- package/bin/requirements-core.cjs +247 -0
- package/bin/resolve-cli.cjs +101 -0
- package/bin/review-mcp-logs.cjs +294 -0
- package/bin/run-account-manager-tlc.cjs +188 -0
- package/bin/run-account-pool-alloy.cjs +158 -0
- package/bin/run-alloy.cjs +153 -0
- package/bin/run-audit-alloy.cjs +187 -0
- package/bin/run-breaker-tlc.cjs +181 -0
- package/bin/run-formal-check.cjs +395 -0
- package/bin/run-formal-verify.cjs +701 -0
- package/bin/run-installer-alloy.cjs +188 -0
- package/bin/run-oauth-rotation-prism.cjs +132 -0
- package/bin/run-oscillation-tlc.cjs +202 -0
- package/bin/run-phase-tlc.cjs +228 -0
- package/bin/run-prism.cjs +446 -0
- package/bin/run-protocol-tlc.cjs +201 -0
- package/bin/run-quorum-composition-alloy.cjs +155 -0
- package/bin/run-sensitivity-sweep.cjs +231 -0
- package/bin/run-stop-hook-tlc.cjs +188 -0
- package/bin/run-tlc.cjs +467 -0
- package/bin/run-transcript-alloy.cjs +173 -0
- package/bin/run-uppaal.cjs +264 -0
- package/bin/secrets.cjs +134 -0
- package/bin/sensitivity-report.cjs +219 -0
- package/bin/sensitivity-sweep-feedback.cjs +194 -0
- package/bin/set-secret.cjs +29 -0
- package/bin/setup-telemetry-cron.sh +36 -0
- package/bin/sweepPtoF.cjs +63 -0
- package/bin/sync-baseline-requirements.cjs +290 -0
- package/bin/task-envelope.cjs +360 -0
- package/bin/telemetry-collector.cjs +229 -0
- package/bin/unified-mcp-server.mjs +735 -0
- package/bin/update-agents.cjs +369 -0
- package/bin/update-scoreboard.cjs +1134 -0
- package/bin/validate-debt-entry.cjs +207 -0
- package/bin/validate-invariant.cjs +419 -0
- package/bin/validate-memory.cjs +389 -0
- package/bin/validate-requirements-haiku.cjs +435 -0
- package/bin/validate-traces.cjs +438 -0
- package/bin/verify-formal-results.cjs +124 -0
- package/bin/verify-quorum-health.cjs +273 -0
- package/bin/write-check-result.cjs +106 -0
- package/bin/xstate-to-tla.cjs +483 -0
- package/bin/xstate-trace-walker.cjs +205 -0
- package/commands/qgsd/add-phase.md +43 -0
- package/commands/qgsd/add-requirement.md +24 -0
- package/commands/qgsd/add-todo.md +47 -0
- package/commands/qgsd/audit-milestone.md +37 -0
- package/commands/qgsd/check-todos.md +45 -0
- package/commands/qgsd/cleanup.md +18 -0
- package/commands/qgsd/close-formal-gaps.md +33 -0
- package/commands/qgsd/complete-milestone.md +136 -0
- package/commands/qgsd/debug.md +166 -0
- package/commands/qgsd/discuss-phase.md +83 -0
- package/commands/qgsd/execute-phase.md +117 -0
- package/commands/qgsd/fix-tests.md +27 -0
- package/commands/qgsd/formal-test-sync.md +32 -0
- package/commands/qgsd/health.md +22 -0
- package/commands/qgsd/help.md +22 -0
- package/commands/qgsd/insert-phase.md +32 -0
- package/commands/qgsd/join-discord.md +18 -0
- package/commands/qgsd/list-phase-assumptions.md +46 -0
- package/commands/qgsd/map-codebase.md +71 -0
- package/commands/qgsd/map-requirements.md +20 -0
- package/commands/qgsd/mcp-restart.md +176 -0
- package/commands/qgsd/mcp-set-model.md +134 -0
- package/commands/qgsd/mcp-setup.md +1371 -0
- package/commands/qgsd/mcp-status.md +274 -0
- package/commands/qgsd/mcp-update.md +238 -0
- package/commands/qgsd/new-milestone.md +44 -0
- package/commands/qgsd/new-project.md +42 -0
- package/commands/qgsd/observe.md +260 -0
- package/commands/qgsd/pause-work.md +38 -0
- package/commands/qgsd/plan-milestone-gaps.md +34 -0
- package/commands/qgsd/plan-phase.md +44 -0
- package/commands/qgsd/polyrepo.md +50 -0
- package/commands/qgsd/progress.md +24 -0
- package/commands/qgsd/queue.md +54 -0
- package/commands/qgsd/quick.md +133 -0
- package/commands/qgsd/quorum-test.md +275 -0
- package/commands/qgsd/quorum.md +707 -0
- package/commands/qgsd/reapply-patches.md +110 -0
- package/commands/qgsd/remove-phase.md +31 -0
- package/commands/qgsd/research-phase.md +189 -0
- package/commands/qgsd/resume-work.md +40 -0
- package/commands/qgsd/set-profile.md +34 -0
- package/commands/qgsd/settings.md +39 -0
- package/commands/qgsd/solve.md +565 -0
- package/commands/qgsd/sync-baselines.md +119 -0
- package/commands/qgsd/triage.md +233 -0
- package/commands/qgsd/update.md +37 -0
- package/commands/qgsd/verify-work.md +38 -0
- package/hooks/dist/config-loader.js +297 -0
- package/hooks/dist/conformance-schema.cjs +12 -0
- package/hooks/dist/gsd-context-monitor.js +64 -0
- package/hooks/dist/qgsd-check-update.js +62 -0
- package/hooks/dist/qgsd-circuit-breaker.js +682 -0
- package/hooks/dist/qgsd-precompact.js +156 -0
- package/hooks/dist/qgsd-prompt.js +653 -0
- package/hooks/dist/qgsd-session-start.js +122 -0
- package/hooks/dist/qgsd-slot-correlator.js +58 -0
- package/hooks/dist/qgsd-spec-regen.js +86 -0
- package/hooks/dist/qgsd-statusline.js +91 -0
- package/hooks/dist/qgsd-stop.js +553 -0
- package/hooks/dist/qgsd-token-collector.js +133 -0
- package/hooks/dist/unified-mcp-server.mjs +669 -0
- package/package.json +95 -0
- package/scripts/build-hooks.js +46 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-audit.sh +45 -0
- package/templates/qgsd.json +49 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qgsd:quorum
|
|
3
|
+
description: Answer a question using full quorum consensus (Claude + native CLI agents + all configured claude-mcp-server instances) following QGSD quorum protocol. Use when no arguments provided to answer the current conversation's open question.
|
|
4
|
+
argument-hint: "[question or prompt]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Bash
|
|
9
|
+
- Task
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<objective>
|
|
15
|
+
Run a question or prompt through the full QGSD quorum (R3 protocol): Claude + native CLI agents (Codex, Gemini, OpenCode, Copilot) + all claude-mcp-server instances configured in `~/.claude.json`.
|
|
16
|
+
|
|
17
|
+
**Two modes** based on context:
|
|
18
|
+
- **Mode A — Pure Question**: No commands required. Claude forms its own position first, then dispatches all slot-workers as parallel Tasks, deliberates to consensus.
|
|
19
|
+
- **Mode B — Execution + Trace Review**: Running commands is necessary before a verdict is possible. Claude runs them, preserves full traces, quorum reviews traces and gives verdicts.
|
|
20
|
+
</objective>
|
|
21
|
+
|
|
22
|
+
<dispatch_pattern>
|
|
23
|
+
**Execution path:** Claude runs the full R3 protocol directly in the main conversation thread.
|
|
24
|
+
Dispatch slot-workers via sibling Task calls (one per slot in `$DISPATCH_LIST` per round — capped by `FAN_OUT_COUNT`).
|
|
25
|
+
No orchestrator intermediary — the fallback logic, round loop, and scoreboard are all inline.
|
|
26
|
+
|
|
27
|
+
Resolve the question to pass:
|
|
28
|
+
|
|
29
|
+
1. If $ARGUMENTS is non-empty → use it directly as the question/prompt.
|
|
30
|
+
2. If $ARGUMENTS is empty → scan the current conversation using this priority order:
|
|
31
|
+
- **Priority 1** — Most recent message containing `?` without a substantive answer yet.
|
|
32
|
+
- **Priority 2** — Most recent message describing a choice/trade-off (keywords: "should we", "which approach", "option A vs", "do we", "whether to").
|
|
33
|
+
- **Priority 3** — Most recent open concern or blocker ("not sure", "concern", "blocker", "unclear", "wondering").
|
|
34
|
+
- If none found: stop with `"No open question found. Provide one explicitly: /qgsd:quorum <question>"`
|
|
35
|
+
|
|
36
|
+
When question is inferred, display before dispatching:
|
|
37
|
+
```
|
|
38
|
+
Using conversation context as question (Priority N - [type]):
|
|
39
|
+
"[inferred question text]"
|
|
40
|
+
```
|
|
41
|
+
</dispatch_pattern>
|
|
42
|
+
|
|
43
|
+
<mode_detection>
|
|
44
|
+
**Default: Mode A.**
|
|
45
|
+
|
|
46
|
+
Switch to **Mode B** only if the question/prompt explicitly requires running commands before answering — e.g.:
|
|
47
|
+
- "should we approve this plan", "does this pass", "is this safe to execute"
|
|
48
|
+
- "run [command] and tell me if...", "verify that [thing] works"
|
|
49
|
+
- "review the output of...", "check if the tests pass and then..."
|
|
50
|
+
|
|
51
|
+
If $ARGUMENTS is empty: use the most recent open question or decision from the current conversation context as the question.
|
|
52
|
+
</mode_detection>
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
> **Worker Task dispatch is PARALLEL per round.** Dispatch all slot workers for a given round as sibling Task calls in one message turn. Between rounds (Bash scoreboard calls, set-availability) remain sequential.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### Provider pre-flight (run once before team capture)
|
|
61
|
+
|
|
62
|
+
Before any model calls, run a fast HTTP probe of the underlying LLM providers:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
node "$HOME/.claude/qgsd-bin/check-provider-health.cjs" --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Parse the JSON output. Build two structures:
|
|
69
|
+
|
|
70
|
+
1. **`$PROVIDER_STATUS`**: `{ providerName: healthy }` — map of provider name to up/down status.
|
|
71
|
+
|
|
72
|
+
2. **`$CLAUDE_MCP_SERVERS`**: flat list of `{ serverName, model, providerName, available }` — extracted from the `servers[]` and `models[]` arrays in each provider entry. A server's `available` is `false` if its provider's `healthy` is `false`.
|
|
73
|
+
|
|
74
|
+
Any server with `available: false` must be marked UNAVAIL immediately — skip its health_check and inference calls entirely. This prevents hangs from unresponsive provider endpoints.
|
|
75
|
+
|
|
76
|
+
3. **`$QUORUM_ACTIVE`**: read from `~/.claude/qgsd.json` (project config takes precedence over global):
|
|
77
|
+
```bash
|
|
78
|
+
node -e "
|
|
79
|
+
const fs = require('fs'), os = require('os'), path = require('path');
|
|
80
|
+
const globalCfg = path.join(os.homedir(), '.claude', 'qgsd.json');
|
|
81
|
+
const projCfg = path.join(process.cwd(), '.claude', 'qgsd.json');
|
|
82
|
+
let cfg = {};
|
|
83
|
+
for (const f of [globalCfg, projCfg]) {
|
|
84
|
+
try { Object.assign(cfg, JSON.parse(fs.readFileSync(f, 'utf8'))); } catch(_){}
|
|
85
|
+
}
|
|
86
|
+
console.log(JSON.stringify(cfg.quorum_active || []));
|
|
87
|
+
"
|
|
88
|
+
```
|
|
89
|
+
If `$QUORUM_ACTIVE` is empty (`[]`), all entries in `$CLAUDE_MCP_SERVERS` participate.
|
|
90
|
+
If non-empty, intersect: only servers whose `serverName` appears in `$QUORUM_ACTIVE` are called.
|
|
91
|
+
A server in `$QUORUM_ACTIVE` but absent from `$CLAUDE_MCP_SERVERS` = skip silently (fail-open).
|
|
92
|
+
|
|
93
|
+
**Pre-flight slot skip:** After building `$CLAUDE_MCP_SERVERS`, immediately filter the list for the quorum run:
|
|
94
|
+
- For each server with `available: false`, log: `Pre-flight skip: <serverName> (<providerName> DOWN)`
|
|
95
|
+
- Remove these servers from the working list for all subsequent steps (team capture, Round 1, deliberation).
|
|
96
|
+
- Reorder the remaining working list: healthy servers first (preserving discovery order within each group).
|
|
97
|
+
- Log the final working list as: `Active slots: <slot1>, <slot2>, ...`
|
|
98
|
+
|
|
99
|
+
**max_quorum_size check:** Read `max_quorum_size` from `~/.claude/qgsd.json` (project config takes precedence; default: 3 if absent):
|
|
100
|
+
```bash
|
|
101
|
+
node -e "
|
|
102
|
+
const fs = require('fs'), os = require('os'), path = require('path');
|
|
103
|
+
const globalCfg = path.join(os.homedir(), '.claude', 'qgsd.json');
|
|
104
|
+
const projCfg = path.join(process.cwd(), '.claude', 'qgsd.json');
|
|
105
|
+
let cfg = {};
|
|
106
|
+
for (const f of [globalCfg, projCfg]) {
|
|
107
|
+
try { Object.assign(cfg, JSON.parse(fs.readFileSync(f, 'utf8'))); } catch(_){}
|
|
108
|
+
}
|
|
109
|
+
console.log(cfg.max_quorum_size ?? 3);
|
|
110
|
+
"
|
|
111
|
+
```
|
|
112
|
+
Count available slots (those not marked UNAVAIL and passing $QUORUM_ACTIVE filter). Include Claude itself as +1.
|
|
113
|
+
If `availableCount < max_quorum_size`:
|
|
114
|
+
- If $ARGUMENTS contains `--force-quorum`: log warning `[WARN] Quorum below max_quorum_size (N available, min M) — proceeding due to --force-quorum` and continue.
|
|
115
|
+
- Otherwise: stop with:
|
|
116
|
+
```
|
|
117
|
+
QUORUM BLOCKED: Only N model(s) available (max_quorum_size = M).
|
|
118
|
+
Available: [list slots]
|
|
119
|
+
UNAVAIL: [list skipped slots with reason]
|
|
120
|
+
Re-run with --force-quorum to override, or wait for providers to recover.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Display pre-flight result inline (one line):
|
|
124
|
+
```
|
|
125
|
+
Provider pre-flight: <providerName>=✓/✗ ... (<N> claude-mcp servers found)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### Team identity capture (idempotent — run once per session)
|
|
131
|
+
|
|
132
|
+
Before any quorum round, capture the active team fingerprint. Build TEAM_JSON directly from `providers.json` — no MCP calls needed for identity.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
node -e "
|
|
136
|
+
const fs = require('fs'), path = require('path'), os = require('os');
|
|
137
|
+
|
|
138
|
+
const searchPaths = [
|
|
139
|
+
path.join(os.homedir(), '.claude', 'qgsd-bin', 'providers.json'),
|
|
140
|
+
];
|
|
141
|
+
try {
|
|
142
|
+
const cj = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8'));
|
|
143
|
+
const u1args = cj?.mcpServers?.['unified-1']?.args ?? [];
|
|
144
|
+
const srv = u1args.find(a => typeof a === 'string' && a.endsWith('unified-mcp-server.mjs'));
|
|
145
|
+
if (srv) searchPaths.unshift(path.join(path.dirname(srv), 'providers.json'));
|
|
146
|
+
} catch(_) {}
|
|
147
|
+
|
|
148
|
+
let providers = [];
|
|
149
|
+
for (const p of searchPaths) {
|
|
150
|
+
try { providers = JSON.parse(fs.readFileSync(p, 'utf8')).providers; break; } catch(_) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const globalCfg = path.join(os.homedir(), '.claude', 'qgsd.json');
|
|
154
|
+
const projCfg = path.join(process.cwd(), '.claude', 'qgsd.json');
|
|
155
|
+
let cfg = {};
|
|
156
|
+
for (const f of [globalCfg, projCfg]) {
|
|
157
|
+
try { Object.assign(cfg, JSON.parse(fs.readFileSync(f, 'utf8'))); } catch(_) {}
|
|
158
|
+
}
|
|
159
|
+
const active = cfg.quorum_active || [];
|
|
160
|
+
|
|
161
|
+
const team = {};
|
|
162
|
+
for (const p of providers) {
|
|
163
|
+
if (active.length > 0 && !active.includes(p.name)) continue;
|
|
164
|
+
team[p.name] = { model: p.model };
|
|
165
|
+
}
|
|
166
|
+
console.log(JSON.stringify(team));
|
|
167
|
+
"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Store result as `TEAM_JSON`. Also build three lookup maps from `providers.json` for use during dispatch:
|
|
171
|
+
- `$SLOT_TIMEOUTS: { slotName: quorum_timeout_ms }` (fallback: 30000)
|
|
172
|
+
- `$SLOT_MODELS: { slotName: model }` (fallback: `"unknown"`)
|
|
173
|
+
- `$SLOT_CLI: { slotName: display_type }` (fallback: `"cli"`)
|
|
174
|
+
|
|
175
|
+
Use `$SLOT_CLI[slotName]` and `$SLOT_MODELS[slotName]` in Task `description=` fields so the parallel UI shows both the CLI binary and LLM being called (e.g., `"gemini-1 [gemini-cli · gemini-3-pro-preview] quorum R1"`).
|
|
176
|
+
|
|
177
|
+
Detect Claude's model ID from: `CLAUDE_MODEL` env var → `ANTHROPIC_MODEL` env var → current session model name from system context.
|
|
178
|
+
|
|
179
|
+
Run:
|
|
180
|
+
```bash
|
|
181
|
+
node "$HOME/.claude/qgsd-bin/update-scoreboard.cjs" init-team \
|
|
182
|
+
--claude-model "<claude_model_id>" \
|
|
183
|
+
--team '<TEAM_JSON>'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The command prints `[init-team] fingerprint: <fp> | no change` if unchanged, or `[init-team] fingerprint: <fp> (updated from <old>) | N agents` if updated. Then proceed to Envelope Risk Level read and Mode A or Mode B.
|
|
187
|
+
|
|
188
|
+
### Envelope Risk Level (ENV-03 — fail-open)
|
|
189
|
+
|
|
190
|
+
Read risk_level from task envelope if available. Falls back gracefully if absent or malformed.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Parse envelope_path from context (passed by plan-phase.md)
|
|
194
|
+
ENVELOPE_PATH=$(echo "${CONTEXT_YAML:-}" | grep '^envelope_path:' | sed 's/envelope_path:[[:space:]]*//')
|
|
195
|
+
|
|
196
|
+
if [ -n "$ENVELOPE_PATH" ] && [ -f "$ENVELOPE_PATH" ]; then
|
|
197
|
+
RISK_LEVEL=$(node -e "
|
|
198
|
+
try {
|
|
199
|
+
const e = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'));
|
|
200
|
+
const valid = ['low','medium','high'];
|
|
201
|
+
const r = e.risk_level;
|
|
202
|
+
if (valid.includes(r)) { console.log(r); } else { process.stderr.write('[quorum] envelope risk_level=' + r + ' (invalid) → using medium\n'); console.log('medium'); }
|
|
203
|
+
} catch(err) {
|
|
204
|
+
process.stderr.write('[quorum] envelope parse error → using medium\n');
|
|
205
|
+
console.log('medium');
|
|
206
|
+
}
|
|
207
|
+
" "$ENVELOPE_PATH" 2>&1)
|
|
208
|
+
echo "Envelope risk_level: ${RISK_LEVEL}"
|
|
209
|
+
else
|
|
210
|
+
RISK_LEVEL="medium"
|
|
211
|
+
if [ -n "$ENVELOPE_PATH" ]; then
|
|
212
|
+
echo "Envelope not found at ${ENVELOPE_PATH} — using default risk (medium)"
|
|
213
|
+
else
|
|
214
|
+
echo "No envelope_path provided — using default risk (medium)"
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Adaptive Fan-Out Dispatch Count (FAN-01..FAN-04)
|
|
220
|
+
|
|
221
|
+
After reading RISK_LEVEL, compute the fan-out dispatch count:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# Map RISK_LEVEL to fan_out_count (total participants = Claude + external slots)
|
|
225
|
+
if [ "$RISK_LEVEL" = "routine" ] || [ "$RISK_LEVEL" = "low" ]; then
|
|
226
|
+
FAN_OUT_COUNT=2
|
|
227
|
+
elif [ "$RISK_LEVEL" = "medium" ]; then
|
|
228
|
+
FAN_OUT_COUNT=3
|
|
229
|
+
else
|
|
230
|
+
# high, absent, or invalid → fail-open to max_quorum_size
|
|
231
|
+
FAN_OUT_COUNT="$MAX_QUORUM_SIZE"
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
echo "Adaptive fan-out: risk_level=${RISK_LEVEL} → fan_out_count=${FAN_OUT_COUNT} (of max ${MAX_QUORUM_SIZE})"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Apply cap — build DISPATCH_LIST:** Take the first `FAN_OUT_COUNT - 1` slots from the active working list (healthy-first order from pre-flight). This is the definitive slot cap. Call this `$DISPATCH_LIST`. All subsequent round dispatches (Round 1 and deliberation) use `$DISPATCH_LIST` — never the full working list.
|
|
238
|
+
|
|
239
|
+
> **Why here and not only in the hook:** `qgsd-prompt.js` enforces this cap via `--n` for main-session prompts. But quorum is also dispatched inline from subagents (e.g., `qgsd-planner` in step 8.5 of `plan-phase.md`) where no UserPromptSubmit hook fires. `quorum.md` must self-enforce the cap for all dispatch contexts.
|
|
240
|
+
|
|
241
|
+
### R6.4 Reduced-Quorum Note (FAN-05)
|
|
242
|
+
|
|
243
|
+
When fan-out is below `max_quorum_size`, emit a reduced-quorum note per R6.4 so the user understands why fewer models are participating. This is an informational note — it does not block execution.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
if [ "$FAN_OUT_COUNT" -lt "$MAX_QUORUM_SIZE" ] 2>/dev/null; then
|
|
247
|
+
EXTERNAL_COUNT=$((FAN_OUT_COUNT - 1))
|
|
248
|
+
echo "[R6.4 reduced-quorum note] Operating with ${FAN_OUT_COUNT} total participants (Claude + ${EXTERNAL_COUNT} external); max_quorum_size is ${MAX_QUORUM_SIZE}. Reason: envelope risk_level=${RISK_LEVEL}. This is intentional — routine/medium tasks use fewer models to reduce token cost (FAN-01, FAN-02)."
|
|
249
|
+
fi
|
|
250
|
+
# When FAN_OUT_COUNT = MAX_QUORUM_SIZE (high/absent): no note emitted.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The RISK_LEVEL variable is available downstream for use by Phase v0.18-04 Adaptive Fan-Out. The fan-out logic is now implemented in both quorum.md (above) and qgsd-prompt.js (which emits `--n N` for downstream ceiling verification).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Mode A — Pure Question
|
|
258
|
+
|
|
259
|
+
### Parse question
|
|
260
|
+
|
|
261
|
+
If $ARGUMENTS is empty, scan the current conversation using this priority order:
|
|
262
|
+
|
|
263
|
+
Priority 1 - Explicit question: Find the most recent message containing a literal "?" that has not yet received a substantive answer. Use that as the question.
|
|
264
|
+
|
|
265
|
+
Priority 2 - Pending decision: Find the most recent message that describes a choice or trade-off between options (keywords: "should we", "which approach", "option A vs", "do we", "whether to"). Use that as the question.
|
|
266
|
+
|
|
267
|
+
Priority 3 - Open concern or blocker: Find the most recent message that raises a concern, flags a risk, or states something is unclear (keywords: "not sure", "concern", "blocker", "question:", "unclear", "wondering"). Restate it as a question.
|
|
268
|
+
|
|
269
|
+
If none of the above applies: stop with:
|
|
270
|
+
"No open question found. Looked for: explicit '?' question, pending decision, or open concern in recent conversation. Provide a question explicitly: /qgsd:quorum <question>"
|
|
271
|
+
|
|
272
|
+
When a question is inferred via any priority, Claude MUST display before proceeding:
|
|
273
|
+
"Using conversation context as question (Priority N - [type]):
|
|
274
|
+
"[inferred question text]""
|
|
275
|
+
|
|
276
|
+
Display:
|
|
277
|
+
```
|
|
278
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
279
|
+
QGSD ► QUORUM: Round 1 — N workers dispatched
|
|
280
|
+
Active: gemini-1, opencode-1, copilot-1, codex-1
|
|
281
|
+
Fallback pool: T1 = unused slots with auth_type=sub; T2 = slots with auth_type≠sub (on UNAVAIL)
|
|
282
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
283
|
+
|
|
284
|
+
Question: [question]
|
|
285
|
+
```
|
|
286
|
+
(The Active line lists the actual active slots resolved at runtime, not hardcoded names. The example above is illustrative — the executor renders it dynamically using the resolved slot list from provider pre-flight.)
|
|
287
|
+
|
|
288
|
+
### Claude's position (Round 1)
|
|
289
|
+
|
|
290
|
+
Before querying any model, state Claude's own answer and reasoning:
|
|
291
|
+
```
|
|
292
|
+
Claude (Round 1): [answer + reasoning — 2–4 sentences]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Store as `$CLAUDE_POSITION`.
|
|
296
|
+
|
|
297
|
+
### Query models (parallel — one Task per slot)
|
|
298
|
+
|
|
299
|
+
Dispatch one `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, ...)` per slot in **`$DISPATCH_LIST`** (capped to `FAN_OUT_COUNT - 1` external slots) as **parallel sibling calls** in one message turn. Do NOT dispatch slots outside `$DISPATCH_LIST`. Build a YAML prompt block per the slot-worker argument spec:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
slot: <slotName>
|
|
303
|
+
round: <round_number>
|
|
304
|
+
timeout_ms: <slot_timeout from $SLOT_TIMEOUTS>
|
|
305
|
+
repo_dir: <absolute path to working directory>
|
|
306
|
+
mode: A
|
|
307
|
+
question: <question text>
|
|
308
|
+
[artifact_path: <path to artifact file>]
|
|
309
|
+
[review_context: <one-sentence framing for how to evaluate the artifact>]
|
|
310
|
+
[request_improvements: true] — when set by calling context (plan-phase, quick)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`review_context` is optional but strongly recommended when `artifact_path` is present.
|
|
314
|
+
Set it to the evaluation criteria appropriate for the artifact type, e.g.:
|
|
315
|
+
- Plan: "This is a pre-execution plan. Evaluate approach and completeness — not whether code already exists."
|
|
316
|
+
- Roadmap: "This is a strategic roadmap. Evaluate phase sequencing and requirements coverage."
|
|
317
|
+
- Test run: "These are post-execution test results. Evaluate whether tests genuinely pass and assertions are meaningful."
|
|
318
|
+
- Audit: "This is a milestone audit. Evaluate whether the work achieves the stated milestone goals."
|
|
319
|
+
|
|
320
|
+
Example dispatch (all Tasks in one message turn):
|
|
321
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="gemini-1 [gemini-cli · gemini-3-pro-preview] quorum R1", prompt=<YAML block>)`
|
|
322
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="codex-1 [codex-cli · gpt-5.3-codex] quorum R1", prompt=<YAML block>)`
|
|
323
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="opencode-1 [opencode-cli · grok-code-fast-1] quorum R1", prompt=<YAML block>)`
|
|
324
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="copilot-1 [copilot-cli · gpt-4.1] quorum R1", prompt=<YAML block>)`
|
|
325
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="claude-1 [claude-code-router · deepseek-ai/DeepSeek-V3.2] quorum R1", prompt=<YAML block>)` ← one per claude-mcp server with `available: true`
|
|
326
|
+
(model="haiku" — slot-workers are orchestrators (read files, build prompt, run Bash subprocess), NOT reasoners. The actual reasoning is done by the external CLI. Haiku is faster with zero quality loss.)
|
|
327
|
+
|
|
328
|
+
The slot-worker reads repo context, builds its own prompt from the YAML arguments, calls the slot via `call-quorum-slot.cjs`, and returns a structured result block.
|
|
329
|
+
|
|
330
|
+
Handle UNAVAILABLE per R6: note unavailability, then apply the **tiered fallback rule** below before continuing.
|
|
331
|
+
|
|
332
|
+
#### Tiered fallback rule (FALLBACK-01)
|
|
333
|
+
|
|
334
|
+
When a dispatched slot returns UNAVAIL, dispatch a replacement in this priority order:
|
|
335
|
+
|
|
336
|
+
Classification is based on runtime `auth_type` from `providers.json` / config — **not** hardcoded slot names. Any slot can be `sub` or `api` depending on how it is configured.
|
|
337
|
+
|
|
338
|
+
1. **T1 — unused sub-CLI slots**: slots in the working list where `auth_type=sub` AND not in `$DISPATCH_LIST`. Build `$T1_UNUSED` = `[working-list slots with auth_type=sub] − $DISPATCH_LIST`. Dispatch `$T1_UNUSED` first — same subscription tier as the primaries, no extra cost.
|
|
339
|
+
2. **T2 — final fallback slots**: slots in the working list where `auth_type≠sub` AND not in `$DISPATCH_LIST`. Dispatch T2 only if `$T1_UNUSED` is empty or fully exhausted/UNAVAIL. Slots already dispatched as primary are excluded from both T1 and T2.
|
|
340
|
+
|
|
341
|
+
**Why this matters:** With `FAN_OUT_COUNT=3`, only 2 external slots are in `$DISPATCH_LIST`. If both primaries are UNAVAIL, any remaining `auth_type=sub` slots should be tried before falling to `auth_type=api` (ccr) slots. The exact slot names in each tier depend on `--n` and the runtime config — they are not fixed.
|
|
342
|
+
|
|
343
|
+
**Display label:** Use `(T1 fallback)` for `auth_type=sub` replacements, `(T2 fallback)` for `auth_type≠sub` replacements.
|
|
344
|
+
|
|
345
|
+
### Evaluate Round 1 — check for consensus
|
|
346
|
+
|
|
347
|
+
Display all positions as a table with one row per team member (native agents first, then claude-mcp servers in discovery order):
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
┌────────────────────────────────┬──────────────────────────────────────────────────────────┐
|
|
351
|
+
│ Model │ Round N Position │
|
|
352
|
+
├────────────────────────────────┼──────────────────────────────────────────────────────────┤
|
|
353
|
+
│ Claude │ [summary — $CLAUDE_POSITION] │
|
|
354
|
+
│ <slot-A> (primary) │ [summary or UNAVAIL] │
|
|
355
|
+
│ ├─ <T1-next> (T1 fallback) │ [summary or UNAVAIL — next unused auth_type=sub slot] │
|
|
356
|
+
│ └─ <T2-next> (T2 fallback) │ [summary or UNAVAIL — first auth_type≠sub slot, T1 done] │
|
|
357
|
+
│ <slot-B> (primary) │ [summary or UNAVAIL] │
|
|
358
|
+
│ ├─ <T1-next> (T1 fallback) │ [summary or UNAVAIL — only if slot-B UNAVAIL + T1 unused] │
|
|
359
|
+
│ └─ <T2-next> (T2 fallback) │ [summary or UNAVAIL — only if all T1 also UNAVAIL] │
|
|
360
|
+
│ <slot-C> (primary) │ [summary or UNAVAIL — only shown if in $DISPATCH_LIST] │
|
|
361
|
+
└────────────────────────────────┴───────────────────────────────────────────────────────────┘
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Actual slot names in each tier are resolved at runtime from `providers.json` based on `auth_type`. T1 = `auth_type=sub` slots not in `$DISPATCH_LIST`; T2 = `auth_type≠sub` slots. With `--n 5` all sub-CLI slots may be primary, leaving T1 empty. Fallback rows (├─ / └─) are only rendered when the corresponding primary returned UNAVAIL and a fallback was dispatched. A T1 row is omitted if that slot was already dispatched as a primary.
|
|
365
|
+
|
|
366
|
+
If all available models agree → skip to **Consensus output**.
|
|
367
|
+
|
|
368
|
+
### Deliberation rounds (R3.3)
|
|
369
|
+
|
|
370
|
+
Run up to 9 deliberation rounds (max 10 total rounds including Round 1).
|
|
371
|
+
|
|
372
|
+
For each round, dispatch one `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, ...)` per slot in **`$DISPATCH_LIST`** as **parallel sibling calls**. Append `prior_positions` to the YAML block for Round 2+ dispatch:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
slot: <slotName>
|
|
376
|
+
round: <round_number>
|
|
377
|
+
timeout_ms: <slot_timeout from $SLOT_TIMEOUTS>
|
|
378
|
+
repo_dir: <absolute path to working directory>
|
|
379
|
+
mode: A
|
|
380
|
+
question: <question text>
|
|
381
|
+
[artifact_path: <same artifact_path as Round 1, if present>]
|
|
382
|
+
[review_context: <same review_context as Round 1, if present>]
|
|
383
|
+
[request_improvements: true] — carry through from Round 1 if set
|
|
384
|
+
prior_positions: |
|
|
385
|
+
• Claude:
|
|
386
|
+
position: [position from $CLAUDE_POSITION]
|
|
387
|
+
citations: [citations from Claude's analysis, or "(none)"]
|
|
388
|
+
• <slotName>:
|
|
389
|
+
position: [position from slot result block, or UNAVAIL]
|
|
390
|
+
citations: [citations field from slot result block, or "(none)"]
|
|
391
|
+
[one entry per active slot in the same format]
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Always carry `artifact_path` and `review_context` through to deliberation rounds unchanged — the worker needs them to inject the correct evaluation framing alongside prior positions.
|
|
395
|
+
|
|
396
|
+
Populate `citations:` from the `citations:` field in each model's slot-worker result block. If the result block had no `citations:` field or it was empty, write `(none)`. For Claude's own position, include any file paths or line numbers Claude cited in its reasoning.
|
|
397
|
+
|
|
398
|
+
Workers are dispatched as **parallel sibling Tasks** per round. Between rounds (Bash scoreboard calls, set-availability) remain sequential.
|
|
399
|
+
|
|
400
|
+
Stop deliberation **immediately** upon CONSENSUS (all available models agree).
|
|
401
|
+
|
|
402
|
+
After 10 total rounds with no consensus → **Escalate**.
|
|
403
|
+
|
|
404
|
+
### Consensus output
|
|
405
|
+
|
|
406
|
+
```
|
|
407
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
408
|
+
QGSD ► QUORUM CONSENSUS REACHED
|
|
409
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
410
|
+
|
|
411
|
+
Question: [question]
|
|
412
|
+
Rounds to consensus: [N]
|
|
413
|
+
|
|
414
|
+
CONSENSUS ANSWER:
|
|
415
|
+
[Full consensus answer — detailed and actionable]
|
|
416
|
+
|
|
417
|
+
Supporting positions:
|
|
418
|
+
• Claude: [brief]
|
|
419
|
+
• Codex: [brief or UNAVAIL]
|
|
420
|
+
• Gemini: [brief or UNAVAIL]
|
|
421
|
+
• OpenCode: [brief or UNAVAIL]
|
|
422
|
+
• Copilot: [brief or UNAVAIL]
|
|
423
|
+
[one line per claude-mcp server: • <display-name>: [brief or UNAVAIL]]
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Update the scoreboard: for each model that voted this round, run:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
# For native agents:
|
|
430
|
+
node "$HOME/.claude/qgsd-bin/update-scoreboard.cjs" \
|
|
431
|
+
--model <model_name> \
|
|
432
|
+
--result <vote_code> \
|
|
433
|
+
--task "<task_label>" \
|
|
434
|
+
--round <round_number> \
|
|
435
|
+
--verdict <VERDICT> \
|
|
436
|
+
--task-description "<question or topic being debated>"
|
|
437
|
+
|
|
438
|
+
# For each claude-mcp server (use slot + full model-id, NOT --model):
|
|
439
|
+
node "$HOME/.claude/qgsd-bin/update-scoreboard.cjs" \
|
|
440
|
+
--slot <slotName> \
|
|
441
|
+
--model-id <fullModelId> \
|
|
442
|
+
--result <vote_code> \
|
|
443
|
+
--task "<task_label>" \
|
|
444
|
+
--round <round_number> \
|
|
445
|
+
--verdict <VERDICT> \
|
|
446
|
+
--task-description "<question or topic being debated>"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- `--model` for native agents: `claude`, `gemini`, `opencode`, `copilot`, `codex`
|
|
450
|
+
- For claude-mcp servers: use `--slot <slotName>` (e.g. `claude-1`) and `--model-id <fullModelId>` (e.g. `deepseek-ai/DeepSeek-V3` — the exact string returned by health_check, NOT a derived short key). This writes to `data.slots{}` with composite key `<slot>:<model-id>`.
|
|
451
|
+
- `--result` values: TP, TN, FP, FN, TP+ (improvement accepted), or leave as empty string if model did not participate. Skip calling update-scoreboard entirely for models that were UNAVAIL.
|
|
452
|
+
- `--task` label: short identifier, e.g. "quick-25" or "plan-ph17"
|
|
453
|
+
- `--round`: the round number that just completed
|
|
454
|
+
- `--verdict`: the consensus verdict (APPROVE | BLOCK | DELIBERATE | CONSENSUS | GAPS_FOUND)
|
|
455
|
+
- `--task-description`: the full debate question/topic (the `[question]` value). Used by Haiku to auto-classify the category. Omit if the question is too long (>500 chars) — use a shortened summary instead.
|
|
456
|
+
|
|
457
|
+
Run one command per model per round. Each call is atomic and idempotent — if re-run for the same task+round+model it overwrites that model's vote and recalculates from scratch.
|
|
458
|
+
|
|
459
|
+
**Post-consensus improvements extraction (Mode A, when `request_improvements: true`):**
|
|
460
|
+
|
|
461
|
+
1. Collect `improvements:` fields from worker result blocks of the **final consensus round** (the last round where all available models agreed — NOT aggregated across all rounds). Filter to blocks where `verdict` is not UNAVAIL and `improvements:` is present and non-empty.
|
|
462
|
+
|
|
463
|
+
2. De-duplicate: if multiple models propose the same improvement (same suggestion text or semantically equivalent), keep only the first occurrence.
|
|
464
|
+
|
|
465
|
+
3. If any improvements collected, display:
|
|
466
|
+
```
|
|
467
|
+
Improvements proposed:
|
|
468
|
+
• <slotName>: [suggestion] — [rationale]
|
|
469
|
+
• <slotName>: [suggestion] — [rationale]
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
4. Emit structured signal (parseable by calling workflow):
|
|
473
|
+
```
|
|
474
|
+
<!-- QUORUM_IMPROVEMENTS_START
|
|
475
|
+
[{"model":"<slotName>","suggestion":"...","rationale":"..."},...]
|
|
476
|
+
QUORUM_IMPROVEMENTS_END -->
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
5. If no improvements collected: emit `<!-- QUORUM_IMPROVEMENTS_START [] QUORUM_IMPROVEMENTS_END -->`
|
|
480
|
+
|
|
481
|
+
6. If `request_improvements` was not set (false or absent): do NOT emit the signal block.
|
|
482
|
+
|
|
483
|
+
**Debate file path:** If `artifact_path` was provided → write to the same directory as the artifact (e.g. `.planning/phases/v0.14-02/QUORUM_DEBATE.md`). Otherwise → `.planning/quorum/debates/YYYY-MM-DD-<short-slug>.md` where `<short-slug>` is the first 6 words of the question lowercased, spaces replaced with hyphens, non-alphanumeric chars stripped.
|
|
484
|
+
|
|
485
|
+
Create `.planning/quorum/debates/` if it does not exist.
|
|
486
|
+
|
|
487
|
+
Write QUORUM_DEBATE.md using the debate file path rule above. Set `Consensus: APPROVE` (Mode A consensus means all models agree on APPROVE). Include one `## Round N` section per round that occurred, populated from the per-round position data collected during this quorum run.
|
|
488
|
+
|
|
489
|
+
The debate file format:
|
|
490
|
+
```markdown
|
|
491
|
+
# Quorum Debate
|
|
492
|
+
Question: <question text>
|
|
493
|
+
Date: <YYYY-MM-DD>
|
|
494
|
+
Consensus: <APPROVE / REJECT / FLAG / ESCALATED>
|
|
495
|
+
Rounds: <N>
|
|
496
|
+
|
|
497
|
+
## Round 1
|
|
498
|
+
| Model | Position | Citations |
|
|
499
|
+
|---|---|---|
|
|
500
|
+
| Claude | <position> | <citations or —> |
|
|
501
|
+
| <slotName> | <position or UNAVAIL> | <citations or —> |
|
|
502
|
+
...
|
|
503
|
+
|
|
504
|
+
## Round N (if deliberation occurred — one section per round)
|
|
505
|
+
[same table format]
|
|
506
|
+
|
|
507
|
+
## Outcome
|
|
508
|
+
<Full consensus answer (Mode A) or verdict + rationale (Mode B) or escalation summary>
|
|
509
|
+
|
|
510
|
+
## Improvements
|
|
511
|
+
| Model | Suggestion | Rationale |
|
|
512
|
+
|---|---|---|
|
|
513
|
+
| <slotName> | ... | ... |
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Only write the `## Improvements` section when `request_improvements: true` AND improvements were proposed. Omit entirely when no improvements or when `request_improvements` is not set.
|
|
517
|
+
|
|
518
|
+
### Escalate — no consensus after 10 rounds
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
522
|
+
QGSD ► QUORUM ESCALATING — NO CONSENSUS AFTER 10 ROUNDS
|
|
523
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
524
|
+
|
|
525
|
+
Question: [question]
|
|
526
|
+
|
|
527
|
+
Final positions:
|
|
528
|
+
• Claude: [position + key reasoning]
|
|
529
|
+
• Codex: [position + key reasoning or UNAVAIL]
|
|
530
|
+
• Gemini: [position + key reasoning or UNAVAIL]
|
|
531
|
+
• OpenCode: [position + key reasoning or UNAVAIL]
|
|
532
|
+
• Copilot: [position + key reasoning or UNAVAIL]
|
|
533
|
+
[one line per claude-mcp server: • <display-name>: [position + key reasoning or UNAVAIL]]
|
|
534
|
+
|
|
535
|
+
Core disagreement: [1–2 sentences on what models disagree about]
|
|
536
|
+
|
|
537
|
+
Claude's recommendation: [Claude's position with rationale]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Update the scoreboard: for each model that voted this round, run:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# For native agents:
|
|
544
|
+
node "$HOME/.claude/qgsd-bin/update-scoreboard.cjs" \
|
|
545
|
+
--model <model_name> \
|
|
546
|
+
--result <vote_code> \
|
|
547
|
+
--task "<task_label>" \
|
|
548
|
+
--round <round_number> \
|
|
549
|
+
--verdict <VERDICT> \
|
|
550
|
+
--task-description "<question or topic being debated>"
|
|
551
|
+
|
|
552
|
+
# For each claude-mcp server (use slot + full model-id, NOT --model):
|
|
553
|
+
node "$HOME/.claude/qgsd-bin/update-scoreboard.cjs" \
|
|
554
|
+
--slot <slotName> \
|
|
555
|
+
--model-id <fullModelId> \
|
|
556
|
+
--result <vote_code> \
|
|
557
|
+
--task "<task_label>" \
|
|
558
|
+
--round <round_number> \
|
|
559
|
+
--verdict <VERDICT> \
|
|
560
|
+
--task-description "<question or topic being debated>"
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
- `--model` for native agents: `claude`, `gemini`, `opencode`, `copilot`, `codex`
|
|
564
|
+
- For claude-mcp servers: use `--slot <slotName>` (e.g. `claude-1`) and `--model-id <fullModelId>` (e.g. `deepseek-ai/DeepSeek-V3` — the exact string returned by health_check, NOT a derived short key). This writes to `data.slots{}` with composite key `<slot>:<model-id>`.
|
|
565
|
+
- `--result` values: TP, TN, FP, FN, TP+ (improvement accepted), or leave as empty string if model did not participate. Skip calling update-scoreboard entirely for models that were UNAVAIL.
|
|
566
|
+
- `--round`: the round number that just completed
|
|
567
|
+
- `--verdict`: the consensus verdict (APPROVE | BLOCK | DELIBERATE | CONSENSUS | GAPS_FOUND)
|
|
568
|
+
|
|
569
|
+
Run one command per model per round. Each call is atomic and idempotent.
|
|
570
|
+
|
|
571
|
+
Write QUORUM_DEBATE.md using the debate file path rule above. Set `Consensus: ESCALATED`. Include one `## Round N` section per round (all 10). Set `## Outcome` to the core disagreement summary and Claude's recommendation from the escalation output above.
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Mode B — Execution + Trace Review
|
|
576
|
+
|
|
577
|
+
### Parse commands
|
|
578
|
+
|
|
579
|
+
Extract command(s) to run from $ARGUMENTS. If unclear, ask the user to specify.
|
|
580
|
+
|
|
581
|
+
Display:
|
|
582
|
+
```
|
|
583
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
584
|
+
QGSD ► QUORUM: Mode B — Execution + Trace Review
|
|
585
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
586
|
+
|
|
587
|
+
Question: [original question]
|
|
588
|
+
Commands: [list]
|
|
589
|
+
|
|
590
|
+
Running commands...
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Execute and capture traces
|
|
594
|
+
|
|
595
|
+
Run each command, capturing full stdout + stderr + exit code.
|
|
596
|
+
|
|
597
|
+
Store as `$TRACES`:
|
|
598
|
+
```
|
|
599
|
+
=== Command: [cmd] ===
|
|
600
|
+
Exit code: N
|
|
601
|
+
Output:
|
|
602
|
+
[full output — not summarized]
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Claude also gives its own verdict before dispatching workers.
|
|
606
|
+
|
|
607
|
+
### Assemble review bundle
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
QUESTION: [original question]
|
|
611
|
+
|
|
612
|
+
=== EXECUTION TRACES ===
|
|
613
|
+
$TRACES
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Dispatch quorum workers via Task (parallel per round)
|
|
617
|
+
|
|
618
|
+
Dispatch one `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, ...)` per slot in **`$DISPATCH_LIST`** (capped to `FAN_OUT_COUNT - 1` external slots) as **parallel sibling calls** in one message turn. Do NOT dispatch slots outside `$DISPATCH_LIST`. Build a YAML prompt block per the slot-worker argument spec:
|
|
619
|
+
|
|
620
|
+
```
|
|
621
|
+
slot: <slotName>
|
|
622
|
+
round: <round_number>
|
|
623
|
+
timeout_ms: <slot_timeout from $SLOT_TIMEOUTS>
|
|
624
|
+
repo_dir: <absolute path to working directory>
|
|
625
|
+
mode: B
|
|
626
|
+
question: <question text>
|
|
627
|
+
traces: |
|
|
628
|
+
<full $TRACES content verbatim>
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
For Round 2+ deliberation, also append:
|
|
632
|
+
```
|
|
633
|
+
prior_positions: |
|
|
634
|
+
• Claude:
|
|
635
|
+
position: [Claude's verdict and reasoning]
|
|
636
|
+
citations: [citations from Claude's analysis, or "(none)"]
|
|
637
|
+
• <slotName>:
|
|
638
|
+
position: [verdict from slot result block, or UNAVAIL]
|
|
639
|
+
citations: [citations field from slot result block, or "(none)"]
|
|
640
|
+
[one entry per active slot in the same format]
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Populate `citations:` from the `citations:` field in each model's slot-worker result block. If the result block had no `citations:` field or it was empty, write `(none)`. For Claude's own position, include any file paths or line numbers Claude cited in its reasoning.
|
|
644
|
+
|
|
645
|
+
Example dispatch (all Tasks in one message turn):
|
|
646
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="gemini-1 [gemini-cli · gemini-3-pro-preview] quorum R1", prompt=<YAML block>)`
|
|
647
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="codex-1 [codex-cli · gpt-5.3-codex] quorum R1", prompt=<YAML block>)`
|
|
648
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="opencode-1 [opencode-cli · grok-code-fast-1] quorum R1", prompt=<YAML block>)`
|
|
649
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="copilot-1 [copilot-cli · gpt-4.1] quorum R1", prompt=<YAML block>)`
|
|
650
|
+
- `Task(subagent_type="qgsd-quorum-slot-worker", model="haiku", max_turns=100, description="claude-1 [claude-code-router · deepseek-ai/DeepSeek-V3.2] quorum R1", prompt=<YAML block>)` ← one per claude-mcp server with `available: true`
|
|
651
|
+
(model="haiku" — slot-workers are orchestrators (read files, build prompt, run Bash subprocess), NOT reasoners. The actual reasoning is done by the external CLI. Haiku is faster with zero quality loss.)
|
|
652
|
+
|
|
653
|
+
The slot-worker reads repo context, builds the Mode B prompt (with execution traces) from the YAML arguments, calls the slot via `call-quorum-slot.cjs`, and returns a structured result block with a `verdict: APPROVE | REJECT | FLAG` field.
|
|
654
|
+
|
|
655
|
+
### Collect verdicts
|
|
656
|
+
|
|
657
|
+
Parse each worker response for `verdict:` and `reasoning:` lines. Mark non-parseable as `UNAVAIL`.
|
|
658
|
+
|
|
659
|
+
Determine consensus:
|
|
660
|
+
- All available APPROVE → `APPROVE`
|
|
661
|
+
- Any REJECT → `REJECT`
|
|
662
|
+
- All FLAG (no APPROVE, no REJECT) → `FLAG`
|
|
663
|
+
- Mixed APPROVE/FLAG → `FLAG`
|
|
664
|
+
- All UNAVAIL → stop: "All quorum models unavailable — cannot evaluate."
|
|
665
|
+
|
|
666
|
+
If split: run deliberation (up to 9 deliberation rounds, max 10 total rounds including Round 1) with traces always included in context.
|
|
667
|
+
|
|
668
|
+
### Output consensus verdict
|
|
669
|
+
|
|
670
|
+
```
|
|
671
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
672
|
+
QGSD ► QUORUM VERDICT
|
|
673
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
674
|
+
|
|
675
|
+
┌────────────────────────────────┬──────────────┬──────────────────────────────────────────┐
|
|
676
|
+
│ Model │ Verdict │ Reasoning │
|
|
677
|
+
├────────────────────────────────┼──────────────┼──────────────────────────────────────────┤
|
|
678
|
+
│ Claude │ [verdict] │ [summary] │
|
|
679
|
+
│ <slot-A> (primary) │ [verdict] │ [summary or UNAVAIL] │
|
|
680
|
+
│ ├─ <T1-next> (T1 fallback) │ [verdict] │ [only if slot-A UNAVAIL + T1 unused] │
|
|
681
|
+
│ └─ <T2-next> (T2 fallback) │ [verdict] │ [only if all T1 also UNAVAIL] │
|
|
682
|
+
│ <slot-B> (primary) │ [verdict] │ [summary or UNAVAIL] │
|
|
683
|
+
│ ├─ <T1-next> (T1 fallback) │ [verdict] │ [only if slot-B UNAVAIL + T1 unused] │
|
|
684
|
+
│ └─ <T2-next> (T2 fallback) │ [verdict] │ [only if all T1 also UNAVAIL] │
|
|
685
|
+
│ <slot-C> (primary) │ [verdict] │ [only if in $DISPATCH_LIST] │
|
|
686
|
+
├────────────────────────────────┼──────────────┼──────────────────────────────────────────┤
|
|
687
|
+
│ CONSENSUS │ [verdict] │ [N APPROVE, N REJECT, N FLAG, N UNAVAIL] │
|
|
688
|
+
└────────────────────────────────┴──────────────┴──────────────────────────────────────────┘
|
|
689
|
+
|
|
690
|
+
Slot names in each tier are resolved at runtime from `auth_type` in `providers.json`. T1 = `auth_type=sub` slots not in `$DISPATCH_LIST`; T2 = `auth_type≠sub` slots. Fallback rows are only rendered when the primary returned UNAVAIL and a replacement was dispatched. A T1 row is omitted if that slot was already dispatched as primary.
|
|
691
|
+
|
|
692
|
+
[rationale — what the traces showed]
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Update the scoreboard with the same `update-scoreboard.cjs` pattern as Mode A.
|
|
696
|
+
|
|
697
|
+
`--model` for native agents: `claude`, `gemini`, `opencode`, `copilot`, `codex`
|
|
698
|
+
`--slot` for claude-mcp servers: use the slot name (e.g. `claude-1`); `--model-id`: use the `model` field returned by the `health_check` response (e.g. `deepseek-ai/DeepSeek-V3`). Use `--slot` + `--model-id` instead of `--model` for all claude-mcp instances.
|
|
699
|
+
`--result` values: TP, TN, FP, FN, TP+ (improvement accepted), UNAVAIL (model skipped), or leave as empty string if model did not participate
|
|
700
|
+
`--task` label: short identifier, e.g. "quick-25" or "plan-ph17"
|
|
701
|
+
`--round`: the round number that just completed
|
|
702
|
+
`--verdict`: the consensus verdict (APPROVE | BLOCK | DELIBERATE | CONSENSUS | GAPS_FOUND)
|
|
703
|
+
`--task-description`: a brief description of what was being verified/reviewed (from $ARGUMENTS or a short summary). Used by Haiku to auto-classify. Optional — omit if not meaningful.
|
|
704
|
+
|
|
705
|
+
Run one command per model per round. Each call is atomic and idempotent — if re-run for the same task+round+model it overwrites that model's vote and recalculates from scratch.
|
|
706
|
+
|
|
707
|
+
Write QUORUM_DEBATE.md using the debate file path rule above. Set `Consensus:` to the final consensus verdict (APPROVE / REJECT / FLAG). Include one `## Round N` section per round that occurred. Set `## Outcome` to the rationale from the verdict output above. If 10 rounds elapsed without full consensus, set `Consensus: ESCALATED`.
|