fix(subagent): publish inherited policy facts to telemetry

Inherited sandbox and approval events were part of the constructor seed. Session.firstLiveSeq classifies every constructor event as replayed history, so telemetry adoption skipped these child-only creation facts even though no parent or prior process had exported them.

Capture the parent overrides at the same synchronous delegation boundary, but append the events during the child factory setup while the session is still unpublished. They remain ordered after fork history, persist with the first child batch, and retain last-event-wins behavior while landing on the live side of the telemetry boundary. This uses the existing setup and session append contracts instead of adding another seed category or telemetry special case.

Add regression coverage for exporting an unpublished suffix without re-exporting constructor history, assert the spawn and fork firstLiveSeq boundaries, and restore the public seed documentation to replay/fork history only.
This commit is contained in:
Tianyi Cui
2026-07-29 00:39:56 +08:00
parent 064205b54d
commit 739647afc0
15 changed files with 51 additions and 63 deletions
@@ -1,4 +1,4 @@
/** Policy inheritance through constructor-seeded child session events. */
/** Policy inheritance through child session events appended before publication. */
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { mkdtemp, readFile, realpath, rm } from 'node:fs/promises'
@@ -69,7 +69,7 @@ function toolResultTexts(agent: Agent): string[] {
}
describe('in-process policy inheritance', () => {
it('seeds parent overrides into a spawn child before its first request', async () => {
it('records parent overrides before publishing a spawn child', async () => {
const script: Script = []
const { ctx, parent } = await setupWalled(script)
const blocked = join(workspace, 'spawn-blocked.txt')
@@ -93,7 +93,7 @@ describe('in-process policy inheritance', () => {
{ type: 'sandbox/mode', seq: 0, data: { mode: 'read-only', source: 'delegation' } },
{ type: 'approval/policy', seq: 1, data: { policy: 'never', source: 'delegation' } },
])
expect(child.session.firstLiveSeq).toBe(2)
expect(child.session.firstLiveSeq).toBe(0)
expect(child.session.header.seedLength).toBeUndefined()
expect(ctx.sandboxPolicy.overrideOf(child.session)).toBe('read-only')
expect(ctx.approval.overrideOf(child.session)).toBe('never')
@@ -125,6 +125,7 @@ describe('in-process policy inheritance', () => {
const child = run.localAgent as Agent
expect(child.session.header.seedLength).toBe(1)
expect(child.session.firstLiveSeq).toBe(seed.length)
expect(child.session.events.filter(event => event.type === 'sandbox/mode')).toMatchObject([
{ seq: 0, data: { mode: 'workspace-write' } },
{ seq: 1, data: { mode: 'read-only', source: 'delegation' } },