diff --git a/README.md b/README.md index c90b119..2ad066f 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Output: `.claude/projects/{YYYY-MM-DD}-{slug}/brief.md` |------|-------|----------| | **Default** | `/trekbrief ` | Dynamic interview until quality gates pass. No question cap. | | **Quick** | `/trekbrief --quick ` | Starts compact (optional sections get at most one probe), still escalates on weak required sections or failed review gate. | -| **Profile** | `/trekbrief --profile ` | (v4.1.0) Pin model profile for the brief phase: `economy` / `balanced` / `premium` / ``. See [Profile system](#profile-system-v410) below. | +| **Profile** | `/trekbrief --profile ` | (v4.1.0) Pin model profile for the brief phase: `economy` / `balanced` / `premium` / `fable` / ``. See [Profile system](#profile-system-v410) below. | `/trekbrief` is **always interactive**. There is no foreground/background mode — the interview requires user input. @@ -779,13 +779,14 @@ An optional architect step between research and plan was previously available vi ## Profile system (v4.1.0) -Three built-in model profiles plus operator-defined `.yaml` (drop in `lib/profiles/`). Each profile pins `phase_models` for the six pipeline phases. The active profile is recorded in plan.md frontmatter as `profile: ` and emitted to JSONL stats for cost-attribution. +Four built-in model profiles plus operator-defined `.yaml` (drop in `lib/profiles/`). Each profile pins `phase_models` for the six pipeline phases. The active profile is recorded in plan.md frontmatter as `profile: ` and emitted to JSONL stats for cost-attribution. | Profile | Brief | Research | Plan | Execute | Review | Continue | Use case | |---------|-------|----------|------|---------|--------|----------|----------| | `economy` | sonnet | sonnet | sonnet | sonnet | sonnet | sonnet | ⚠ **Experimental** (uncalibrated Jaccard floor) — lowest cost; high-confidence small-scope tasks (opt-in via `--profile economy`) | | `balanced` | sonnet | sonnet | opus | sonnet | opus | sonnet | Mixed — opus where reasoning depth pays off (opt-in via `--profile balanced`) | | `premium` (default) | opus | opus | opus | opus | opus | opus | Maximum quality — Opus on every phase (default since the 2026-05-13 operator decision) | +| `fable` | fable | fable | fable | fable | fable | fable | Max quality — Fable 5 (Mythos-class, above Opus) on every phase (opt-in via `--profile fable`); reasoning effort inherits from the session | Lookup order: @@ -810,9 +811,9 @@ Default JSONL stats stream (`${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl`) is unchan ## Cost profile -The default `premium` profile runs **Opus on every phase** — the orchestrator (one per command), the exploration and review swarms (5–10 sub-agents per command, all `model: opus`-pinned in `agents/*.md`), and the executor (one per plan session). The model is **uniform per phase**: there is no "Opus orchestrates, Sonnet runs the swarms" split — a phase resolves to one model and both the orchestrator and its sub-agents use it. For cheaper runs, opt into `--profile balanced` (Sonnet on brief/research/execute/continue, Opus on plan + review) or `--profile economy` (Sonnet everywhere). Per-command cost is published in `${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl` if you want exact numbers. +The default `premium` profile runs **Opus on every phase** of the pipeline's agent work — the exploration and review swarms (5–10 sub-agents per command; spawn sites inject the composed brief > profile > frontmatter resolution, with `agents/*.md` `model: opus` pins as the fallback) and the executor (one per plan session). The command orchestrator itself is not profile-controlled: as of v5.9, command frontmatter omits `model:`, so the orchestrator follows the session model. For cheaper runs, opt into `--profile balanced` (Sonnet on brief/research/execute/continue, Opus on plan + review) or `--profile economy` (Sonnet everywhere); for maximum quality, `--profile fable` (Fable 5 on every phase). Per-command cost is published in `${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl` if you want exact numbers. -The `opus` alias resolves to **Opus 4.8** (default reasoning effort `high`) and `sonnet` to Sonnet 4.6. Note two distinct effort axes that share the word "effort": brief `phase_signals.effort` (low/standard/high) tunes *orchestration shape* — how many agents and passes run — while native `effort:` on selected agents (retrieval at `medium`, adversarial-reasoning at `high`) tunes the *per-spawn reasoning budget*. See [`docs/profiles.md`](docs/profiles.md) § Model & effort axes. +The `opus` alias resolves to **Opus 4.8** (default reasoning effort `high`), `sonnet` to Sonnet 4.6, and `fable` to **Fable 5** (Mythos-class, above Opus; default reasoning effort `high` — xhigh requires a session-level setting, see [`docs/profiles.md`](docs/profiles.md)). Note two distinct effort axes that share the word "effort": brief `phase_signals.effort` (low/standard/high) tunes *orchestration shape* — how many agents and passes run — while native `effort:` on selected agents (retrieval at `medium`, adversarial-reasoning at `high`) tunes the *per-spawn reasoning budget*. See [`docs/profiles.md`](docs/profiles.md) § Model & effort axes. For per-profile cost estimates, see [`docs/profiles.md`](docs/profiles.md). diff --git a/docs/operations.md b/docs/operations.md index 129cd7b..0ecfd41 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -28,13 +28,14 @@ A revived Path C (post-v2.2.xxx) would require: (1) re-architecting tool-list to ## Profile system (`--profile`, v4.1.0) -Three built-in model profiles plus operator-defined `.yaml`. Each profile pins `phase_models` for the six pipeline phases (`brief`, `research`, `plan`, `execute`, `review`, `continue`). Profile is recorded in plan.md frontmatter as `profile: ` and emitted to `${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl` for cost-attribution. +Four built-in model profiles plus operator-defined `.yaml`. Each profile pins `phase_models` for the six pipeline phases (`brief`, `research`, `plan`, `execute`, `review`, `continue`). Profile is recorded in plan.md frontmatter as `profile: ` and emitted to `${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl` for cost-attribution. | Profile | Brief | Research | Plan | Execute | Review | Continue | Use case | |---------|-------|----------|------|---------|--------|----------|----------| | `economy` | sonnet | sonnet | sonnet | sonnet | sonnet | sonnet | ⚠ **Experimental** (uncalibrated Jaccard floor) — lowest cost; high-confidence small-scope tasks (operator-opt-in via `--profile economy`) | | `balanced` | sonnet | sonnet | opus | sonnet | opus | sonnet | Mixed — opus where reasoning depth pays off (operator-opt-in via `--profile balanced`) | | `premium` (default) | opus | opus | opus | opus | opus | opus | Maximum quality — Opus on every phase. Default since 2026-05-13 operator request; also the hardcoded resolver default returned by `resolveProfile()` in `lib/profiles/resolver.mjs` | +| `fable` | fable | fable | fable | fable | fable | fable | Max quality — Fable 5 (Mythos-class, above Opus) on every phase (operator-opt-in via `--profile fable`); reasoning effort inherits from the session — see `docs/profiles.md` §Model & effort axes | ### Lookup order @@ -45,7 +46,7 @@ Three built-in model profiles plus operator-defined `.yaml`. Each profil ### Custom profiles -Create `voyage-profiles/.yaml` in the repo root (or `~/.claude/voyage-profiles/.yaml`) to define a **new** tier — the name must not be a built-in. The validator (`lib/validators/profile-validator.mjs`) enforces: every `phase_models[].phase` must be a known phase enum; every `phase_models[].model` must match `^(opus|sonnet)(\b|-).*` or one of the canonical short names. `findProfilePath` (`lib/profiles/resolver.mjs`) resolves **built-in first** (`lib/profiles/.yaml` for `economy`/`balanced`/`premium`), then repo-root `voyage-profiles/`, then `~/.claude/voyage-profiles/`. A custom file named after a built-in therefore **cannot** shadow it (custom profiles must use new names); for the same custom name, repo-root takes precedence over home. +Create `voyage-profiles/.yaml` in the repo root (or `~/.claude/voyage-profiles/.yaml`) to define a **new** tier — the name must not be a built-in. The validator (`lib/validators/profile-validator.mjs`) enforces: every `phase_models[].phase` must be a known phase enum; every `phase_models[].model` must exactly match an entry in `BASE_ALLOWED_MODELS` (`['sonnet', 'opus', 'fable']`; `haiku` only with `VOYAGE_ALLOW_HAIKU=1`). `findProfilePath` (`lib/profiles/resolver.mjs`) resolves **built-in first** (`lib/profiles/.yaml` for `economy`/`balanced`/`premium`/`fable`), then repo-root `voyage-profiles/`, then `~/.claude/voyage-profiles/`. A custom file named after a built-in therefore **cannot** shadow it (custom profiles must use new names); for the same custom name, repo-root takes precedence over home. Drift between plan-frontmatter `profile:` and step-manifest `profile_used:` emits a `MANIFEST_PROFILE_DRIFT` warning from `plan-validator --strict` (Step 20). Plan remains valid; the warning surfaces accidental tier-mismatch. diff --git a/docs/profiles.md b/docs/profiles.md index ea757f8..5966bf4 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -6,14 +6,15 @@ cost estimation (with disclaimer). ## Built-in profiles -Three pre-defined tiers ship with v4.1, located at -`lib/profiles/{economy,balanced,premium}.yaml`. +Four pre-defined tiers ship with the plugin (fable added in v5.9), located at +`lib/profiles/{economy,balanced,premium,fable}.yaml`. | Profile | Brief | Research | Plan | Execute | Review | Continue | Use case | |---------|-------|----------|------|---------|--------|----------|----------| | `economy` | sonnet | sonnet | sonnet | sonnet | sonnet | sonnet | ⚠ **Experimental** (uncalibrated Jaccard floor) — lowest cost; small-scope tasks where you have high confidence the brief is right | | `balanced` | sonnet | sonnet | opus | sonnet | opus | sonnet | Mixed — opus where reasoning depth pays off (plan synthesis + adversarial review); opt-in via `--profile balanced` | | `premium` (default) | opus | opus | opus | opus | opus | opus | Maximum quality — Opus on every phase + external research on (default since the 2026-05-13 operator decision) | +| `fable` | fable | fable | fable | fable | fable | fable | Max quality — Fable 5 (Mythos-class, above Opus) on every phase; opt-in via `--profile fable`; reasoning effort inherits from the session (see Model & effort axes) | `premium` is the default tier — set by the 2026-05-13 operator decision and matched by the hardcoded resolver default in `lib/profiles/resolver.mjs`. It @@ -22,7 +23,8 @@ roughly 5× the sub-agent cost of an all-sonnet run, accepted as a deliberate trade-off. Drop to `--profile balanced` (opus only on the two phases where quality matters most — Plan synthesis + Review — and sonnet everywhere else) or `--profile economy` (sonnet everywhere) when cost or latency matters more -than depth. +than depth. Step up to `--profile fable` (Fable 5 on every phase) when +maximum quality is wanted end-to-end and cost is not a constraint. `economy` is *strictly experimental* in v4.1, and says so in the profile data itself: `lib/profiles/economy.yaml` carries `experimental: true`. The @@ -36,10 +38,19 @@ back to `balanced`. ## Model & effort axes -`opus` and `sonnet` are model **aliases**, not pinned ids. As of Claude Code -2.1.154 the `opus` alias resolves to **Opus 4.8**, whose default reasoning -effort is **`high`**; `sonnet` resolves to Sonnet 4.6. The profile table above -selects *which alias* runs each phase — it does not touch reasoning effort. +`opus`, `sonnet`, and `fable` are model **aliases**, not pinned ids. As of +Claude Code 2.1.154 the `opus` alias resolves to **Opus 4.8**, whose default +reasoning effort is **`high`**; `sonnet` resolves to Sonnet 4.6; `fable` +resolves to **Fable 5** (Mythos-class, positioned above Opus), whose default +reasoning effort is also `high`. The profile table above selects *which +alias* runs each phase — it does not touch reasoning effort. + +**Reasoning effort inherits from the session.** Voyage effort (orchestration +shape — which agents/passes run) and model reasoning effort are different +axes. Fable 5's default reasoning effort is `high`, NOT xhigh, and switching +model resets effort to the model default — xhigh does not follow the model. +To run the fable tier at xhigh, set it at session level: `/effort xhigh`, the +`effortLevel` setting, or `CLAUDE_CODE_EFFORT_LEVEL`. Two different things share the word "effort" in Voyage. They are **orthogonal axes** — same name, different mechanism: @@ -54,8 +65,8 @@ axes** — same name, different mechanism: The `phase-signal-resolver.mjs` helper only reads the **orchestration** axis (`phase_signals.effort`, gated against `low/standard/high`) plus the optional -per-phase `model` (gated against `['sonnet','opus']`). It never emits native -`effort:`. +per-phase `model` (gated against `['sonnet','opus','fable']`). It never emits +native `effort:`. **Native `effort:` on agents.** Voyage sets the reasoning axis statically on selected agents, additively over the Opus-4.8 default: @@ -120,11 +131,13 @@ The validator (`lib/validators/profile-validator.mjs`) enforces: - Every `phase_models[].phase` must be a known phase enum: `brief` / `research` / `plan` / `execute` / `review` / `continue` -- Every `phase_models[].model` must match `^(opus|sonnet)(\b|-).*` or - one of the canonical short names +- Every `phase_models[].model` must exactly match an entry in + `BASE_ALLOWED_MODELS` (`['sonnet', 'opus', 'fable']` in + `lib/validators/profile-validator.mjs`; `haiku` only with + `VOYAGE_ALLOW_HAIKU=1`) - All six phases must be present (no partial profiles) -The three built-in names (`economy`, `balanced`, `premium`) resolve to their +The four built-in names (`economy`, `balanced`, `premium`, `fable`) resolve to their bundled yaml first — `findProfilePath()` returns the built-in before consulting `voyage-profiles/`, so a same-named custom file is ignored and cannot shadow a built-in. To customize, give your profile a new name and reference it via diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index f54f588..6921cf4 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -918,7 +918,7 @@ test('S15: default-profile name is consistent across resolver + all profile docs assert.equal(profile_source, 'default', 'resolveProfile({}, {}) must report source=default'); assert.equal(def, 'premium', 'resolver hardcoded default is premium (operator decision 2026-05-13, commit 40d8742)'); - const OTHERS = ['economy', 'balanced', 'premium'].filter((p) => p !== def); + const OTHERS = ['economy', 'balanced', 'premium', 'fable'].filter((p) => p !== def); for (const doc of PROFILE_DOCS) { const body = read(doc); assert.ok( @@ -941,7 +941,7 @@ test('S15: default-profile name is consistent across resolver + all profile docs test('S15: profile tables encode each built-in yaml phase_models exactly', () => { // Column order in every profile table: Profile | Brief | Research | Plan | Execute | Review | Continue | Use case const PHASES = ['brief', 'research', 'plan', 'execute', 'review', 'continue']; - for (const name of ['economy', 'balanced', 'premium']) { + for (const name of ['economy', 'balanced', 'premium', 'fable']) { const pm = loadProfile(name).phase_models; // {brief:'opus', ...} const expected = PHASES.map((ph) => pm[ph]); for (const doc of PROFILE_DOCS) {