mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
f2a350ba7f
Anthropic announced nested subagent support (depth=5) on 2026-06-09; the runtime currently strips Task at parent->child spawn (upstream denylist, empirically confirmed by our committed probe: level=1 status=NO_AGENT_TOOL, matching ruflo ADR-147). This lands the readiness infrastructure that is independently useful today and activates with zero changes when the gate lifts: - aqe hooks post-task gains --parent-agent-id / --depth (validated: identifier charset, integer 0 <= d <= 32; depth=0 survives as 0); provenance persisted to qe_trajectories.metadata_json (stage 1 — no schema migration) and forwarded into ReasoningBank feedback so learning can segment patterns per hierarchy level - TaskExecution gains optional parentAgentId/depth (additive types) - scripts/probe-nested-spawn-depth.mjs: single-shot empirical probe, committed transcript under docs/probes/ — re-run after every `claude update`; TASK_TOOL_LIVE is the signal for follow-up phases - fresh-project fix found by the E2E: persistTaskOutcome assumed captured_experiences/experience_applications exist, but they are middleware-created — Stream B silently no-oped on fresh installs; canonical DDL now ensured in the hook path Deliberately deferred (documented in ADR-101 P1): tools: frontmatter retrofit on shipped qe-*.md agents — Claude Code frontmatter is a restrictive allowlist and would sever inherited MCP tool access; needs maintainer sign-off + wildcard verification first. 8 unit tests green; E2E on a fresh temp project verified persistence, rejections, and depth=0 boundary. tsc + eslint clean. Part of #520 (improvement 4/7, Tier 2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.7 KiB
JavaScript
85 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ADR-101 — Empirical probe: can spawned subagents spawn subagents?
|
|
*
|
|
* Anthropic announced nested subagent support (depth=5) on 2026-06-09, but
|
|
* the runtime currently strips the Task tool at parent→child spawn time
|
|
* (upstream denylist — see ruflo ADR-147 for the original investigation).
|
|
* This script is the regression test that tells us when the gate lifts:
|
|
* re-run it after every `claude update`.
|
|
*
|
|
* Usage: node scripts/probe-nested-spawn-depth.mjs
|
|
* Output: verdict on stdout; full transcript appended under docs/probes/
|
|
*
|
|
* Verdicts:
|
|
* level=1 status=NO_AGENT_TOOL — denylist active (expected today)
|
|
* level=N status=TASK_TOOL_LIVE — nested spawning works to level N:
|
|
* activate ADR-101 follow-up phases
|
|
* status=PROBE_INCONCLUSIVE — output didn't match; inspect transcript
|
|
*/
|
|
|
|
import { execFile } from 'node:child_process';
|
|
import { mkdirSync, appendFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const probesDir = join(root, 'docs', 'probes');
|
|
|
|
const PROMPT = `PROBE for nested subagent support. Follow these steps exactly, no extra prose:
|
|
1. Use your Task/Agent tool (subagent_type: general-purpose) to spawn ONE subagent with exactly this instruction:
|
|
"Output the single line 'level=1 task_tool=yes' if you have a Task or Agent tool available to spawn subagents, or 'level=1 task_tool=no' if you do not. If yes, additionally spawn one general-purpose subagent instructing it to output 'level=2 task_tool=yes' or 'level=2 task_tool=no' by the same rule, and append its output line to yours. Return only these lines."
|
|
2. Output 'level=0 task_tool=yes' followed by the subagent's returned lines, verbatim, one per line. Nothing else.`;
|
|
|
|
function runProbe() {
|
|
return new Promise((resolve) => {
|
|
const child = execFile(
|
|
'claude',
|
|
['-p', PROMPT, '--max-turns', '10', '--output-format', 'text'],
|
|
{ timeout: 600_000, maxBuffer: 10 * 1024 * 1024 },
|
|
(error, stdout, stderr) => resolve({ error, stdout: stdout ?? '', stderr: stderr ?? '' })
|
|
);
|
|
child.on('error', (error) => resolve({ error, stdout: '', stderr: String(error) }));
|
|
});
|
|
}
|
|
|
|
const { error, stdout, stderr } = await runProbe();
|
|
|
|
const lines = stdout.split('\n').map((l) => l.trim());
|
|
const levels = new Map();
|
|
for (const line of lines) {
|
|
const m = line.match(/^level=(\d+)\s+task_tool=(yes|no)$/);
|
|
if (m) levels.set(Number(m[1]), m[2] === 'yes');
|
|
}
|
|
|
|
let verdict;
|
|
if (error && !stdout) {
|
|
verdict = `status=PROBE_FAILED error=${error.code ?? error.message}`;
|
|
} else if (levels.size === 0) {
|
|
verdict = 'status=PROBE_INCONCLUSIVE (no level lines in output — inspect transcript)';
|
|
} else {
|
|
const deepestYes = Math.max(...[...levels.entries()].filter(([, yes]) => yes).map(([l]) => l));
|
|
const firstNo = [...levels.entries()].find(([, yes]) => !yes)?.[0];
|
|
verdict =
|
|
firstNo !== undefined && firstNo <= 1
|
|
? `level=${firstNo} status=NO_AGENT_TOOL (upstream denylist active — expected until Anthropic flips the gate)`
|
|
: `level=${deepestYes} status=TASK_TOOL_LIVE (nested spawning works — activate ADR-101 follow-up phases)`;
|
|
}
|
|
|
|
const stamp = new Date().toISOString();
|
|
const record = [
|
|
`=== nested-spawn probe ${stamp} ===`,
|
|
`verdict: ${verdict}`,
|
|
`--- stdout ---`,
|
|
stdout.trim(),
|
|
stderr.trim() ? `--- stderr ---\n${stderr.trim()}` : '',
|
|
'',
|
|
].join('\n');
|
|
|
|
mkdirSync(probesDir, { recursive: true });
|
|
const outFile = join(probesDir, `probe-nested-spawn-${stamp.slice(0, 10)}.txt`);
|
|
appendFileSync(outFile, record);
|
|
|
|
console.log(verdict);
|
|
console.log(`transcript: ${outFile}`);
|