refactor(events): emit agent runtime events directly

Remove the legacy EventKind/Event envelope mapping and let agent event emission build pkg/events.Event values directly.

Keep HookMeta as the shared hook metadata shape and preserve legacy observe string aliases by mapping them to runtime event kinds.

Validation: GOCACHE=/tmp/picoclaw-go-cache go test ./pkg/agent; make lint
This commit is contained in:
Hoshina 2026-04-26 16:55:02 +08:00
parent b954e6b8dc
commit 795ee362ea
19 changed files with 162 additions and 269 deletions

View file

@ -13,7 +13,7 @@ The repository no longer ships standalone example source files. The Go and Pytho
| Type | Interface | Stage | Can modify data | | Type | Interface | Stage | Can modify data |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Observer | `RuntimeEventObserver` / `EventObserver` | Runtime event bus broadcast | No | | Observer | `RuntimeEventObserver` | Runtime event bus broadcast | No |
| LLM interceptor | `LLMInterceptor` | `before_llm` / `after_llm` | Yes | | LLM interceptor | `LLMInterceptor` | `before_llm` / `after_llm` | Yes |
| Tool interceptor | `ToolInterceptor` | `before_tool` / `after_tool` | Yes | | Tool interceptor | `ToolInterceptor` | `before_tool` / `after_tool` | Yes |
| Tool approver | `ToolApprover` | `approve_tool` | No, returns allow/deny | | Tool approver | `ToolApprover` | `approve_tool` | No, returns allow/deny |

View file

@ -13,7 +13,7 @@
| 类型 | 接口 | 作用阶段 | 能否改写 | | 类型 | 接口 | 作用阶段 | 能否改写 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 观察型 | `RuntimeEventObserver` / `EventObserver` | runtime event bus 广播事件时 | 否 | | 观察型 | `RuntimeEventObserver` | runtime event bus 广播事件时 | 否 |
| LLM 拦截型 | `LLMInterceptor` | `before_llm` / `after_llm` | 是 | | LLM 拦截型 | `LLMInterceptor` | `before_llm` / `after_llm` | 是 |
| Tool 拦截型 | `ToolInterceptor` | `before_tool` / `after_tool` | 是 | | Tool 拦截型 | `ToolInterceptor` | `before_tool` / `after_tool` | 是 |
| Tool 审批型 | `ToolApprover` | `approve_tool` | 否,返回批准/拒绝 | | Tool 审批型 | `ToolApprover` | `approve_tool` | 否,返回批准/拒绝 |

View file

@ -2,7 +2,7 @@
> 当前状态:本文是 hook 系统的早期设计记录。事件系统升级后,观察型 hook 的主路径已经切到 > 当前状态:本文是 hook 系统的早期设计记录。事件系统升级后,观察型 hook 的主路径已经切到
> `pkg/events.Event``RuntimeEventObserver` 和进程 hook 的 `hook.runtime_event` > `pkg/events.Event``RuntimeEventObserver` 和进程 hook 的 `hook.runtime_event`
> 文中提到的 `agent.Event``EventKind``hook.event` 只代表迁移期兼容层,不应作为新代码接口 > `agent.Event``EventKind``hook.event` 兼容层已经删除
## 背景 ## 背景
@ -58,19 +58,16 @@ pi-mono 的核心思路更接近当前分支:
- `pkg/events` 定义 runtime event envelope、kind、scope、source、severity 和 fan-out bus - `pkg/events` 定义 runtime event envelope、kind、scope、source、severity 和 fan-out bus
- `pkg/agent/event_payloads.go` 保留 agent domain payload - `pkg/agent/event_payloads.go` 保留 agent domain payload
- `pkg/agent/eventbus.go` 只作为迁移期兼容层存在 - agent domain payload 保留在 `pkg/agent/event_payloads.go`
- `pkg/agent/loop.go` 中的 `runTurn()` 已在 turn、llm、tool、interrupt、follow-up、summary 等节点发射事件 - `pkg/agent/loop.go` 中的 `runTurn()` 已在 turn、llm、tool、interrupt、follow-up、summary 等节点发射事件
- `pkg/agent/steering.go` 已支持 steering、graceful interrupt、hard abort - `pkg/agent/steering.go` 已支持 steering、graceful interrupt、hard abort
- `pkg/agent/turn.go` 已维护 turn phase、恢复点、active turn、abort 状态 - `pkg/agent/turn.go` 已维护 turn phase、恢复点、active turn、abort 状态
### 现有缺口 ### 现有缺口
当前分支还缺四件事: 早期设计时的缺口包括 HookManager、Before/After LLM、Before/After Tool、审批型 hook
以及 sub-turn 接入。当前实现已经覆盖主 turn 的 HookManager、LLM/Tool 拦截和审批;
- 没有 HookManager只有旧 agent EventBus sub-turn 事件已接入 runtime event bus。
- 没有 Before/After LLM、Before/After Tool 这种同步拦截点
- 没有审批型 hook
- 子 agent 仍走 `pkg/tools/SubagentManager + RunToolLoop`,没有接入 `pkg/agent` 的 turn tree 和事件流
### 一个关键现实 ### 一个关键现实
@ -122,7 +119,7 @@ type EventObserver interface {
} }
``` ```
这类 hook 直接订阅 runtime event bus 即可。`OnEvent(ctx, agent.Event)` 仅用于迁移期兼容。 这类 hook 直接订阅 runtime event bus 即可。
适用场景: 适用场景:

View file

@ -19,8 +19,8 @@ func (al *AgentLoop) newTurnEventScope(agentID, sessionKey string, turnCtx *Turn
} }
} }
func (ts turnEventScope) meta(iteration int, source, tracePath string) EventMeta { func (ts turnEventScope) meta(iteration int, source, tracePath string) HookMeta {
return EventMeta{ return HookMeta{
AgentID: ts.agentID, AgentID: ts.agentID,
TurnID: ts.turnID, TurnID: ts.turnID,
SessionKey: ts.sessionKey, SessionKey: ts.sessionKey,
@ -31,41 +31,45 @@ func (ts turnEventScope) meta(iteration int, source, tracePath string) EventMeta
} }
} }
func (al *AgentLoop) emitEvent(kind EventKind, meta EventMeta, payload any) { func (al *AgentLoop) emitEvent(kind runtimeevents.Kind, meta HookMeta, payload any) {
clonedMeta := cloneEventMeta(meta) clonedMeta := cloneHookMeta(meta)
evt := Event{ eventCtx := cloneTurnContext(clonedMeta.turnContext)
Kind: kind, evt := runtimeevents.Event{
Meta: clonedMeta, Kind: kind,
Context: cloneTurnContext(clonedMeta.turnContext), Source: runtimeevents.Source{Component: "agent", Name: clonedMeta.AgentID},
Payload: payload, Scope: runtimeScopeFromHookMeta(clonedMeta, eventCtx),
Correlation: runtimeCorrelationFromHookMeta(clonedMeta),
Severity: runtimeSeverityForAgentEvent(kind, payload),
Payload: payload,
Attrs: runtimeAttrsFromHookMeta(clonedMeta),
} }
if al == nil { if al == nil {
return return
} }
al.logEvent(evt) al.logEvent(evt, clonedMeta, eventCtx)
al.publishRuntimeEvent(evt) al.publishRuntimeEvent(evt)
} }
func (al *AgentLoop) logEvent(evt Event) { func (al *AgentLoop) logEvent(evt runtimeevents.Event, meta HookMeta, eventCtx *TurnContext) {
fields := map[string]any{ fields := map[string]any{
"event_kind": evt.Kind.String(), "event_kind": evt.Kind.String(),
"agent_id": evt.Meta.AgentID, "agent_id": meta.AgentID,
"turn_id": evt.Meta.TurnID, "turn_id": meta.TurnID,
"session_key": evt.Meta.SessionKey, "session_key": meta.SessionKey,
"iteration": evt.Meta.Iteration, "iteration": meta.Iteration,
} }
if evt.Meta.TracePath != "" { if meta.TracePath != "" {
fields["trace"] = evt.Meta.TracePath fields["trace"] = meta.TracePath
} }
if evt.Meta.Source != "" { if meta.Source != "" {
fields["source"] = evt.Meta.Source fields["source"] = meta.Source
} }
appendEventContextFields(fields, evt.Context) appendEventContextFields(fields, eventCtx)
switch payload := evt.Payload.(type) { switch payload := evt.Payload.(type) {
case TurnStartPayload: case TurnStartPayload:

View file

@ -7,6 +7,7 @@ import (
"sync" "sync"
"time" "time"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
@ -41,7 +42,7 @@ func (m *legacyContextManager) Compact(_ context.Context, req *CompactRequest) e
// Sync emergency compression — budget exceeded. // Sync emergency compression — budget exceeded.
if result, ok := m.forceCompression(req.SessionKey); ok { if result, ok := m.forceCompression(req.SessionKey); ok {
m.al.emitEvent( m.al.emitEvent(
EventKindContextCompress, runtimeevents.KindAgentContextCompress,
m.al.newTurnEventScope("", req.SessionKey, nil).meta(0, "forceCompression", "turn.context.compress"), m.al.newTurnEventScope("", req.SessionKey, nil).meta(0, "forceCompression", "turn.context.compress"),
ContextCompressPayload{ ContextCompressPayload{
Reason: req.Reason, Reason: req.Reason,
@ -246,7 +247,7 @@ func (m *legacyContextManager) summarizeSession(agent *AgentInstance, sessionKey
agent.Sessions.TruncateHistory(sessionKey, keepCount) agent.Sessions.TruncateHistory(sessionKey, keepCount)
agent.Sessions.Save(sessionKey) agent.Sessions.Save(sessionKey)
m.al.emitEvent( m.al.emitEvent(
EventKindSessionSummarize, runtimeevents.KindAgentSessionSummarize,
m.al.newTurnEventScope(agent.ID, sessionKey, nil).meta(0, "summarizeSession", "turn.session.summarize"), m.al.newTurnEventScope(agent.ID, sessionKey, nil).meta(0, "summarizeSession", "turn.session.summarize"),
SessionSummarizePayload{ SessionSummarizePayload{
SummarizedMessages: len(validMessages), SummarizedMessages: len(validMessages),

View file

@ -1,48 +0,0 @@
package agent
import runtimeevents "github.com/sipeed/picoclaw/pkg/events"
func runtimeKindForAgentEvent(kind EventKind) runtimeevents.Kind {
switch kind {
case EventKindTurnStart:
return runtimeevents.KindAgentTurnStart
case EventKindTurnEnd:
return runtimeevents.KindAgentTurnEnd
case EventKindLLMRequest:
return runtimeevents.KindAgentLLMRequest
case EventKindLLMDelta:
return runtimeevents.KindAgentLLMDelta
case EventKindLLMResponse:
return runtimeevents.KindAgentLLMResponse
case EventKindLLMRetry:
return runtimeevents.KindAgentLLMRetry
case EventKindContextCompress:
return runtimeevents.KindAgentContextCompress
case EventKindSessionSummarize:
return runtimeevents.KindAgentSessionSummarize
case EventKindToolExecStart:
return runtimeevents.KindAgentToolExecStart
case EventKindToolExecEnd:
return runtimeevents.KindAgentToolExecEnd
case EventKindToolExecSkipped:
return runtimeevents.KindAgentToolExecSkipped
case EventKindSteeringInjected:
return runtimeevents.KindAgentSteeringInjected
case EventKindFollowUpQueued:
return runtimeevents.KindAgentFollowUpQueued
case EventKindInterruptReceived:
return runtimeevents.KindAgentInterruptReceived
case EventKindSubTurnSpawn:
return runtimeevents.KindAgentSubTurnSpawn
case EventKindSubTurnEnd:
return runtimeevents.KindAgentSubTurnEnd
case EventKindSubTurnResultDelivered:
return runtimeevents.KindAgentSubTurnResultDelivered
case EventKindSubTurnOrphan:
return runtimeevents.KindAgentSubTurnOrphan
case EventKindError:
return runtimeevents.KindAgentError
default:
return runtimeevents.Kind("agent." + kind.String())
}
}

View file

@ -41,8 +41,8 @@ func TestAgentLoop_PublishesRuntimeEvents(t *testing.T) {
}() }()
al.emitEvent( al.emitEvent(
EventKindToolExecStart, runtimeevents.KindAgentToolExecStart,
EventMeta{ HookMeta{
AgentID: "main", AgentID: "main",
TurnID: "turn-1", TurnID: "turn-1",
ParentTurnID: "parent-turn", ParentTurnID: "parent-turn",

View file

@ -1,104 +1,8 @@
package agent package agent
import ( // HookMeta contains correlation fields shared by agent hook requests and
"fmt" // runtime events emitted from turn processing.
"time" type HookMeta struct {
)
// EventKind identifies a structured agent-loop event.
//
// Deprecated: use github.com/sipeed/picoclaw/pkg/events.Kind for new runtime
// event consumers. This legacy kind exists only during the runtime event
// migration window.
type EventKind uint8
const (
// EventKindTurnStart is emitted when a turn begins processing.
EventKindTurnStart EventKind = iota
// EventKindTurnEnd is emitted when a turn finishes, successfully or with an error.
EventKindTurnEnd
// EventKindLLMRequest is emitted before a provider chat request is made.
EventKindLLMRequest
// EventKindLLMDelta is emitted when a streaming provider yields a partial delta.
EventKindLLMDelta
// EventKindLLMResponse is emitted after a provider chat response is received.
EventKindLLMResponse
// EventKindLLMRetry is emitted when an LLM request is retried.
EventKindLLMRetry
// EventKindContextCompress is emitted when session history is forcibly compressed.
EventKindContextCompress
// EventKindSessionSummarize is emitted when asynchronous summarization completes.
EventKindSessionSummarize
// EventKindToolExecStart is emitted immediately before a tool executes.
EventKindToolExecStart
// EventKindToolExecEnd is emitted immediately after a tool finishes executing.
EventKindToolExecEnd
// EventKindToolExecSkipped is emitted when a queued tool call is skipped.
EventKindToolExecSkipped
// EventKindSteeringInjected is emitted when queued steering is injected into context.
EventKindSteeringInjected
// EventKindFollowUpQueued is emitted when an async tool queues a follow-up system message.
EventKindFollowUpQueued
// EventKindInterruptReceived is emitted when a soft interrupt message is accepted.
EventKindInterruptReceived
// EventKindSubTurnSpawn is emitted when a sub-turn is spawned.
EventKindSubTurnSpawn
// EventKindSubTurnEnd is emitted when a sub-turn finishes.
EventKindSubTurnEnd
// EventKindSubTurnResultDelivered is emitted when a sub-turn result is delivered.
EventKindSubTurnResultDelivered
// EventKindSubTurnOrphan is emitted when a sub-turn result cannot be delivered.
EventKindSubTurnOrphan
// EventKindError is emitted when a turn encounters an execution error.
EventKindError
eventKindCount
)
var eventKindNames = [...]string{
"turn_start",
"turn_end",
"llm_request",
"llm_delta",
"llm_response",
"llm_retry",
"context_compress",
"session_summarize",
"tool_exec_start",
"tool_exec_end",
"tool_exec_skipped",
"steering_injected",
"follow_up_queued",
"interrupt_received",
"subturn_spawn",
"subturn_end",
"subturn_result_delivered",
"subturn_orphan",
"error",
}
// String returns the stable string form of an EventKind.
func (k EventKind) String() string {
if k >= eventKindCount {
return fmt.Sprintf("event_kind(%d)", k)
}
return eventKindNames[k]
}
// Event is the structured envelope broadcast by the agent EventBus.
//
// Deprecated: use github.com/sipeed/picoclaw/pkg/events.Event for new
// observation code. Agent payload types remain supported.
type Event struct {
Kind EventKind
Time time.Time
Meta EventMeta
Context *TurnContext
Payload any
}
// EventMeta contains correlation fields shared by all agent-loop events.
type EventMeta struct {
AgentID string AgentID string
TurnID string TurnID string
ParentTurnID string ParentTurnID string

View file

@ -9,7 +9,7 @@ import (
const runtimeEventPublishTimeout = 100 * time.Millisecond const runtimeEventPublishTimeout = 100 * time.Millisecond
func (al *AgentLoop) publishRuntimeEvent(evt Event) { func (al *AgentLoop) publishRuntimeEvent(evt runtimeevents.Event) {
if al == nil || al.runtimeEvents == nil { if al == nil || al.runtimeEvents == nil {
return return
} }
@ -17,29 +17,21 @@ func (al *AgentLoop) publishRuntimeEvent(evt Event) {
ctx, cancel := context.WithTimeout(context.Background(), runtimeEventPublishTimeout) ctx, cancel := context.WithTimeout(context.Background(), runtimeEventPublishTimeout)
defer cancel() defer cancel()
al.runtimeEvents.Publish(ctx, runtimeevents.Event{ al.runtimeEvents.Publish(ctx, evt)
Kind: runtimeKindForAgentEvent(evt.Kind),
Source: runtimeevents.Source{Component: "agent", Name: evt.Meta.AgentID},
Scope: runtimeScopeFromAgentEvent(evt),
Correlation: runtimeCorrelationFromAgentEvent(evt),
Severity: runtimeSeverityForAgentEvent(evt),
Payload: evt.Payload,
Attrs: runtimeAttrsFromAgentEvent(evt),
})
} }
func runtimeScopeFromAgentEvent(evt Event) runtimeevents.Scope { func runtimeScopeFromHookMeta(meta HookMeta, eventCtx *TurnContext) runtimeevents.Scope {
scope := runtimeevents.Scope{ scope := runtimeevents.Scope{
AgentID: evt.Meta.AgentID, AgentID: meta.AgentID,
SessionKey: evt.Meta.SessionKey, SessionKey: meta.SessionKey,
TurnID: evt.Meta.TurnID, TurnID: meta.TurnID,
} }
if evt.Context == nil || evt.Context.Inbound == nil { if eventCtx == nil || eventCtx.Inbound == nil {
return scope return scope
} }
inbound := evt.Context.Inbound inbound := eventCtx.Inbound
scope.Channel = inbound.Channel scope.Channel = inbound.Channel
scope.Account = inbound.Account scope.Account = inbound.Account
scope.ChatID = inbound.ChatID scope.ChatID = inbound.ChatID
@ -52,21 +44,23 @@ func runtimeScopeFromAgentEvent(evt Event) runtimeevents.Scope {
return scope return scope
} }
func runtimeCorrelationFromAgentEvent(evt Event) runtimeevents.Correlation { func runtimeCorrelationFromHookMeta(meta HookMeta) runtimeevents.Correlation {
return runtimeevents.Correlation{ return runtimeevents.Correlation{
TraceID: evt.Meta.TracePath, TraceID: meta.TracePath,
ParentTurnID: evt.Meta.ParentTurnID, ParentTurnID: meta.ParentTurnID,
} }
} }
func runtimeSeverityForAgentEvent(evt Event) runtimeevents.Severity { func runtimeSeverityForAgentEvent(kind runtimeevents.Kind, payload any) runtimeevents.Severity {
switch evt.Kind { switch kind {
case EventKindError, EventKindSubTurnOrphan: case runtimeevents.KindAgentError, runtimeevents.KindAgentSubTurnOrphan:
return runtimeevents.SeverityError return runtimeevents.SeverityError
case EventKindLLMRetry, EventKindContextCompress, EventKindToolExecSkipped: case runtimeevents.KindAgentLLMRetry,
runtimeevents.KindAgentContextCompress,
runtimeevents.KindAgentToolExecSkipped:
return runtimeevents.SeverityWarn return runtimeevents.SeverityWarn
case EventKindTurnEnd: case runtimeevents.KindAgentTurnEnd:
payload, ok := evt.Payload.(TurnEndPayload) payload, ok := payload.(TurnEndPayload)
if !ok { if !ok {
return runtimeevents.SeverityInfo return runtimeevents.SeverityInfo
} }
@ -78,8 +72,8 @@ func runtimeSeverityForAgentEvent(evt Event) runtimeevents.Severity {
default: default:
return runtimeevents.SeverityInfo return runtimeevents.SeverityInfo
} }
case EventKindToolExecEnd: case runtimeevents.KindAgentToolExecEnd:
payload, ok := evt.Payload.(ToolExecEndPayload) payload, ok := payload.(ToolExecEndPayload)
if ok && payload.IsError { if ok && payload.IsError {
return runtimeevents.SeverityWarn return runtimeevents.SeverityWarn
} }
@ -89,13 +83,13 @@ func runtimeSeverityForAgentEvent(evt Event) runtimeevents.Severity {
} }
} }
func runtimeAttrsFromAgentEvent(evt Event) map[string]any { func runtimeAttrsFromHookMeta(meta HookMeta) map[string]any {
attrs := make(map[string]any, 2) attrs := make(map[string]any, 2)
if evt.Meta.Source != "" { if meta.Source != "" {
attrs["agent_source"] = evt.Meta.Source attrs["agent_source"] = meta.Source
} }
if evt.Meta.Iteration != 0 { if meta.Iteration != 0 {
attrs["iteration"] = evt.Meta.Iteration attrs["iteration"] = meta.Iteration
} }
if len(attrs) == 0 { if len(attrs) == 0 {
return nil return nil

View file

@ -311,14 +311,49 @@ func processHookObserveKindsFromConfig(observe []string) ([]string, bool, error)
} }
func validHookEventKinds() map[string]string { func validHookEventKinds() map[string]string {
kinds := make(map[string]string, int(eventKindCount)*2) runtimeKinds := []runtimeevents.Kind{
for kind := EventKind(0); kind < eventKindCount; kind++ { runtimeevents.KindAgentTurnStart,
runtimeKind := runtimeKindForAgentEvent(kind).String() runtimeevents.KindAgentTurnEnd,
kinds[kind.String()] = runtimeKind runtimeevents.KindAgentLLMRequest,
kinds[runtimeKind] = runtimeKind runtimeevents.KindAgentLLMDelta,
runtimeevents.KindAgentLLMResponse,
runtimeevents.KindAgentLLMRetry,
runtimeevents.KindAgentContextCompress,
runtimeevents.KindAgentSessionSummarize,
runtimeevents.KindAgentToolExecStart,
runtimeevents.KindAgentToolExecEnd,
runtimeevents.KindAgentToolExecSkipped,
runtimeevents.KindAgentSteeringInjected,
runtimeevents.KindAgentFollowUpQueued,
runtimeevents.KindAgentInterruptReceived,
runtimeevents.KindAgentSubTurnSpawn,
runtimeevents.KindAgentSubTurnEnd,
runtimeevents.KindAgentSubTurnResultDelivered,
runtimeevents.KindAgentSubTurnOrphan,
runtimeevents.KindAgentError,
} }
kinds[runtimeevents.KindAgentToolExecStart.String()] = runtimeevents.KindAgentToolExecStart.String() kinds := make(map[string]string, len(runtimeKinds)*2)
kinds[runtimeevents.KindAgentToolExecEnd.String()] = runtimeevents.KindAgentToolExecEnd.String() for _, kind := range runtimeKinds {
kinds[runtimeevents.KindAgentToolExecSkipped.String()] = runtimeevents.KindAgentToolExecSkipped.String() kinds[kind.String()] = kind.String()
}
kinds["turn_start"] = runtimeevents.KindAgentTurnStart.String()
kinds["turn_end"] = runtimeevents.KindAgentTurnEnd.String()
kinds["llm_request"] = runtimeevents.KindAgentLLMRequest.String()
kinds["llm_delta"] = runtimeevents.KindAgentLLMDelta.String()
kinds["llm_response"] = runtimeevents.KindAgentLLMResponse.String()
kinds["llm_retry"] = runtimeevents.KindAgentLLMRetry.String()
kinds["context_compress"] = runtimeevents.KindAgentContextCompress.String()
kinds["session_summarize"] = runtimeevents.KindAgentSessionSummarize.String()
kinds["tool_exec_start"] = runtimeevents.KindAgentToolExecStart.String()
kinds["tool_exec_end"] = runtimeevents.KindAgentToolExecEnd.String()
kinds["tool_exec_skipped"] = runtimeevents.KindAgentToolExecSkipped.String()
kinds["steering_injected"] = runtimeevents.KindAgentSteeringInjected.String()
kinds["follow_up_queued"] = runtimeevents.KindAgentFollowUpQueued.String()
kinds["interrupt_received"] = runtimeevents.KindAgentInterruptReceived.String()
kinds["subturn_spawn"] = runtimeevents.KindAgentSubTurnSpawn.String()
kinds["subturn_end"] = runtimeevents.KindAgentSubTurnEnd.String()
kinds["subturn_result_delivered"] = runtimeevents.KindAgentSubTurnResultDelivered.String()
kinds["subturn_orphan"] = runtimeevents.KindAgentSubTurnOrphan.String()
kinds["error"] = runtimeevents.KindAgentError.String()
return kinds return kinds
} }

View file

@ -91,7 +91,7 @@ type ToolApprover interface {
} }
type LLMHookRequest struct { type LLMHookRequest struct {
Meta EventMeta `json:"meta"` Meta HookMeta `json:"meta"`
Context *TurnContext `json:"context,omitempty"` Context *TurnContext `json:"context,omitempty"`
Model string `json:"model"` Model string `json:"model"`
Messages []providers.Message `json:"messages,omitempty"` Messages []providers.Message `json:"messages,omitempty"`
@ -105,7 +105,7 @@ func (r *LLMHookRequest) Clone() *LLMHookRequest {
return nil return nil
} }
cloned := *r cloned := *r
cloned.Meta = cloneEventMeta(r.Meta) cloned.Meta = cloneHookMeta(r.Meta)
cloned.Context = cloneTurnContext(r.Context) cloned.Context = cloneTurnContext(r.Context)
cloned.Messages = cloneProviderMessages(r.Messages) cloned.Messages = cloneProviderMessages(r.Messages)
cloned.Tools = cloneToolDefinitions(r.Tools) cloned.Tools = cloneToolDefinitions(r.Tools)
@ -114,7 +114,7 @@ func (r *LLMHookRequest) Clone() *LLMHookRequest {
} }
type LLMHookResponse struct { type LLMHookResponse struct {
Meta EventMeta `json:"meta"` Meta HookMeta `json:"meta"`
Context *TurnContext `json:"context,omitempty"` Context *TurnContext `json:"context,omitempty"`
Model string `json:"model"` Model string `json:"model"`
Response *providers.LLMResponse `json:"response,omitempty"` Response *providers.LLMResponse `json:"response,omitempty"`
@ -125,14 +125,14 @@ func (r *LLMHookResponse) Clone() *LLMHookResponse {
return nil return nil
} }
cloned := *r cloned := *r
cloned.Meta = cloneEventMeta(r.Meta) cloned.Meta = cloneHookMeta(r.Meta)
cloned.Context = cloneTurnContext(r.Context) cloned.Context = cloneTurnContext(r.Context)
cloned.Response = cloneLLMResponse(r.Response) cloned.Response = cloneLLMResponse(r.Response)
return &cloned return &cloned
} }
type ToolCallHookRequest struct { type ToolCallHookRequest struct {
Meta EventMeta `json:"meta"` Meta HookMeta `json:"meta"`
Context *TurnContext `json:"context,omitempty"` Context *TurnContext `json:"context,omitempty"`
Tool string `json:"tool"` Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"` Arguments map[string]any `json:"arguments,omitempty"`
@ -146,7 +146,7 @@ func (r *ToolCallHookRequest) Clone() *ToolCallHookRequest {
return nil return nil
} }
cloned := *r cloned := *r
cloned.Meta = cloneEventMeta(r.Meta) cloned.Meta = cloneHookMeta(r.Meta)
cloned.Context = cloneTurnContext(r.Context) cloned.Context = cloneTurnContext(r.Context)
cloned.Arguments = cloneStringAnyMap(r.Arguments) cloned.Arguments = cloneStringAnyMap(r.Arguments)
cloned.HookResult = cloneToolResult(r.HookResult) cloned.HookResult = cloneToolResult(r.HookResult)
@ -154,7 +154,7 @@ func (r *ToolCallHookRequest) Clone() *ToolCallHookRequest {
} }
type ToolApprovalRequest struct { type ToolApprovalRequest struct {
Meta EventMeta `json:"meta"` Meta HookMeta `json:"meta"`
Context *TurnContext `json:"context,omitempty"` Context *TurnContext `json:"context,omitempty"`
Tool string `json:"tool"` Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"` Arguments map[string]any `json:"arguments,omitempty"`
@ -165,14 +165,14 @@ func (r *ToolApprovalRequest) Clone() *ToolApprovalRequest {
return nil return nil
} }
cloned := *r cloned := *r
cloned.Meta = cloneEventMeta(r.Meta) cloned.Meta = cloneHookMeta(r.Meta)
cloned.Context = cloneTurnContext(r.Context) cloned.Context = cloneTurnContext(r.Context)
cloned.Arguments = cloneStringAnyMap(r.Arguments) cloned.Arguments = cloneStringAnyMap(r.Arguments)
return &cloned return &cloned
} }
type ToolResultHookResponse struct { type ToolResultHookResponse struct {
Meta EventMeta `json:"meta"` Meta HookMeta `json:"meta"`
Context *TurnContext `json:"context,omitempty"` Context *TurnContext `json:"context,omitempty"`
Tool string `json:"tool"` Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"` Arguments map[string]any `json:"arguments,omitempty"`
@ -185,7 +185,7 @@ func (r *ToolResultHookResponse) Clone() *ToolResultHookResponse {
return nil return nil
} }
cloned := *r cloned := *r
cloned.Meta = cloneEventMeta(r.Meta) cloned.Meta = cloneHookMeta(r.Meta)
cloned.Context = cloneTurnContext(r.Context) cloned.Context = cloneTurnContext(r.Context)
cloned.Arguments = cloneStringAnyMap(r.Arguments) cloned.Arguments = cloneStringAnyMap(r.Arguments)
cloned.Result = cloneToolResult(r.Result) cloned.Result = cloneToolResult(r.Result)

View file

@ -10,6 +10,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/constants" "github.com/sipeed/picoclaw/pkg/constants"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/tools" "github.com/sipeed/picoclaw/pkg/tools"
@ -72,7 +73,7 @@ toolLoop:
}) })
al.emitEvent( al.emitEvent(
EventKindToolExecStart, runtimeevents.KindAgentToolExecStart,
ts.eventMeta("runTurn", "turn.tool.start"), ts.eventMeta("runTurn", "turn.tool.start"),
ToolExecStartPayload{ ToolExecStartPayload{
Tool: toolName, Tool: toolName,
@ -192,7 +193,7 @@ toolLoop:
} }
al.emitEvent( al.emitEvent(
EventKindToolExecEnd, runtimeevents.KindAgentToolExecEnd,
ts.eventMeta("runTurn", "turn.tool.end"), ts.eventMeta("runTurn", "turn.tool.end"),
ToolExecEndPayload{ ToolExecEndPayload{
Tool: toolName, Tool: toolName,
@ -238,7 +239,7 @@ toolLoop:
for j := i + 1; j < len(normalizedToolCalls); j++ { for j := i + 1; j < len(normalizedToolCalls); j++ {
skippedTC := normalizedToolCalls[j] skippedTC := normalizedToolCalls[j]
al.emitEvent( al.emitEvent(
EventKindToolExecSkipped, runtimeevents.KindAgentToolExecSkipped,
ts.eventMeta("runTurn", "turn.tool.skipped"), ts.eventMeta("runTurn", "turn.tool.skipped"),
ToolExecSkippedPayload{ ToolExecSkippedPayload{
Tool: skippedTC.Name, Tool: skippedTC.Name,
@ -285,7 +286,7 @@ toolLoop:
exec.allResponsesHandled = false exec.allResponsesHandled = false
denyContent := hookDeniedToolContent("Tool execution denied by hook", decision.Reason) denyContent := hookDeniedToolContent("Tool execution denied by hook", decision.Reason)
al.emitEvent( al.emitEvent(
EventKindToolExecSkipped, runtimeevents.KindAgentToolExecSkipped,
ts.eventMeta("runTurn", "turn.tool.skipped"), ts.eventMeta("runTurn", "turn.tool.skipped"),
ToolExecSkippedPayload{ ToolExecSkippedPayload{
Tool: toolName, Tool: toolName,
@ -324,7 +325,7 @@ toolLoop:
exec.allResponsesHandled = false exec.allResponsesHandled = false
denyContent := hookDeniedToolContent("Tool execution denied by approval hook", approval.Reason) denyContent := hookDeniedToolContent("Tool execution denied by approval hook", approval.Reason)
al.emitEvent( al.emitEvent(
EventKindToolExecSkipped, runtimeevents.KindAgentToolExecSkipped,
ts.eventMeta("runTurn", "turn.tool.skipped"), ts.eventMeta("runTurn", "turn.tool.skipped"),
ToolExecSkippedPayload{ ToolExecSkippedPayload{
Tool: toolName, Tool: toolName,
@ -354,7 +355,7 @@ toolLoop:
"iteration": iteration, "iteration": iteration,
}) })
al.emitEvent( al.emitEvent(
EventKindToolExecStart, runtimeevents.KindAgentToolExecStart,
ts.eventMeta("runTurn", "turn.tool.start"), ts.eventMeta("runTurn", "turn.tool.start"),
ToolExecStartPayload{ ToolExecStartPayload{
Tool: toolName, Tool: toolName,
@ -403,7 +404,7 @@ toolLoop:
"channel": ts.channel, "channel": ts.channel,
}) })
al.emitEvent( al.emitEvent(
EventKindFollowUpQueued, runtimeevents.KindAgentFollowUpQueued,
ts.scope.meta(iteration, "runTurn", "turn.follow_up.queued"), ts.scope.meta(iteration, "runTurn", "turn.follow_up.queued"),
FollowUpQueuedPayload{ FollowUpQueuedPayload{
SourceTool: asyncToolName, SourceTool: asyncToolName,
@ -569,7 +570,7 @@ toolLoop:
toolResultMsg.Media = append(toolResultMsg.Media, toolResult.Media...) toolResultMsg.Media = append(toolResultMsg.Media, toolResult.Media...)
} }
al.emitEvent( al.emitEvent(
EventKindToolExecEnd, runtimeevents.KindAgentToolExecEnd,
ts.eventMeta("runTurn", "turn.tool.end"), ts.eventMeta("runTurn", "turn.tool.end"),
ToolExecEndPayload{ ToolExecEndPayload{
Tool: toolName, Tool: toolName,
@ -614,7 +615,7 @@ toolLoop:
for j := i + 1; j < len(normalizedToolCalls); j++ { for j := i + 1; j < len(normalizedToolCalls); j++ {
skippedTC := normalizedToolCalls[j] skippedTC := normalizedToolCalls[j]
al.emitEvent( al.emitEvent(
EventKindToolExecSkipped, runtimeevents.KindAgentToolExecSkipped,
ts.eventMeta("runTurn", "turn.tool.skipped"), ts.eventMeta("runTurn", "turn.tool.skipped"),
ToolExecSkippedPayload{ ToolExecSkippedPayload{
Tool: skippedTC.Name, Tool: skippedTC.Name,

View file

@ -6,6 +6,7 @@ import (
"context" "context"
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
@ -50,7 +51,7 @@ func (p *Pipeline) Finalize(
ts.ingestMessage(turnCtx, al, finalMsg) ts.ingestMessage(turnCtx, al, finalMsg)
if err := ts.agent.Sessions.Save(ts.sessionKey); err != nil { if err := ts.agent.Sessions.Save(ts.sessionKey); err != nil {
al.emitEvent( al.emitEvent(
EventKindError, runtimeevents.KindAgentError,
ts.eventMeta("runTurn", "turn.error"), ts.eventMeta("runTurn", "turn.error"),
ErrorPayload{ ErrorPayload{
Stage: "session_save", Stage: "session_save",

View file

@ -12,6 +12,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/constants" "github.com/sipeed/picoclaw/pkg/constants"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
@ -114,7 +115,7 @@ func (p *Pipeline) CallLLM(
} }
al.emitEvent( al.emitEvent(
EventKindLLMRequest, runtimeevents.KindAgentLLMRequest,
ts.eventMeta("runTurn", "turn.llm.request"), ts.eventMeta("runTurn", "turn.llm.request"),
LLMRequestPayload{ LLMRequestPayload{
Model: exec.llmModel, Model: exec.llmModel,
@ -200,7 +201,7 @@ func (p *Pipeline) CallLLM(
// Retry without media if vision is unsupported // Retry without media if vision is unsupported
if hasMediaRefs(exec.callMessages) && isVisionUnsupportedError(err) && retry < maxRetries { if hasMediaRefs(exec.callMessages) && isVisionUnsupportedError(err) && retry < maxRetries {
al.emitEvent( al.emitEvent(
EventKindLLMRetry, runtimeevents.KindAgentLLMRetry,
ts.eventMeta("runTurn", "turn.llm.retry"), ts.eventMeta("runTurn", "turn.llm.retry"),
LLMRetryPayload{ LLMRetryPayload{
Attempt: retry + 1, Attempt: retry + 1,
@ -247,7 +248,7 @@ func (p *Pipeline) CallLLM(
if isTimeoutError && retry < maxRetries { if isTimeoutError && retry < maxRetries {
backoff := time.Duration(retry+1) * 5 * time.Second backoff := time.Duration(retry+1) * 5 * time.Second
al.emitEvent( al.emitEvent(
EventKindLLMRetry, runtimeevents.KindAgentLLMRetry,
ts.eventMeta("runTurn", "turn.llm.retry"), ts.eventMeta("runTurn", "turn.llm.retry"),
LLMRetryPayload{ LLMRetryPayload{
Attempt: retry + 1, Attempt: retry + 1,
@ -275,7 +276,7 @@ func (p *Pipeline) CallLLM(
if isContextError && retry < maxRetries && !ts.opts.NoHistory { if isContextError && retry < maxRetries && !ts.opts.NoHistory {
al.emitEvent( al.emitEvent(
EventKindLLMRetry, runtimeevents.KindAgentLLMRetry,
ts.eventMeta("runTurn", "turn.llm.retry"), ts.eventMeta("runTurn", "turn.llm.retry"),
LLMRetryPayload{ LLMRetryPayload{
Attempt: retry + 1, Attempt: retry + 1,
@ -334,7 +335,7 @@ func (p *Pipeline) CallLLM(
if err != nil { if err != nil {
al.emitEvent( al.emitEvent(
EventKindError, runtimeevents.KindAgentError,
ts.eventMeta("runTurn", "turn.error"), ts.eventMeta("runTurn", "turn.error"),
ErrorPayload{ ErrorPayload{
Stage: "llm", Stage: "llm",
@ -394,7 +395,7 @@ func (p *Pipeline) CallLLM(
) )
} }
al.emitEvent( al.emitEvent(
EventKindLLMResponse, runtimeevents.KindAgentLLMResponse,
ts.eventMeta("runTurn", "turn.llm.response"), ts.eventMeta("runTurn", "turn.llm.response"),
LLMResponsePayload{ LLMResponsePayload{
ContentLen: len(exec.response.Content), ContentLen: len(exec.response.Content),

View file

@ -8,6 +8,7 @@ import (
"sync" "sync"
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/session" "github.com/sipeed/picoclaw/pkg/session"
@ -206,7 +207,7 @@ func (al *AgentLoop) enqueueSteeringMessage(scope, agentID string, msg providers
"scope": normalizeSteeringScope(scope), "scope": normalizeSteeringScope(scope),
}) })
meta := EventMeta{ meta := HookMeta{
Source: "Steer", Source: "Steer",
TracePath: "turn.interrupt.received", TracePath: "turn.interrupt.received",
} }
@ -230,7 +231,7 @@ func (al *AgentLoop) enqueueSteeringMessage(scope, agentID string, msg providers
} }
al.emitEvent( al.emitEvent(
EventKindInterruptReceived, runtimeevents.KindAgentInterruptReceived,
meta, meta,
InterruptReceivedPayload{ InterruptReceivedPayload{
Kind: InterruptKindSteering, Kind: InterruptKindSteering,
@ -410,7 +411,7 @@ func (al *AgentLoop) InterruptGraceful(hint string) error {
} }
al.emitEvent( al.emitEvent(
EventKindInterruptReceived, runtimeevents.KindAgentInterruptReceived,
ts.eventMeta("InterruptGraceful", "turn.interrupt.received"), ts.eventMeta("InterruptGraceful", "turn.interrupt.received"),
InterruptReceivedPayload{ InterruptReceivedPayload{
Kind: InterruptKindGraceful, Kind: InterruptKindGraceful,
@ -438,7 +439,7 @@ func (al *AgentLoop) InterruptHard() error {
} }
al.emitEvent( al.emitEvent(
EventKindInterruptReceived, runtimeevents.KindAgentInterruptReceived,
ts.eventMeta("InterruptHard", "turn.interrupt.received"), ts.eventMeta("InterruptHard", "turn.interrupt.received"),
InterruptReceivedPayload{ InterruptReceivedPayload{
Kind: InterruptKindHard, Kind: InterruptKindHard,

View file

@ -8,6 +8,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/providers/messageutil" "github.com/sipeed/picoclaw/pkg/providers/messageutil"
@ -422,7 +423,7 @@ func spawnSubTurn(
parentTS.mu.Unlock() parentTS.mu.Unlock()
// 6. Emit Spawn event // 6. Emit Spawn event
al.emitEvent(EventKindSubTurnSpawn, al.emitEvent(runtimeevents.KindAgentSubTurnSpawn,
childTS.eventMeta("spawnSubTurn", "subturn.spawn"), childTS.eventMeta("spawnSubTurn", "subturn.spawn"),
SubTurnSpawnPayload{ SubTurnSpawnPayload{
AgentID: childTS.agentID, AgentID: childTS.agentID,
@ -453,7 +454,7 @@ func spawnSubTurn(
if err != nil { if err != nil {
status = "error" status = "error"
} }
al.emitEvent(EventKindSubTurnEnd, al.emitEvent(runtimeevents.KindAgentSubTurnEnd,
childTS.eventMeta("spawnSubTurn", "subturn.end"), childTS.eventMeta("spawnSubTurn", "subturn.end"),
SubTurnEndPayload{ SubTurnEndPayload{
AgentID: childTS.agentID, AgentID: childTS.agentID,
@ -526,7 +527,7 @@ func deliverSubTurnResult(al *AgentLoop, parentTS *turnState, childID string, re
"recover": r, "recover": r,
}) })
if result != nil && al != nil { if result != nil && al != nil {
al.emitEvent(EventKindSubTurnOrphan, al.emitEvent(runtimeevents.KindAgentSubTurnOrphan,
parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"), parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"),
SubTurnOrphanPayload{ParentTurnID: parentTS.turnID, ChildTurnID: childID, Reason: "panic"}, SubTurnOrphanPayload{ParentTurnID: parentTS.turnID, ChildTurnID: childID, Reason: "panic"},
) )
@ -541,7 +542,7 @@ func deliverSubTurnResult(al *AgentLoop, parentTS *turnState, childID string, re
// If parent turn has already finished, treat this as an orphan result // If parent turn has already finished, treat this as an orphan result
if isFinished || resultChan == nil { if isFinished || resultChan == nil {
if result != nil && al != nil { if result != nil && al != nil {
al.emitEvent(EventKindSubTurnOrphan, al.emitEvent(runtimeevents.KindAgentSubTurnOrphan,
parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"), parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"),
SubTurnOrphanPayload{ParentTurnID: parentTS.turnID, ChildTurnID: childID, Reason: "parent_finished"}, SubTurnOrphanPayload{ParentTurnID: parentTS.turnID, ChildTurnID: childID, Reason: "parent_finished"},
) )
@ -557,7 +558,7 @@ func deliverSubTurnResult(al *AgentLoop, parentTS *turnState, childID string, re
case resultChan <- result: case resultChan <- result:
// Successfully delivered // Successfully delivered
if al != nil { if al != nil {
al.emitEvent(EventKindSubTurnResultDelivered, al.emitEvent(runtimeevents.KindAgentSubTurnResultDelivered,
parentTS.eventMeta("deliverSubTurnResult", "subturn.result_delivered"), parentTS.eventMeta("deliverSubTurnResult", "subturn.result_delivered"),
SubTurnResultDeliveredPayload{ContentLen: len(result.ForLLM)}, SubTurnResultDeliveredPayload{ContentLen: len(result.ForLLM)},
) )
@ -571,7 +572,7 @@ func deliverSubTurnResult(al *AgentLoop, parentTS *turnState, childID string, re
}) })
if result != nil && al != nil { if result != nil && al != nil {
al.emitEvent( al.emitEvent(
EventKindSubTurnOrphan, runtimeevents.KindAgentSubTurnOrphan,
parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"), parentTS.eventMeta("deliverSubTurnResult", "subturn.orphan"),
SubTurnOrphanPayload{ SubTurnOrphanPayload{
ParentTurnID: parentTS.turnID, ParentTurnID: parentTS.turnID,

View file

@ -61,7 +61,7 @@ func cloneStringMap(src map[string]string) map[string]string {
return cloned return cloned
} }
func cloneEventMeta(meta EventMeta) EventMeta { func cloneHookMeta(meta HookMeta) HookMeta {
meta.turnContext = cloneTurnContext(meta.turnContext) meta.turnContext = cloneTurnContext(meta.turnContext)
return meta return meta
} }

View file

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/config"
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
@ -28,7 +29,7 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
turnStatus := TurnEndStatusCompleted turnStatus := TurnEndStatusCompleted
defer func() { defer func() {
al.emitEvent( al.emitEvent(
EventKindTurnEnd, runtimeevents.KindAgentTurnEnd,
ts.eventMeta("runTurn", "turn.end"), ts.eventMeta("runTurn", "turn.end"),
TurnEndPayload{ TurnEndPayload{
Status: turnStatus, Status: turnStatus,
@ -40,7 +41,7 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
}() }()
al.emitEvent( al.emitEvent(
EventKindTurnStart, runtimeevents.KindAgentTurnStart,
ts.eventMeta("runTurn", "turn.start"), ts.eventMeta("runTurn", "turn.start"),
TurnStartPayload{ TurnStartPayload{
UserMessage: ts.userMessage, UserMessage: ts.userMessage,
@ -140,7 +141,7 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
}) })
} }
al.emitEvent( al.emitEvent(
EventKindSteeringInjected, runtimeevents.KindAgentSteeringInjected,
ts.eventMeta("runTurn", "turn.steering.injected"), ts.eventMeta("runTurn", "turn.steering.injected"),
SteeringInjectedPayload{ SteeringInjectedPayload{
Count: len(pendingMessages), Count: len(pendingMessages),
@ -249,7 +250,7 @@ func (al *AgentLoop) abortTurn(ts *turnState) (turnResult, error) {
if !ts.opts.NoHistory { if !ts.opts.NoHistory {
if err := ts.restoreSession(ts.agent); err != nil { if err := ts.restoreSession(ts.agent); err != nil {
al.emitEvent( al.emitEvent(
EventKindError, runtimeevents.KindAgentError,
ts.eventMeta("abortTurn", "turn.error"), ts.eventMeta("abortTurn", "turn.error"),
ErrorPayload{ ErrorPayload{
Stage: "session_restore", Stage: "session_restore",
@ -414,7 +415,7 @@ func (al *AgentLoop) askSideQuestion(
llmModel := activeModel llmModel := activeModel
if al.hooks != nil { if al.hooks != nil {
llmReq, decision := al.hooks.BeforeLLM(ctx, &LLMHookRequest{ llmReq, decision := al.hooks.BeforeLLM(ctx, &LLMHookRequest{
Meta: EventMeta{ Meta: HookMeta{
Source: "askSideQuestion", Source: "askSideQuestion",
TracePath: "turn.llm.request", TracePath: "turn.llm.request",
turnContext: cloneTurnContext(turnCtx), turnContext: cloneTurnContext(turnCtx),
@ -494,8 +495,8 @@ func (al *AgentLoop) askSideQuestion(
resp, err = callSideLLM(messages) resp, err = callSideLLM(messages)
if err != nil && hasMediaRefs(messages) && isVisionUnsupportedError(err) { if err != nil && hasMediaRefs(messages) && isVisionUnsupportedError(err) {
al.emitEvent( al.emitEvent(
EventKindLLMRetry, runtimeevents.KindAgentLLMRetry,
EventMeta{ HookMeta{
Source: "askSideQuestion", Source: "askSideQuestion",
TracePath: "turn.llm.retry", TracePath: "turn.llm.retry",
turnContext: cloneTurnContext(turnCtx), turnContext: cloneTurnContext(turnCtx),
@ -521,7 +522,7 @@ func (al *AgentLoop) askSideQuestion(
// Apply after_llm hooks // Apply after_llm hooks
if al.hooks != nil { if al.hooks != nil {
llmResp, decision := al.hooks.AfterLLM(ctx, &LLMHookResponse{ llmResp, decision := al.hooks.AfterLLM(ctx, &LLMHookResponse{
Meta: EventMeta{ Meta: HookMeta{
Source: "askSideQuestion", Source: "askSideQuestion",
TracePath: "turn.llm.response", TracePath: "turn.llm.response",
turnContext: cloneTurnContext(turnCtx), turnContext: cloneTurnContext(turnCtx),

View file

@ -442,9 +442,9 @@ func (ts *turnState) hardAbortRequested() bool {
return ts.hardAbort return ts.hardAbort
} }
func (ts *turnState) eventMeta(source, tracePath string) EventMeta { func (ts *turnState) eventMeta(source, tracePath string) HookMeta {
snap := ts.snapshot() snap := ts.snapshot()
return EventMeta{ return HookMeta{
AgentID: snap.AgentID, AgentID: snap.AgentID,
TurnID: snap.TurnID, TurnID: snap.TurnID,
SessionKey: snap.SessionKey, SessionKey: snap.SessionKey,