Skip to content

两个事件域

运行时事件分为两个域,选对域是第一步决策:

  • 持久化 session 事件turn/*step/*user/messageassistant/*tool/*)活在只追加的 session 日志上,并通过 session/event 广播。当事实必须跨重载存活时用它。
  • 活跃 agent/* 事件携带活跃的 Agent 并按作用域过滤到它。用来观察或拦截进行中的工作。

本页是"活跃"那一半。持久化序列见仓库的 docs/agent-lifecycle.md

turn 与 step 术语

来自 docs/architecture.md

  • step 是一次模型请求加上它调用的工具。
  • turn 是零个或多个 step:在它的第一个输入被认领之前打开,在不再亏欠任何响应时关闭。

持久化边界是 turn/startstep/start → … → step/endturn/end,由驱动器通过 session.append 记录。被拒绝或为空的首次认领仍会关闭一个没有消耗任何 step 的持久化 turn,因此日志记录了这次尝试。

Agent 接口

packages/core/agent/src/runtime-types.ts 定义公开的 Agent

成员含义
idsession 共享的单一身份
optionsprovider 路由与模型(providermodelmaxTokens
session活跃 session;其日志是持久化的事实来源
inboxagent 拥有的持久化待处理工作的投影
status'idle' | 'running'
ctxagent 作用域上下文;贡献在销毁时展开
send(message, target, wakeup)把输入路由到某个 inbox 边界,可选唤醒驱动器
followup(message)排队一次普通跟进 turn 并唤醒
steer(message)为最近的一个 step 提交转向(steering)
inject(message)为下一次 pre-step 排队模型面对的上下文,不唤醒
cancel(cause, options)中止活跃 turn 或 turn 间任务
whenIdle()在整个 agent 活动达到静默后 resolve
runMaintenance(task)从真正的 idle 阶段运行一个非 turn 维护任务

生命周期状态

AgentStatus 只有两个值:'idle'(无驱动器活跃)与 'running'(唤醒输入已开始可取消的 pre-step 处理,驱动器正在排空、关闭或 checkpoint turn)。销毁从注册表移除 agent——它不是第三个可观察状态。

活跃事件(来自 packages/core/agent/src/runtime-types.ts):

事件模式含义
agent/createdemit一个完全配置好的 agent + 活跃 session 已发布
agent/disposedemit一个 agent 离开了注册表
agent/statusemit状态在 idlerunning 间翻转
agent/inbox/insertedemit一条消息进入了活跃 inbox
agent/inbox/claimedemit一条消息在其打开的 turn 内离开了 inbox
agent/inbox/discardedemit一条消息从活跃 inbox 被丢弃
agent/session-startemitsession 生命周期开始,在首个 turn 之前发生一次(sourcestartup/resume/clear/compact
agent/pre-stepwaterfall拒绝一个拟议 step 或替换进入它的消息
agent/requestwaterfall替换冻结的调用配置
agent/request-errorwaterfall在重试/关闭前处理一次失败的模型请求尝试
agent/turn-stoppingserialturn 即将关闭;反对的监听者可以 steer
agent/erroremit某个 step 或 turn 出错

Waterfall 事件(pre-steprequestrequest-error)与 llm/stream,以及三个 tools/* 事件,都要求监听者调用 next() 来委托;agent/turn-stopping 是 serial 且没有 next()

Scope key = 活跃 agent

packages/core/scope/src/index.ts 是让按 agent 事件路由安全的库。key 就是 agent 对象本身:

  • createScope(ctx, key) 铸造一个带不透明 ScopeKey 标签的 Context,外加精确/共享 disposer。
  • scopeTarget(base, key) 构建一个仅路由的 carrier,其过滤器放行匹配该 key 或任一祖先bindScopeParent)的监听者。事件沿链向上流动、从不向下——因此一个常驻组合可以观察在其下组合出的每个 agent。
  • scopeOf(ctx) 读取最近的 scope 标签。

packages/core/agent-loop/src/agent.ts 中,每个 ReactLoopAgent 铸造自己的 scope 并派生 agent.ctx

ts
this.scope = createScope(loopCtx, this)
this.ctx = this.scope.ctx.extend({ agent: this })
this.dispatch = agentEvents(loopCtx, this)

scope 正是"setup 窗口"组合作用域化世界的地方:通过 agent.ctx 注册的一切(作用域工具、prompt 段、restrict()、监听者、await 的子插件)都是 agent 局部的,在首次 prompt 组装之前就已存在,并在销毁时展开。

事件分发模型

packages/core/agent/src/dispatch.ts 把 agent 主体与其 scope carrier 耦合,使二者无法分叉:

ts
export function agentEvents(ctx, agent, carrier = agentCarrier(agent)): AgentEventDispatch {
  // 通过 emit / serial / waterfall 融合分发,把 `agent` 注入 payload
}

carrier 作为 Cordis 监听者过滤器的 this 传入;emit 路径自己解析过滤后的回调集,并逐个监听者地包含同步抛出与返回 promise 的拒绝,因此通知无法否决生命周期进展。循环驱动器在 agent 构造函数中构建一次分发器,并在热路径上复用。

注册表主体事件——agent/createdagent/disposed,以及配对的 session/created/session/disposed/session/event——通过注册表(packages/core/agent/src/index.ts)分发,注册表按 carrier 过滤,因此 agent 作用域监听者只收到 this agent 的事件,而无作用域监听者全局观察。

Setup 窗口:CreateAgentOptions.setup

编程式创建经由 AgentRegistry.create → 注册的 AgentFactory@deepseek-ai/dsh-agent-loop)。工厂铸造 agentCtx在插入或宣布 session 或 agent 之前 await setup,因此观察者永远不会看到未完全配置的世界:

ts
setup?: (agentCtx: Context) => AgentSetupCommit | Promise<AgentSetupCommit | void> | void

通过 agentCtx 注册的一切(作用域工具、prompt 段/变量、监听者、await 的子插件)在 session/createdagent/createdagent/session-start 以及首次 prompt 组装之前就已存在。Setup 只做组合,从不驱动——只在创建 resolve 之后驱动 agent。setup 抛出、commit 抛出或属主销毁都会回滚 scope,且不发布任何一个 id。

resume 对持久化 session 镜像这个窗口:先加载持久化,然后铸造 agentCtx 并 await setup,同时重建的 session 与 agent 保持未发布。

发布顺序

工厂的 publish(source) 序列(packages/core/agent-loop/src/index.ts):

  1. 把 agent 插入注册表(enter)——作用域已生效,但尚未宣布。
  2. 宣布(agent/created)——同步监听者失败会否决发布。
  3. 发出 agent/session-start { source }——第一个启动驱动扩展点。
  4. 启动机器(驱动器)。

同步的 agent/created 监听者失败会回滚,并把任何已开始的创建宣布配对为 agent/disposedsession/disposed。启动器通过 CONFIGURED_AGENT_IDENTITIES_KEY 拥有配置 agent 的确切 session 身份,该 key 在任何 Loader 条目挂载之前用 ctx.provide() 设置。

驱动一个 turn

ReactLoopAgent.kick()turn()turn() 返回 true 期间循环。一个 turn:

text
turn/start { turn }
  preStep(target, {turn, step})
    认领 inbox 批次             每条消息 agent/inbox/claimed
    system-prompt/assemble waterfall
    agent/pre-step waterfall  → enter(messages) | reject
  若 enter:逐条消息追加 user/message
    step/start
      buildRequest -> agent/request waterfall -> llm/stream -> assistant/chunk*
        -> assistant/message
      工具调用 -> tools/pre-execute|execute|post-execute -> tool/result*
    step/end
    若 turnEnds 且下一个 step 的 inbox 为空:
      agent/turn-stopping (serial)    — 监听者可以 steer()
  turn/end { reason }

每个 step 读取插件注册的 prompt 段与工具 schema,并从日志派生模型历史(deriveMessages())。模型可见即已记录:任何到达模型请求的内容都必须能从日志重建。

拆除

AgentHandle.dispose()(只返回给创建该 agent 的消费者属主)停止循环、await 其退出、展开作用域世界——通过 agent.ctx 做出的每个注册项按相反顺序销毁——然后才注销该 agent(发出 agent/disposed)并从存储中移除其 session。cancel(cause) 是突然路径:不保留 inbox 时清除排队与转向工作,并中止活跃 turn 或 turn 间任务。whenIdle() 同时跟随活跃活动及其背后释放的任何替换工作。

延伸阅读

  • 架构一览——核心包对照表与事件分类。
  • 扩展(Cordis)系统——驱动循环的类型化事件与可逆副作用。
  • 仓库文档:docs/agent-lifecycle.md(持久化序列)、docs/event-producer-consumer.md(每个事件由谁分发/监听)、docs/subsystems/core.md
  • 源码:packages/core/agent/src/runtime-types.tspackages/core/agent/src/dispatch.tspackages/core/agent-loop/src/agent.ts
  • 源码:packages/core/scope/src/index.tspackages/core/session/src/types.tsSessionEventMap)。