fix review findings: one frozen seed through the waterfall; stale agent/request docs
Codex diff review, round 1, two (A) findings: - The agent/request fallback resolved the RAW seed object — on later steps the session's cached header fold — so a delegating listener (await next(), mutate, return) could rewrite the fold in place and the change would compare as already-baseline: no delta logged, the persisted log unable to reconstruct the request (the dev invariant would fire on the divergence, but the log would still lie). One structuredClone'd, deep-frozen seed now serves both the listener chain and the fallback — in-place shaping after delegation throws — and Session.requestHeader() freezes its fold on update, so the leak class is unrepresentable from either side. Pinned by a loop-level delegating-mutator test. - Doc sweep for the old contract: agent README's event row (mutate GenerateOptions / tool filtering → frozen config seed, replacement out, logged header), compact-basic's module JSDoc (summarize routed through agent/request → direct one-shot at llm/stream), and architecture.md's event-domain line (request mutation → call-config shaping).
This commit is contained in:
@@ -240,6 +240,35 @@ describe('request stability across the loop', () => {
|
||||
expectPrefixExtension(adapter.requests[0]!, adapter2.requests[0]!)
|
||||
})
|
||||
|
||||
it('a delegating listener cannot mutate the seed through next() — the fold stays log-true', async () => {
|
||||
const adapter = new MockAdapter([textResponse('one'), textResponse('two')])
|
||||
const ctx = await harness(adapter)
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
ctx.on('agent/request', async (_agent, _turn, _step, _config, next) => {
|
||||
const config = await next()
|
||||
// next() resolves the SAME frozen seed — in-place shaping after
|
||||
// delegation is unrepresentable, so a "mutate what next() returned"
|
||||
// listener cannot desync the log from the request (nor reach the
|
||||
// session's cached header fold, which is deep-cloned away and itself
|
||||
// frozen).
|
||||
expect(Object.isFrozen(config)).toBe(true)
|
||||
expect(() => { (config as { temperature?: number }).temperature = 0.9 }).toThrow(TypeError)
|
||||
return config
|
||||
})
|
||||
|
||||
send(agent, 'first')
|
||||
await waitForIdle(ctx, agent)
|
||||
send(agent, 'second')
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
// No delta was logged (nothing really changed), and the session's own
|
||||
// fold is immutable state.
|
||||
expect(agent.session.events.filter(e => e.type === 'request/header-delta')).toHaveLength(0)
|
||||
expect(Object.isFrozen(agent.session.requestHeader())).toBe(true)
|
||||
expect(adapter.requests[1]!.temperature).toBeUndefined()
|
||||
})
|
||||
|
||||
it('THEOREM: every request rebuilds byte-equal from the session log alone', async () => {
|
||||
const adapter = new MockAdapter([
|
||||
toolCallResponse('c1', 'echo', { text: 'one' }, 'calling'),
|
||||
|
||||
Reference in New Issue
Block a user