refactor(agent): replace the per-step advice seam with agent/session-prefix
Review discussion converged on the industry shape (Claude Code caches user context per conversation; Codex separates initial context from diffs; Kimi appends at continuation boundaries to protect prompt caching): stable openers belong in a compose-once prefix, mid-session changes belong in append-only history — not in a per-request slot. agent/session-prefix fires ONCE per loop instance, lazily on its first request-building step: the composed Message[] is deep-frozen, cached on the transmission bookkeeping, recorded as EpochHeader.messagePrefix on the anchoring 'initial'/'resume' snapshot, and reused verbatim for every request the instance sends — prefix stability is structural, not a producer discipline, and a resume recomposes with attributable drift. The request is messagePrefix + boundary snapshot. The per-step RequestAdvice/RequestAdviceContext surface and the messageSuffix header field are dropped: the tail slot had no consumer, and every current update pattern (new AGENTS.md discovered, memory update, skills change) routes through the existing append-only history channels — inject(), tools/post-execute additionalContext, prompt-submit additionalContext — each paid once and prefix-cached thereafter. The messagePrefix delta arm stays for codec totality; the loop never produces one in practice.
This commit is contained in:
@@ -707,19 +707,18 @@ describe('request-reconstruction cross-check (llm/stream)', () => {
|
||||
expect(() => { dispatch(ctx, options) }).not.toThrow()
|
||||
})
|
||||
|
||||
it('expects the folded header\'s request-only messages to frame the derivation (prefix + derived + suffix)', async () => {
|
||||
it('expects the folded header\'s session prefix ahead of the derivation (prefix + derived)', async () => {
|
||||
const { ctx, session, boundary } = await requestSetup()
|
||||
const prefix = { role: 'user' as const, content: [{ type: 'text' as const, text: '<system-reminder>catalog</system-reminder>' }] }
|
||||
const suffix = { role: 'user' as const, content: [{ type: 'text' as const, text: 'trailing note' }] }
|
||||
session.append('request/header-delta', { messagePrefix: [prefix], messageSuffix: [suffix] })
|
||||
// The framed request matches the fold…
|
||||
const framed = Object.freeze({ model: 'm', messages: Object.freeze([prefix, ...boundary, suffix]), sessionId: session.id })
|
||||
expect(() => { dispatch(ctx, framed) }).not.toThrow()
|
||||
// …a request that DROPPED the logged framing diverges…
|
||||
session.append('request/header-delta', { messagePrefix: [prefix] })
|
||||
// The prefixed request matches the fold…
|
||||
const prefixed = Object.freeze({ model: 'm', messages: Object.freeze([prefix, ...boundary]), sessionId: session.id })
|
||||
expect(() => { dispatch(ctx, prefixed) }).not.toThrow()
|
||||
// …a request that DROPPED the logged prefix diverges…
|
||||
const bare = Object.freeze({ model: 'm', messages: Object.freeze([...boundary]), sessionId: session.id })
|
||||
expect(() => { dispatch(ctx, bare) }).toThrow(/diverges from the boundary derivation/)
|
||||
// …and so does one that misplaced it (suffix sent as a prefix).
|
||||
const misplaced = Object.freeze({ model: 'm', messages: Object.freeze([suffix, prefix, ...boundary]), sessionId: session.id })
|
||||
// …and so does one that misplaced it (prefix sent after the history).
|
||||
const misplaced = Object.freeze({ model: 'm', messages: Object.freeze([...boundary, prefix]), sessionId: session.id })
|
||||
expect(() => { dispatch(ctx, misplaced) }).toThrow(/diverges from the boundary derivation/)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user