pantheon-opencode 1.0.7 → 1.1.1-beta.0

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.
@@ -264,6 +264,14 @@ export function installOpenCode(
264
264
  writeIfChanged(join(dstPluginDir, "package.json"), readFileSync(pkgSrc, "utf8"), dryRun)
265
265
  stats.created++
266
266
  }
267
+ // Install plugin dependencies (@opentui/core, @opentui/solid, solid-js)
268
+ if (!dryRun) {
269
+ try {
270
+ execSync('npm install --production --no-audit --no-fund', { cwd: dstPluginDir, stdio: 'pipe' })
271
+ } catch (e) {
272
+ console.warn(' ⚠️ Failed to install TUI plugin dependencies:', e.message)
273
+ }
274
+ }
267
275
 
268
276
  }
269
277
 
@@ -687,7 +695,7 @@ export function installOpenCode(
687
695
  // -----------------------------------------------------------------------
688
696
  if (componentSet.has('runtime')) {
689
697
  try {
690
- setupVenv(target, { dryRun })
698
+ setupVenv(target, { dryRun, force: clean })
691
699
  const health = healthCheck(target, { dryRun })
692
700
 
693
701
  // Print health summary
@@ -702,7 +710,7 @@ export function installOpenCode(
702
710
  }
703
711
  } catch (err) {
704
712
  console.error(` ❌ Setup failed: ${err.message}`)
705
- stats.errors++
713
+ throw err // Fatal — abort installation
706
714
  }
707
715
  }
708
716
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { spawnSync } from 'node:child_process'
13
- import { existsSync } from 'node:fs'
13
+ import { existsSync, rmSync } from 'node:fs'
14
14
  import { dirname, join } from 'node:path'
15
15
  import { fileURLToPath } from 'node:url'
16
16
 
@@ -21,14 +21,23 @@ const ROOT = join(__dirname, '..', '..')
21
21
  * Set up a Python virtual environment and install MCP dependencies.
22
22
  *
23
23
  * @param {string} target - Target installation directory (e.g. ~/.config/opencode)
24
- * @param {{ dryRun?: boolean, skipInstall?: boolean }} [options]
24
+ * @param {{ dryRun?: boolean, skipInstall?: boolean, force?: boolean }} [options]
25
25
  * @returns {{ venvPath: string, python: string }}
26
26
  */
27
- export function setupVenv(target, { dryRun = false, skipInstall = false } = {}) {
27
+ export function setupVenv(target, { dryRun = false, skipInstall = false, force = false } = {}) {
28
28
  const venvPath = join(target, '.venv')
29
29
  const pythonBin = join(venvPath, 'bin', 'python3')
30
30
 
31
- // Step 1: Create venv if not exists
31
+ // Step 1: Force recreate venv if --force
32
+ if (force && existsSync(venvPath)) {
33
+ if (!dryRun) {
34
+ console.log(' Removing existing .venv (--force)...')
35
+ rmSync(venvPath, { recursive: true })
36
+ }
37
+ console.log(' ✅ .venv removed')
38
+ }
39
+
40
+ // Step 1b: Create venv if not exists
32
41
  if (!existsSync(pythonBin)) {
33
42
  if (!dryRun) {
34
43
  console.log(' Creating .venv...')
@@ -58,28 +67,34 @@ export function setupVenv(target, { dryRun = false, skipInstall = false } = {})
58
67
 
59
68
  if (!dryRun) {
60
69
  // Upgrade pip first (avoids resolver warnings)
70
+ // Ensure PIP_USER=0 to install into venv, not user site-packages
71
+ const pipEnv = { ...process.env, PIP_USER: '0' }
72
+
61
73
  spawnSync(pip, ['install', '--upgrade', 'pip'], {
62
74
  stdio: 'ignore',
63
75
  timeout: 30_000,
76
+ env: pipEnv,
64
77
  })
65
78
 
66
- // Install requirements
79
+ // Install requirements — attempt 1: normal
67
80
  const r = spawnSync(pip, ['install', '-r', reqFile], {
68
81
  stdio: 'inherit',
69
82
  timeout: 120_000,
83
+ env: pipEnv,
70
84
  })
71
85
 
72
86
  if (r.status !== 0) {
73
- // If failed, try with --break-system-packages (PEP 668 workaround)
87
+ // Attempt 2: --break-system-packages (PEP 668 / externally-managed)
74
88
  console.log(' ⚠️ First attempt failed, retrying with --break-system-packages...')
75
89
  const r2 = spawnSync(pip, ['install', '-r', reqFile, '--break-system-packages'], {
76
90
  stdio: 'inherit',
77
91
  timeout: 120_000,
92
+ env: pipEnv,
78
93
  })
79
94
  if (r2.status !== 0) {
80
95
  throw new Error(
81
96
  'Failed to install MCP dependencies. ' +
82
- 'Try: pip install -r src/mcp/requirements-mcp.txt --break-system-packages'
97
+ 'Try: PIP_USER=0 ' + pip + ' install -r ' + reqFile + ' --break-system-packages'
83
98
  )
84
99
  }
85
100
  }
@@ -101,9 +116,10 @@ function main() {
101
116
  const target = targetIdx !== -1 ? args[targetIdx + 1] : process.cwd()
102
117
  const dryRun = args.includes('--dry-run')
103
118
  const skipInstall = args.includes('--skip-install')
119
+ const force = args.includes('--force')
104
120
 
105
121
  try {
106
- const result = setupVenv(target, { dryRun, skipInstall })
122
+ const result = setupVenv(target, { dryRun, skipInstall, force })
107
123
  console.log(` 📍 Python: ${result.python}`)
108
124
  } catch (err) {
109
125
  console.error(`\n ❌ ${err.message}`)
@@ -60,7 +60,7 @@ console.log(` Agents in routing.yml: ${routingAgents.length}`)
60
60
  // Get canonical agent files
61
61
  const agentsDir = join(ROOT, 'src', 'agents')
62
62
  const canonicalFiles = readdirSync(agentsDir)
63
- .filter((f) => f.endsWith('.md'))
63
+ .filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md')
64
64
  .map((f) => f.replace('.md', ''))
65
65
  .sort()
66
66
 
@@ -0,0 +1,26 @@
1
+ # Agent Reference — Pantheon
2
+
3
+ This directory contains the definitions for all 14 Pantheon agents. Each agent is defined in a `.agent.md` file with YAML frontmatter specifying tools, model tier, capabilities, and delegation rules.
4
+
5
+ ## Agents
6
+
7
+ | Agent | File | Role |
8
+ |-------|------|------|
9
+ | @zeus | [zeus.agent.md](zeus.agent.md) | Central orchestrator |
10
+ | @athena | [athena.agent.md](athena.agent.md) | Strategic planner |
11
+ | @apollo | [apollo.agent.md](apollo.agent.md) | Investigation scout |
12
+ | @hermes | [hermes.agent.md](hermes.agent.md) | Backend specialist |
13
+ | @aphrodite | [aphrodite.agent.md](aphrodite.agent.md) | Frontend specialist |
14
+ | @demeter | [demeter.agent.md](demeter.agent.md) | Database specialist |
15
+ | @themis | [themis.agent.md](themis.agent.md) | Quality & security gate |
16
+ | @prometheus | [prometheus.agent.md](prometheus.agent.md) | Infrastructure specialist |
17
+ | @hephaestus | [hephaestus.agent.md](hephaestus.agent.md) | AI pipelines |
18
+ | @nyx | [nyx.agent.md](nyx.agent.md) | Observability |
19
+ | @gaia | [gaia.agent.md](gaia.agent.md) | Remote sensing |
20
+ | @iris | [iris.agent.md](iris.agent.md) | GitHub operations |
21
+ | @mnemosyne | [mnemosyne.agent.md](mnemosyne.agent.md) | Memory bank |
22
+ | @talos | [talos.agent.md](talos.agent.md) | Hotfix agent |
23
+
24
+ ## Full Reference
25
+
26
+ See [AGENTS.md](../../AGENTS.md) for the complete agent reference with roles, capabilities, and invocation patterns.
@@ -21,6 +21,51 @@ For investigation agents (Apollo, Athena), return structured findings with:
21
21
  - File paths with line numbers
22
22
  - Relevant code snippets (max 3 lines each)
23
23
 
24
+ ## Council Specialist Response Format
25
+
26
+ When responding to a `/pantheon` council synthesis invocation, specialists MUST return this structured format:
27
+
28
+ ```
29
+ ## specialist_response
30
+ **position:** <clear one-sentence position answering the question>
31
+ **reasoning:** <2-4 sentences with logical chain>
32
+ **trade_offs:** <what's gained vs lost, specific>
33
+ **risks:** <what could go wrong, max 3 items>
34
+ **confidence:** High | Medium | Low
35
+ **agreement_signals:** agree:@agent1 on <issue>, agree:@agent2 on <issue> | disagree:@agent3 on <issue>
36
+ **specific_claims:** <count of specific factual claims in response>
37
+ ```
38
+
39
+ ### Field Rules
40
+
41
+ | Field | Required | Validation |
42
+ |-------|----------|------------|
43
+ | position | ✅ | Single sentence, must directly answer the question |
44
+ | reasoning | ✅ | 2-4 sentences with logical chain |
45
+ | trade_offs | ✅ | At least one gain and one loss, specific to context |
46
+ | risks | ✅ | Max 3 bullet points |
47
+ | confidence | ✅ | One of: High, Medium, Low |
48
+ | agreement_signals | Optional | Format: `agree:@agent on issue \| disagree:@agent on issue`. Use only when you can reference other specialists expected positions. |
49
+ | specific_claims | ✅ | Integer count of verifiable factual statements in the response |
50
+
51
+ ### Confidence Guidelines
52
+
53
+ - **High**: Position backed by 3+ specific claims with evidence or direct experience
54
+ - **Medium**: Position backed by 1-2 specific claims
55
+ - **Low**: Position is opinion-based or speculative
56
+
57
+ ### Example
58
+ ```
59
+ ## specialist_response
60
+ **position:** The BackgroundJobBoard should be used for council crash recovery
61
+ **reasoning:** The board already supports running→completed→reconciled state machine with persistence. Registering council dispatches there adds ~50ms overhead but prevents total session loss on context crash. The WAL pattern ensures no data loss on restart.
62
+ **trade_offs:** Gain: crash recovery for multi-minute council sessions. Lose: ~50ms registration overhead per council.
63
+ **risks:** Board persistence failure could block council start — implement with fire-and-forget error handling
64
+ **confidence:** High
65
+ **agreement_signals:** agree:@themis on persist-before-notify
66
+ **specific_claims:** 3
67
+ ```
68
+
24
69
  ## Memory Context
25
70
  If this agent used `memory_recall` or `memory_search`, include the relevant memory entries
26
71
  used as context for the response.
@@ -65,3 +65,55 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
65
65
  5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
66
66
 
67
67
  Isso elimina ~300 tokens de reasoning por delegacao quando o cache acerta.
68
+
69
+ ## Council Decisions Namespace
70
+
71
+ Council synthesis decisions are persisted in a dedicated `council_decisions` namespace for precedent fast-path retrieval:
72
+
73
+ ### Write Path
74
+ After every `/pantheon` council synthesis completes, Zeus stores:
75
+ ```
76
+ memory_store({
77
+ namespace: "council_decisions",
78
+ key: "council:<yyyy-mm-dd>:<slug>",
79
+ value: {
80
+ question: "original question",
81
+ specialists: ["@agent1", "@agent2"],
82
+ recommendation: "final recommendation",
83
+ confidence: "High|Medium|Low",
84
+ agreements: ["point1", "point2"],
85
+ divergences: [{"issue": "...", "resolution": "..."}],
86
+ response_rate: "X of Y",
87
+ themis_audit: "approved|issues"
88
+ },
89
+ metadata: {
90
+ type: "council_decision",
91
+ specialist_count: N,
92
+ model_tier_used: "premium|default|fast"
93
+ }
94
+ })
95
+ ```
96
+
97
+ ### Read Path (Precedent Fast-Path)
98
+ Before dispatching a new council, Zeus runs:
99
+ ```
100
+ memory_search(question, top_k=2, namespace="council_decisions")
101
+ ```
102
+
103
+ Result interpretation:
104
+ | Score | Age | Action |
105
+ |-------|-----|--------|
106
+ | > 0.85 | < 30 days | Return precedent as fast-path answer. Skip council dispatch entirely. |
107
+ | > 0.85 | >= 30 days | Return with warning "Reavaliar se contexto mudou" + proceed with council |
108
+ | 0.5 - 0.85 | Any | Include as context for specialists but still dispatch council |
109
+ | < 0.5 | Any | Ignore, proceed with fresh council |
110
+
111
+ ### TTL & Maintenance
112
+ - Council decisions are LONG-TERM (no TTL or TTL = 365 days)
113
+ - Stale decisions (age > 90 days) should be flagged but NOT deleted — they remain as historical record
114
+ - Purge only via explicit namespace cleanup when decisions are superseded by ADRs
115
+
116
+ ### When NOT to Use
117
+ - Routine task summaries → use `default` namespace (auto-store by Zeus)
118
+ - Sprint/progress tracking → use `session` namespace
119
+ - ADR-level architecture decisions → delegate to @mnemosyne for permanent documentation
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Council synthesis — dispatch 2-4 specialists inline for multi-perspective decisions"
2
+ description: "Council synthesis — dispatch 2-4 specialists inline for multi-perspective decisions with precedent fast-path, confidence cross-validation, rebuttal rounds, and Themis audit gate"
3
3
  name: "Zeus Council Synthesis"
4
4
  applyTo: "agents/zeus.agent.md"
5
5
  ---
@@ -26,39 +26,197 @@ config:
26
26
  sequenceDiagram
27
27
  participant U as User
28
28
  participant Z as Zeus
29
+ participant M as Memory (Precedent)
30
+ participant A as Apollo (Research)
31
+ participant B as BackgroundJobBoard
29
32
  participant S1 as Specialist 1
30
33
  participant S2 as Specialist 2
31
34
  participant S3 as Specialist 3
35
+ participant W as Web Search
36
+ participant T as Themis (Audit + Moderator)
32
37
 
33
- U->>Z: /pantheon question
34
- Note over Z: Detect multi-perspective trigger
35
- Z->>Z: Select 2-4 specialists
36
- par Dispatch all specialists
37
- Z->>S1: Domain-specific query
38
- Z->>S2: Domain-specific query
39
- Z->>S3: Domain-specific query
38
+ U->>Z: /pantheon [--research] question
39
+ Z->>M: memory_search(council_decisions)
40
+ alt Precedent found (score > 0.85)
41
+ M-->>Z: ⚠️ Cached decision
42
+ Z->>U: Fast-path precedent
43
+ else No precedent
44
+ opt --research flag
45
+ Z->>A: 30s pre-scan
46
+ A-->>Z: Context brief
47
+ end
48
+ Z->>B: registerLaunch({council session})
49
+ par Dispatch all specialists
50
+ Z->>S1: Domain query + shared context
51
+ Z->>S2: Domain query + shared context
52
+ Z->>S3: Domain query + shared context
53
+ end
54
+ par Collect responses
55
+ S1-->>Z: Structured response
56
+ S2-->>Z: Structured response
57
+ S3-->>Z: Structured response
58
+ end
59
+ Note over Z: Cross-validate confidence
60
+ alt Agreement rate < 50%
61
+ Z->>S1: Rebuttal round
62
+ Z->>S2: Rebuttal round
63
+ Z->>S3: Rebuttal round
64
+ S1-->>Z: Refined response
65
+ S2-->>Z: Refined response
66
+ S3-->>Z: Refined response
67
+ Note over Z: Ainda divergência?
68
+ alt Agreement rate STILL < 50%
69
+ Z->>W: browser_search() for each divergence point
70
+ W-->>Z: Evidence brief with sources
71
+ Z->>T: Dispatches as moderator
72
+ Note over T: Reviews evidence + specialist responses
73
+ T-->>Z: Moderator verdict
74
+ end
75
+ end
76
+ Z->>Z: Synthesize (with evidence if tie-broken)
77
+ Z->>T: Audit synthesis fidelity
78
+ T-->>Z: ✅ / ⚠️ Issues
79
+ Z->>M: Store decision
80
+ Z->>B: Mark reconciled
81
+ Z->>U: 🏛️ Council Synthesis
40
82
  end
41
- par Collect responses
42
- S1-->>Z: Position + trade-offs
43
- S2-->>Z: Position + trade-offs
44
- S3-->>Z: Position + trade-offs
45
- end
46
- Note over Z: Synthesize agreements + divergences + recommendation
47
- Z->>U: 🏛️ Council Synthesis
48
83
  ```
49
84
 
50
- ## Dispatch with Timeout
85
+ ## Dispatch Sequence (9-Step Protocol)
86
+
87
+ ### Step 0 — Precedent Fast-Path (Fase 1)
88
+ Before any dispatch: `memory_search(query, top_k=2, namespace="council_decisions")`
89
+ - Score > 0.85 AND age < 30 days → return precedent verbatim with note "⚠️ Decisão de [data] — reavaliar se contexto mudou". SKIP entire dispatch.
90
+ - Score > 0.85 AND age > 30 days → return precedent with ⚠️ "Reavaliar se contexto mudou — decisão tem mais de 30 dias"
91
+ - Else → proceed to Step 0b
92
+
93
+ ### Step 0b — Apollo Pre-Scan (Fase 2, --research flag)
94
+ If `/pantheon --research <question>`: dispatch @apollo with 30s timeout. Inject findings as `shared_context` into ALL specialist prompts. Skip if flag absent.
95
+
96
+ ### Step 1 — Register on BackgroundJobBoard (Fase 1)
97
+ Before dispatching specialists:
98
+ ```
99
+ board.registerLaunch({
100
+ taskID: "council:<uuid>",
101
+ parentSessionID: "<session-id>",
102
+ agent: "zeus",
103
+ description: "Council: <question>",
104
+ objective: "Synthesize specialist recommendations"
105
+ })
106
+ ```
107
+ Enables crash recovery — if context is lost mid-synthesis, the board preserves session state.
108
+
109
+ ### Step 2 — Select Specialists
110
+ Use domain-to-specialist mapping table (below). Max 3 specialists.
111
+
112
+ ### Step 3 — Dispatch ALL task() calls in ONE message
113
+ Each specialist prompt MUST include:
114
+ - Structured output template (see Specialist Output Format below)
115
+ - `shared_context` if Apollo pre-scan was done
116
+ - Timeout per role: 120s for reviewers, 60s for explorers/implementers
117
+
118
+ ### Step 4 — Collect Responses
119
+ Wait for all responses. Note TIMEOUT agents. Partial results OK only for read-only specialists (@apollo, @gaia).
120
+
121
+ ### Step 5 — Confidence Cross-Validation (Fase 2)
122
+ For each specialist response, validate confidence claims:
51
123
 
52
- Send ALL `task()` calls in a single message. Each specialist must respond concisely (2-4 sentences).
124
+ | Reported | Minimum Required | Action |
125
+ |----------|-----------------|--------|
126
+ | High | ≥ 3 specific claims | If fewer → downgrade to Medium, annotate "(auto-downgraded from High: only N specific claims)" |
127
+ | Medium | ≥ 1 specific claim | Pass |
128
+ | Low | Any response | Pass |
53
129
 
54
- ⚠️ **TIMEOUT RULE:** If a specialist does not respond after other specialists have responded, proceed with partial results. Do NOT wait indefinitely.
130
+ "Specific claim" = statement with evidence, data point, or verifiable fact (not opinion).
55
131
 
56
- 1. Detect multi-perspective trigger pattern
57
- 2. Select 2-4 specialists based on domain (see tables below)
58
- 3. Dispatch ALL `task()` calls in ONE message
59
- 4. Wait for responses — note any specialists that don't respond as TIMEOUT
60
- 5. Synthesize: include "X of Y specialists responded" in output
61
- 6. Adjust confidence: down if key specialists timed out
132
+ ### Step 6 — Divergence-Gated Rebuttal Round (Fase 2, conditional)
133
+ Calculate agreement rate = `min(agreements, divergences) / total_points` from structured `agreement_signals`. If < 50% agreement:
134
+ - Dispatch ONE rebuttal round: specialists see ALL other responses and refine their own
135
+ - Updated responses treated as final
136
+ - Hard cap: MAX 3 total rounds (initial + 2 rebuttals)
137
+ - Skip if any TIMEOUT occurred (can't rebuttal without all voices)
138
+
139
+ ### Step 6b — Modo Desempate com Evidência (Fase 2, conditional)
140
+
141
+ If after Step 6 the agreement rate is STILL < 50% (rebuttal did not resolve divergence):
142
+
143
+ 1. **Identify divergence points** — Parse all specialist responses (initial + rebuttal) and extract specific issues where positions differ
144
+ 2. **Web research** — For EACH divergence point, Zeus calls `browser_search()` or `webfetch()` to find factual evidence:
145
+ - Benchmarks, documentation, GitHub issues, official sources
146
+ - At least 2 independent sources per point when possible
147
+ - Focus on factual data, not opinion
148
+ 3. **Compile Evidence Brief** — Structure findings as:
149
+ ```
150
+ ## evidence_brief
151
+ **divergence_point_1:** <description>
152
+ **evidence_found:** <facts from web, max 3 sentences>
153
+ **sources:** [URL1, URL2]
154
+
155
+ **divergence_point_2:** ...
156
+ ```
157
+ 4. **Dispatch @themis as moderator** — Send ONE task with:
158
+ - All specialist responses (initial + rebuttal)
159
+ - The evidence brief
160
+ - Prompt: "Act as impartial moderator. These specialists disagree on [points]. Here is web evidence on each point. Issue a final verdict for each divergence point, citing specific evidence. Return structured verdict."
161
+ 5. **Themis returns moderator verdict** in this format:
162
+ ```
163
+ ## moderator_verdict
164
+ **divergence_points_analyzed:** <count>
165
+ **verdicts:**
166
+ - <point 1>: <decision> — evidence: <citation>
167
+ - <point 2>: <decision> — evidence: <citation>
168
+ **overall_direction:** <which approach is better supported by evidence>
169
+ **confidence:** High | Medium | Low
170
+ **unresolved:** <any points still unclear despite evidence>
171
+ ```
172
+ 6. **Zeus incorporates verdict** into synthesis (Step 7), showing which evidence supported which conclusion
173
+
174
+ **Rules:**
175
+ - Only triggers when agreement rate < 50% AFTER rebuttal round
176
+ - Skip if NO divergence points are web-researchable (purely subjective/opinion-based disagreements)
177
+ - Max 3 web searches per council (prevents runaway)
178
+ - If Themis confidence is Low or points remain unresolved, note this explicitly in synthesis
179
+ - Themis moderator role is SEPARATE from the Themis Audit Gate (Step 8) — they serve different functions
180
+
181
+ ### Step 7 — Zeus Synthesize
182
+ Use structured `agreement_signals` to auto-detect agreements/divergences. Output synthesis template (see below).
183
+
184
+ ### Step 8 — Themis Audit Gate (Fase 1)
185
+ Post-synthesis, dispatch @themis:
186
+ ```
187
+ task(subagent_type: "themis", prompt: "Audit this council synthesis for fidelity. Compare raw specialist responses against the synthesized output. Check: (1) Any specialist misrepresented? (2) Divergences hidden or softened? (3) Confidence claims match response specificity? Return ✅ or list specific issues.")
188
+ ```
189
+ - If ✅ → proceed
190
+ - If issues → fix each issue before delivering to user
191
+
192
+ ### Step 9 — Persist & Reconcile (Fase 1)
193
+ ```
194
+ memory_store({
195
+ namespace: "council_decisions",
196
+ key: "council:<yyyy-mm-dd>:<slug>",
197
+ value: JSON.stringify({
198
+ question, specialists, recommendation, confidence,
199
+ agreements, divergences, precedent_used, timestamp
200
+ }),
201
+ metadata: {type: "council_decision", specialist_count: N}
202
+ })
203
+ board.markReconciled("<task-id>")
204
+ ```
205
+
206
+ ## Specialist Output Format (Fase 1 — machine-parseable structured fields)
207
+
208
+ Specialists MUST return these structured fields in their response:
209
+
210
+ ```
211
+ ## specialist_response
212
+ **position:** <clear one-sentence position>
213
+ **reasoning:** <2-4 sentences>
214
+ **trade_offs:** <what's gained vs lost>
215
+ **risks:** <what could go wrong>
216
+ **confidence:** High | Medium | Low
217
+ **agreement_signals:** agree: @agent1, @agent2 on [issue] | disagree: @agent3 on [issue]
218
+ **specific_claims:** <count of specific factual claims in response>
219
+ ```
62
220
 
63
221
  ## Domain-to-Specialist Mapping
64
222
 
@@ -73,7 +231,7 @@ Send ALL `task()` calls in a single message. Each specialist must respond concis
73
231
  | Observability | nyx, hermes |
74
232
  | General | athena, themis, hermes |
75
233
 
76
- ## Synthesis Output Template
234
+ ## Synthesis Output Template (enhanced)
77
235
 
78
236
  ```
79
237
  ## 🏛️ Council Synthesis
@@ -82,6 +240,8 @@ Send ALL `task()` calls in a single message. Each specialist must respond concis
82
240
  **Date:** <date>
83
241
  **Response rate:** X of Y specialists responded
84
242
  **Timed out:** @agent1, @agent2 (if any)
243
+ **Precedent used:** yes/no (which one, date)
244
+ **Research context:** <shared_context summary> (if --research)
85
245
 
86
246
  ### Specialist Perspectives
87
247
  | Agent | Position | Trade-offs | Confidence |
@@ -95,11 +255,70 @@ Send ALL `task()` calls in a single message. Each specialist must respond concis
95
255
  | Issue | Side A | Side B | Resolution |
96
256
  |-------|--------|--------|------------|
97
257
 
258
+ ### Evidence & Moderation (if tie-break activated)
259
+ **Divergence points researched:** <list>
260
+ **Evidence summary:** <key findings from web>
261
+ **Moderator verdict:** <Themis final determination>
262
+ **Sources:** <citations>
263
+
98
264
  ### Recommendation
99
265
  <decisive conclusion>
100
266
 
267
+ ### Audit Gate
268
+ **Status:** ✅ Themis approved | ⚠️ Issues corrected
269
+
101
270
  ### Decision Gate
102
271
  **Confidence:** High/Medium/Low (adjusted for response rate)
103
272
  ```
104
273
 
105
- > **Note**: The user can explicitly invoke this via `/pantheon <question>`. The command dispatches to Zeus who runs the council inline.
274
+ ## Themis Audit Gate Rules (Fase 1)
275
+
276
+ 1. Every specialist mentioned in synthesis? → ✅
277
+ 2. Divergences from raw responses preserved (not softened/hidden)? → ✅
278
+ 3. No specialist attributed a position they didn't state? → ✅
279
+ 4. Confidence claims match response specificity? → ✅
280
+ 5. If any ❌, list specific issues for Zeus to fix before delivery
281
+
282
+ ## Rebuttal Round Rules (Fase 2)
283
+
284
+ 1. Only triggers when agreement rate < 50%
285
+ 2. MAX 3 rounds total (including initial dispatch)
286
+ 3. Skip if any TIMEOUT (can't rebuttal without all voices)
287
+ 4. Each rebuttal round: specialist sees ALL other responses + current synthesis draft
288
+ 5. Refined responses replace originals for the final synthesis
289
+ 6. If agreement rate STILL < 50% after rebuttal → Step 6b (Modo Desempate com Evidência)
290
+
291
+ ## Crash Recovery via BackgroundJobBoard (Fase 1)
292
+
293
+ If Zeus context crashes mid-council:
294
+ 1. On restart, `board.recoverRunningJobs()` marks running jobs as error
295
+ 2. Check `board.formatForPrompt()` for unreconciled terminal jobs
296
+ 3. Re-dispatch council with same question, noting: "⚠️ Retry — previous council session crashed. Using same question."
297
+
298
+ ## Performance Budget
299
+
300
+ | Step | Latency | Phase |
301
+ |------|---------|-------|
302
+ | Precedent fast-path | < 500ms | Fase 1 |
303
+ | Apollo pre-scan | +30s (optional, --research) | Fase 2 |
304
+ | Specialist dispatch | bounded by slowest (60-120s) | — |
305
+ | Rebuttal round | +60s (conditional, < 50% agreement) | Fase 2 |
306
+ | Web search (tie-break) | +15s (conditional, < 50% after rebuttal) | Fase 2 |
307
+ | Themis moderator | +15s (conditional, tie-break activated) | Fase 2 |
308
+ | Themis audit | +15s | Fase 1 |
309
+ | **Total worst case** (research + rebuttal + tie-break) | ~255s | — |
310
+ | **Total typical** (no research, no rebuttal, no tie-break) | ~45-90s | — |
311
+
312
+ > **Note**: The user can explicitly invoke this via `/pantheon <question>`. The `--research` flag adds Apollo pre-scan. All decisions are stored in `council_decisions` memory namespace for fast-path retrieval on future councils.
313
+
314
+ ## Modo Desempate Rules (Fase 2)
315
+
316
+ 1. Only triggers when agreement rate < 50% AFTER rebuttal round
317
+ 2. If no divergence points are web-researchable (pure opinion disagreement), skip — Zeus synthesizes with lower confidence
318
+ 3. Max 3 web searches per council invocation
319
+ 4. Themis moderator role is DISTINCT from Themis audit gate:
320
+ - Moderator: resolves substantive disagreement between specialists (reads evidence, gives verdict)
321
+ - Audit gate: checks synthesis fidelity against raw responses (quality assurance)
322
+ Both run in the same council invocation, at different steps
323
+ 5. If Themis moderator confidence is Low, mark as "unresolved tie" in the synthesis output
324
+ 6. Evidence from web search must be cited with source URL or document reference — no anonymous claims
package/src/plugin.ts CHANGED
@@ -1,4 +1,42 @@
1
1
  import type { PluginConfig } from 'opencode'
2
+ import { BackgroundJobBoard } from './pantheon/background-job-board.ts'
3
+ import { FilePersistenceAdapter } from './pantheon/file-persistence.ts'
4
+
5
+ // ─── Background Job Board Singleton ────────────────────────────────────
6
+
7
+ const board = new BackgroundJobBoard({
8
+ maxConcurrentPerAgent: 3,
9
+ signalDir: '.pantheon/deepwork/board-signals',
10
+ })
11
+
12
+ // Wire up file-based persistence and recover orphaned jobs from crashes
13
+ const persistence = new FilePersistenceAdapter('.pantheon/board/state.json')
14
+ board.setPersistence(persistence)
15
+
16
+ // Recover on startup — any jobs left in "running" state become "error"
17
+ board.recoverRunningJobs().catch((err) =>
18
+ console.error('[Pantheon Plugin] Failed to recover running jobs:', err),
19
+ )
20
+
21
+ // Log board state changes to console for observability
22
+ board.onTerminal((taskID: string) => {
23
+ const job = board.get(taskID)
24
+ if (job) {
25
+ console.log(
26
+ `[Pantheon Plugin] Board terminal: [${job.alias}] ${job.description} → ${job.state}${job.resultSummary ? ` — ${job.resultSummary}` : ''}`,
27
+ )
28
+ }
29
+ })
30
+
31
+ /**
32
+ * Get the global BackgroundJobBoard singleton.
33
+ *
34
+ * All modules (Zeus, auto-wake, etc.) should access the board through this
35
+ * function to ensure a single consistent state machine across the plugin.
36
+ */
37
+ export function getBackgroundJobBoard(): BackgroundJobBoard {
38
+ return board
39
+ }
2
40
 
3
41
  export default {
4
42
  name: 'pantheon',