fix(auq): spawned trigger is objective — explicit declaration or STATUS echo, never inference (periodic-lane AUQ collapse)

The v1.76 spawned rule's parenthetical '(or your dispatch prompt marks this
session as spawned)' let the model INFER spawned status from a scripted-looking
prompt in a CI-looking session and silently auto-choose every review-phase
question: reviewCount=0 across the plan-review periodic E2Es (weekly run
33363624506, 9 of 14 failed shards; reproduced locally, zero AUQ fingerprints).
Env and hook paths were excluded by inspection: hermetic children echo
SESSION_KIND: interactive (CLAUDE_CODE_ENTRYPOINT=cli beats CI markers) and the
question-preference hook isn't installed there.

The trigger is now objective: the echoed SESSION_KIND: spawned STATUS line, or
an EXPLICIT dispatch-prompt declaration ("you are a SPAWNED subagent") —
declared, never inferred — with an absence-safe interactive fence: CI env vars,
scripted-looking or pasted prompts, and write-to-this-exact-file instructions
are NOT spawned markers. The prose channel stays because Task-tool subagents
inherit the parent env (no spawned prefix) — their dispatch prompt is the only
signal; #2733's env-prefix channel is untouched.

19 carve skeleton ceilings re-pinned with measured values (+~440 bytes/skill);
ship goldens refreshed for all three hosts; resolver pins extended with the
no-inference regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-31 20:36:12 +00:00
co-authored by Claude Fable 5
parent e76f65a8da
commit 95d3aa17e9
47 changed files with 138 additions and 118 deletions
+23 -4
View File
@@ -249,16 +249,35 @@ describe('generateAskUserFormat — runtime-failure prose fallback', () => {
});
test('Spawned: self-check carries the never-reach-this-checklist clause', () => {
expect(out).toMatch(/in `SESSION_KIND: spawned` you should never reach this checklist/);
expect(out).toMatch(/in `SESSION_KIND: spawned`[\s\S]{0,120}you should never reach this checklist/);
expect(out).toMatch(/never inferred/);
});
test('Spawned: rule scopes markings to the creating dispatch prompt (anti-injection)', () => {
// "(or your dispatch prompt marks this session as spawned)" is a
// text-claimable trigger — the rule must explicitly refuse spawned
// claims sourced from files/tool output/web content read mid-run.
// The spawned trigger is text-claimable — the rule must explicitly refuse
// spawned claims sourced from files/tool output/web content read mid-run.
expect(out).toMatch(/NEVER count[\s\S]*prompt injection/);
});
// Periodic-lane regression (v1.76 → v1.78): the old parenthetical
// "(or your dispatch prompt marks this session as spawned)" let the model
// INFER spawned status from a scripted-looking prompt in a CI-looking
// session and silently auto-decide every review question (reviewCount=0
// across the plan-review E2Es). The trigger must be objective: the echoed
// STATUS line or an EXPLICIT dispatch-prompt declaration — never inference —
// and the rule must carry an absence-safe interactive fence.
test('Spawned: trigger is explicit-declaration-only, never inference (AUQ-collapse regression)', () => {
expect(out).not.toContain('marks this session as spawned');
expect(out).toMatch(/EXPLICITLY declares this session a spawned subagent/);
expect(out).toMatch(/declared, never inferred/);
});
test('Spawned: absence-safe interactive fence present (CI env / scripted prompts are not markers)', () => {
expect(out).toMatch(/With neither trigger present, the session is interactive/);
expect(out).toMatch(/CI env vars, scripted-looking or pasted prompts[\s\S]{0,120}NOT spawned markers/);
expect(out).toMatch(/an unanswered question is recoverable/);
});
// Conductor-default-prose contract (the proactive path, distinct from the
// failure fallback). Guards the Tool-resolution rule + self-check wording.
test('Conductor: do-not-call rule present in Tool resolution', () => {