refactor(agent-loop): simplify message machine

This commit is contained in:
_Kerman
2026-07-30 13:49:57 +08:00
parent d554ae3019
commit f2e20c1ef0
212 changed files with 1326 additions and 2382 deletions

View File

@@ -155,8 +155,8 @@ export class BasicCompactService extends CompactService {
}
})
ctx.on('agent/settled', (agent) => {
this.overflowRetries.delete(agent)
ctx.on('agent/status', (agent, status) => {
if (status === 'idle') this.overflowRetries.delete(agent)
})
// A successful response starts a fresh overflow-recovery sequence even
@@ -169,15 +169,11 @@ export class BasicCompactService extends CompactService {
ctx.on('agent/request-error', async (
agent,
_turn,
_step,
_error,
failure,
_priorFailures,
_retryPolicy,
context,
signal,
next,
) => {
const { failure } = context
if (failure.code !== CONTEXT_WINDOW_EXCEEDED_CODE || signal.aborted) return next()
this.overflowAgents.set(agent.session, agent)
const target = routedTarget(agent.session)

View File

@@ -104,7 +104,7 @@ function promptInput(text: string): SummarizationInput {
function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
const session = new Session(SessionId(`conversation-${turns}`))
for (let turn = 1; turn <= turns; turn += 1) {
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn })
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `${text} user ${turn}` }],
source: { kind: 'user' },
@@ -133,7 +133,6 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
}
session.append('turn/start', {
turn: turns + 1,
trigger: { kind: 'message', source: { kind: 'user' } },
})
return session
}
@@ -142,7 +141,7 @@ function toolConversation(): Session {
const session = new Session(SessionId('tools'))
for (let turn = 1; turn <= 3; turn += 1) {
const callId = CallId(`call-${turn}`)
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn })
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `request ${turn} `.repeat(300) }],
source: { kind: 'user' },
@@ -182,7 +181,7 @@ function toolConversation(): Session {
session.append('step/end', { turn, step: 1 })
session.append('turn/end', { turn, reason: { kind: 'completed' } })
}
session.append('turn/start', { turn: 4, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 4 })
return session
}
@@ -190,7 +189,7 @@ function toolConversation(): Session {
function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Session {
const session = new Session(SessionId(`oversized-tool-${chars}`))
const callId = CallId('oversized')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
if (withCompactablePrompt) {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'older history '.repeat(200) }],
@@ -227,7 +226,7 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 2 })
return session
}
@@ -474,7 +473,7 @@ describe('pressure measurement and retention', () => {
it('skips when no durable routed model exists instead of using AgentOptions fallback', async () => {
const compact = service(compactConfig)
const session = new Session(SessionId('headerless'))
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
await expect(compact.compactIfNeeded(agent(session, MODEL), 'pressure', SIGNAL))
.resolves.toBeNull()
expect(compact.calls).toHaveLength(0)
@@ -557,7 +556,7 @@ describe('pressure measurement and retention', () => {
const compact = service(compactConfig)
const session = new Session(SessionId('single-tool-pair'))
const callId = CallId('single-call')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
session.append('step/start', { turn: 1, step: 1 })
session.append('request/header', {
header: { config: { provider: MODEL, model: MODEL } },
@@ -647,7 +646,7 @@ describe('pressure measurement and retention', () => {
it('declines when envelope pressure is high but the surface has no compactable range', async () => {
const compact = service(compactConfig)
const empty = new Session(SessionId('empty'))
empty.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
empty.append('turn/start', { turn: 1 })
empty.append('request/header', {
header: { config: { provider: MODEL, model: MODEL }, system: 'x'.repeat(100_000) },
reason: 'initial',
@@ -723,7 +722,7 @@ describe('pressure measurement and retention', () => {
const ctx = createContext()
const session = new Session(SessionId('one-tool-pair'))
const callId = CallId('only')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
session.append('step/start', { turn: 1, step: 1 })
session.append('assistant/message', {
turn: 1,
@@ -1058,7 +1057,7 @@ describe('compaction region transaction', () => {
it('lets a model-independent custom summarizer compact without a conversation model', async () => {
const compact = service()
const session = new Session(SessionId('model-less-region'))
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'history '.repeat(100) }],
source: { kind: 'user' },
@@ -1665,7 +1664,6 @@ describe('automatic listener and loader composition', () => {
const session = new Session(SessionId('headerless-overflow'))
session.append('turn/start', {
turn: 1,
trigger: { kind: 'message', source: { kind: 'user' } },
})
await expect(recover(ctx, agent(session, MODEL), overflow())).resolves.toBe(false)

View File

@@ -190,7 +190,6 @@ function overflowHistorySeed(): SessionEvent[] {
const sentinel = turn === 1 ? 'OLD HISTORY SENTINEL' : 'RECENT HISTORY'
session.append('turn/start', {
turn,
trigger: { kind: 'message', source: { kind: 'user' } },
})
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `${sentinel} ${'old context '.repeat(200)}` }],

View File

@@ -38,7 +38,6 @@ function appendToolStep(
const callId = CallId(call)
session.append('turn/start', {
turn,
trigger: { kind: 'message', source: { kind: 'user' } },
})
session.append('step/start', { turn, step: 1 })
session.append('assistant/message', {
@@ -165,7 +164,6 @@ describe('ToolResultPruneService session transaction', () => {
})
session.append('turn/start', {
turn: 2,
trigger: { kind: 'message', source: { kind: 'user' } },
})
const result = service().pruneSession(session)
@@ -214,7 +212,6 @@ describe('ToolResultPruneService session transaction', () => {
appendToolStep(session, 3, 'c', [{ type: 'text', text: 'C'.repeat(80) }])
session.append('turn/start', {
turn: 4,
trigger: { kind: 'message', source: { kind: 'user' } },
})
const prune = service()
const first = prune.pruneSession(session)
@@ -231,7 +228,6 @@ describe('ToolResultPruneService session transaction', () => {
appendToolStep(session, 1, 'a', [{ type: 'text', text: 'A'.repeat(100) }])
session.append('turn/start', {
turn: 2,
trigger: { kind: 'message', source: { kind: 'user' } },
})
service().pruneSession(session)
const replay = new Session(session.id, [...session.events])
@@ -250,7 +246,6 @@ describe('ToolResultPruneService session transaction', () => {
expect(() => prune.pruneSession(session)).toThrow(/outside any open turn/)
session.append('turn/start', {
turn: 2,
trigger: { kind: 'message', source: { kind: 'user' } },
})
expect(() => prune.pruneSession(session)).not.toThrow()
})

View File

@@ -23,7 +23,7 @@ const summary = (overrides: Record<string, unknown> = {}) => ({
})
function startTurn(session: ReturnType<Context['sessions']['create']>, turn = 1): void {
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn })
}
describe('compaction invariants', () => {
@@ -45,7 +45,7 @@ describe('compaction invariants', () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('turn/start', { turn: 1 })
session.append('compact/start', { turn: 1 })
await ctx.plugin(InvariantService)
await ctx.plugin(CompactInvariant)
@@ -59,7 +59,7 @@ describe('compaction invariants', () => {
expect(() => {
ctx.emit('session/event', session, {
type: 'turn/start', seq: 0, time: 0,
data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
data: { turn: 1 },
})
ctx.emit('session/event', session, {
type: 'step/start', seq: 1, time: 1, data: { turn: 1, step: 1 },