docs(voyage): S33 — reconcile agent inventory (21 spawnable + 3 reference docs) + record D1–D3 considered-and-kept

Balance-backlog S33 (DOC, non-breaking). The D1–D3 forks resolved conservatively
(balance-backlog-plan.md), collapsing the model + observability work into a
documentation record. Three deliverables, all doc-only, no code/frontmatter change:

- V35 (doc half): the "24 agents" headline is reconciled to its honest split —
  21 spawnable (one dormant: synthesis-agent, Δ≈0) + 3 orchestrator reference
  docs (planning-/research-/review-orchestrator document the inline /trek*
  workflow; not spawned). Each orchestrator header now declares itself a
  "reference document, not a spawnable capability". README + CLAUDE.md state the
  split; counts in the new pins are DERIVED from agents/ so they survive reword.
- D2 (V32 rationale): docs/observability.md gains a "Why direct export rather
  than a native collector" section — direct export keeps the path / SSRF /
  field-allowlist guards in audited in-process code (the S21 hardening) instead
  of re-hosting a collector; textfile mode remains the collector escape hatch.
- D3 (kept-opus): docs/voyage-vs-cc-balance-analysis.md §10 decision record —
  opus on V09 (glue), V35 (dormant), V11 (retrieval), V16 (mechanical), V08
  (researchers) was reconsidered for sonnet and KEPT (pin 40d8742 firm).

No agent frontmatter changed — tests/lib/agent-frontmatter.test.mjs is the
structural model source-of-truth and is untouched (diff is description-only:
model: opus + tools lists unchanged, no Agent tool granted). No Handover-1
change; no exporter/gemini-bridge removal. Non-breaking, no version bump.

tests/lib/doc-consistency.test.mjs: +5 S33 pins (inventory split derived from
agents/; synthesis-agent dormant; orchestrator relabel; observability D2
rationale; analysis-doc D1–D3 record). Tests 739 (737 pass / 2 skip / 0 fail),
bar `node --test`; `claude plugin validate` green (1 accepted warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
Kjell Tore Guttormsen 2026-06-20 09:49:11 +02:00
commit 2df0cbb372
8 changed files with 160 additions and 6 deletions

View file

@ -53,6 +53,8 @@ Full flag reference for each command (modes, `--gates`, `--profile`, breaking ch
| contrarian-researcher | opus | Counter-evidence, overlooked alternatives |
| gemini-bridge | opus | Gemini Deep Research second opinion (conditional) |
> **Inventory (S33 reconcile).** 24 agent files = **21 spawnable** (one of which, `synthesis-agent`, ships **dormant** — Δ≈0, wired to nothing, see `docs/T1-synthesis-poc-results.md`) **+ 3 orchestrator reference docs** (`planning-/research-/review-orchestrator` document the inline `/trek*` workflow; they are reference documentation, not spawnable capabilities). All 24 stay `model: opus` (operator pin `40d8742`); the glue/mechanical/retrieval/dormant roles (V09 gemini-bridge, V16 session-decomposer, V11 retrieval agents, V08 researchers, V35 synthesis) were reconsidered for a sonnet downgrade and **kept opus** — document-only, no frontmatter change (the decision record lives in `docs/voyage-vs-cc-balance-analysis.md` §10).
> **Model & effort (CC 2.1.154+).** `opus` resolves to **Opus 4.8** (default reasoning effort `high`); `sonnet` to Sonnet 4.6. Select agents carry native `effort:` — retrieval agents (`task-finder`, `git-historian`, `dependency-tracer`, `architecture-mapper`) at `medium`, adversarial-reasoning agents (`plan-critic`, `risk-assessor`, `contrarian-researcher`, `review-coordinator`) at `high`. This native per-spawn **reasoning** effort is a different axis from brief `phase_signals.effort` (orchestration shape — which agents/passes run). See `docs/profiles.md` §Model & effort axes.
## Reference docs (read on demand)

View file

@ -802,6 +802,7 @@ Top-level layout:
```
trekplan/
├── agents/ 24 specialized agents (all `model: opus`-pinned; per-phase model set by `--profile`)
│ └ 21 spawnable (1 dormant: synthesis-agent, Δ≈0) + 3 orchestrator reference docs (not spawned)
├── commands/ 6 slash commands (trekbrief, trekresearch, trekplan, trekexecute, trekreview, trekcontinue) + trekendsession helper
├── templates/ Frontmatter templates for brief, research, plan, session, launch
├── hooks/ 7 hooks (pre-bash, pre-write, session-title, post-bash-stats, pre-compact-flush, post-compact-flush, otel-export)

View file

@ -2,8 +2,10 @@
name: planning-orchestrator
description: |
Inline reference (v2.4.0) — documents the planning workflow that
/trekplan executes in main context. Historically not spawned as a
sub-agent: before Claude Code 2.1.172 the harness did not expose the
/trekplan executes in main context.
This file is a reference document, not a spawnable capability.
Historically not spawned as a sub-agent: before Claude Code 2.1.172
the harness did not expose the
Agent tool to sub-agents, so a background orchestrator could not spawn
the exploration swarm (architecture-mapper, task-finder, plan-critic,
etc.). As of CC 2.1.172 sub-agents can spawn sub-agents (up to 5 levels

View file

@ -2,8 +2,10 @@
name: research-orchestrator
description: |
Inline reference (v2.4.0) — documents the research workflow that
/trekresearch executes in main context. Historically not spawned as
a sub-agent: before Claude Code 2.1.172 the harness did not expose the
/trekresearch executes in main context.
This file is a reference document, not a spawnable capability.
Historically not spawned as a sub-agent: before Claude Code 2.1.172
the harness did not expose the
Agent tool to sub-agents, so a background orchestrator could not spawn
the research swarm. As of CC 2.1.172 sub-agents can spawn sub-agents
(up to 5 levels deep), so a delegated-orchestration redesign is under

View file

@ -2,8 +2,10 @@
name: review-orchestrator
description: |
Inline reference (v3.2.0) — documents the review workflow that
/trekreview executes in main context. Historically not spawned
as a sub-agent: before Claude Code 2.1.172 the harness did not expose
/trekreview executes in main context.
This file is a reference document, not a spawnable capability.
Historically not spawned as a sub-agent: before Claude Code 2.1.172
the harness did not expose
the Agent tool to sub-agents, so a background orchestrator could not
spawn the reviewer swarm (brief-conformance-reviewer,
code-correctness-reviewer, review-coordinator). As of CC 2.1.172

View file

@ -114,6 +114,40 @@ The exporter is hardened against three CWE classes:
| `prom/node-exporter` | `1.10.2` | textfile collector path normalization |
| `grafana/grafana` | `11.4.0` | datasource provisioning hardening |
## Why direct export rather than a native collector
A balance review (`docs/voyage-vs-cc-balance-analysis.md` §4, V32) asked
whether the custom exporters should be dropped in favour of pointing the
standard `OTEL_*` environment variables at a co-located OTLP collector,
letting that collector own egress and field selection. The operator
decision (D2, 2026-06-20) is to **keep direct export**. The rationale is
the security boundary, not a preference for re-hosting a collector:
- **The three guards run in-process, before any byte leaves Voyage.**
`path-validator.mjs` (CWE-22), `endpoint-validator.mjs` (CWE-918 / SSRF),
and `field-allowlist.mjs` (CWE-212) are applied inside `otel-export.mjs`
and covered by `tests/hooks/otel-export-validators.test.mjs`. The
records carry operator-private data (paths, prompts, brief content);
the allowlist drops everything not explicitly named before export.
- **A native-collector design moves that boundary out of audited code.**
Handing raw JSONL to a sidecar collector means either re-expressing the
field allowlist in collector config (a second source of truth that can
drift) or shipping un-allowlisted private fields and trusting the
collector's egress rules. The S21 SSRF hardening — 169.254.169.254
permanently blocked, loopback/RFC1918 gated behind
`VOYAGE_OTEL_ALLOW_PRIVATE` — is a property of `endpoint-validator.mjs`
and would have to be re-created in collector configuration to be
preserved.
- **The collector path is still available, by design.** Operators who
want collector semantics (retry, persistence, relabelling) use
`textfile` mode and scrape `voyage.prom` with node-exporter / vector /
otel-collector. Direct export is the minimal default, not a rejection
of collectors — it keeps the data-sanitization boundary in Voyage's
own validated code for the common case.
This is a deliberate direct-export-over-collector choice; the custom
exporters and their guards are kept, not pruned.
## Limitations
- **Stop-hook is normal-exit only.** If Claude Code crashes or is killed

View file

@ -194,3 +194,34 @@ All items are **operator-gated** (scope-guard: analysis only). Tagged **[non-bre
## 9. Meta-note (dogfooding the Workflow tool)
Running this analysis *on* the CC Workflow tool produced first-hand evidence for the audit's own V27/V29 rows: the `pipeline()` shape gave clean coverage (35/35, no row dropped), schema-forced output eliminated JSON-parse fragility (the CC-27 F2 win, reconfirmed), and the conditional adversarial stage worked. Costs also reconfirmed: ~2.97M subagent tokens / 96 agents / 4.6 min for a 35×3 fan-out — Workflow is a good substrate for *bounded, verifiable, parallel* fan-out, exactly the ~20% core CC-27 identified, and a poor fit for the ~80% judgment-heavy glue (scout + synthesis, which ran inline in main context here). This is dogfooding of the **Workflow tool**, not of Voyage's pipeline (the latter was declined by the operator).
---
## 10. Decision record — resolved forks (operator, 2026-06-20)
§4/§6 deferred three forks to the operator. All three resolved to the
**conservative** option (preserve the capability / the security code / the
deliberate pin). The model and observability work therefore collapses from
code-deletion to a documentation record; the only real code changes in the
backlog are V15 (export trim, S31) and V30 (economy calibration, S34). These
decisions are now the implemented baseline of the multi-session backlog plan
(`docs/balance-backlog-plan.md`), shipped across S31S34.
| Fork | Item | Resolution | What changed |
|------|------|------------|--------------|
| **D1** | V09 `gemini-bridge` | **Keep it as an agent.** | No removal / inline-rewiring. The capability count stays 24; its model is governed by D3 (stays opus). |
| **D2** | V32 observability export | **Keep `lib/exporters/*` + `otel-export.mjs`; document the direct-export rationale.** | Doc-only — no deletion of S21 security code. The rationale (preserve the in-process path / SSRF / field-allowlist guards rather than re-host a collector) is recorded in `docs/observability.md` §"Why direct export rather than a native collector". |
| **D3** | 24-agent `model: opus` pin (`40d8742`) | **Keep the pin firm.** | Document-only. opus on V09 (glue — does not reason), V35 (dormant `synthesis-agent`), V11 (retrieval agents, already `effort:medium`), V16 (`session-decomposer`, mechanical parsing), and V08 (researchers) was reconsidered for a sonnet downgrade and **kept opus**. **No agent frontmatter is changed**`tests/lib/agent-frontmatter.test.mjs` remains the structural source-of-truth and is untouched. |
**Inventory framing (V35 doc half, reconciled).** The "24 agents" headline is
**21 spawnable + 3 orchestrator reference docs**. The three orchestrators
(`planning-/research-/review-orchestrator`) document the inline `/trek*`
workflow and are not spawned as sub-agents. Of the 21 spawnable, one —
`synthesis-agent` — ships **dormant** (Δ≈0, wired to nothing;
`docs/T1-synthesis-poc-results.md`). Reconciled across `README.md`,
`CLAUDE.md`, and this doc; pinned in `tests/lib/doc-consistency.test.mjs` (S33).
**Still explicitly NOT done** (out of scope, per the plan): no `gemini-bridge`
removal (D1), no exporter deletion (D2), no model downgrade (D3), no Handover-1
change, no Workflow substrate swap (CC-27 declined), no delegated-orchestration
redesign (CC-26 lean-NO), no reopening of the premium-default / framing-gate pins.

View file

@ -1100,3 +1100,83 @@ test('S32: trekbrief Phase 3 delegates Q&A turn-taking to AskUserQuestion (V01
'question to ask (policy); AskUserQuestion does the asking (engine). No hand-rolled menu loop.',
);
});
// ── S33 — documentation consolidation: record the considered-and-kept decisions ──
//
// The D1D3 forks (balance-backlog-plan.md) resolved conservatively, collapsing
// the model + observability work into a documentation record. These pins make
// the three deliverables enforced doc-truth:
// • V35 (doc half) — the "24 agents" framing is reconciled to its honest split
// (21 spawnable + 3 reference orchestrators, of which synthesis-agent is the
// one dormant spawnable). Counts are DERIVED from agents/ so they survive
// reword and track new files.
// • D2 (V32 rationale) — observability.md documents why direct export rather
// than a native collector (preserves the S21 in-process guards).
// • D3 (kept-opus) — the analysis doc carries a decision record citing the pin
// commit; the model frontmatter is NOT changed (agent-frontmatter.test.mjs
// stays the structural source-of-truth and is untouched by S33).
// Fix the SOURCE a pin guards, not the test.
test('S33: README + CLAUDE.md reconcile the agent inventory (spawnable + reference orchestrators = file count)', () => {
const agentFiles = listMd('agents');
const total = agentFiles.length;
const orchestrators = agentFiles.filter((f) => /-orchestrator\.md$/.test(f));
const refCount = orchestrators.length;
const spawnable = total - refCount;
assert.equal(refCount, 3, `expected exactly 3 *-orchestrator reference docs, got ${refCount}`);
for (const [name, body] of [['README.md', read('README.md')], ['CLAUDE.md', read('CLAUDE.md')]]) {
assert.ok(
body.includes(`${spawnable} spawnable`),
`${name} must state "${spawnable} spawnable" agents (derived: ${total} files ${refCount} reference orchestrators)`,
);
assert.ok(
body.includes(`${refCount} orchestrator reference doc`),
`${name} must label the ${refCount} orchestrators as reference docs (not spawnable capabilities)`,
);
}
});
test('S33: the inventory framing names synthesis-agent as the one dormant spawnable', () => {
// 1 of the spawnable agents ships dormant (Δ≈0, wired to nothing). The framing
// must say so, so the headline count is honest about live vs. dormant capability.
for (const [name, body] of [['README.md', read('README.md')], ['CLAUDE.md', read('CLAUDE.md')]]) {
assert.ok(
/synthesis-agent/.test(body) && /dormant/i.test(body),
`${name} inventory framing must name synthesis-agent as the dormant spawnable`,
);
}
});
test('S33: the 3 orchestrator files declare themselves reference docs, not spawnable capabilities (V35 relabel)', () => {
for (const f of listMd('agents').filter((x) => /-orchestrator\.md$/.test(x))) {
assert.ok(
/reference document, not a spawnable capability/i.test(read(`agents/${f}`)),
`agents/${f} must declare "reference document, not a spawnable capability" (V35 doc-half relabel)`,
);
}
});
test('S33: docs/observability.md documents the direct-export-vs-collector rationale (D2)', () => {
const t = read('docs/observability.md');
assert.ok(
/direct[- ]export/i.test(t) && /collector/i.test(t),
'observability.md must document why Voyage exports directly rather than via a native collector (D2)',
);
// The rationale must anchor on the in-process security boundary it preserves
// (the S21 hardening), not read as an unjustified re-host.
assert.ok(
/allowlist/i.test(t) && /(SSRF|CWE-918|CWE-212)/.test(t),
'the direct-export rationale must cite the in-process guards it preserves (field allowlist + SSRF/path)',
);
});
test('S33: the analysis doc records the D1D3 resolved forks (considered-and-kept, pin firm)', () => {
const t = read('docs/voyage-vs-cc-balance-analysis.md');
assert.ok(/Decision record/i.test(t), 'analysis doc must carry a "Decision record" addendum for the resolved forks');
for (const d of ['D1', 'D2', 'D3']) {
assert.ok(new RegExp(`\\b${d}\\b`).test(t), `analysis-doc decision record must name fork ${d}`);
}
// D3 kept the model pin firm — the record must cite the pin commit so a future
// reader sees the downgrade was reconsidered-and-declined, not overlooked.
assert.ok(/40d8742/.test(t), 'decision record must cite the model-pin commit 40d8742 (D3 kept firm, document-only)');
});