目前子agent不会通过#sym:Emit 将消息写入数据库吗

最后发布时间 : 2026-10-19 23:49:16 浏览量 :

Searched for text func recordFileRead (go-project · gobrave/internal/agent/tool/coder/**), 1 result

Read , lines 1 to 45

是的,目前子 Agent 的 Emit 完全不会落库。

原因

子 Agent 在 agentTool.runTask 里使用的是独立 Runtime:

answer, err := sub.Run(ctx, agent.Request{...}, agent.NewStandaloneRuntime(nil))

而 standaloneRuntime.Emit 的实现是:

func (r *standaloneRuntime) Emit(ctx context.Context, event StreamEvent) error {
	if r.handler != nil {
		return r.handler(ctx, event)
	}
	return nil   // handler 为 nil → 直接丢弃
}

对比父 Agent 的 taskRuntime.Emit:

if isBlockEvent(event.Type) {
	r.svc.emitEvent(ctx, r.taskID, EventStream, event)   // 落库 + 广播
} else {
	r.svc.broadcastEvent(ctx, r.taskID, EventStream, event)
}

所以两条链路是:

flowchart LR A[父 Agent native.Run] --> B[taskRuntime.Emit] B --> C[emitEvent / broadcastEvent] C --> D[(DB + WS/SSE)] E[子 Agent native.Run] --> F[standaloneRuntime.Emit] F --> G[handler=nil → 丢弃]

子 Agent 内部哪些 Emit 被丢弃

streamOnce / executeTools 里所有事件都走这个 no-op:

  • StreamEventText / StreamEventReasoningDelta(增量)
  • StreamEventReasoning / StreamEventMessage(完整块)
  • StreamEventToolCall / StreamEventToolResult(子 Agent 自己的工具调用)
  • StreamEventDone

唯一会进数据库的内容

父 Agent 在收到 agent 工具结果后,会通过父 Runtime 下发一条 StreamEventToolResult:

c.emitToolResult(ctx, rt, call, results[i])   // rt 是父 Agent 的 taskRuntime

所以 DB 里只能看到:

  • 父会话的 tool_call(name=agent,含 prompt / prompts)
  • 父会话的 tool_result(含子 Agent 的最终回答或合并结果)

子 Agent 的思考过程、内部 glob/grep/view 调用、中间轮次全部不可见。

两个容易误判的点

  1. 权限桥接用的是父 Runtime
    PrepareToolContext 复用 a.toolContext(req, rt),子 Agent 工具若调用 RequirePermission,会走父 Runtime 并可能触发 permission 事件落库。但子 Agent 只有 glob/grep/ls/view/sourcegraph,都不请求权限,所以实际不会触发。

  2. view 的 recordFileRead 不落库
    internal/agent/tool/coder/file.go 里只是进程内的 map[string]fileRecord + mutex,用于 edit/write 前的读取时间校验,不涉及数据库。

如果希望子 Agent 也落库

需要把 NewStandaloneRuntime(nil) 换成能持久化的 Runtime,并且通常要先给子 Agent 建子会话(即上次说的 ParentSessionID 接线)。否则即使 Emit 有 handler,也没有 taskID / sessionID 可以挂载事件。