fix(spawn): forbid the Agent tool's name parameter at every voyage spawn site
akashic-intelligence lost /trekplan Phase 9 twice to reviewer agents that never returned. Reproduced here and measured: the cause is the Agent tool's `name` parameter, not the agent definitions. Passing `name` does not label a subagent - it changes its kind. The spawn is recorded as taskKind "in_process_teammate" (spawnDepth 0) instead of a real subagent (spawnDepth 1). A teammate's final assistant text is not a return value; it reaches the parent only if the teammate itself calls SendMessage(to: "main"). plan-critic and scope-guardian declare tools: [Read, Glob, Grep] - no SendMessage - so as teammates they are structurally incapable of returning, whatever the prompt says. Denominators: named 0/5 returned; named + explicit SendMessage 1/1; unnamed 3/3 (plan-critic and scope-guardian each returned full findings + the JSON block in ~110s). Model override 2/2 non-returning, so the override is not the variable. All 5 named agents produced correct final text in their transcripts - only delivery failed, and that output is recoverable on disk. The defect is a harness behaviour, so it is documented rather than silently worked around: docs/agent-return-channel-defect.md carries the mechanism, every denominator, the two broken queries that nearly became facts, the unmeasured cells, and a recommended working shape for consumers (use agents, drop `name`; do not fall back to inline review, which costs the dedup step). Prevention is pinned, TDD red->green: the four spawning commands each state the rule and name the mechanism, and doc-consistency derives the spawning set from the command files so the pin cannot go vacuous. Suite 1013 (1011/0/2), +5 from baseline 1008. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014MfB5Ecp8vvGyou8uFGxBV
This commit is contained in:
parent
4b261db005
commit
f79c5f6606
6 changed files with 297 additions and 0 deletions
|
|
@ -1582,6 +1582,48 @@ for (const cmd of ['trekresearch', 'trekplan', 'trekreview', 'trekexecute']) {
|
|||
// fixed model in every session; if deterministic pinning is ever wanted again,
|
||||
// do it consciously and update this pin's rationale.
|
||||
|
||||
// ── S85 — every spawn site must forbid the Agent tool's `name` parameter ────
|
||||
// MEASURED 2026-08-17 (this repo, session c37b89f7), after akashic-intelligence
|
||||
// lost a whole Phase 9 to it. Passing `name` does not rename a subagent — it
|
||||
// changes its kind: the meta record flips from a real subagent
|
||||
// (`spawnDepth: 1`) to `taskKind: "in_process_teammate"` (`spawnDepth: 0`).
|
||||
// A teammate's final assistant text is NOT a return value; it reaches the
|
||||
// parent only if the teammate itself calls SendMessage(to: "main").
|
||||
// voyage's reviewers declare `tools: ["Read","Glob","Grep"]` — no SendMessage —
|
||||
// so as teammates they are STRUCTURALLY incapable of returning, whatever the
|
||||
// prompt says. Denominators: named 0/5 returned, named-with-explicit-
|
||||
// SendMessage 1/1, unnamed 3/3 (plan-critic + scope-guardian both returned
|
||||
// full findings + JSON block in ~110s). Full measurement:
|
||||
// docs/agent-return-channel-defect.md.
|
||||
//
|
||||
// The derived set is asserted exactly, so the pin cannot go vacuous if a
|
||||
// command stops spawning or a new spawning command is added.
|
||||
|
||||
const SPAWNING_COMMANDS = ['trekbrief', 'trekplan', 'trekresearch', 'trekreview'];
|
||||
|
||||
test('S85: the set of agent-spawning commands is exactly the four that carry the no-name rule', () => {
|
||||
const spawns = listMd('commands')
|
||||
.filter((f) => /^Launch\b/m.test(read(`commands/${f}`)))
|
||||
.map((f) => f.replace(/\.md$/, ''))
|
||||
.sort();
|
||||
assert.deepEqual(spawns, [...SPAWNING_COMMANDS].sort(),
|
||||
'a command started or stopped spawning agents — add/remove it from SPAWNING_COMMANDS and give it the no-name rule');
|
||||
});
|
||||
|
||||
for (const cmd of SPAWNING_COMMANDS) {
|
||||
test(`S85: commands/${cmd}.md forbids the Agent tool's name parameter at its spawn sites`, () => {
|
||||
const text = read(`commands/${cmd}.md`);
|
||||
assert.ok(
|
||||
text.includes('in_process_teammate'),
|
||||
`commands/${cmd}.md must name the mechanism (in_process_teammate) so the rule is not mistaken for style`,
|
||||
);
|
||||
assert.ok(
|
||||
/never pass .*`name`/i.test(text),
|
||||
`commands/${cmd}.md must state that the Agent tool's \`name\` parameter is never passed — a named agent completes its work but its result never reaches the orchestrator`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test('v5.9: no commands/*.md frontmatter carries a model: key (session inheritance by omission)', () => {
|
||||
const offenders = [];
|
||||
for (const f of listMd('commands')) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue