fix(research): the contrarian runs as a second wave with the leading answer to attack
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f32baf50ad
commit
b16e39ccd9
5 changed files with 104 additions and 15 deletions
|
|
@ -823,7 +823,7 @@ flowchart TB
|
||||||
BR --> RES --> PL --> EX --> RV
|
BR --> RES --> PL --> EX --> RV
|
||||||
```
|
```
|
||||||
|
|
||||||
`* = conditional`: convention-scanner / test-strategist on medium+ codebases (50+ files); research-scout for unknown external tech; security-/contrarian-researcher when a leading recommendation forms (or always at `effort=high`); brief-conformance-reviewer skipped under `--quick`.
|
`* = conditional`: convention-scanner / test-strategist on medium+ codebases (50+ files); research-scout for unknown external tech; security-researcher when a leading recommendation forms (or always at `effort=high`); contrarian-researcher, when selected, runs in wave 2 after the other external agents return, with their leading answer to attack (always at `effort=high`); brief-conformance-reviewer skipped under `--quick`.
|
||||||
|
|
||||||
> **Which Claude Code primitive each phase uses — and the alternatives considered (Workflow substrate, delegated orchestrator, dormant synthesis-agent)** → see [docs/architecture.md §Primitives per step](docs/architecture.md#primitives-per-step-decision-matrix).
|
> **Which Claude Code primitive each phase uses — and the alternatives considered (Workflow substrate, delegated orchestrator, dormant synthesis-agent)** → see [docs/architecture.md §Primitives per step](docs/architecture.md#primitives-per-step-decision-matrix).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,12 +88,22 @@ Based on the mode, determine which agent groups to launch:
|
||||||
| `docs-researcher` | Official documentation, RFCs, vendor docs |
|
| `docs-researcher` | Official documentation, RFCs, vendor docs |
|
||||||
| `community-researcher` | Real-world experience, issues, blog posts, discussions |
|
| `community-researcher` | Real-world experience, issues, blog posts, discussions |
|
||||||
| `security-researcher` | CVEs, audit history, supply chain risks |
|
| `security-researcher` | CVEs, audit history, supply chain risks |
|
||||||
| `contrarian-researcher` | Counter-evidence, overlooked alternatives, reasons to reconsider |
|
| `contrarian-researcher` | Counter-evidence, overlooked alternatives, reasons to reconsider; runs in wave 2, against wave 1's leading answer |
|
||||||
|
|
||||||
### Phase 2 — Parallel research
|
### Phase 2 — Parallel research
|
||||||
|
|
||||||
Launch ALL selected agents **in parallel** using the Agent tool — one message,
|
Launch the selected agents in two waves.
|
||||||
multiple tool calls. This maximizes concurrency.
|
|
||||||
|
**Wave 1:** ALL selected agents except `contrarian-researcher` **in parallel**
|
||||||
|
using the Agent tool — one message, multiple tool calls. This maximizes
|
||||||
|
concurrency.
|
||||||
|
|
||||||
|
**Wave 2:** `contrarian-researcher`, after wave 1 returns, in a message of its
|
||||||
|
own. Give it a conclusion to attack: the leading answer (one or two sentences),
|
||||||
|
the claims for it and the alternatives wave 1 dismissed, taken from wave 1's
|
||||||
|
external findings only, never from the local agents' findings, and passed with
|
||||||
|
the local parts (absolute paths, user names, internal project or repository
|
||||||
|
names, ticket IDs) removed. It searches with the words it is given.
|
||||||
|
|
||||||
**Prompting local agents for research (not planning):**
|
**Prompting local agents for research (not planning):**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -384,7 +384,8 @@ changes nothing (SC1). Keep the native-swarm anchors intact ("in parallel",
|
||||||
5. **On a real report:** keep `effective_engine = deep-research`, log
|
5. **On a real report:** keep `effective_engine = deep-research`, log
|
||||||
`Engine: deep-research (active)`, and carry the report into Phase 6 triangulation
|
`Engine: deep-research (active)`, and carry the report into Phase 6 triangulation
|
||||||
as the external-findings input (adapted in Phase 7 — see the Deep-research engine
|
as the external-findings input (adapted in Phase 7 — see the Deep-research engine
|
||||||
adapter below).
|
adapter below); then run wave 2 (`contrarian-researcher`, see Launch rules)
|
||||||
|
from the report's leading answer.
|
||||||
|
|
||||||
### External agents (scope = both or external)
|
### External agents (scope = both or external)
|
||||||
|
|
||||||
|
|
@ -413,7 +414,22 @@ ticket IDs) already removed: the agents search with the words they are given.
|
||||||
|
|
||||||
### Launch rules
|
### Launch rules
|
||||||
|
|
||||||
- Launch ALL selected agents **in parallel** in a single message
|
- Launch in two waves, both in Phase 4 (before Phase 4.5 and Phase 5, so outside
|
||||||
|
the Phase 5 loop scope marker that `hooks/scripts/pre-agent-cap.mjs` reads).
|
||||||
|
- **Wave 1:** ALL selected agents except `contrarian-researcher` **in parallel**
|
||||||
|
in a single message.
|
||||||
|
- **Wave 2:** `contrarian-researcher`, when selected, after wave 1 returns, in a
|
||||||
|
message of its own. Give it a conclusion to attack: the leading answer (one or
|
||||||
|
two sentences), the claims for it and the alternatives wave 1 dismissed,
|
||||||
|
taken from wave 1's external findings only, never from the local agents'
|
||||||
|
findings (Hard rules → Independence), and stripped of local parts like the
|
||||||
|
research question above (absolute paths, user names, internal project or
|
||||||
|
repository names, ticket IDs). It searches with the words it is given, so no
|
||||||
|
local file name or project term may be in them. When wave 1's external
|
||||||
|
findings hold no leading answer, say so and give it the research question
|
||||||
|
alone, stripped the same way. With `--engine deep-research`
|
||||||
|
(`effective_engine = deep-research`), wave 2 runs after the engine returns,
|
||||||
|
from the leading answer of its report.
|
||||||
- Inject the resolved model at every spawn: `phase_signal_result.model` (brief
|
- Inject the resolved model at every spawn: `phase_signal_result.model` (brief
|
||||||
signal > profile), with the agent's frontmatter `model:` as the fallback — see
|
signal > profile), with the agent's frontmatter `model:` as the fallback — see
|
||||||
Hard rules → Cost. The orchestrator itself follows the session model.
|
Hard rules → Cost. The orchestrator itself follows the session model.
|
||||||
|
|
@ -923,12 +939,11 @@ commands surface, don't re-enforce.
|
||||||
### High-effort behavior (v5.1.1)
|
### High-effort behavior (v5.1.1)
|
||||||
|
|
||||||
When `phase_signal_result.effort == 'high'` for the `research` phase,
|
When `phase_signal_result.effort == 'high'` for the `research` phase,
|
||||||
run the FULL swarm regardless of normal triggering rules: 5 local
|
run the FULL swarm regardless of normal selection rules: 5 local
|
||||||
agents + 4 external agents, AND force `contrarian-researcher` to
|
agents + 4 external agents, AND force `contrarian-researcher` to
|
||||||
always-on. Normally `contrarian-researcher` triggers conditionally when
|
always-on: in high-effort mode `contrarian-researcher` runs
|
||||||
a leading recommendation is emerging from initial agents; in
|
unconditionally, as wave 2 (see Launch rules), so the final brief
|
||||||
high-effort mode it runs unconditionally so the final brief always
|
always carries an adversarial counter-evidence pass.
|
||||||
carries an adversarial counter-evidence pass.
|
|
||||||
|
|
||||||
The former third-party second-opinion bridge pass is **unavailable,
|
The former third-party second-opinion bridge pass is **unavailable,
|
||||||
skipped** (dropped in v5.10.1 by operator directive). High effort no
|
skipped** (dropped in v5.10.1 by operator directive). High effort no
|
||||||
|
|
@ -943,7 +958,9 @@ turns per under-illuminated dimension, budgeted by
|
||||||
`--local`, `external_research_enabled: false`) overrides the effort
|
`--local`, `external_research_enabled: false`) overrides the effort
|
||||||
signal whenever the external phase does not run at all.
|
signal whenever the external phase does not run at all.
|
||||||
|
|
||||||
Standard effort (or absent): use the existing conditional triggers.
|
Standard effort (or absent): the selected agents run as in Phase 4, and
|
||||||
|
`contrarian-researcher`, when selected, runs as wave 2 after the other
|
||||||
|
agents return (see Launch rules).
|
||||||
Low effort: inline research only, no agent swarm (existing
|
Low effort: inline research only, no agent swarm (existing
|
||||||
`--quick`-equivalent code-path).
|
`--quick`-equivalent code-path).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,11 +206,16 @@ test('trekresearch — Phase 5 pins survive only while the prose does (mutation
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trekresearch — High-effort behavior keeps the standard/low effort sentences verbatim', () => {
|
test('trekresearch — High-effort behavior gives standard effort the wave-2 sentence and keeps the low effort sentence verbatim', () => {
|
||||||
const text = read();
|
const text = read();
|
||||||
|
const flatText = text.replace(/\s+/g, ' ');
|
||||||
assert.ok(
|
assert.ok(
|
||||||
text.includes('Standard effort (or absent): use the existing conditional triggers.'),
|
!flatText.includes('use the existing conditional triggers'),
|
||||||
'the standard-effort sentence must survive the Phase 5 rewrite verbatim',
|
'standard effort must not point at a conditional trigger that nothing defines',
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
flatText.includes('Standard effort (or absent): the selected agents run as in Phase 4, and `contrarian-researcher`, when selected, runs as wave 2 after the other agents return (see Launch rules).'),
|
||||||
|
'the standard-effort sentence must say the contrarian runs as wave 2 when selected',
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
text.includes('Low effort: inline research only, no agent swarm'),
|
text.includes('Low effort: inline research only, no agent swarm'),
|
||||||
|
|
|
||||||
|
|
@ -1199,3 +1199,60 @@ test('[#102] sibling: commands/trekexecute.md names project-dir and repo-root ca
|
||||||
assert.doesNotMatch(text, /plugin-root candidates/);
|
assert.doesNotMatch(text, /plugin-root candidates/);
|
||||||
assert.equal((text.match(/project-dir and repo-root candidates/g) || []).length, 2);
|
assert.equal((text.match(/project-dir and repo-root candidates/g) || []).length, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- #113: the contrarian gets a conclusion to attack -----------------------
|
||||||
|
// contrarian-researcher was launched in the same parallel message as the agents
|
||||||
|
// whose conclusion it is meant to attack, and standard effort pointed at a
|
||||||
|
// "conditional trigger" that nothing defined. It now runs as wave 2, after wave
|
||||||
|
// 1 returns, given wave 1's leading answer from the external findings only,
|
||||||
|
// with the local parts stripped (the de-localising rule of External agents), so
|
||||||
|
// no local file name or project term reaches its web queries.
|
||||||
|
|
||||||
|
const RESEARCH = 'commands/trekresearch.md';
|
||||||
|
const RESEARCH_ORCH = 'agents/research-orchestrator.md';
|
||||||
|
|
||||||
|
test('[#113] Phase 4 launches contrarian-researcher as wave 2 after wave 1, with the leading answer and claims from external findings only, and names the deep-research case', () => {
|
||||||
|
const text = read(RESEARCH);
|
||||||
|
const ext = partAt(RESEARCH, '### External agents (scope = both or external)', (l) => /^## /.test(l));
|
||||||
|
assert.doesNotMatch(ext, /Launch ALL selected agents \*\*in parallel\*\*/, 'Launch rules still start every selected agent, the contrarian too, in one parallel message');
|
||||||
|
assert.match(ext, /Launch in two waves, both in Phase 4 \(before Phase 4\.5 and Phase 5/);
|
||||||
|
assert.match(ext, /\*\*Wave 1:\*\* ALL selected agents except `contrarian-researcher` \*\*in parallel\*\* in a single message/);
|
||||||
|
assert.match(ext, /\*\*Wave 2:\*\* `contrarian-researcher`, when selected, after wave 1 returns/);
|
||||||
|
assert.match(ext, /the leading answer \(one or two sentences\), the claims for it and the alternatives wave 1 dismissed/);
|
||||||
|
assert.match(ext, /taken from wave 1's external findings only, never from the local agents' findings/, 'wave 2 does not say external findings only');
|
||||||
|
assert.match(ext, /stripped of local parts like the research question above \(absolute paths, user names, internal project or repository names, ticket IDs\)/, 'wave 2 does not apply the stripping rule');
|
||||||
|
assert.match(ext, /Pass the research question to them with the local parts \(absolute paths, user names, internal project or repository names, ticket IDs\) already removed/);
|
||||||
|
assert.match(ext, /With `--engine deep-research` \(`effective_engine = deep-research`\), wave 2 runs after the engine returns, from the leading answer of its report/);
|
||||||
|
const p4 = partAt(RESEARCH, '## Phase 4 — Parallel research (agent swarm)', (l) => /^## /.test(l));
|
||||||
|
assert.match(p4, /then run wave 2 \(`contrarian-researcher`, see Launch rules\) from the report's leading answer/, 'the deep-research step 5 does not hand over to wave 2');
|
||||||
|
const w2 = text.indexOf('**Wave 2:**');
|
||||||
|
assert.ok(w2 > text.indexOf('## Phase 4 —') && w2 < text.indexOf('## Phase 4.5 —'), 'wave 2 is not in Phase 4, before Phase 4.5');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[#113] no conditional trigger is left, and high effort keeps contrarian-researcher unconditional, as wave 2', () => {
|
||||||
|
const text = flat(read(RESEARCH));
|
||||||
|
assert.doesNotMatch(text, /triggers conditionally when a leading recommendation is emerging/);
|
||||||
|
assert.doesNotMatch(text, /use the existing conditional triggers/);
|
||||||
|
const high = flat(section(read(RESEARCH), '### High-effort behavior (v5.1.1)'));
|
||||||
|
assert.ok(high.includes('force `contrarian-researcher` to always-on: in high-effort mode `contrarian-researcher` runs unconditionally, as wave 2 (see Launch rules), so the final brief always carries an adversarial counter-evidence pass'),
|
||||||
|
'high effort does not keep the contrarian unconditional as wave 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[#113] sibling: README.md', () => {
|
||||||
|
const text = flat(read('README.md'));
|
||||||
|
assert.doesNotMatch(text, /contrarian-researcher when a leading recommendation forms/);
|
||||||
|
assert.ok(text.includes('contrarian-researcher, when selected, runs in wave 2 after the other external agents return, with their leading answer to attack (always at `effort=high`)'),
|
||||||
|
'README.md does not describe wave 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[#113] sibling: agents/research-orchestrator.md', () => {
|
||||||
|
const p2 = flat(section(read(RESEARCH_ORCH), '### Phase 2 — Parallel research'));
|
||||||
|
assert.doesNotMatch(p2, /Launch ALL selected agents \*\*in parallel\*\*/);
|
||||||
|
assert.match(p2, /\*\*Wave 1:\*\* ALL selected agents except `contrarian-researcher` \*\*in parallel\*\* using the Agent tool — one message, multiple tool calls/);
|
||||||
|
assert.match(p2, /\*\*Wave 2:\*\* `contrarian-researcher`, after wave 1 returns/);
|
||||||
|
assert.match(p2, /the leading answer \(one or two sentences\), the claims for it and the alternatives wave 1 dismissed/);
|
||||||
|
assert.match(p2, /taken from wave 1's external findings only, never from the local agents' findings/);
|
||||||
|
assert.match(p2, /with the local parts \(absolute paths, user names, internal project or repository names, ticket IDs\) removed/);
|
||||||
|
assert.ok(flat(read(RESEARCH_ORCH)).includes('| `contrarian-researcher` | Counter-evidence, overlooked alternatives, reasons to reconsider; runs in wave 2, against wave 1\'s leading answer |'),
|
||||||
|
'the agent table does not say the contrarian runs in wave 2');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue