# Architecture Report — output of architecture-mapper Task being planned: Add a per-wave concurrency cap to trekexecute headless launches. ## Summary Voyage is a contract-driven Claude Code plugin (Node ESM, zero runtime deps). The pipeline is six commands (`commands/*.md`) backed by a `lib/` of pure, unit-tested validators/parsers and a thin `scripts/` layer of measurement and codegen harnesses. trekexecute is the disciplined plan/session-spec executor; its headless path (Phase 2.6) fans out parallel "waves" of `claude -p` subprocesses driven by a Bash launcher template. ## Tech stack | Layer | Choice | Evidence | |-------|--------|----------| | Language | JavaScript (ESM, `.mjs`) | `lib/**/*.mjs`, `"type":"module"` in package.json | | Tests | `node:test` + `node:assert/strict` | every `tests/**/*.test.mjs` | | Validation | hand-rolled validators returning `{valid,errors,warnings}` | `lib/util/result.mjs` | | Orchestration substrate | command prose + Bash + Agent/Task tool | `commands/trekexecute.md` | | Headless launch | Bash here-doc template, backgrounded subprocesses | `templates/headless-launch-template.md` | ## Key patterns - **3-layer module pattern** — Content validator → raw-text wrapper → CLI shim (`if (import.meta.url === \`file://${process.argv[1]}\`)`), repeated across `lib/validators`, `lib/parsers`, `lib/review`. Any new lib must follow it. - **Structured Result type** — `issue(code,message,hint,location)` + `fail()` / `ok()` from `lib/util/result.mjs`. Stable error codes are the contract. - **Prose-as-orchestrator** — commands carry the control flow in markdown; the harness executes it. Schema-drift defenses are *inlined* into command prose so they survive even when agent docs are not implicitly loaded. ## Anti-patterns / debt near the task - The headless launcher backgrounds **all** wave members at once with no upper bound on concurrent `claude -p` processes; concurrency is implicit in how many steps a wave contains. No central place caps it. - Wave composition (which steps go in which wave) is computed by session-decomposer, but the *launch* fan-out is template Bash, so a cap would straddle a JS (decomposer) / Bash (launcher) boundary. ## Module map (task-relevant) ``` commands/trekexecute.md # Phase 2.6 parallel-wave orchestration prose templates/headless-launch-template.md # the Bash fan-out site lib/util/result.mjs # error shape any new guard returns agents/session-decomposer.md # produces the wave/dependency graph ``` ## Boundaries The cap is a launch-time concern (Bash template + the Phase 2.6 prose that generates it). It does not belong in the pure `lib/` validators unless we add a small "max parallelism" resolver that the prose reads. Recommend a lib resolver (testable) + a template wire-in (the actual `xargs -P` / job-slot mechanism). ## How Phase 2.6 assembles a wave (detail) The executor reads the plan's `## Step N` blocks and the dependency edges session-decomposer emitted (`depends_on:` frontmatter). Steps with no unmet dependency at the current frontier form a wave. For each wave the prose: 1. builds a `SHARED_CONTEXT_FILE` (brief + plan + relevant exploration digest) passed to every member via `--append-system-prompt-file` (cache-prefix material — see q3 experiment); 2. emits one `claude -p … &` invocation per member from the here-doc, each with `--max-turns`, `--max-budget-usd`, `GIT_OPTIONAL_LOCKS=0`, and the GH#36071 push-before-cleanup workaround; 3. collects the backgrounded PIDs and `wait`s for the batch to drain before advancing the frontier. The cap belongs strictly between (2) and (3): bound how many of the emitted members run concurrently, leaving wave *composition* (1) untouched. ## Layering verdict Three layers, in increasing blast radius: (a) a pure resolver in `lib/` (arithmetic only — trivially testable, follows the 3-layer module pattern); (b) Phase 2.6 prose passing the resolved integer + a `--max-parallel` flag into the template; (c) the template's fan-out mechanism. Keep (a) the single source of the number; (c) should only consume it. This matches every prior launcher hardening, which added one bounded externality (budget, locks, turns) at a time without reshaping wave composition. ## Cross-cutting observations - The launcher is the most operationally sensitive file in the repo (it spends money and mutates git). Every edit here is co-reviewed with its doc-consistency needle list — treat the needle test as part of the contract, not an afterthought. - Nothing in `lib/` currently imports anything launcher-related; the resolver will be a leaf module. Good — it can be tested and shipped independently of the template wire-in, enabling a TDD-first slice.