pantheon-opencode 1.6.0-beta.5 → 1.6.0-beta.6
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/AGENTS.md +18 -18
- package/CHANGELOG.md +14 -0
- package/README.md +83 -1
- package/package.json +5 -5
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/run-node-tests.mjs +189 -0
- package/skills-lock.json +1 -1
- package/src/agents/aphrodite.md +1 -1
- package/src/agents/athena.md +1 -0
- package/src/agents/demeter.md +1 -1
- package/src/agents/hephaestus.md +1 -1
- package/src/agents/hermes.md +1 -1
- package/src/agents/iris.md +1 -0
- package/src/agents/nyx.md +1 -0
- package/src/agents/themis.md +1 -0
- package/src/agents/zeus.md +10 -9
- package/src/instructions/memory-protocol.instructions.md +6 -6
- package/src/instructions/zeus-anti-stall.instructions.md +10 -10
- package/src/instructions/zeus-council-synthesis.instructions.md +2 -2
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +1 -1
- package/src/skills/auto-continue/SKILL.md +4 -4
package/AGENTS.md
CHANGED
|
@@ -271,7 +271,7 @@ Each agent file defines overrides in its `## 🧠 Memory Protocol` section:
|
|
|
271
271
|
|
|
272
272
|
Para otimizar decisoes de delegacao e reduzir gasto de tokens:
|
|
273
273
|
|
|
274
|
-
1. **memory_search(task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
|
|
274
|
+
1. **memory_search(query=task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
|
|
275
275
|
2. Se score > 0.85 → reutiliza agente + background_mode do cache
|
|
276
276
|
3. Se score ≤ 0.85 → aplica regras estaticas e memory_store() com:
|
|
277
277
|
- key: deleg:<task_type>
|
|
@@ -284,8 +284,8 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
|
|
|
284
284
|
as a string (got object). json.dumps it first. Example: metadata='{"type":
|
|
285
285
|
"decision", "score": 0.9}'`.
|
|
286
286
|
|
|
287
|
-
4. **kv_store("deleg:<pattern>",
|
|
288
|
-
5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
|
|
287
|
+
4. **kv_store(namespace="deleg", key="deleg:<pattern>", value=...)** para padroes recorrentes de delegacao
|
|
288
|
+
5. **kv_get(namespace="deleg", key="deleg:<pattern>")** para reusar decisoes ja tomadas
|
|
289
289
|
|
|
290
290
|
Isso elimina ~300 tokens de reasoning por delegacao quando o cache acerta.
|
|
291
291
|
|
|
@@ -299,7 +299,7 @@ After every `/pantheon` council synthesis completes, Zeus stores:
|
|
|
299
299
|
memory_store({
|
|
300
300
|
namespace: "council_decisions",
|
|
301
301
|
key: "council:<yyyy-mm-dd>:<slug>",
|
|
302
|
-
value: {
|
|
302
|
+
value: JSON.stringify({
|
|
303
303
|
question: "original question",
|
|
304
304
|
specialists: ["@agent1", "@agent2"],
|
|
305
305
|
recommendation: "final recommendation",
|
|
@@ -310,7 +310,7 @@ memory_store({
|
|
|
310
310
|
themis_audit: "approved|issues",
|
|
311
311
|
precedent_used: false,
|
|
312
312
|
timestamp: "<ISO-8601>"
|
|
313
|
-
},
|
|
313
|
+
}),
|
|
314
314
|
metadata: JSON.stringify({
|
|
315
315
|
type: "council_decision",
|
|
316
316
|
specialist_count: N,
|
|
@@ -328,7 +328,7 @@ encoded as a string (got object). json.dumps it first. Example: metadata='{"type
|
|
|
328
328
|
### Read Path (Precedent Fast-Path)
|
|
329
329
|
Before dispatching a new council, Zeus runs:
|
|
330
330
|
```
|
|
331
|
-
memory_search(question, top_k=2, namespace="council_decisions")
|
|
331
|
+
memory_search(query=question, top_k=2, namespace="council_decisions")
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
Result interpretation:
|
|
@@ -426,16 +426,16 @@ All session state lives in **pantheon-persistence** (namespace `checkpoint:<slug
|
|
|
426
426
|
No file I/O, no checkpoint_session.py — TTL (4h) handles cleanup automatically.
|
|
427
427
|
|
|
428
428
|
### Heartbeat Check
|
|
429
|
-
- If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
|
|
429
|
+
- If `context_get(slug=slug, key="heartbeat", session_id=SESSION_ID)` returns a checkin older than 300s, log a stall warning and resume
|
|
430
430
|
- Write heartbeat after every anti-stall recovery action:
|
|
431
431
|
```
|
|
432
|
-
context_save(slug, "heartbeat",
|
|
432
|
+
context_save(slug=slug, key="heartbeat", content=JSON.stringify({"status": "alive", "last_action": "...", "turn_count": N}), session_id=SESSION_ID)
|
|
433
433
|
```
|
|
434
434
|
|
|
435
435
|
### Checkpoint Auto-Save (Pré-Compactação)
|
|
436
436
|
Before ANY delegate dispatch, save a checkpoint:
|
|
437
437
|
```
|
|
438
|
-
context_save(slug, "phase:N",
|
|
438
|
+
context_save(slug=slug, key="phase:N", content=JSON.stringify({
|
|
439
439
|
"phase": {"current": N, "total": M, "name": "..."},
|
|
440
440
|
"turn_count": N, "agent": "...", "summary": "..."
|
|
441
441
|
}), session_id=SESSION_ID)
|
|
@@ -456,15 +456,15 @@ o Zeus DEVE salvar o estado atual:
|
|
|
456
456
|
2. Salve heartbeat + phase atual + tarefas pendentes
|
|
457
457
|
3. Só então permita que a compactação prossiga
|
|
458
458
|
```
|
|
459
|
-
# Ao iniciar sessão:
|
|
460
|
-
result = context_save(slug, "init", session_state)
|
|
459
|
+
# Ao iniciar sessão (session_id é REQUIRED — use o id da sessão corrente):
|
|
460
|
+
result = context_save(slug=slug, key="init", content=JSON.stringify(session_state), session_id=SESSION_ID)
|
|
461
461
|
SESSION_ID = result.session_id # ← guarde para toda a sessão
|
|
462
462
|
|
|
463
463
|
# Antes de CADA delegação:
|
|
464
|
-
context_save(slug, f"pre:{agent}", current_state, session_id=SESSION_ID)
|
|
464
|
+
context_save(slug=slug, key=f"pre:{agent}", content=JSON.stringify(current_state), session_id=SESSION_ID)
|
|
465
465
|
|
|
466
466
|
# Após retorno do agente:
|
|
467
|
-
context_save(slug, f"post:{agent}", result_state, session_id=SESSION_ID)
|
|
467
|
+
context_save(slug=slug, key=f"post:{agent}", content=JSON.stringify(result_state), session_id=SESSION_ID)
|
|
468
468
|
```
|
|
469
469
|
Isso garante que o estado sobreviva à compactação — o "latest" pointer
|
|
470
470
|
sempre aponta para o checkpoint mais recente, mesmo após compactação.
|
|
@@ -473,9 +473,9 @@ sempre aponta para o checkpoint mais recente, mesmo após compactação.
|
|
|
473
473
|
### Context Retrieval
|
|
474
474
|
Next-phase agents retrieve previous context via:
|
|
475
475
|
```
|
|
476
|
-
context_get(slug,
|
|
477
|
-
context_get(slug, "phase:3")
|
|
478
|
-
context_list(slug)
|
|
476
|
+
context_get(slug=slug, session_id=SESSION_ID) # most recent checkpoint ("latest" is the default key)
|
|
477
|
+
context_get(slug=slug, key="phase:3", session_id=SESSION_ID) # specific phase
|
|
478
|
+
context_list(slug=slug, session_id=SESSION_ID) # all checkpoints
|
|
479
479
|
```
|
|
480
480
|
|
|
481
481
|
### Long-Session Progress
|
|
@@ -535,7 +535,7 @@ sequenceDiagram
|
|
|
535
535
|
participant T as Themis (Audit + Moderator)
|
|
536
536
|
|
|
537
537
|
U->>Z: /pantheon [--research] question
|
|
538
|
-
Z->>M: memory_search(council_decisions)
|
|
538
|
+
Z->>M: memory_search(query, namespace="council_decisions")
|
|
539
539
|
alt Precedent found (score > 0.85)
|
|
540
540
|
M-->>Z: ⚠️ Cached decision
|
|
541
541
|
Z->>U: Fast-path precedent
|
|
@@ -584,7 +584,7 @@ sequenceDiagram
|
|
|
584
584
|
## Dispatch Sequence (9-Step Protocol)
|
|
585
585
|
|
|
586
586
|
### Step 0 — Precedent Fast-Path (Fase 1)
|
|
587
|
-
Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
|
|
587
|
+
Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query=question, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
|
|
588
588
|
|
|
589
589
|
### Step 0b — Apollo Pre-Scan (Fase 2, --research flag)
|
|
590
590
|
If `/pantheon --research <question>`: dispatch @apollo with 30s timeout. Inject findings as `shared_context` into ALL specialist prompts. Skip if flag absent.
|
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
18
18
|
|
|
19
19
|
## ✅ Closed Issues
|
|
20
20
|
|
|
21
|
+
## [v1.6.0-beta.6] - 2026-10-05
|
|
22
|
+
|
|
23
|
+
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
|
24
|
+
move this section to a versioned entry and reset the template below. -->
|
|
25
|
+
|
|
26
|
+
## 🆕 What's New
|
|
27
|
+
|
|
28
|
+
- **Política de suporte documentada: a linha 1.6.x é a ÚLTIMA compatível com OpenCode 1.X.** A partir de **1.7, toda release é breaking change com alvo em OpenCode 2.** O limite está escrito no `README.md` (seção "Compatibility and support policy", com matriz de suporte) e em `docs/INSTALLATION.md` (seção "OpenCode V1/V2 — contrato de plugin"), não duplicado em um documento novo: os dois arquivos já eram o ponto de entrada do contrato de plugin, e um terceiro documento seria um terceiro lugar para divergir. O texto distingue as duas células da linha 1.6.x — a célula 1.X é o contrato V1 (6 tools, lifecycle do board, hooks V1, compaction V1) e a célula OpenCode 2 é o contrato V2, que é real porém **menor**: 3 tools, sem as `pantheon_goal_*` e sem a matriz de delegação caller→target. **O suporte a OpenCode 2 na linha 1.6 é documentado como parcial, e não como completo**, por duas razões verificadas: (a) o canário de ferramentas V2 foi escrito contra `opencode 2.0.22`, host que não é publicado em nenhum canal npm — contra o `@opencode-ai/cli` instalável o mesmo canário falha 12/12, porque `/api/experimental/session/{id}/wait` foi promovido para `/api/session/{id}/wait` (404 no caminho antigo) e `ctx.tool.list` não existe mais; corrigindo apenas a rota, 10/12 passam no mesmo host, o que caracteriza descasamento canário/host e não regressão medida do plugin (issue #216, PR #213); (b) não existe perna V1 de validação end-to-end — `scripts/test-opencode-v2-sandbox.sh` é V2-only e rejeita `--run v1` — portanto a versão exata de host 1.X exercitada end-to-end pela linha 1.6 fica registrada como **não verificada**, e o caminho 1.X é sustentado pelo pin de dependência (`@opencode-ai/plugin` / `@opencode-ai/sdk` `1.18.33`), pelos testes unitários do gate de geração e pelos testes end-to-end do installer sobre a config emitida.
|
|
29
|
+
|
|
30
|
+
## 🐞 Fixed
|
|
31
|
+
|
|
32
|
+
- **Os exemplos de chamada MCP nos arquivos de prompt ensinavam um contrato que não existe no schema real.** Um `pantheon-persistence.kv_get` ao vivo foi rejeitado com `namespace: Missing key`; a linha que o produziu foi `src/agents/zeus.md:189` (`depth = kv_get("deleg:depth") ?? 0`), cuja cópia instalada em `~/.config/opencode/agents/zeus.md` é a que executou. A causa não era o servidor MCP: os arquivos de agente e de instrução ensinavam chamadas posicionais sem `namespace`, enquanto o schema real é `kv_get({ namespace /* obrigatório, sem default */, key, scope? })` — `kv_store.namespace` é igualmente obrigatório e `kv_store.value` é `str`. **A correção está nos docs, e não em adicionar um `namespace` default:** um default escreveria estado de coordenação no namespace errado silenciosamente, então *obrigatório* é o desenho correto. A mesma classe de defeito foi corrigida em `context_save` (posicional, com um helper `json()` inexistente e sem o `session_id` obrigatório → forma por palavra-chave com `content=JSON.stringify(...)`), em `memory_store` (`value` precisa ser uma string JSON codificada → agora iguala o padrão já correto de `metadata`, o que resolveu uma aut contradição no mesmo arquivo), na aritmética de profundidade (envolvida em `String(...)`, porque `kv_store.value` é `str` e não `int`) e em `memory_search(query, ...)` → `query=` em todas as instâncias. `AGENTS.md` é artefato gerado e foi reconstruído, nunca editado à mão.
|
|
33
|
+
- **Um bypass de permissão vivo: `edit: deny` não cobria `hashline_edit`.** A regra `edit: deny` é avaliada por nome de ferramenta, e `hashline_edit` não é `edit` — então os agentes restritos a `edit: deny` (`zeus`, `athena`, `iris`, `nyx`, `themis`) podiam editar arquivos assim mesmo, escrevendo no disco por um caminho que a negação não cobria. `hashline_edit: deny` foi adicionado a esses cinco agentes. **Não era uma condição de corrida nem um erro de configuração: era uma tabela de permissões incompleta**, o que significa que a negação nunca esteve de fato aplicada a quem só deveria ler.
|
|
34
|
+
- **`test:node` reportava sucesso tendo coletado zero testes.** `node --test <glob>` sai com 0 quando o glob não expande (Node >= 22), e foi exatamente o que aconteceu: um glob que nunca expandiu produziu um gate verde que não executou nada. Pior, `tests/` estava ausente da allow-list `files` do pacote, então um consumidor que rodasse `npm test` recebia esse verde. Agora os scripts de teste falham em voz alta quando a coleta é zero, e a guarda tem teste próprio (`tests/run-node-tests.test.mjs`) que a dirige como subprocesso — o contrato é o código de saída, então testá-la em processo não provaria nada.
|
|
21
35
|
## [v1.6.0-beta.5] - 2026-10-05
|
|
22
36
|
|
|
23
37
|
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
package/README.md
CHANGED
|
@@ -31,7 +31,9 @@ way to plan work, make progress, check results, and keep useful project context.
|
|
|
31
31
|
## Start in 2 minutes
|
|
32
32
|
|
|
33
33
|
Requirements: [OpenCode 1.18.4+](https://opencode.ai/docs/) and Node.js
|
|
34
|
-
22.22.2+ (or 24.15.0+ / 26+).
|
|
34
|
+
22.22.2+ (or 24.15.0+ / 26+). For which OpenCode generation each Pantheon
|
|
35
|
+
release line supports, see
|
|
36
|
+
[Compatibility and support policy](#compatibility-and-support-policy).
|
|
35
37
|
|
|
36
38
|
Pantheon declares `engines.node` as `^22.22.2 || ^24.15.0 || >=26.0.0`. The
|
|
37
39
|
floor reflects what the dependency tree actually needs — the transitive
|
|
@@ -89,6 +91,81 @@ services you choose to use. Check the
|
|
|
89
91
|
[releases](https://github.com/ils15/pantheon-opencode/releases) and
|
|
90
92
|
[changelog](CHANGELOG.md) for the latest published changes.
|
|
91
93
|
|
|
94
|
+
## Compatibility and support policy
|
|
95
|
+
|
|
96
|
+
> ### ⚠️ 1.6.x is the LAST line that supports OpenCode 1.X
|
|
97
|
+
>
|
|
98
|
+
> **OpenCode 1.X support ends after the 1.6 line.** From **1.7 onward, every
|
|
99
|
+
> release is a breaking change targeting OpenCode 2.** A 1.6.x install is the
|
|
100
|
+
> last one that runs on an OpenCode 1.X host; do not plan an upgrade across the
|
|
101
|
+
> 1.6 → 1.7 boundary while your host is still on OpenCode 1.X.
|
|
102
|
+
|
|
103
|
+
| Pantheon line | OpenCode 1.X host | OpenCode 2 host |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| **1.6.x** (current line) | **Supported.** V1 plugin contract | Supported, reduced surface — V2 contract |
|
|
106
|
+
| **1.7 and later** | **Not supported.** Breaking change | Target of the 1.7+ work |
|
|
107
|
+
|
|
108
|
+
Both columns of the 1.6.x row are real, and they are not the same claim. Read
|
|
109
|
+
[the plugin contract](#opencode-v1v2--dual-version-160-beta1) for what each
|
|
110
|
+
generation actually registers.
|
|
111
|
+
|
|
112
|
+
### What "supported on OpenCode 1.X" concretely means
|
|
113
|
+
|
|
114
|
+
On the 1.6 line, an OpenCode 1.X host gets the **V1 plugin**:
|
|
115
|
+
`src/plugin.ts` plus `src/plugins/pantheon-hooks.ts`, registered under the
|
|
116
|
+
singular `plugin` config key. That surface registers 6 tools
|
|
117
|
+
(`hashline_edit`, the 3 `pantheon_goal_*` tools, `pantheon_cost`,
|
|
118
|
+
`pantheon_model`), the BackgroundJobBoard lifecycle, the V1 event/tool hooks and
|
|
119
|
+
the V1 compaction path. It is selected automatically: the generation gate
|
|
120
|
+
(`--opencode-version`, else `OPENCODE_VERSION`, else a `OPENCODE_BIN` basename of
|
|
121
|
+
`opencode2`, else a `--version` probe where major ≥ 2 selects V2) resolves a 1.X
|
|
122
|
+
host to V1.
|
|
123
|
+
|
|
124
|
+
The two generations are exclusive. The installer strips Pantheon references from
|
|
125
|
+
both config shapes and registers only the selected one, so a 1.X host never
|
|
126
|
+
loads the V2 plugin and a V2 host never loads the V1 plugin. Forcing V2 on a 1.X
|
|
127
|
+
host is an unsupported mixed/wrong-generation configuration, not a supported mode.
|
|
128
|
+
|
|
129
|
+
### What the OpenCode 2 path is — and is not
|
|
130
|
+
|
|
131
|
+
**Not inert.** The V2 path (`src/plugin-v2`, registered as a directory because
|
|
132
|
+
the V2 loader rejects bare file paths) registers real tools with real
|
|
133
|
+
`input`/`output` schemas via `ctx.tool.transform()`, 5 event subscriptions,
|
|
134
|
+
session hooks and a tool `execute.before` hook that enforces read-only sessions.
|
|
135
|
+
It is a smaller surface than V1 by design, not a stub: the 3 goal tools and the
|
|
136
|
+
V1 caller→target delegation matrix are absent, and `getUnsupportedFeatures()`
|
|
137
|
+
reports that as `goal-tools` and `delegation-matrix`.
|
|
138
|
+
|
|
139
|
+
**Not complete either, and the V2 support claim is currently narrowed.** Two
|
|
140
|
+
facts, both recorded in this repository rather than smoothed over:
|
|
141
|
+
|
|
142
|
+
- **The host-backed V2 gate is red on the installable OpenCode CLI.** The V2 tool
|
|
143
|
+
canary (`tests/canary/plugin-v2-tool-canary.test.mjs`) was written against
|
|
144
|
+
`opencode 2.0.22`, which no npm channel publishes. Against the installable
|
|
145
|
+
`@opencode-ai/cli` host, `/api/experimental/session/{id}/wait` has been
|
|
146
|
+
promoted to `/api/session/{id}/wait` (the old path 404s) and `ctx.tool.list`
|
|
147
|
+
is absent, so the canary fails 12/12 there. That is a canary/host-generation
|
|
148
|
+
mismatch, not a measured plugin regression — with only the route renamed, 10/12
|
|
149
|
+
pass on the same host, including the edit being applied and the mutant still
|
|
150
|
+
caught. Tracked as [issue
|
|
151
|
+
#216](https://github.com/ils15/pantheon-opencode/issues/216); the fail-closed
|
|
152
|
+
CI change is [PR #213](https://github.com/ils15/pantheon-opencode/pull/213).
|
|
153
|
+
- **The 2.x host claims in `src/plugin-v2.ts` are measured against `2.0.22`
|
|
154
|
+
specifically**, not against the current installable CLI. Re-measurement
|
|
155
|
+
against the current host is outstanding and is tracked in the same issue.
|
|
156
|
+
|
|
157
|
+
Treat the 1.6 line's OpenCode 2 support as **real but partial**, and do not read
|
|
158
|
+
the 1.6.x row as "OpenCode 2 is finished".
|
|
159
|
+
|
|
160
|
+
### Verified against which OpenCode versions
|
|
161
|
+
|
|
162
|
+
| What | Verified against | How |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| V1 plugin + generation gate | The OpenCode 1.18.x line. This package pins `@opencode-ai/plugin` and `@opencode-ai/sdk` to `1.18.33`; the highest published `opencode-ai` host package (the one shipping the `opencode` binary) is `1.18.34` | Dependency pin (`package.json`); generation-gate unit tests over injected host banners; installer end-to-end tests asserting the emitted `opencode.json` shape |
|
|
165
|
+
| V2 plugin tool surface | `opencode 2.0.22` (the host the canary was written against) | `tests/canary/plugin-v2-tool-canary.test.mjs`, driving `hashline_edit` through a real `opencode serve` |
|
|
166
|
+
| V2 against the current installable CLI | **Not verified.** Canary fails 12/12; see issue #216 | — |
|
|
167
|
+
| Exact 1.X host version the 1.6 line is exercised against end-to-end | **Unverified.** No V1 host leg exists: the sandbox runner is V2-only and `--run v1` is rejected | — |
|
|
168
|
+
|
|
92
169
|
## Delegation (native `task()`)
|
|
93
170
|
|
|
94
171
|
Pantheon delegates exclusively through OpenCode's native `task()` child-session
|
|
@@ -301,6 +378,11 @@ Key order does not matter.
|
|
|
301
378
|
|
|
302
379
|
## Updating between releases (beta.5+)
|
|
303
380
|
|
|
381
|
+
> **Read [Compatibility and support policy](#compatibility-and-support-policy)
|
|
382
|
+
> before upgrading across a line boundary.** 1.6.x is the last line that
|
|
383
|
+
> supports an OpenCode 1.X host; 1.7+ is breaking-change territory targeting
|
|
384
|
+
> OpenCode 2. Within the 1.6 line, `update` is the supported path.
|
|
385
|
+
|
|
304
386
|
One command keeps an existing installation current:
|
|
305
387
|
|
|
306
388
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-opencode",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.6",
|
|
4
4
|
"description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"hooks:install": "pre-commit install",
|
|
42
42
|
"hooks:update": "pre-commit autoupdate",
|
|
43
43
|
"test:security": "npm run secret-scan",
|
|
44
|
-
"test:node": "node --test tests/*.mjs tests/pantheon/*.mjs",
|
|
45
|
-
"coverage": "echo 'Node coverage (report-only, NO threshold is enforced).' && echo 'Covers: the .mjs/.js modules under scripts/, src/ and bin/ that test:node exercises.' && echo 'Does NOT cover: test:ts (hand-rolled tsx loop, no node --test runner) and test:ci (pytest).' && echo ' TypeScript under src/ (41 files) is therefore entirely unmeasured here.' && echo '' && node --
|
|
44
|
+
"test:node": "node scripts/run-node-tests.mjs --label 'test:node' 'tests/*.mjs' 'tests/pantheon/*.mjs'",
|
|
45
|
+
"coverage": "echo 'Node coverage (report-only, NO threshold is enforced).' && echo 'Covers: the .mjs/.js modules under scripts/, src/ and bin/ that test:node exercises.' && echo 'Does NOT cover: test:ts (hand-rolled tsx loop, no node --test runner) and test:ci (pytest).' && echo ' TypeScript under src/ (41 files) is therefore entirely unmeasured here.' && echo '' && node scripts/run-node-tests.mjs --label 'coverage' -- --experimental-test-coverage --test-coverage-include='scripts/**/*.mjs' --test-coverage-include='src/**/*.mjs' --test-coverage-include='src/**/*.js' --test-coverage-include='bin/**/*.mjs' 'tests/*.mjs' 'tests/pantheon/*.mjs'",
|
|
46
46
|
"coverage:plugin-v2": "node scripts/check-plugin-v2-coverage.mjs",
|
|
47
47
|
"test:ts": "for f in tests/pantheon/*.test.ts; do if [ \"$f\" = tests/pantheon/plugin-v2-contract.test.ts ]; then NODE_OPTIONS=--conditions=import tsx \"$f\"; else tsx \"$f\"; fi || exit 1; done",
|
|
48
|
-
"test:hooks": "node --test tests/pantheon/plugin-v2-hook-canary.test.mjs",
|
|
49
|
-
"test:tool-canary": "node --test tests/canary/plugin-v2-tool-canary.test.mjs",
|
|
48
|
+
"test:hooks": "node scripts/run-node-tests.mjs --label 'test:hooks' 'tests/pantheon/plugin-v2-hook-canary.test.mjs'",
|
|
49
|
+
"test:tool-canary": "node scripts/run-node-tests.mjs --label 'test:tool-canary' 'tests/canary/plugin-v2-tool-canary.test.mjs'",
|
|
50
50
|
"test:all": "npm run test:ci && npm run test:node && npm run test:ts",
|
|
51
51
|
"test": "npm run test:all",
|
|
52
52
|
"test:ci": "python3 -m pytest -q --tb=short 2>&1"
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon",
|
|
3
3
|
"publisher": "ils15",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.6",
|
|
5
5
|
"displayName": "Pantheon",
|
|
6
6
|
"description": "Conductor-Delegate orchestration platform with 14 specialized agents, 21 skills, and OpenCode support.",
|
|
7
7
|
"agents": "./src/agents",
|
package/pyproject.toml
CHANGED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Run `node --test` over an explicit file list, and FAIL when zero tests are collected.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: `node --test <glob>` exits 0 when the glob matches nothing (Node >= 22).
|
|
6
|
+
* A test script whose file set is absent from the checkout -- or absent from the published
|
|
7
|
+
* package, because the `files` allow-list omits it -- therefore reports success while
|
|
8
|
+
* collecting nothing. That is the worst possible failure mode for a release gate: it
|
|
9
|
+
* manufactures confidence.
|
|
10
|
+
*
|
|
11
|
+
* This runner closes that hole from two independent sides:
|
|
12
|
+
* 1. It expands the globs ITSELF and refuses to start the runner with zero files, so the
|
|
13
|
+
* runner is never handed an unexpanded pattern that silently matches nothing.
|
|
14
|
+
* 2. After a run that exited 0, it re-reads the collected count from the machine-readable
|
|
15
|
+
* TAP summary and fails closed when that count is 0, unparseable, or missing.
|
|
16
|
+
*
|
|
17
|
+
* Side 2 is deliberately independent of side 1: a future Node change to `--test` glob
|
|
18
|
+
* handling, a glob that expands to a directory of empty files, or an emptied test file all
|
|
19
|
+
* still get caught.
|
|
20
|
+
*
|
|
21
|
+
* Human-readable output is unchanged: the default `spec` reporter still goes to stdout, and
|
|
22
|
+
* the TAP reporter is diverted to a scratch file that is read once and deleted.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* node scripts/run-node-tests.mjs [--label <name>] [-- <node flags...>] <glob>...
|
|
26
|
+
*
|
|
27
|
+
* Examples:
|
|
28
|
+
* node scripts/run-node-tests.mjs 'tests/*.mjs' 'tests/pantheon/*.mjs'
|
|
29
|
+
* node scripts/run-node-tests.mjs -- --experimental-test-coverage 'tests/*.mjs'
|
|
30
|
+
*/
|
|
31
|
+
import { spawnSync } from 'node:child_process'
|
|
32
|
+
import { globSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
33
|
+
import { tmpdir } from 'node:os'
|
|
34
|
+
import { join } from 'node:path'
|
|
35
|
+
import { fileURLToPath } from 'node:url'
|
|
36
|
+
|
|
37
|
+
const ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..')
|
|
38
|
+
|
|
39
|
+
function usage(message) {
|
|
40
|
+
console.error(`zero-test-guard: ${message}`)
|
|
41
|
+
console.error(
|
|
42
|
+
'usage: node scripts/run-node-tests.mjs [--label <name>] [-- <node flags...>] <glob>...',
|
|
43
|
+
)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Parse `[--label <name>] [-- <node flags...>] <glob>...`. */
|
|
48
|
+
function parseArgs(argv) {
|
|
49
|
+
const separator = argv.indexOf('--')
|
|
50
|
+
const own = separator === -1 ? argv : argv.slice(0, separator)
|
|
51
|
+
const nodeFlags = separator === -1 ? [] : argv.slice(separator + 1)
|
|
52
|
+
|
|
53
|
+
let label = 'node tests'
|
|
54
|
+
const globs = []
|
|
55
|
+
for (let i = 0; i < own.length; i++) {
|
|
56
|
+
if (own[i] === '--label') {
|
|
57
|
+
const value = own[i + 1]
|
|
58
|
+
if (value === undefined) usage('--label requires a value')
|
|
59
|
+
label = value
|
|
60
|
+
i += 1
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if (own[i].startsWith('-')) usage(`unknown option ${own[i]}`)
|
|
64
|
+
globs.push(own[i])
|
|
65
|
+
}
|
|
66
|
+
if (globs.length === 0) usage('at least one glob is required')
|
|
67
|
+
return { label, globs, nodeFlags }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Expand globs to a deduped, ordered file list.
|
|
72
|
+
* Order follows the positional order of the globs so that the suite runs in the same order
|
|
73
|
+
* as the shell used to; within a glob, matches are sorted for determinism.
|
|
74
|
+
*/
|
|
75
|
+
function expand(globs) {
|
|
76
|
+
const files = []
|
|
77
|
+
const seen = new Set()
|
|
78
|
+
for (const pattern of globs) {
|
|
79
|
+
const matches = globSync(pattern, { cwd: ROOT })
|
|
80
|
+
.filter((match) => !match.includes('node_modules') && !match.includes('__pycache__'))
|
|
81
|
+
.sort()
|
|
82
|
+
for (const match of matches) {
|
|
83
|
+
if (seen.has(match)) continue
|
|
84
|
+
seen.add(match)
|
|
85
|
+
files.push(match)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return files
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function reportMissingFiles(label, globs) {
|
|
92
|
+
console.error(`zero-test-guard: ${label} matched 0 test files — refusing to report success.`)
|
|
93
|
+
console.error(` globs: ${globs.join(' ')}`)
|
|
94
|
+
console.error(' This is the "test suite silently passes when it collects nothing" failure.')
|
|
95
|
+
console.error(' Either the test files are missing from this checkout, or they are missing')
|
|
96
|
+
console.error(' from the published package (check the "files" allow-list in package.json).')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function reportZeroCollected(label, files) {
|
|
100
|
+
console.error(`zero-test-guard: ${label} collected 0 tests — refusing to report success.`)
|
|
101
|
+
console.error(` ${files.length} file(s) matched, but not one test case was collected:`)
|
|
102
|
+
for (const file of files) console.error(` ${file}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Count the real test cases collected in a TAP summary.
|
|
107
|
+
*
|
|
108
|
+
* Node counts a test FILE that declares no test cases as one passing "test"
|
|
109
|
+
* (`ok 1 - tests/foo.test.mjs`), so the summary's `# tests N` is >= the file count and
|
|
110
|
+
* can never reach 0 once any file matched. Relying on it alone would let a suite whose
|
|
111
|
+
* every test file was emptied out report success.
|
|
112
|
+
*
|
|
113
|
+
* The reliable discriminator is the subtest NAME: a file-level placeholder is named with
|
|
114
|
+
* the file path that was passed to the runner, a real test case is named with its test
|
|
115
|
+
* name. So a top-level subtest whose name is one of our matched paths is a placeholder
|
|
116
|
+
* and is not counted as a collected case.
|
|
117
|
+
*
|
|
118
|
+
* Returns null when the summary cannot be read or parsed -- the caller fails closed.
|
|
119
|
+
*/
|
|
120
|
+
function countCollectedTests(tapPath, files) {
|
|
121
|
+
let tap = ''
|
|
122
|
+
try {
|
|
123
|
+
tap = readFileSync(tapPath, 'utf8')
|
|
124
|
+
} catch {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
// The summary count is still asserted to exist: without it the stream is not TAP.
|
|
128
|
+
if (!/^#\s*tests\s+\d+\s*$/m.test(tap)) return null
|
|
129
|
+
|
|
130
|
+
const placeholders = new Set(files)
|
|
131
|
+
let collected = 0
|
|
132
|
+
for (const match of tap.matchAll(/^# Subtest: (.+)$/gm)) {
|
|
133
|
+
if (placeholders.has(match[1].trim())) continue
|
|
134
|
+
collected += 1
|
|
135
|
+
}
|
|
136
|
+
return collected
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { label, globs, nodeFlags } = parseArgs(process.argv.slice(2))
|
|
140
|
+
const files = expand(globs)
|
|
141
|
+
|
|
142
|
+
if (files.length === 0) {
|
|
143
|
+
reportMissingFiles(label, globs)
|
|
144
|
+
process.exit(1)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const scratch = mkdtempSync(join(tmpdir(), 'pantheon-node-tests-'))
|
|
148
|
+
const tapPath = join(scratch, 'summary.tap')
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const run = spawnSync(
|
|
152
|
+
process.execPath,
|
|
153
|
+
[
|
|
154
|
+
'--test',
|
|
155
|
+
'--test-reporter=spec',
|
|
156
|
+
'--test-reporter-destination=stdout',
|
|
157
|
+
'--test-reporter=tap',
|
|
158
|
+
`--test-reporter-destination=${tapPath}`,
|
|
159
|
+
...nodeFlags,
|
|
160
|
+
...files,
|
|
161
|
+
],
|
|
162
|
+
{ cwd: ROOT, stdio: 'inherit' },
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if (run.error) {
|
|
166
|
+
console.error(`zero-test-guard: could not start the test runner: ${run.error.message}`)
|
|
167
|
+
process.exit(1)
|
|
168
|
+
}
|
|
169
|
+
if (run.signal) {
|
|
170
|
+
console.error(`zero-test-guard: the test runner was killed by ${run.signal}`)
|
|
171
|
+
process.exit(1)
|
|
172
|
+
}
|
|
173
|
+
// Real failures keep the runner's own exit code; the guard only speaks up when the
|
|
174
|
+
// runner claimed success it cannot justify.
|
|
175
|
+
if (run.status !== 0) process.exit(run.status)
|
|
176
|
+
|
|
177
|
+
const collected = countCollectedTests(tapPath, files)
|
|
178
|
+
if (collected === null) {
|
|
179
|
+
console.error('zero-test-guard: could not read the collected test count from the TAP summary.')
|
|
180
|
+
console.error(' Failing closed: an unreadable summary cannot certify that any test ran.')
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
if (collected === 0) {
|
|
184
|
+
reportZeroCollected(label, files)
|
|
185
|
+
process.exit(1)
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
rmSync(scratch, { recursive: true, force: true })
|
|
189
|
+
}
|
package/skills-lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"src/skills/agent-coordination/SKILL.md": "cfd040ef29e3dd0fd5e0e67773874facf7c06e3284898afe1fddba4456c3f1aa",
|
|
3
3
|
"src/skills/artifact-management/SKILL.md": "c60e493666bedf46e9113d0dd20106489d5c5a73905e1370fcfc1c0d71c59185",
|
|
4
|
-
"src/skills/auto-continue/SKILL.md": "
|
|
4
|
+
"src/skills/auto-continue/SKILL.md": "a7a4ec7ab35970bec36bcc286b95881b43a75e3bcdb626a17ebb9e397dd1a708",
|
|
5
5
|
"src/skills/clonedeps/SKILL.md": "3e0b43cf96da98c83699dc48db3fd58fbfda84b9022bb70d3747875c81c3e124",
|
|
6
6
|
"src/skills/code-review-checklist/SKILL.md": "0e59367c65ef4608db80a0674279f32cfc3f67dca7236f233b61a47fe506579f",
|
|
7
7
|
"src/skills/codemap/SKILL.md": "87af5271e1576e154cadff313eab16264b726e43d094520736b7a5b03b409a4a",
|
package/src/agents/aphrodite.md
CHANGED
|
@@ -122,7 +122,7 @@ Frontend: `tdd-with-agents`, `visual-review-pipeline`, `incremental-implementati
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
## Session Context Retrieval
|
|
125
|
-
When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
|
|
125
|
+
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
126
126
|
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
127
127
|
"current_phase", or "gotchas" from the retrieved context.
|
|
128
128
|
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/athena.md
CHANGED
package/src/agents/demeter.md
CHANGED
|
@@ -111,7 +111,7 @@ Compress working context with the `context-compression` skill (L1, Pantheon-nati
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
## Session Context Retrieval
|
|
114
|
-
When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
|
|
114
|
+
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
115
115
|
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
116
116
|
"current_phase", or "gotchas" from the retrieved context.
|
|
117
117
|
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/hephaestus.md
CHANGED
|
@@ -64,7 +64,7 @@ Compress working context with the `context-compression` skill (L1, Pantheon-nati
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
## Session Context Retrieval
|
|
67
|
-
When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
|
|
67
|
+
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
68
68
|
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
69
69
|
"current_phase", or "gotchas" from the retrieved context.
|
|
70
70
|
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/hermes.md
CHANGED
|
@@ -270,7 +270,7 @@ Implementação: `tdd-with-agents`, `incremental-implementation`, `code-review-c
|
|
|
270
270
|
|
|
271
271
|
|
|
272
272
|
## Session Context Retrieval
|
|
273
|
-
When dispatched by Zeus, call context_get(slug, "latest", session_id=SESSION_ID)
|
|
273
|
+
When dispatched by Zeus, call context_get(slug=slug, key="latest", session_id=SESSION_ID)
|
|
274
274
|
if session_slug is provided in dispatch metadata. Apply any "remaining_tasks",
|
|
275
275
|
"current_phase", or "gotchas" from the retrieved context.
|
|
276
276
|
If context_get returns None, proceed fresh (first phase or expired session).
|
package/src/agents/iris.md
CHANGED
package/src/agents/nyx.md
CHANGED
package/src/agents/themis.md
CHANGED
package/src/agents/zeus.md
CHANGED
|
@@ -5,6 +5,7 @@ reasoning_effort: medium
|
|
|
5
5
|
permission:
|
|
6
6
|
read: allow
|
|
7
7
|
edit: deny
|
|
8
|
+
hashline_edit: deny
|
|
8
9
|
bash: deny
|
|
9
10
|
task:
|
|
10
11
|
"*": allow
|
|
@@ -32,7 +33,7 @@ skills:
|
|
|
32
33
|
## Memory Protocol
|
|
33
34
|
|
|
34
35
|
**Auto-Store:** Ao receber subtask_summary, chame `memory_store()` com summary/files_changed/tests/status. Sempre.
|
|
35
|
-
**Pre-work:** `memory_search("<feature>", top_k=3)` antes de planejar qualquer coisa.
|
|
36
|
+
**Pre-work:** `memory_search(query="<feature>", top_k=3)` antes de planejar qualquer coisa.
|
|
36
37
|
|
|
37
38
|
## Golden Rule
|
|
38
39
|
|
|
@@ -79,7 +80,7 @@ O fluxo é SEMPRE: **Planejar → Especificar → Delegar → Revisar**. Zeus nu
|
|
|
79
80
|
Antes de usar a arvore de roteamento, consulte o memory:
|
|
80
81
|
|
|
81
82
|
```
|
|
82
|
-
memory_search(task_prompt, top_k=2)
|
|
83
|
+
memory_search(query=task_prompt, top_k=2)
|
|
83
84
|
→ score > 0.85?
|
|
84
85
|
SIM → usa resultado cacheado (agent, background, pattern)
|
|
85
86
|
NAO → aplica arvore de roteamento + memory_store() pra proxima vez
|
|
@@ -90,8 +91,8 @@ memory_search(task_prompt, top_k=2)
|
|
|
90
91
|
Para padroes de delegacao recorrentes, grave no KV:
|
|
91
92
|
|
|
92
93
|
```
|
|
93
|
-
kv_store("delegation:<pattern>", "{agent: ..., background: true/false}")
|
|
94
|
-
kv_get("delegation:<pattern>") → reusa decisao sem memory_search
|
|
94
|
+
kv_store(namespace="deleg", key="delegation:<pattern>", value="{agent: ..., background: true/false}")
|
|
95
|
+
kv_get(namespace="deleg", key="delegation:<pattern>") → reusa decisao sem memory_search
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
### Telemetria de delegacao (Nyx P1-3)
|
|
@@ -186,12 +187,12 @@ Wave announcement obrigatorio.
|
|
|
186
187
|
Limite maximo de 2 niveis de nesting: Zeus -> subagente -> sub-subagente.
|
|
187
188
|
|
|
188
189
|
```
|
|
189
|
-
depth = kv_get("deleg:depth") ?? 0
|
|
190
|
+
depth = kv_get(namespace="deleg", key="deleg:depth") ?? 0
|
|
190
191
|
if depth >= 2 → NAO delegar, ESCALAR para o usuario
|
|
191
|
-
else → kv_store("deleg:depth", depth + 1)
|
|
192
|
+
else → kv_store(namespace="deleg", key="deleg:depth", value=String(depth + 1))
|
|
192
193
|
|
|
193
194
|
Quando subagente retornar:
|
|
194
|
-
kv_store("deleg:depth", max(0, depth - 1))
|
|
195
|
+
kv_store(namespace="deleg", key="deleg:depth", value=String(max(0, depth - 1)))
|
|
195
196
|
```
|
|
196
197
|
|
|
197
198
|
Zeus (nivel 0) -> Apollo/Hermes (nivel 1) -> sub-subagente (nivel 2 max).
|
|
@@ -220,7 +221,7 @@ Quando um subagente RECUSA ou retorna "escopo fora do meu dominio" (ex: edit: de
|
|
|
220
221
|
|
|
221
222
|
- **Causa provavel:** agente errado selecionado no roteamento (match por palavra-chave, nao por capacidade).
|
|
222
223
|
- **ACAO:** RE-ROTEAR imediatamente para o agente correto — NAO retry com prompt reformulado.
|
|
223
|
-
- Registre o caso no cache de delegacao (`kv_store("deleg:<pattern>",
|
|
224
|
+
- Registre o caso no cache de delegacao (`kv_store(namespace="deleg", key="deleg:<pattern>", value=...)` para aprendizado futuro).
|
|
224
225
|
- Recusa legitima do agente = comportamento correto do guard; o defeito esta na selecao (zeus), nao no agente.
|
|
225
226
|
|
|
226
227
|
## TODO Enforcer (Auto-Retry)
|
|
@@ -237,7 +238,7 @@ Apos task_status(wait=true), verifique:
|
|
|
237
238
|
"reviewer-only", "fora do meu dominio", ou similar negativa de dominio)?
|
|
238
239
|
SIM → NAO retry com prompt rephrased.
|
|
239
240
|
RE-ROTEAR para o agente correto (ver "Taxonomia de Recusa (P0-3)").
|
|
240
|
-
Registre o caso: kv_store("deleg:<pattern>", "{agente_correto, ...}").
|
|
241
|
+
Registre o caso: kv_store(namespace="deleg", key="deleg:<pattern>", value="{agente_correto, ...}").
|
|
241
242
|
|
|
242
243
|
FALHA REAL (timeout, crash, resposta vazia, context exceeded)?
|
|
243
244
|
SIM → retry 1x com prompt rephrased (diferente, mais especifico)
|
|
@@ -54,7 +54,7 @@ Each agent file defines overrides in its `## 🧠 Memory Protocol` section:
|
|
|
54
54
|
|
|
55
55
|
Para otimizar decisoes de delegacao e reduzir gasto de tokens:
|
|
56
56
|
|
|
57
|
-
1. **memory_search(task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
|
|
57
|
+
1. **memory_search(query=task_prompt, top_k=2)** antes de aplicar a arvore de roteamento
|
|
58
58
|
2. Se score > 0.85 → reutiliza agente + background_mode do cache
|
|
59
59
|
3. Se score ≤ 0.85 → aplica regras estaticas e memory_store() com:
|
|
60
60
|
- key: deleg:<task_type>
|
|
@@ -67,8 +67,8 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
|
|
|
67
67
|
as a string (got object). json.dumps it first. Example: metadata='{"type":
|
|
68
68
|
"decision", "score": 0.9}'`.
|
|
69
69
|
|
|
70
|
-
4. **kv_store("deleg:<pattern>",
|
|
71
|
-
5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
|
|
70
|
+
4. **kv_store(namespace="deleg", key="deleg:<pattern>", value=...)** para padroes recorrentes de delegacao
|
|
71
|
+
5. **kv_get(namespace="deleg", key="deleg:<pattern>")** para reusar decisoes ja tomadas
|
|
72
72
|
|
|
73
73
|
Isso elimina ~300 tokens de reasoning por delegacao quando o cache acerta.
|
|
74
74
|
|
|
@@ -82,7 +82,7 @@ After every `/pantheon` council synthesis completes, Zeus stores:
|
|
|
82
82
|
memory_store({
|
|
83
83
|
namespace: "council_decisions",
|
|
84
84
|
key: "council:<yyyy-mm-dd>:<slug>",
|
|
85
|
-
value: {
|
|
85
|
+
value: JSON.stringify({
|
|
86
86
|
question: "original question",
|
|
87
87
|
specialists: ["@agent1", "@agent2"],
|
|
88
88
|
recommendation: "final recommendation",
|
|
@@ -93,7 +93,7 @@ memory_store({
|
|
|
93
93
|
themis_audit: "approved|issues",
|
|
94
94
|
precedent_used: false,
|
|
95
95
|
timestamp: "<ISO-8601>"
|
|
96
|
-
},
|
|
96
|
+
}),
|
|
97
97
|
metadata: JSON.stringify({
|
|
98
98
|
type: "council_decision",
|
|
99
99
|
specialist_count: N,
|
|
@@ -111,7 +111,7 @@ encoded as a string (got object). json.dumps it first. Example: metadata='{"type
|
|
|
111
111
|
### Read Path (Precedent Fast-Path)
|
|
112
112
|
Before dispatching a new council, Zeus runs:
|
|
113
113
|
```
|
|
114
|
-
memory_search(question, top_k=2, namespace="council_decisions")
|
|
114
|
+
memory_search(query=question, top_k=2, namespace="council_decisions")
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
Result interpretation:
|
|
@@ -56,16 +56,16 @@ All session state lives in **pantheon-persistence** (namespace `checkpoint:<slug
|
|
|
56
56
|
No file I/O, no checkpoint_session.py — TTL (4h) handles cleanup automatically.
|
|
57
57
|
|
|
58
58
|
### Heartbeat Check
|
|
59
|
-
- If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
|
|
59
|
+
- If `context_get(slug=slug, key="heartbeat", session_id=SESSION_ID)` returns a checkin older than 300s, log a stall warning and resume
|
|
60
60
|
- Write heartbeat after every anti-stall recovery action:
|
|
61
61
|
```
|
|
62
|
-
context_save(slug, "heartbeat",
|
|
62
|
+
context_save(slug=slug, key="heartbeat", content=JSON.stringify({"status": "alive", "last_action": "...", "turn_count": N}), session_id=SESSION_ID)
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### Checkpoint Auto-Save (Pré-Compactação)
|
|
66
66
|
Before ANY delegate dispatch, save a checkpoint:
|
|
67
67
|
```
|
|
68
|
-
context_save(slug, "phase:N",
|
|
68
|
+
context_save(slug=slug, key="phase:N", content=JSON.stringify({
|
|
69
69
|
"phase": {"current": N, "total": M, "name": "..."},
|
|
70
70
|
"turn_count": N, "agent": "...", "summary": "..."
|
|
71
71
|
}), session_id=SESSION_ID)
|
|
@@ -86,15 +86,15 @@ o Zeus DEVE salvar o estado atual:
|
|
|
86
86
|
2. Salve heartbeat + phase atual + tarefas pendentes
|
|
87
87
|
3. Só então permita que a compactação prossiga
|
|
88
88
|
```
|
|
89
|
-
# Ao iniciar sessão:
|
|
90
|
-
result = context_save(slug, "init", session_state)
|
|
89
|
+
# Ao iniciar sessão (session_id é REQUIRED — use o id da sessão corrente):
|
|
90
|
+
result = context_save(slug=slug, key="init", content=JSON.stringify(session_state), session_id=SESSION_ID)
|
|
91
91
|
SESSION_ID = result.session_id # ← guarde para toda a sessão
|
|
92
92
|
|
|
93
93
|
# Antes de CADA delegação:
|
|
94
|
-
context_save(slug, f"pre:{agent}", current_state, session_id=SESSION_ID)
|
|
94
|
+
context_save(slug=slug, key=f"pre:{agent}", content=JSON.stringify(current_state), session_id=SESSION_ID)
|
|
95
95
|
|
|
96
96
|
# Após retorno do agente:
|
|
97
|
-
context_save(slug, f"post:{agent}", result_state, session_id=SESSION_ID)
|
|
97
|
+
context_save(slug=slug, key=f"post:{agent}", content=JSON.stringify(result_state), session_id=SESSION_ID)
|
|
98
98
|
```
|
|
99
99
|
Isso garante que o estado sobreviva à compactação — o "latest" pointer
|
|
100
100
|
sempre aponta para o checkpoint mais recente, mesmo após compactação.
|
|
@@ -103,9 +103,9 @@ sempre aponta para o checkpoint mais recente, mesmo após compactação.
|
|
|
103
103
|
### Context Retrieval
|
|
104
104
|
Next-phase agents retrieve previous context via:
|
|
105
105
|
```
|
|
106
|
-
context_get(slug,
|
|
107
|
-
context_get(slug, "phase:3")
|
|
108
|
-
context_list(slug)
|
|
106
|
+
context_get(slug=slug, session_id=SESSION_ID) # most recent checkpoint ("latest" is the default key)
|
|
107
|
+
context_get(slug=slug, key="phase:3", session_id=SESSION_ID) # specific phase
|
|
108
|
+
context_list(slug=slug, session_id=SESSION_ID) # all checkpoints
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
### Long-Session Progress
|
|
@@ -36,7 +36,7 @@ sequenceDiagram
|
|
|
36
36
|
participant T as Themis (Audit + Moderator)
|
|
37
37
|
|
|
38
38
|
U->>Z: /pantheon [--research] question
|
|
39
|
-
Z->>M: memory_search(council_decisions)
|
|
39
|
+
Z->>M: memory_search(query, namespace="council_decisions")
|
|
40
40
|
alt Precedent found (score > 0.85)
|
|
41
41
|
M-->>Z: ⚠️ Cached decision
|
|
42
42
|
Z->>U: Fast-path precedent
|
|
@@ -85,7 +85,7 @@ sequenceDiagram
|
|
|
85
85
|
## Dispatch Sequence (9-Step Protocol)
|
|
86
86
|
|
|
87
87
|
### Step 0 — Precedent Fast-Path (Fase 1)
|
|
88
|
-
Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
|
|
88
|
+
Run the precedent read path defined in `## Memory Protocol > Council Decisions Namespace` (`memory_search(query=question, top_k=2, namespace="council_decisions")`). If no precedent applies, proceed to Step 0b.
|
|
89
89
|
|
|
90
90
|
### Step 0b — Apollo Pre-Scan (Fase 2, --research flag)
|
|
91
91
|
If `/pantheon --research <question>`: dispatch @apollo with 30s timeout. Inject findings as `shared_context` into ALL specialist prompts. Skip if flag absent.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pantheon-tui",
|
|
9
|
-
"version": "1.6.0-beta.
|
|
9
|
+
"version": "1.6.0-beta.6",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@opencode-ai/plugin": "1.18.33",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.6",
|
|
5
5
|
"description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
@@ -163,19 +163,19 @@ Should I now run the migration tests? [waiting]
|
|
|
163
163
|
Session state is **not** file-based. It lives in **pantheon-persistence** under the
|
|
164
164
|
`checkpoint:<slug>` namespace and is cleaned up automatically by a 4h TTL.
|
|
165
165
|
|
|
166
|
-
Write state with `context_save(slug, key,
|
|
167
|
-
`context_get(slug, "latest", session_id=...)`. Capture `session_id` from the first `context_save`
|
|
166
|
+
Write state with `context_save(slug=slug, key=key, content=JSON.stringify(state), session_id=...)` and read it back with
|
|
167
|
+
`context_get(slug=slug, key="latest", session_id=...)`. Capture `session_id` from the first `context_save`
|
|
168
168
|
of the session and reuse it for the rest of the session, so the `latest` pointer survives
|
|
169
169
|
compaction.
|
|
170
170
|
|
|
171
171
|
### Heartbeat Check
|
|
172
|
-
- If `context_get(slug, "heartbeat")` returns a checkin older than 300s, log a stall warning and resume
|
|
172
|
+
- If `context_get(slug=slug, key="heartbeat", session_id=...)` returns a checkin older than 300s, log a stall warning and resume
|
|
173
173
|
- Write a heartbeat after every anti-stall recovery action
|
|
174
174
|
|
|
175
175
|
### Checkpoint Rules
|
|
176
176
|
1. Save a checkpoint before **every** delegate dispatch and after every agent returns
|
|
177
177
|
2. Include current phase, turn count, and remaining tasks
|
|
178
|
-
3. On resume: `context_get(slug, "latest")` restores the most recent checkpoint
|
|
178
|
+
3. On resume: `context_get(slug=slug, key="latest", session_id=...)` restores the most recent checkpoint
|
|
179
179
|
|
|
180
180
|
> Do not create `heartbeat.json`, `checkpoint-<N>.json`, or `session.json` under
|
|
181
181
|
> `.pantheon/deepwork/`. That file-based mechanism is retired — TTL handles cleanup, and reading
|