fix(#921,#922): orchestrators must not fork; plan-phase Agent gate is attempt-based (#926)
`context: fork` strips the `Agent` tool from a subagent's environment.
Spawning orchestrators (`/gsd-autonomous`, `/gsd-execute-phase`,
`/gsd-plan-phase`) depend on `Agent` to dispatch sub-agents; running
them forked silently disables the core capability they exist to provide
(#921). Remove `context: fork` from all three command frontmatter files.
`effort: xhigh` (introduced by #769) is preserved.
The `<runtime_compatibility>` Agent-availability guard added by #913 was
checking whether `Agent` was present *before* attempting the call. On
runtimes where the tool list is dynamically resolved this produced
false-negative aborts in sessions that have the tool (#922). Replace
the introspection-based pattern with an attempt-based gate: always
attempt the `Agent()` call; stop only if a real tool-unavailable error
is returned. This preserves #853's backgrounded-session close-off and
false negatives.
Tests updated: enh-769-context-fork-effort.install.test.cjs asserts the
three orchestrators lack `context: fork` and that the converter still
passes the field through for non-orchestrator commands; plan-phase-drift-
guard.test.cjs adds four assertions for the attempt-based gate language;
workflow-size-budget unchanged (budgets not exceeded).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit b866b95296)
This commit is contained in:
committed by
github-actions[bot]
parent
27af818312
commit
a6229ec123
@@ -193,3 +193,70 @@ describe('plan-phase workflow: top-level spawn guard (#913)', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── (D) Attempt-based Agent gate (#922) ─────────────────────────────────────
|
||||
|
||||
describe('plan-phase workflow: attempt-based Agent availability gate (#922)', () => {
|
||||
// Extract the runtime_compatibility block for targeted assertions
|
||||
const rtBlock = (() => {
|
||||
const m = workflow.match(/<runtime_compatibility>([\s\S]*?)<\/runtime_compatibility>/);
|
||||
return m ? m[1] : '';
|
||||
})();
|
||||
|
||||
// Extract the "Other runtimes" clause specifically
|
||||
const otherRuntimesClause = (() => {
|
||||
const m = rtBlock.match(/\*\*Other runtimes[^*]*\*\*[^\n]*\n([\s\S]*?)(?=\n\*\*|$)/);
|
||||
return m ? m[0] : rtBlock;
|
||||
})();
|
||||
|
||||
test('Other runtimes clause does not authorize stopping on a self-assessed absence (#922)', () => {
|
||||
// The pre-#922 wording ("if the Agent tool is genuinely absent") let the model
|
||||
// self-assess and stop without ever attempting a call. The fixed wording must
|
||||
// not contain phrasing that authorizes that pattern.
|
||||
const forbiddenPatterns = [
|
||||
/if the Agent tool is genuinely absent/i,
|
||||
/if.*Agent.*genuinely absent/i,
|
||||
];
|
||||
for (const pattern of forbiddenPatterns) {
|
||||
assert.ok(
|
||||
!pattern.test(otherRuntimesClause),
|
||||
`plan-phase "Other runtimes" clause must not authorize stopping on a self-assessed Agent absence — ` +
|
||||
`use attempt-based gate instead (#922). Found: ${otherRuntimesClause.trim()}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('Other runtimes clause pins "Always attempt the actual Agent() call" language (#922)', () => {
|
||||
// Pin the exact contract phrase so a future edit that changes to "try to determine
|
||||
// availability" or "check if Agent is available" does not silently reintroduce introspection.
|
||||
assert.ok(
|
||||
otherRuntimesClause.includes('Always attempt the actual') ||
|
||||
otherRuntimesClause.includes('always attempt the actual'),
|
||||
`plan-phase "Other runtimes" clause must pin "Always attempt the actual Agent() call" (or equivalent) (#922). ` +
|
||||
`Found: ${otherRuntimesClause.trim()}`
|
||||
);
|
||||
});
|
||||
|
||||
test('Other runtimes clause pins "real tool-unavailable error" as the only valid stop signal (#922)', () => {
|
||||
// Must tie the stop to a real returned error, not a self-assessed absence.
|
||||
assert.ok(
|
||||
otherRuntimesClause.includes('real tool-unavailable error') ||
|
||||
otherRuntimesClause.includes('tool-unavailable error returned'),
|
||||
`plan-phase "Other runtimes" clause must state only a real tool-unavailable error from Agent() authorizes stopping (#922). ` +
|
||||
`Found: ${otherRuntimesClause.trim()}`
|
||||
);
|
||||
});
|
||||
|
||||
test('Other runtimes clause still prohibits inline role collapse (#922 preserves #913)', () => {
|
||||
// Even after the attempt-based rewrite the clause must keep the no-inline-collapse guard.
|
||||
const hasNoInline =
|
||||
otherRuntimesClause.toLowerCase().includes('do not') &&
|
||||
(otherRuntimesClause.toLowerCase().includes('inline') ||
|
||||
otherRuntimesClause.toLowerCase().includes('collapse'));
|
||||
assert.ok(
|
||||
hasNoInline,
|
||||
`plan-phase "Other runtimes" clause must still prohibit inline role collapse even with the attempt-based gate (#922). ` +
|
||||
`Found: ${otherRuntimesClause.trim()}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user