fix(plan): every planning and research spawn takes the resolved model, and no turn budget is promised
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eafe023c74
commit
074feef475
5 changed files with 132 additions and 16 deletions
|
|
@ -87,7 +87,7 @@ Classify:
|
|||
- **Medium** (50–500 files)
|
||||
- **Large** (> 500 files)
|
||||
|
||||
Codebase size controls `maxTurns` per agent, NOT which agents run.
|
||||
Codebase size decides only whether convention-scanner runs.
|
||||
|
||||
### Phase 1b — Brief review
|
||||
|
||||
|
|
@ -120,8 +120,7 @@ scan results only.
|
|||
**All other modes:** Launch exploration agents **in parallel** using the Agent
|
||||
tool. Use specialized agents from the plugin.
|
||||
|
||||
**All agents run for all codebase sizes.** Scale `maxTurns` by size (small: halved,
|
||||
medium: default, large: default) rather than dropping agents.
|
||||
**All agents run for all codebase sizes.**
|
||||
|
||||
| Agent | Small | Medium | Large | Purpose |
|
||||
|-------|-------|--------|-------|---------|
|
||||
|
|
@ -134,7 +133,7 @@ medium: default, large: default) rather than dropping agents.
|
|||
| `research-scout` | Conditional | Conditional | Conditional | External docs (only when unfamiliar tech detected AND not covered by briefs) |
|
||||
| `convention-scanner` | No | Yes | Yes | Coding conventions, naming, style, test patterns |
|
||||
|
||||
**Convention Scanner** — use the `convention-scanner` plugin agent (model: "opus")
|
||||
**Convention Scanner** — use the `convention-scanner` plugin agent (model: phase_signal_result.model, the agent's frontmatter as the fallback)
|
||||
for medium+ codebases only. Pass the task description as context.
|
||||
|
||||
**research-scout** — launch conditionally if the task involves technologies, APIs,
|
||||
|
|
@ -512,5 +511,4 @@ You can:
|
|||
If you don't know the exact change, mark it as `[ASSUMPTION]` and explain what
|
||||
information is missing.
|
||||
- **Honesty:** If the task is trivial, say so. Don't inflate the plan.
|
||||
- **Adaptive:** All agents run for all sizes. Scale turns down for small codebases,
|
||||
not agent count.
|
||||
- **Adaptive:** All agents run for all sizes.
|
||||
|
|
|
|||
|
|
@ -314,6 +314,10 @@ Determine the output directory from the plan slug:
|
|||
|
||||
Launch the **session-decomposer** agent:
|
||||
|
||||
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
|
||||
`--brief` branch, where the resolver does not run, or a resolver that printed
|
||||
nothing) the agent's own `model:` applies.
|
||||
|
||||
```
|
||||
Plan file: {plan path}
|
||||
Plugin root: ${CLAUDE_PLUGIN_ROOT}
|
||||
|
|
@ -412,7 +416,7 @@ Then continue to the next phase inline.
|
|||
|
||||
## Phase 4 — Codebase sizing
|
||||
|
||||
Determine codebase scale to calibrate agent turns (not agent count).
|
||||
Determine codebase scale to decide whether convention-scanner runs.
|
||||
|
||||
Run via Bash:
|
||||
```
|
||||
|
|
@ -454,6 +458,10 @@ Follow the writing-criteria skill (each success criterion is one command the
|
|||
criteria runner accepts, and it can go red) and the reviewing-adversarially skill.
|
||||
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
|
||||
|
||||
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
|
||||
`--brief` branch, where the resolver does not run, or a resolver that printed
|
||||
nothing) the agent's own `model:` applies.
|
||||
|
||||
Handle the verdict:
|
||||
- **PROCEED** — continue to Phase 5.
|
||||
- **PROCEED_WITH_RISKS** — continue, carry flagged risks as `[ASSUMPTION]` in the plan.
|
||||
|
|
@ -494,8 +502,7 @@ message). Use the specialized agents from the `agents/` directory.
|
|||
> 2026-08-17: named 0/5 returned, unnamed 3/3. Mechanism, denominators, and the
|
||||
> on-disk recovery path: `docs/agent-return-channel-defect.md`.
|
||||
|
||||
**All agents run for all codebase sizes.** Scale `maxTurns` by size (small: halved,
|
||||
medium: default, large: default) instead of dropping agents.
|
||||
**All agents run for all codebase sizes.**
|
||||
|
||||
| Agent | Small | Medium | Large | Purpose |
|
||||
|-------|-------|--------|-------|---------|
|
||||
|
|
@ -512,6 +519,10 @@ research-scout's web search is not a local read; the line above bounds its local
|
|||
|
||||
### Always launch (all codebase sizes):
|
||||
|
||||
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
|
||||
`--brief` branch, where the resolver does not run, or a resolver that printed
|
||||
nothing) the agent's own `model:` applies.
|
||||
|
||||
**architecture-mapper** — full codebase structure, tech stack, patterns, anti-patterns.
|
||||
Prompt: "Analyze the architecture of this codebase. The task being planned is: {task}.
|
||||
Brief file: {brief_path} — read it first.
|
||||
|
|
@ -549,7 +560,7 @@ Read local files only inside the repo under exploration, plus paths this prompt
|
|||
|
||||
### Launch for medium+ codebases (50+ files):
|
||||
|
||||
**Convention Scanner** — use the `convention-scanner` plugin agent (model: "opus")
|
||||
**Convention Scanner** — use the `convention-scanner` plugin agent (model: phase_signal_result.model, the agent's frontmatter as the fallback)
|
||||
for medium+ codebases only.
|
||||
Prompt: "Discover the coding conventions relevant to this task: {task}. Brief file: {brief_path} — read it first. Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names). Provide concrete examples from the codebase, not generic advice."
|
||||
|
||||
|
|
@ -567,6 +578,9 @@ topics have corresponding research files, skip research-scout entirely.
|
|||
|
||||
If yes (and not covered by attached briefs): launch **research-scout** in
|
||||
parallel with the other agents.
|
||||
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
|
||||
`--brief` branch, where the resolver does not run, or a resolver that printed
|
||||
nothing) the agent's own `model:` applies.
|
||||
Prompt: "Research the following technologies for this task: {task}.
|
||||
Specific questions: {list specific questions about the technology}.
|
||||
Technologies to research: {list}. Brief file: {brief_path} — read it first.
|
||||
|
|
@ -587,7 +601,8 @@ Common reasons for deep-dives:
|
|||
- A test pattern was identified but the test infrastructure needs more detail
|
||||
- A risk was flagged but the actual impact needs verification
|
||||
|
||||
For each significant gap, spawn a targeted deep-dive agent (model: "opus",
|
||||
For each significant gap, spawn a targeted deep-dive agent (model: phase_signal_result.model, else opus —
|
||||
Explore is Claude Code's built-in agent and has no Voyage frontmatter to fall back to,
|
||||
subagent_type: "Explore") with a narrow, specific brief. Each deep-dive prompt
|
||||
carries these two lines:
|
||||
Brief file: {brief_path} — read it first.
|
||||
|
|
@ -962,6 +977,10 @@ Both reviewers are asked for `voyage:writing-criteria` and
|
|||
writing-criteria rule in one sentence in its prompt, for the brief's criteria
|
||||
it traces into `## Verification`.
|
||||
|
||||
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
|
||||
`--brief` branch, where the resolver does not run, or a resolver that printed
|
||||
nothing) the agent's own `model:` applies.
|
||||
|
||||
**plan-critic** — adversarial review of the plan.
|
||||
Prompt: "Review this implementation plan for the task: {task}.
|
||||
Plan file: {plan path}. Read it and find every problem — missing steps,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Supported flags:
|
|||
is `{model, source}`, plus
|
||||
`effort` only when the brief's phase signal sets one (without it the
|
||||
phase runs at effort `standard`: no change). It is captured as
|
||||
`phase_signal_result` and used at Agent-spawn sites in Phase 4 to inject
|
||||
`phase_signal_result` and used at Agent-spawn sites in Phase 4 and Phase 5 to inject
|
||||
the resolved model:
|
||||
|
||||
```bash
|
||||
|
|
@ -418,8 +418,6 @@ ticket IDs) already removed: the agents search with the words they are given.
|
|||
Measured 2026-08-17: named 0/5 returned, unnamed 3/3. Mechanism,
|
||||
denominators, and the on-disk recovery path:
|
||||
`docs/agent-return-channel-defect.md`.
|
||||
- Scale maxTurns by codebase size for local agents (same as trekplan):
|
||||
small = halved, medium/large = default
|
||||
- convention-scanner: medium+ codebases only (50+ files)
|
||||
|
||||
## Phase 4.5 — Dimension discovery
|
||||
|
|
@ -493,7 +491,9 @@ Review all agent results. Identify knowledge gaps — dimensions where findings
|
|||
are thin, contradictory, or missing (**under-illuminated dimensions**).
|
||||
|
||||
**Standard and low effort — unchanged single pass.** For each significant gap,
|
||||
launch a targeted follow-up agent (model: "opus") with a narrow, specific
|
||||
launch a targeted follow-up agent (model: phase_signal_result.model, else opus — a
|
||||
follow-up may be one of Claude Code's built-in agents, which have no Voyage
|
||||
frontmatter to fall back to; see Launch rules) with a narrow, specific
|
||||
brief; a follow-up that searches the web names the `researching-sources` skill
|
||||
in its prompt, as above. Maximum 2 follow-ups. If no gaps exist, skip: "Initial research
|
||||
sufficient — no follow-ups needed." Then go to Phase 6.
|
||||
|
|
|
|||
2
tests/fixtures/jsonl-schemas.md
vendored
2
tests/fixtures/jsonl-schemas.md
vendored
|
|
@ -21,7 +21,7 @@
|
|||
|-----------|--------|-------------|----------|---------------|-----|
|
||||
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1125-1149 | profile, phase_models, profile_source | none |
|
||||
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:782-808 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
|
||||
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1275-1303 | profile, phase_models, parallel_agents, profile_source | none |
|
||||
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1294-1322 | profile, phase_models, parallel_agents, profile_source | none |
|
||||
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2909-2931 | profile, phase_models, profile_source | none |
|
||||
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
|
||||
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
|
||||
|
|
|
|||
|
|
@ -301,3 +301,102 @@ test('[#52] sibling: agents/planning-orchestrator.md says "execute" gives the /t
|
|||
assert.doesNotMatch(text, /Say "execute" to implement/, 'planning-orchestrator still says "execute" implements');
|
||||
assert.ok(text.split('\n').includes('- Say "execute" for the /trekexecute line'), 'missing line: - Say "execute" for the /trekexecute line');
|
||||
});
|
||||
|
||||
// --- #48 / #12 / #57 ---------------------------------------------------------------
|
||||
// The planning and research spawn sites pinned `model: "opus"`, past the
|
||||
// resolved model (brief signal > profile > agent frontmatter); every spawn now
|
||||
// takes `phase_signal_result.model`. No `maxTurns` budget is promised: the
|
||||
// Agent tool takes none, so "scale turns by size" was a promise nothing kept.
|
||||
|
||||
/**
|
||||
* From the line holding `marker` (it must be the only one) to the end of the
|
||||
* blank-line-separated paragraph after the marker's own paragraph, flattened.
|
||||
*/
|
||||
function paraAfter(rel, marker) {
|
||||
const lines = read(rel).split('\n');
|
||||
const hits = lines.filter((l) => l.includes(marker)).length;
|
||||
assert.equal(hits, 1, `${rel}: expected exactly one line holding "${marker}"; found ${hits}`);
|
||||
const start = lines.findIndex((l) => l.includes(marker));
|
||||
let i = start;
|
||||
while (i < lines.length && lines[i].trim() !== '') i++;
|
||||
while (i < lines.length && lines[i].trim() === '') i++;
|
||||
while (i < lines.length && lines[i].trim() !== '') i++;
|
||||
return flat(lines.slice(start, i).join('\n'));
|
||||
}
|
||||
|
||||
const PASS_MODEL = /Pass `model: \{phase_signal_result\.model\}` at this spawn; without it \(the `--brief` branch, where the resolver does not run, or a resolver that printed nothing\) the agent's own `model:` applies\./;
|
||||
|
||||
test('[#48] commands/trekplan.md pins no spawn to `model: "opus"`', () => {
|
||||
assert.doesNotMatch(read('commands/trekplan.md'), /model: "opus"/);
|
||||
});
|
||||
|
||||
for (const [where, marker] of [
|
||||
['session-decomposer (Phase 1.5)', 'Launch the **session-decomposer** agent:'],
|
||||
['brief-reviewer (Phase 4b)', 'Launch the **brief-reviewer** agent.'],
|
||||
['### Always launch (Phase 5)', '### Always launch (all codebase sizes):'],
|
||||
['research-scout (Phase 5)', 'launch **research-scout** in'],
|
||||
['plan-critic and scope-guardian (Phase 9)', 'it traces into `## Verification`.'],
|
||||
]) {
|
||||
test(`[#48] ${where}: the spawn takes phase_signal_result.model, the agent's own model: as the fallback`, () => {
|
||||
const part = paraAfter('commands/trekplan.md', marker);
|
||||
assert.match(part, PASS_MODEL, `${where}: missing the resolved-model paragraph after "${marker}"`);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
}
|
||||
|
||||
test('[#48] convention-scanner (Phase 5): the spawn takes phase_signal_result.model, the frontmatter as the fallback', () => {
|
||||
const part = paraAfter('commands/trekplan.md', 'use the `convention-scanner` plugin agent');
|
||||
assert.match(part, /\(model: phase_signal_result\.model, the agent's frontmatter as the fallback\)/);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
|
||||
test('[#48] deep-dives (Phase 6): the spawn takes phase_signal_result.model, else opus', () => {
|
||||
const part = paraAfter('commands/trekplan.md', 'spawn a targeted deep-dive agent');
|
||||
assert.match(part, /\(model: phase_signal_result\.model, else opus — Explore is Claude Code's built-in agent and has no Voyage frontmatter to fall back to, subagent_type: "Explore"\)/);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
|
||||
test('[#48] sibling: agents/planning-orchestrator.md:137 takes phase_signal_result.model', () => {
|
||||
const text = read('agents/planning-orchestrator.md');
|
||||
assert.doesNotMatch(text, /model: "opus"/);
|
||||
const part = partAt('agents/planning-orchestrator.md', '**Convention Scanner** — use the `convention-scanner` plugin agent', toBlank);
|
||||
assert.match(part, /\(model: phase_signal_result\.model, the agent's frontmatter as the fallback\)/);
|
||||
});
|
||||
|
||||
test('[#12] trekresearch Phase 5 follow-ups take phase_signal_result.model, else opus', () => {
|
||||
const text = read('commands/trekresearch.md');
|
||||
const p5 = flat(section(text, '## Phase 5 — Targeted follow-ups'));
|
||||
assert.doesNotMatch(p5, /model: "opus"/);
|
||||
const part = partAt('commands/trekresearch.md', 'launch a targeted follow-up agent', toBlank);
|
||||
assert.match(part, /launch a targeted follow-up agent \(model: phase_signal_result\.model, else opus — a follow-up may be one of Claude Code's built-in agents, which have no Voyage frontmatter to fall back to; see Launch rules\)/);
|
||||
assert.match(flat(text), /captured as `phase_signal_result` and used at Agent-spawn sites in Phase 4 and Phase 5 to inject the resolved model/);
|
||||
});
|
||||
|
||||
test('[#57] commands/trekresearch.md promises no maxTurns budget', () => {
|
||||
const text = read('commands/trekresearch.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
assert.match(text, /- convention-scanner: medium\+ codebases only \(50\+ files\)/);
|
||||
});
|
||||
|
||||
test('[#57] sibling: commands/trekplan.md promises no maxTurns budget, and Phase 4 calibrates no turns', () => {
|
||||
const text = read('commands/trekplan.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
const p4 = flat(section(text, '## Phase 4 — Codebase sizing'));
|
||||
assert.doesNotMatch(p4, /calibrate agent turns/);
|
||||
assert.match(p4, /Determine codebase scale to decide whether convention-scanner runs\./);
|
||||
assert.match(text, /^\*\*All agents run for all codebase sizes\.\*\*$/m);
|
||||
});
|
||||
|
||||
test('[#57] sibling: agents/planning-orchestrator.md promises no maxTurns budget and scales no turns', () => {
|
||||
const text = read('agents/planning-orchestrator.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
assert.doesNotMatch(text, /Scale turns/i);
|
||||
assert.match(text, /^\*\*All agents run for all codebase sizes\.\*\*$/m);
|
||||
assert.match(text, /^- \*\*Adaptive:\*\* All agents run for all sizes\.$/m);
|
||||
});
|
||||
|
||||
test('keep: /trekexecute\'s controller and its model note stay pinned to opus', () => {
|
||||
const text = read('commands/trekexecute.md');
|
||||
assert.match(text, /`subagent_type: "general-purpose"`, `model: "opus"`, no `name`/);
|
||||
assert.match(flat(text), /controller's `model: "opus"` — so `phase_signal_result\.model` still has no spawn site here/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue