一行行拆解 agent-loop:AI Agent 的"思考循环"到底是怎么转的
ä¸è¡è¡æè§£ agent-loopï¼AI Agent ç"æè循ç¯"å°åºæ¯æä¹è½¬ç
ææµªç« 2026-08-20 0 é 读11åé第5ç« ï¼Turn / Step 循ç¯ââagent-loop æºç æè§£
ç³»åï¼DeepSeek Harness æºç 宿 ï½ è¿åº¦ 5/16
åæä»åºï¼github.com/deepseek-aiâ¦
䏿¬¡æ¨¡å请æ±éèçåå 个äºä»¶ãåç§å忍¡å¼ãä¸ä¸ªç¶ææºåä¸å¥å¹¶åè°åº¦å¨ã
ä¸ä¸ç« æä»¬çäº Session æ¥å¿çæ°æ®ç»æï¼ç¥éäºäºä»¶æ¯æä¹åçãè¿ä¸ç« æ¥çäºä»¶æ¯æä¹äº§ççââæ·±å
¥ packages/core/agent-loop/ çæºç ï¼æè§£é©±å¨æ´ä¸ª agent è¿è½¬ç弿ã
ææ¯ææµªç«ï¼è¿ä¸ç« ä¿¡æ¯å¯åº¦å¾é«ï¼å»ºè®®å稳ã
5.1 turn å step çå ³ç³»ï¼æ§å¶è¾¹ç䏿¨¡å请æ±
dsh ç agent å¾ªç¯æä¸¤ä¸ªå±æ¬¡ï¼
turnï¼è½®æ¬¡ï¼ æ¯æ§å¶è¾¹çãä¸ä¸ª turn ä»ç¨æ·è¾å
¥å°è¾¾å¼å§ï¼å° agent å³å®åæ¢ç»æãä¸ä¸ª turn å¯ä»¥å
å«é¶ä¸ªæå¤ä¸ª stepãturn çç»æç± turn-stopping äºä»¶å³å®ââå¦ææ²¡ææ°æ¶æ¯è¿å
¥ next-step inboxï¼agent å°±ä¼åæ¢ã
stepï¼æ¥éª¤ï¼ æ¯æ¨¡å请æ±è¾¹çãä¸ä¸ª step = 䏿¬¡æ¨¡åè°ç¨ + è¿æ¬¡è°ç¨è¯·æ±çææå·¥å ·æ§è¡ã模åè¿åçæ¯ä¸æ¡ tool_callï¼å·¥å ·è°ç¨ï¼é½ä¼å¨å½å step å 被è°åº¦æ§è¡ï¼ç»æä½ä¸ºä¸ä¸æ¥çè¾å ¥ã
æ¥çä¸ä¸ªå¤ step ç turn 卿¥å¿éçæ ·åï¼
turn/start (turn=1)
â
âââ step/start (turn=1, step=1)
â user/message â "帮æä¿® bug"
â system-prompt/assemble waterfall â ç»è£
æç¤ºè¯ç段 + å·¥å
· schema
â agent/request waterfall â {provider, model, ...}
â llm/stream waterfall â æµå¼åç
â assistant/chunk à N â æµå¼åç
â assistant/message â ç»è£
åç宿´åå¤ï¼å« sourceEventSeqs æå chunksï¼
â tool/call â fs_read
â tool/result â æä»¶å
容
â step/end
â
âââ step/start (turn=1, step=2)
â user/message â å·¥å
·ç»æä½ä¸ºæ°è¾å
¥
â system-prompt/assemble â éæ°ç»è£
â agent/request â å¯è½æ´æ°é
ç½®
â llm/stream â æµå¼
â assistant/chunk à N
â assistant/message â "ææ¾å°äº bug"
â tool/call â fs_edit
â tool/result â ç¼è¾æå
â step/end
â
âââ step/start (turn=1, step=3)
â user/message â å·¥å
·ç»æ
â assistant/chunk à N
â assistant/message â "bug 已修å¤"ï¼æ tool_callï¼
â step/end
â
â¼
agent/turn-stopping (serial) â æ next æ¶æ¯ â ç¡®è®¤åæ¢
turn/end (turn=1, reason=completed)
step 3 没æäº§ç tool_callï¼è¿æå³ç模å认为任å¡å®æäºãconcludesTurn æ 记为 trueï¼turn æ£å¸¸ç»æã
对æ¯è¡¨ï¼
| 维度 | turn | step |
|---|---|---|
| æ¯ä»ä¹ | æ§å¶è¾¹ç | 模å请æ±è¾¹ç |
| æ°éå ³ç³» | 1 个 turn å« 0â¦N 个 step | 1 个 step å« 1 次模åè°ç¨ + 0â¦N ä¸ªå·¥å ·è°ç¨ |
| å¼å§æ¡ä»¶ | ç¨æ·è¾å ¥å°è¾¾ inbox | pre-step å³ç为 enter |
| ç»ææ¡ä»¶ | turn-stopping éè¿ä¸æ æ°æ¶æ¯ | 模åè¿åæ tool_call æææå·¥å ·æ§è¡å®æ¯ |
| å¯è¢«ä¸æ¢ | æ¯ï¼abortï¼ | æ¯ï¼abortï¼ |
5.2 inbox æºå¶ï¼æ¶æ¯çéåä¸é¢å
卿·±å ¥ turn ä¹åï¼å çæ¶æ¯æ¯æä¹å°è¾¾ agent çã
dsh ç agent æä¸ä¸ª Inboxï¼æ¶ä»¶ç®±ï¼ï¼å®æ¯æ¶æ¯éåçæ½è±¡ãæ¶æ¯ä¸æ¯ç´æ¥å¡è¿æ¨¡å请æ±çï¼èæ¯å è¿å ¥ inboxï¼ç± agent 循ç¯å¨åéçæ¶æºé¢åï¼claimï¼ã
Inbox æä¸¤ä¸ªç®æ æ§½ä½ï¼
| æ§½ä½ | 说æ |
|---|---|
| next-turn | ä¸ä¸ä¸ª turn å¼å§æ¶é¢åçæ¶æ¯ |
| next-step | å½å turn çä¸ä¸ä¸ª step é¢åçæ¶æ¯ |
å½ç¨æ·å䏿¡æ¶æ¯æ¶ï¼å®è¿å
¥ next-turn æ§½ä½ãagent 循ç¯å¼å§ä¸ä¸ªæ° turn æ¶ï¼ä» next-turn é¢åæ¶æ¯ãå·¥å
·æ§è¡äº§ççä¸ä¸ææ¶æ¯ï¼æä»¶åæ´éç¥çï¼è¿å
¥ next-step æ§½ä½ï¼å½å turn çä¸ä¸ä¸ª step é¢åã
æ¥ç inbox çä¸ä¸ªäºä»¶ï¼é½æ¯ emit 模å¼ï¼
// packages/core/agent-loop/src/agent.tsï¼èéï¼
this.inbox = new Inbox(session, {
inserted: (message) => { this.dispatch.emit('agent/inbox/inserted', { message }) },
discarded: (message) => { this.dispatch.emit('agent/inbox/discarded', { message }) },
claimed: (message, turn) => { this.dispatch.emit('agent/inbox/claimed', { message, turn }) },
})
insertedï¼æ¶æ¯è¢«æå ¥ inboxdiscardedï¼æ¶æ¯è¢«ä¸¢å¼ï¼æ¯å¦è¢«æ°æ¶æ¯æ¤æï¼claimedï¼æ¶æ¯è¢« agent é¢å
æ¥ç send æ¹æ³ï¼çè§£æ¶æ¯å¦ä½è¿å
¥ inboxï¼
// packages/core/agent-loop/src/agent.tsï¼èéï¼
send(message: UserMessage, target: InboxTarget, wakeup: boolean): void {
const wakingAfterAbort = wakeup && this.phase.kind !== 'idle' && this.phase.abort.signal.aborted
const resolvedTarget = wakingAfterAbort ? 'next-turn' : target
this.inbox.splice(resolvedTarget, Infinity, 0, [message])
if (wakeup) this.wakeDriver(wakingAfterAbort)
}
注æ wakingAfterAbort çé»è¾ï¼å¦ææ¶æ¯æ¯ wakingï¼å¤éï¼ç±»åï¼ä¸å½å phase 䏿¯ idle ä¸ abort 已触åï¼ä¸ä¸æ¬¡ turn è¢«ä¸æ¢äºï¼ï¼é£ä¹è¿æ¡æ¶æ¯ä¼è¢«éå®åå° next-turn æ§½ä½ãå 为 waking æ¶æ¯ä¸è½å å
¥ä¸ä¸ªå·²è¢«ä¸æ¢çæ´»å¨ï¼å®å¿
é¡»å¯å¨ä¸ä¸ä¸ª turnã
éå¥ï¼inbox 䏿¯ç®åç FIFOï¼First In First Outï¼å è¿å åºï¼éåï¼èæ¯ä¸ä¸ªæä¼å 级åç®æ æ§½ä½çè°åº¦ç»æã
5.3 Phase ç¶ææºï¼idle / maintenance / running
agent-loop çæ ¸å¿æ¯ä¸ä¸ªç¶ææºï¼ç§°ä¸º Phaseãæ¥çå®ä¹ï¼
// packages/core/agent-loop/src/agent.tsï¼èéï¼
type Phase =
| { kind: 'idle'; lastTurn: number }
| {
kind: 'maintenance'
abort: AbortController
lastTurn: number
wakeRequested: boolean
}
| { kind: 'running'; abort: AbortController; turn: number; step: number; wakeRequested: boolean }
ä¸ä¸ªç¶æï¼
| ç¶æ | å«ä¹ | 坿§è¡æä½ |
|---|---|---|
| idle | 空é²ï¼æ²¡ææ£å¨æ§è¡ç turn | çå¾ æ°æ¶æ¯å¤é |
| maintenance | ç»´æ¤ä¸ï¼æ£å¨å turn ç»æåçæ¸ ç | å¯è¢«å¤éè¯·æ±ææ |
| running | æ£å¨æ§è¡ä¸ä¸ª turn çæä¸ª step | å¯è¢« abort 䏿¢ |
ç¶æè½¬æ¢ï¼
idle ââwakeupâââ¶ running
â
â turn ç»æ
â¼
maintenance
â
â æ¸
ç宿
â¼
idle
â
â wakeup during maintenance
â¼
runningï¼æ° turnï¼
æ¥ç setPhase æ¹æ³ï¼å®è´è´£ç¶æåæ¢åå¤é¨éç¥ï¼
// packages/core/agent-loop/src/agent.tsï¼èéï¼
private setPhase(next: Phase): void {
const previousStatus = this.status
this.phase = next
const status = this.status
if (status !== previousStatus) {
this.dispatch.emit('agent/status', { status })
}
}
get status(): AgentStatus {
return this.phase.kind === 'idle' || this.phase.kind === 'maintenance' ? 'idle' : 'running'
}
å¤é¨å¯è§çç¶æåªæä¸¤ç§ï¼idle å runningãmaintenance 对å¤è¡¨ç°ä¸º idleï¼å 为ä¸å¨æ§è¡æ¨¡å请æ±ï¼ï¼ä½å
é¨åºåäº maintenance å idle æ¯ä¸ºäºå¤çãæ¸
çè¿ç¨ä¸æ¶å°æ°æ¶æ¯ãçæ
åµã
5.4 äºä»¶æµæè§£ï¼pre-step / request / stream / turn-stopping
ç°å¨æ¥çä¸ä¸ªå®æ´ turn çäºä»¶æµã代ç å¨ agent.ts ç turn() æ¹æ³ä¸ã
turn å¼å§
// packages/core/agent-loop/src/agent.tsï¼èéï¼
private async turn(): Promise<boolean> {
const phase = this.phase
const { signal } = phase.abort
signal.throwIfAborted()
const turn = phase.turn + 1
this.session.append('turn/start', { turn })
phase.turn = turn
let turnEnds: TurnEndReason | null = null
let target: InboxTarget = 'next-turn'
turn å¼å§æ¶ï¼å
éå¢ turn åºå·ï¼è¿½å turn/start äºä»¶å°æ¥å¿ãtarget åå§ä¸º next-turnï¼è¡¨ç¤ºä» next-turn æ§½ä½é¢åæ¶æ¯ã
pre-stepï¼å³å®è¦ä¸è¦è¿å ¥è¿ä¸ª step
while (true) {
signal.throwIfAborted()
const step = phase.step + 1
const decision = await this.preStep(target, { turn, step })
if (decision.kind === 'reject') {
turnEnds = { kind: 'blocked' }
return false
}
if (turnEnds && decision.messages.length === 0) break
preStep æ¹æ³éåå agent/pre-step waterfall äºä»¶ï¼
private async preStep(target: InboxTarget, position: { turn: number; step: number }): Promise<PreparedStep> {
const claimed = this.inbox.claim(target, position.turn)
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal))
const decision = await this.dispatch.waterfall(
'agent/pre-step', { messages: claimed, ...position, signal },
(): Promise<PreStepDecision> => Promise.resolve<PreStepDecision>({
kind: 'enter',
messages: context === undefined ? claimed : [...claimed, context],
}),
)
return decision.kind === 'reject' ? decision : { ...decision, assembly }
}
æµç¨æ¯ï¼ä» inbox é¢åæ¶æ¯ â ç»è£ system prompt â åå pre-step waterfallï¼é»è®¤è¿å enterï¼ã妿çå¬å¨è¿å rejectï¼turn 以 blocked ç»æã
step æ§è¡ï¼è¯·æ±æ¨¡å + æµå¼è¾åº + å·¥å ·è°ç¨
è¿å
¥ step åï¼å
追å step/start äºä»¶ï¼ç¶åææ¶æ¯åå
¥æ¥å¿ï¼åå起模å请æ±ï¼
this.session.append('step/start', { turn, step })
for (const message of decision.messages) {
this.session.append('user/message', message, { surfaceOp: 'append' })
}
const stepEnd = await this.step(decision.assembly)
step() æ¹æ³å
é¨ï¼èéå
³é®é¨åï¼ï¼
private async step(assembly: PromptAssembly): Promise<StepEndReason | null> {
const { request, preparedCall } = await this.buildRequest(
turn, step, assembly.tools, system, this.session.deriveMessages(), signal,
)
const assembler = new BlockAssembler()
const chunkSeqs: number[] = []
const stream = preparedCall?.stream(request) ?? this.loopCtx.llm.stream(request)
for await (const chunk of stream) {
chunkSeqs.push(this.session.append('assistant/chunk', { turn, step, chunk }).seq)
assembler.push(chunk)
}
const finish = assembler.finish
// ... é误å¤ç ...
const message = createAssistantMessage({ content: assembler.blocks(), source: { ... } })
this.session.append('assistant/message', { turn, step, message, usage }, { surfaceOp: 'append', sourceEventSeqs: chunkSeqs })
if (finish.kind === 'max-tokens') return { kind: 'max-tokens' }
const toolCalls = message.content.filter(block => block.type === 'tool-call')
if (toolCalls.length === 0) return { kind: 'completed' }
const { concluded } = await executeToolCalls(this.loopCtx, turn, step, toolCalls, signal, context => this.inbox.splice('next-step', ...))
return concluded ? { kind: 'completed' } : null
}
è¿æ®µä»£ç åäºä»¥ä¸äºæ ï¼
buildRequestï¼ç»è£ 模å请æ±ï¼ä¸ä¸è详述ï¼- æµå¼è¯»åï¼
for await (const chunk of stream)é chunk 追å å°æ¥å¿ - ç»è£
æ¶æ¯ï¼
BlockAssembleræ chunk ç»è£ æå®æ´ç assistant message - è¿½å æ¶æ¯äºä»¶ï¼
assistant/message带sourceEventSeqsæå产çå®ç chunk åºåå· - å¤æç»æåå ï¼max-tokensï¼è¾¾å° token ä¸éï¼ãcompletedï¼æ tool_callï¼ãnullï¼æ tool_callï¼ç»§ç»ï¼
- æ§è¡å·¥å
·è°ç¨ï¼
executeToolCallsè°åº¦å·¥å ·ï¼ä¸ä¸è详述ï¼
buildRequestï¼é ç½®å¦ä½å³å®
private async buildRequest(turn, step, tools, system, boundaryMessages, signal): Promise<{ request, preparedCall }> {
const persistedHeader = session.requestHeader()
const route = { provider: this.options.provider ?? '', model: this.options.model ?? '' }
// ... æå»º seedConfig ...
const proposedConfig = await this.dispatch.waterfall(
'agent/request', { turn, step, signal },
() => Promise.resolve(seedConfig),
)
if (!proposedConfig.provider || !proposedConfig.model) {
throw new Error(`agent has no provider/model`)
}
preparedCall = await this.loopCtx.llm.prepareCall(proposedConfig, signal)
// ... 追å request/header äºä»¶ ...
}
agent/request æ¯ waterfall äºä»¶ï¼çå¬å¨å¯ä»¥ä¿®æ¹ providerãmodelãreasoningEffortï¼æ¨çå度ï¼çé
ç½®ãæç»å¦ææ²¡æ provider æ modelï¼æéã
turn-stoppingï¼å³å®è¦ä¸è¦å䏿¥
å½ step ç»æä¸æ turnEndsï¼ç»æåå ï¼æ¶ï¼æ£æ¥ next-step inboxï¼
if (turnEnds && this.inbox.nextStep.length === 0) {
await this.dispatch.serial('agent/turn-stopping', { turn, signal })
signal.throwIfAborted()
}
if (turnEnds && this.inbox.nextStep.length === 0) break
target = 'next-step'
注æé»è¾ï¼å
æ£æ¥ inbox æ¯å¦ä¸ºç©º â åå turn-stopping serial äºä»¶ â 忬¡æ£æ¥ inboxã为ä»ä¹æ£æ¥ä¸¤æ¬¡ï¼å 为 serial äºä»¶ççå¬å¨å¯è½å¾ inbox é塿¶æ¯ï¼æ¯å¦ä¸ä¸ªãè¿æäºæ
没åå®ãççå¬å¨æ³¨å
¥ä¸æ¡ç»è·æ¶æ¯ï¼ãå¦æç¬¬äºæ¬¡æ£æ¥æ¶ inbox ä¸ä¸ºç©ºäºï¼turn å°±ä¸ä¼åã
宿´äºä»¶æµå¾
turn/start
â
â¼
agent/pre-step waterfall ââ reject âââ¶ turn/end (blocked, æ step)
â enter
â¼
step/start
â
âââ user/message à Nï¼é¢åçæ¶æ¯ + 注å
¥çä¸ä¸æï¼
âââ system-prompt/assemble waterfallï¼ç»è£
æç¤ºè¯ç段 + å·¥å
· schemaï¼
âââ agent/request waterfallï¼å¯ä¿®æ¹ provider/model é
ç½®ï¼
âââ llm/stream waterfall â assistant/chunk à Nï¼durableï¼
âââ assistant/messageï¼ç»è£
ç»æ + sourceEventSeqs ååºç²¾ç¡®ç chunk seqï¼å«ç©ºå表ï¼
â
âââ æ tool_callï¼
â âââ æ¯ âââ¶ executeToolCalls
â â âââ tool/call à Nï¼durableï¼æ§è¡åè®°å½ï¼
â â âââ tools/pre-execute â tools/execute â tools/post-executeï¼ä¸ä¸ª waterfallï¼
â â âââ tool/result à Nï¼durableï¼å䏿¨¡åå¯è§ç»æï¼
â â âââ ç»æè¿å
¥ next-step inbox
â âââ å¦ âââ¶ stepEnd = completed
â
â¼
step/end
â
â¼
turnEnds æå¼ ä¸ next-step inbox 为空ï¼
âââ æ¯ âââ¶ agent/turn-stopping (serial, æ next()) âââ¶ åæ¬¡æ£æ¥ inbox
â â âââ ä»ä¸ºç©º âââ¶ break
â â âââ ä¸ä¸ºç©º âââ¶ ç»§ç»ï¼target = next-stepï¼
âââ å¦ âââ¶ ç»§ç»ï¼target = next-stepï¼
â
â¼
turn/end
5.5 å·¥å ·å¹¶åè°åº¦ï¼barrier / rolling pool / abort å¤ç
å·¥å
·è°ç¨ä¸æ¯ä¸²è¡æ§è¡çãdsh æä¸ä¸ªå¹¶åè°åº¦å¨ï¼å¨ packages/core/agent-loop/src/tool-calls.ts ä¸å®ç°ã
è°åº¦æ¨¡å
æ¯ä¸ªå·¥å ·è°ç¨æä¸ä¸ª execution modeï¼æ§è¡æ¨¡å¼ï¼ï¼
| æ¨¡å¼ | 说æ |
|---|---|
| exclusive | ç¬å æ§è¡ï¼å½¢æä¸ä¸ª barrierï¼å±éï¼ï¼åç»è°ç¨å¿ é¡»çå®å®æ |
| parallel | å¯å¹¶è¡æ§è¡ï¼è¿å ¥ rolling poolï¼æ»å¨æ± ï¼ |
è°åº¦å¨çæ ¸å¿é»è¾ï¼
// packages/core/agent-loop/src/tool-calls.tsï¼èéï¼
export async function executeToolCalls(ctx, turn, step, toolCalls, signal, acceptContext) {
const agent = ctx.agents.requireInitiator()
const planned: PlannedCall[] = toolCalls.map(block => ({
block,
exec: { callId: block.id, name: block.name, arguments: parseArguments(block.arguments), agent, signal },
}))
let next = 0
let concluded = false
while (next < planned.length) {
const first = planned[next]
const mode = ctx.tools.executionMode(first.exec).kind
const group = mode === 'parallel' ? planned.slice(next) : [first]
const outcome = await runGroup(ctx, turn, step, group, mode, signal, acceptContext)
next += outcome.consumed
concluded ||= outcome.concluded
if (outcome.aborted) {
for (const call of planned.slice(next)) appendSkippedToolCall(session, turn, step, call.block)
return { concluded }
}
}
return { concluded }
}
æµç¨æ¯ï¼
- æææ tool_call 转æ PlannedCall
- ä»ä¸ä¸ä¸ªæªæ§è¡çè°ç¨å¼å§ï¼æ£æ¥å®ç execution mode
- å¦ææ¯ parallelï¼æå®åå颿æ parallel è°ç¨ç»æä¸ç»ï¼è¿ rolling pool
- å¦ææ¯ exclusiveï¼åç¬ä¸ç»ï¼å½¢æ barrier
- æ§è¡è¿ä¸ç»ï¼çå®å®æååç»§ç»
- 妿 abort äºï¼ä¸ºæææªå¯å¨çè°ç¨è¿½å åæçè·³è¿äºä»¶
rolling pool çæäº¤é¡ºåº
å ³é®è®¾è®¡ï¼dispatch å¯ä»¥éå ï¼ä½ç»æææ¨¡åé¡ºåºæäº¤ã
æ¥ç runGroup çæ ¸å¿é»è¾ï¼
// packages/core/agent-loop/src/tool-calls.tsï¼èéï¼
async function runGroup(ctx, turn, step, group, mode, signal, acceptContext): Promise<GroupOutcome> {
const slots: (Slot | undefined)[] = group.map(() => undefined)
let committed = 0
let nextToStart = 0
const commitReady = async (): Promise<void> => {
while (committed < group.length) {
const slot = slots[committed]
if (slot === undefined) break // éå°æªå®æç slotï¼åæ¢æäº¤
const result = slot.needsPost
? await ctx.tools[TOOL_RUNTIME_SCHEDULER].finalize(slot.exec, slot.result)
: ctx.tools[TOOL_RUNTIME_SCHEDULER].finish(slot.exec, slot.result)
appendToolResult(session, turn, step, group[committed].block, result, callSeqs[committed])
committed++
}
}
committed åªå¨è¿ç»ç已宿 slot ä¸åè¿ã妿 slot[2] 宿äºä½ slot[1] è¿å¨è·ï¼slot[2] çç»æä¸ä¼å
æäº¤ãè¿ä¿è¯äºæ¥å¿ä¸ç tool/result äºä»¶é¡ºåºä¸æ¨¡åç»åºç tool_call 顺åºä¸è´ã
为ä»ä¹è¿å¾éè¦ï¼å 为模åçä¸ä¸æ¥è¯·æ±ä¼ä»æ¥å¿æ´¾çæ¶æ¯åå²ï¼å·¥å ·ç»æç顺åºä¼å½±å模åççè§£ãå¦æé¡ºåºä¹±äºï¼æ¨¡åå¯è½ä¼å°æã
abort å¤ç
å½ abort è§¦åæ¶ï¼
- 忢å¯å¨æ°è°ç¨
- çå¾ å·²å¯å¨çè°ç¨å®æï¼drainï¼
- 为已å¯å¨çè°ç¨æ£å¸¸æäº¤ç»æ
- 为æªå¯å¨çè°ç¨è¿½å åæçè·³è¿äºä»¶ï¼
TOOL_ABORTED_BEFORE_DISPATCHï¼
åæè·³è¿äºä»¶æ¯ä¸ºäºä¿ææ¥å¿ç宿´æ§ââæ¯ä¸ª tool/call é½è¦æå¯¹åºç tool/resultï¼è¿æ ·åæ¾æææã
æ¥ç代ç ï¼
// packages/core/agent-loop/src/tool-calls.tsï¼èéï¼
if (outcome.aborted) {
for (const call of planned.slice(next)) appendSkippedToolCall(session, turn, step, call.block)
return { concluded }
}
appendSkippedToolCall 为æ¯ä¸ªæªå¯å¨çè°ç¨è¿½å tool/call åä¸ä¸ªå¸¦ TOOL_ABORTED_BEFORE_DISPATCH é误ç ç tool/resultã
éå¥ï¼è°åº¦å¨ä¸åªæ¯ãè·å·¥å ·ãï¼å®è¿è¦ä¿è¯æ¥å¿ç宿´æ§ãæ¯ä¸ª call é½è¦æ resultï¼abort äºä¹è¦è¡¥ä¸ã
max-tokens ç sticky è¯ä¹
è¿æä¸ä¸ªç»èå¼å¾æ³¨æãå¨ turn() æ¹æ³éæè¿æ®µæ³¨éåé»è¾ï¼
// max-tokens is sticky: once any step hits the ceiling, later steps
// that complete normally must not downgrade the turn outcome.
if (turnEnds === null || turnEnds.kind !== 'max-tokens') turnEnds = stepEnd
妿ä¸ä¸ª step å 为 max-tokensï¼è¾¾å° token ä¸éï¼ç»æäºï¼åç»ç step å³ä½¿æ£å¸¸å®æï¼completedï¼ï¼turn çç»æåå ä¹ä¿æ max-tokensãè¿æ¯ãstickyãï¼ç²æ§ï¼è¯ä¹ââä¸ä¸ªå·²ç»åççäºå®ï¼è¾¾å°äº token ä¸éï¼ä¸åºè¯¥è¢«åç»çæ£å¸¸å®ææ¹æã
æ¬ç« å°ç»
| è¦ç¹ | 说æ |
|---|---|
| turn | æ§å¶è¾¹çï¼å« 0â¦N 个 step |
| step | 模å请æ±è¾¹çï¼å« 1 次模åè°ç¨ + 0â¦N ä¸ªå·¥å ·è°ç¨ |
| inbox | æ¶æ¯éåï¼next-turn å next-step ä¸¤ä¸ªæ§½ä½ |
| Phase ç¶ææº | idle / maintenance / running |
| agent/pre-step | waterfall äºä»¶ï¼å¯ä¿®æ¹æ¶æ¯ææç»è¿å ¥ step |
| agent/request | waterfall äºä»¶ï¼å¯ä¿®æ¹æ¨¡å请æ±é ç½® |
| agent/turn-stopping | serial äºä»¶ï¼é¡ºåºæ§è¡ï¼çå¬å¨å¯æ³¨å ¥ç»è·æ¶æ¯ |
| æµå¼è¾åº | assistant/chunk é token è¿½å æ¥å¿ |
| sourceEventSeqs | assistant/message æå产çå®ç chunk åºåå· |
| å·¥å ·è°åº¦æ¨¡å | exclusiveï¼barrierï¼å parallelï¼rolling poolï¼ |
| æäº¤é¡ºåº | dispatch å¯éå ï¼ç»æææ¨¡åé¡ºåºæäº¤ |
| abort å¤ç | drain å·²å¯å¨è°ç¨ + 为æªå¯å¨è°ç¨åæè·³è¿äºä»¶ |
| max-tokens sticky | 䏿¦è§¦åï¼ä¸è¢«åç» completed é级 |
ä¸ç« é¢å
agent-loop ç¥éä»ä¹æ¶åè°ç¨å·¥å ·äºï¼ä½å·¥å ·å é¨å°åºæä¹æ§è¡ï¼æéæ£æ¥å¨åªåï¼è¶ æ¶éè¯æä¹å®ç°ï¼ç»ææä¹å¤çï¼
ä¸ä¸ç« ï¼æä»¬æ¥æ dsh çå·¥å ·æ§è¡æµæ°´çº¿ââä» pre-execute å° post-execute ç宿´é¾è·¯ã
ææ¯ææµªç«ï¼è¿ä¸ç« æ¯æ´ä¸ªç³»åæç¡¬æ ¸çé¨åä¹ä¸ï¼å»ºè®®åå¤çãä½ å¯¹ agent 循ç¯ç设计æä»ä¹çæ³ï¼è¯è®ºåºèèã
ç³»åè¿åº¦ï¼5/8 ï½ ä¸ä¸ç« ï¼å·¥å ·æ§è¡æµæ°´çº¿
Aitishiku.com