diff --git a/packages/subagent/subagent-inprocess/tests/inheritance.spec.ts b/packages/subagent/subagent-inprocess/tests/inheritance.spec.ts index 17620f7774..899772b964 100644 --- a/packages/subagent/subagent-inprocess/tests/inheritance.spec.ts +++ b/packages/subagent/subagent-inprocess/tests/inheritance.spec.ts @@ -84,8 +84,7 @@ describe('in-process policy inheritance', () => { const { ctx, parent } = await setupWalled(script) const blocked = join(workspace, 'spawn-blocked.txt') setSandboxMode(parent.session, 'read-only') - // The parent keeps the interactive deployment default: the child pin must - // not depend on any parent approval override. + // No parent approval override: the child pin must not depend on one. expect(ctx.approval.overrideOf(parent.session)).toBeUndefined() const parentLogLength = parent.session.events.length script.push( @@ -125,8 +124,7 @@ describe('in-process policy inheritance', () => { .join('\n') expect(contextText).toContain('Current DSH file policy: read-only') expect(contextText).toContain('Approval prompts are disabled') - // The delegation-scope statement is a runtime-context fact, so the - // deployment system prompt stays uniform across parents and children. + // The statement rides runtime context; the system prompt stays uniform. expect(contextText).toContain('You are a delegated subagent') expect(request.data.header.system).not.toContain('Approval prompts are disabled') expect(request.data.header.system).not.toContain('You are a delegated subagent') @@ -215,8 +213,7 @@ describe('in-process policy inheritance', () => { it('rejects a child escalation deterministically even when an answerer would allow it', async () => { const script: Script = [] const { ctx, parent } = await setupWalled(script) - // A root answerer that would GRANT: the pinned 'never' must resolve - // before any answerer is consulted, so this never runs for the child. + // A granting answerer proves the pin resolves before any answerer runs. let consulted = false ctx.on('approval/request', () => { consulted = true @@ -243,7 +240,6 @@ describe('in-process policy inheritance', () => { expect(consulted).toBe(false) expect(toolResultTexts(child).join('\n')) .toContain('the user rejected escalating this operation to "workspace-write"') - // The deterministic rejection still leaves the full audit pair on the child log. const asked = child.session.events.find( (event): event is SessionEvent<'approval/asked'> => event.type === 'approval/asked', ) diff --git a/packages/subagent/subagent-inprocess/tests/structured.spec.ts b/packages/subagent/subagent-inprocess/tests/structured.spec.ts index 36e63283da..eb9815b56b 100644 --- a/packages/subagent/subagent-inprocess/tests/structured.spec.ts +++ b/packages/subagent/subagent-inprocess/tests/structured.spec.ts @@ -247,8 +247,7 @@ describe('in-process structured output', () => { const result = await run.result expect(result.stopReason).toBe('error') expect(result.structured).toBeUndefined() - // Exactly one model request and one caller-supplied user message (the - // delegation runtime-context snapshot aside): no nudge turn exists. + // Exactly one model request and one caller-supplied user message: no nudge turn exists. expect(adapter.requests.length).toBe(1) const child = ctx.agents.get(run.id)! expect(child.session.events.filter(e => e.type === 'user/message' && e.data.source.kind !== 'plugin').length).toBe(1) diff --git a/packages/subagent/subagent/src/child-agent.ts b/packages/subagent/subagent/src/child-agent.ts index bc0cf949b9..d1728ad74a 100644 --- a/packages/subagent/subagent/src/child-agent.ts +++ b/packages/subagent/subagent/src/child-agent.ts @@ -113,13 +113,9 @@ export interface ChildComposition { } /** - * Model-facing statement every in-process child receives: the permission - * scope is fixed at delegation and approval prompts are unavailable, so the - * child reports a scope limitation instead of retrying denied operations. - * A runtime-context contribution (not a system-prompt section) because it is - * a per-session fact: the deployment's system prompt stays uniform across - * parents and children, and the statement joins the same durable snapshot - * that carries the sandbox-policy and approval-policy sentences. + * Model-facing delegation-scope statement for every in-process child. A + * runtime-context contribution rather than a system-prompt section, so the + * deployment's system prompt stays uniform across parents and children. */ export const SUBAGENT_DELEGATION_CONTEXT = 'You are a delegated subagent: your permission scope was fixed when you were started and cannot be ' @@ -131,14 +127,12 @@ export const SUBAGENT_DELEGATION_CONTEXT * Apply one child's scoped composition inside its creation window: the fixed * delegation-scope statement, a shadowing persona section, and a tool * restriction, all owned by the child's scope and therefore invisible to its - * parent and siblings. Both creation and cold resume pass through here, so a - * resumed child keeps the same statement. + * parent and siblings. Creation and cold resume both pass through here. * @param childCtx - the child agent's scoped creation context. * @param composition - the persona and tool filter to install. */ export function applyChildComposition(childCtx: Context, composition: ChildComposition): void { - // After sandbox:policy (110) and approval:policy (115): scope, then policy, - // then what a delegated child does about a denial. + // Order 120: after the sandbox:policy (110) and approval:policy (115) sentences. childCtx.systemPrompt.context({ name: 'subagent:delegation', order: 120, text: SUBAGENT_DELEGATION_CONTEXT }) if (composition.persona !== undefined) { childCtx.systemPrompt.section({ name: 'deployment:persona', order: 0, text: composition.persona }) @@ -151,11 +145,9 @@ export interface DelegatedPolicyOverrides { /** The parent session's explicit sandbox-mode override, or `undefined` without one. */ readonly sandboxMode: SandboxMode | undefined /** - * The child's pinned approval policy, or `undefined` when no approval - * capability is composed. Always `'never'` with one composed: a delegated - * child acts only within the sandbox scope fixed at delegation, so the - * composed `ApprovalService` rejects every child ask deterministically - * instead of waiting on a prompt no one is watching. + * `'never'` whenever the approval capability is composed, `undefined` + * otherwise: a delegated child acts only within the sandbox scope fixed at + * delegation, so its asks are rejected deterministically. */ readonly approvalPolicy: 'never' | undefined } @@ -163,12 +155,10 @@ export interface DelegatedPolicyOverrides { /** * Capture the policy to seed into one delegation. Call synchronously before * the child start's first await: a later parent switch belongs to the - * parent's future, not to this child. The sandbox scope is the parent - * session's explicit override — deployment defaults and one-shot grants are - * never captured, so an unswitched parent leaves the child following the - * deployment default dynamically. The approval policy is never inherited: it - * is pinned to `'never'` whenever the approval capability is composed, - * regardless of the parent's own policy. + * parent's future, not to this child. Only the parent session's explicit + * sandbox override is captured — never deployment defaults or one-shot + * grants — and the approval policy is pinned to `'never'` regardless of the + * parent's own policy. * @param parent - the delegating parent agent. * @returns the sandbox override (or `undefined` without one) and the approval pin. */ diff --git a/packages/subagent/subagent/tests/continuation-inheritance.spec.ts b/packages/subagent/subagent/tests/continuation-inheritance.spec.ts index 1e539c28c2..2bc63888ae 100644 --- a/packages/subagent/subagent/tests/continuation-inheritance.spec.ts +++ b/packages/subagent/subagent/tests/continuation-inheritance.spec.ts @@ -74,8 +74,7 @@ describe('continuable policy inheritance', () => { it('seeds the parent sandbox override and pins approval to never', async () => { const { ctx, parent } = await setup([textResponse('child done')]) setSandboxMode(parent.session, 'danger-full-access') - // The parent keeps the interactive deployment default: the child pin must - // not depend on any parent approval override. + // No parent approval override: the child pin must not depend on one. expect(ctx.approval.overrideOf(parent.session)).toBeUndefined() let child: Agent | undefined ctx.on('agent/created', ({ agent }) => { @@ -95,11 +94,10 @@ describe('continuable policy inheritance', () => { { type: 'sandbox/mode', data: { mode: 'danger-full-access', source: 'delegation' } }, { type: 'approval/policy', data: { policy: 'never', source: 'delegation' } }, ]) - // Durable: a reload folds the same effective policy; the parent keeps its own. + // Durable: a reload folds the same effective policy. expect(effectiveSandboxMode(loaded.events)).toBe('danger-full-access') expect(effectiveApprovalPolicy(loaded.events)).toBe('never') expect(ctx.approval.overrideOf(parent.session)).toBeUndefined() - // The child's runtime-context snapshot states the fixed delegation scope. const runtimeContext = loaded.events.find( (event): event is SessionEvent<'user/message'> => event.type === 'user/message' && event.data.source.kind === 'plugin' diff --git a/packages/subagent/subagent/tests/continuation.spec.ts b/packages/subagent/subagent/tests/continuation.spec.ts index 6e23f6ccae..da3c474aec 100644 --- a/packages/subagent/subagent/tests/continuation.spec.ts +++ b/packages/subagent/subagent/tests/continuation.spec.ts @@ -103,7 +103,7 @@ function hasUserText(events: readonly SessionEvent[], text: string): boolean { && event.data.content.some(block => block.type === 'text' && block.text === text)) } -/** Every caller-supplied user-role message text in log order, for FIFO assertions (framework runtime-context snapshots excluded). */ +/** Caller-supplied user message texts in log order (runtime-context snapshots excluded). */ function userTexts(events: readonly SessionEvent[]): string[] { return events.flatMap(event => event.type === 'user/message' && event.data.source.kind !== 'plugin' ? event.data.content.flatMap(block => block.type === 'text' ? [block.text] : []) diff --git a/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts b/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts index c74fb94646..47d0fb3269 100644 --- a/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts +++ b/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts @@ -411,7 +411,7 @@ describe('dsh-tool-subagent-report', () => { }) }) -/** Prove report delivery uses ordinary logged user messages (framework runtime-context snapshots excluded). */ +/** Prove report delivery uses ordinary logged user messages (runtime-context snapshots excluded). */ function userTexts(events: readonly SessionEvent[]): string[] { return events.flatMap(event => event.type === 'user/message' && event.data.source.kind !== 'plugin' ? event.data.content.flatMap(block => block.type === 'text' ? [block.text] : [])