docs(subagent): trim comments in the delegated-policy additions
This commit is contained in:
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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] : [])
|
||||
|
||||
@@ -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] : [])
|
||||
|
||||
Reference in New Issue
Block a user