TauriTavern/docs/Agent/RunEventJournal.md

20 KiB
Raw Blame History

TauriTavern Agent Run Event Journal

本文档定义 Agent Run 的事件日志、状态机、订阅、恢复、取消与审批语义。

Run Journal 是 Agent 系统的真相源。没有 journal 的 Agent 只是一个难以调试的异步流程。

1. 原则

  1. Append-only:事件只能追加,不能原地修改。
  2. Ordered:每个 run 内 event seq 单调递增。
  3. Durable:关键副作用前后必须落 journal。
  4. Replayable:UI timeline、debug、resume 应尽量从 journal 重建。
  5. User-visible:用户能看到工具调用、审批、workspace mutation、commit 与错误。

2. 文件格式

第一期建议 JSONL:

events.jsonl

每行一个 event envelope:

{
  "seq": 12,
  "id": "evt_...",
  "runId": "run_...",
  "timestamp": "2026-04-26T00:00:00Z",
  "level": "info",
  "type": "tool_call_completed",
  "payload": {},
  "causality": {
    "parentEventId": "evt_...",
    "requestId": "model_req_..."
  }
}

要求:

  • seq 由 repository 分配。
  • id 全局唯一或 run 内唯一均可,但必须稳定。
  • type 使用 snake_case。
  • payload 必须可反序列化为 tagged enum。
  • 大文本、二进制、长 tool result 不直接塞进 event,使用 resource ref。

3. Run Status

当前已落地状态:

Created
InitializingWorkspace
AssemblingContext
CallingModel
DispatchingTool
ApplyingWorkspacePatch
AwaitingHostCommit
Finishing
Completed
Cancelling
Cancelled
Failed

后续规划状态:

Created
InitializingWorkspace
AssemblingContext
Planning
Running
CallingModel
AwaitingApproval
DispatchingTool
ApplyingWorkspacePatch
AwaitingHostCommit
Finishing
Completed
Cancelling
Cancelled
Failed

终态:

Completed
Cancelled
Failed

状态迁移必须通过 event 记录,例如:

status_changed { from, to, reason }

4. Event 类型

当前实际写入的主要事件:

run_created
generation_intent_recorded
status_changed
workspace_initialized
persistent_projection_initialized
context_assembled
agent_invocation_created
agent_invocation_started
agent_invocation_completed
agent_invocation_failed
agent_invocation_cancelled
agent_invocation_transferred
agent_task_registered
agent_task_queued
agent_task_started
agent_task_completed
agent_task_failed
agent_task_cancelled
agent_delegate_started
agent_handoff_requested
agent_handoff_accepted
agent_await_started
agent_await_completed
task_return_completed
model_request_created
model_call_attempt_started
model_call_attempt_failed
model_call_retry_scheduled
model_response_stored
provider_state_updated
model_completed
direct_output_captured
tool_call_requested
tool_call_started
tool_result_stored
tool_call_completed
tool_call_failed
workspace_file_written
workspace_patch_applied
agent_loop_finished
artifact_assembled
commit_started
commit_draft_created
persistent_changes_committed
persistent_changes_commit_failed
persistent_state_metadata_update_requested
persistent_state_metadata_updated
persistent_state_metadata_update_failed
run_committed
run_completed
run_partial_success
run_cancel_requested
run_cancelled
drift_recovery_attempted
run_failed

多 invocation 事件必须写入 canonical event scope:

{
  "eventScope": {
    "invocationId": "inv_root",
    "relatedInvocationIds": ["inv_child"]
  }
}

eventScope.invocationId 是事件主归属 invocation;eventScope.relatedInvocationIds 是同一事件关联的其它 invocation。readEvents({ invocationId }) 先按该 event scope 过滤,再应用 seq 分页。旧 journal 没有 canonical scope 时,后端可以用历史 payload 字段兼容读取,但新事件不应依赖字段名推断作为主契约。

以下小节同时包含当前已落地事件和后续阶段设计事件;实现新事件时必须更新 docs/CurrentState/AgentFramework.md。

Handoff event sequence

当前 agent.handoff 的典型事件序列:

agent_handoff_requested
agent_invocation_created      # kind = handoff
agent_task_registered         # continuation = transfer_control
agent_handoff_accepted
agent_invocation_transferred  # source invocation
agent_loop_finished
agent_task_started            # handoff task starts when target invocation is prepared
agent_invocation_started      # target invocation
context_assembled
skill_scopes_resolved
...
agent_task_completed          # incoming handoff task completes when target finishes or hands off again

Handoff 的 task / invocation 结构、prompt brief 与失败边界见 docs/Agent/Handoff.md。

4.1 Run Lifecycle

run_started
status_changed
run_completed
run_partial_success
run_cancel_requested
run_cancelled
drift_recovery_attempted
run_failed

run_failed payload:

{
  "code": "tool_policy_denied",
  "message": "Tool mcp.foo is not allowed by current plan node",
  "technicalMessage": "Validation error: tool_policy_denied: ...",
  "retryable": false,
  "userRetryable": false,
  "details": {}
}
  • retryable:宿主可不询问用户、安全地自动重试。RateLimited、Transient 与结构化 network UpstreamFailure 等暂态错误为 true,并消费同一 Profile modelRetry 预算。例如 model.upstream_invalid_response 表示上游响应体本应承载 provider JSON 契约但不可读或不是合法 JSON,属于可重试的 transient invalid response;它不应用于 request build、provider 明确拒绝、tool call / native metadata / response schema 等本地契约错误。
  • userRetryable:用户可通过 UI 手动重试。retryable=true 时一定为 true;此外 model.tool_call_required、agent.max_tool_rounds_exceeded 等轮次耗尽后的指令漂移错误也是 userRetryable=true,但 禁止 自动重试。若 run 已经有任意 host-confirmed 自动或显式 chat commit,终态会改为 run_partial_success,不会复用 run_failed.userRetryable,避免用户在已保留输出上直接重试造成重复消息。

run_partial_success 是一个独立终态:当一次 run 已经通过自动或显式 Committer 向 chat 发布过至少一次 host-confirmed 结果,但后续仍因 drift、dispatch、workspace.finish、persistent commit 或 persistent metadata 写回错误失败时,executor 会保留这些 chat commit,并写 run_partial_success。它不是 clean success:错误仍在 payload 中暴露,retryable / userRetryable 固定为 false,宿主 UI 应以 warning 展示“已保留部分结果”。只有 persistent_state_metadata_updated 成功后,chat message 才能带有可作为下一轮 base 的 persistStateId。

{
  "code": "model.tool_call_required",
  "message": "model must use Agent tools and finish through workspace_finish",
  "technicalMessage": "Validation error: model.tool_call_required: ...",
  "retryable": false,
  "userRetryable": false,
  "details": {},
  "preservedCommitCount": 1,
  "preservedCommits": [
    {
      "path": "output/main.md",
      "mode": "replace",
      "messageId": "10",
      "round": 4
    }
  ]
}

run_rollback_targets 作为显式丢弃 / 旧版本清理事件形状保留。它不再是 committed drift 的默认终态路径:自动 rollback 会浪费模型已经通过 host 确认的输出。宿主收到该事件时,rollback 必须 fail-fast:目标缺失、runId 不匹配、rollback strategy 缺失、swipe 状态不安全或宿主删除 API 不可用都必须报错,不能静默跳过或扩大为整条消息删除。若之后暴露 retry 动作,也必须使用 run handle / generation_intent_recorded 的 typed generation intent,不得触发 DOM regenerate 按钮。

{
  "reasonCode": "model.tool_call_required",
  "round": 5,
  "targetCount": 1,
  "targets": [
    {
      "path": "output/main.md",
      "mode": "replace",
      "messageId": "10",
      "round": 4
    }
  ]
}

Soft drift recovery:在直接 fail-fast 之前,loop runner 会做"软纠正":当模型返回 0 tool_calls 且包含纯文本时,runtime 会把该文本捕获到当前 profile messageBody artifact 所在 root 的 direct_output.md(默认 output/direct_output.md)并写入 direct_output_captured;随后把纯文本回复推进 history,再追加一条合成的 user 消息提醒它必须通过 Agent 工具继续。root run 如果直接输出就是目标回复,应显式 workspace_commit 该捕获文件,再调用 workspace_finish;如果需要修订已提交内容,必须先 workspace_apply_patch / workspace_write_file,再 workspace_commit,最后 workspace_finish。return-mode child 则必须用 task_return 结束。direct output recovery 没有独立的一次性尝试上限;只要仍有下一轮模型调用预算就继续纠偏。每次尝试都会写一条 drift_recovery_attempted 事件,便于宿主 UI 给用户显示"系统正在纠正…"提示:

{
  "attempt": 1,
  "maxAttempts": 79,
  "maxRounds": 80,
  "limitReason": "max_rounds",
  "round": 9,
  "committedCount": 1,
  "reasonCode": "model.tool_call_required"
}

maxAttempts 是兼容字段,表示在当前 maxRounds 下 direct-output recovery 的理论上限;实际停止条件由 maxRounds / cancel 决定,而不是另一套隐藏 retry budget。

  • 恢复成功 → run 继续,不会发 run_rollback_targets,也不会写 run_failed
  • 恢复失败(模型再次返回 0 tool_calls 且已没有下一轮预算)→ 若没有成功 chat commit,写 run_failed(userRetryable=true);若已有成功 chat commit,写 run_partial_success 并保留输出
  • 上述 rollback / partial-success 语义与前端入口无关;普通发送、/trigger、regenerate 与 overswipe 只要进入 Agent run,都必须遵守同一 journal 与 host commit 契约。

4.1.1 User Guidance

user_guidance_submitted
user_guidance_applied
user_guidance_discarded

User guidance 是 active AgentRun 的 run-scoped 用户输入,不是普通聊天消息。提交成功后 runtime 必须先写 user_guidance_submitted,再将 guidance 放入当前 run mailbox;下一次 root / handoff 前台 invocation 创建模型请求前,runtime 将 pending guidance 合并为一条 canonical role=user message,并写 user_guidance_applied。

已经发出的 provider request 不可被热修改。若 guidance 在模型调用、工具调用、host commit 等过程中提交,只能影响后续安全模型请求边界。run cancel、finish、failure / partial success 会关闭 mailbox,并对尚未应用的 guidance 写 user_guidance_discarded。

事件 payload 必须包含 guidanceId 或 guidanceIds、chars、words、preview、status,并带 invocationId / canonical eventScope 以便 Timeline 可以归入前台 Agent 链。user_guidance_submitted 在 V1 直接内联受长度限制的完整 text,便于 Timeline detail 和审计读取;applied / discarded 事件用 ids、统计和 preview 关联,不重复写全文。guidance 文本不应写入普通 chat history。

4.2 Workspace

workspace_initialized
workspace_file_written
workspace_patch_requested
workspace_patch_applied
workspace_patch_failed
workspace_file_deleted
workspace_rollback_completed

Workspace event 不应内联大文件全文。面向 Agent/UI 的 payload 应记录 path、sha256、chars、words、patch ref;字节数仅保留在文件系统与 hash 等内部实现边界。

4.3 Context

context_assembly_started
context_component_added
context_component_skipped
context_assembled
context_assembly_failed

context_component_skipped 必须有 reason,例如 budget、policy hidden、empty。

Policy 拒绝不是 skipped,而是 failure。

当前每个 Agent invocation 的 context_assembled 会记录 invocationId、canonical request summary、toolSnapshotPath、紧凑 toolSnapshot 摘要与完整 toolTurn。冻结 descriptor/schema 只在 input/invocations/<invocation-id>/tool_snapshot.json 写一次;journal 不重复内联大 schema。toolTurn 包含 snapshotId、有序 canonical ToolId 列表与 typed choice,因此可以证明本轮 provider 实际看到的能力边界。

4.4 Model

model_request_created
model_request_sent
model_delta
model_tool_call_delta
model_response_stored
provider_state_updated
model_completed
direct_output_captured
model_failed

当前 model_request_created 记录 canonical request summary(source、custom format、model、message count、tool count)、round、invocationId 与同一 invocation 的 toolTurn,不默认记录完整 prompt。长期应记录 request ref、profile id、provider/source、model、token estimate;完整 prompt 是否保存取决于调试设置与隐私策略。

当前 model_response_stored 会把完整 AgentModelResponse 写入 model-responses/round-XXX.json,event 只记录路径与摘要。provider_state_updated 只记录 provider_state 摘要字段,不记录完整内部 payload。

model_completed 是 UI timeline 的模型回合入口:

{
  "round": 1,
  "modelResponsePath": "model-responses/round-001.json",
  "toolCallCount": 1,
  "textChars": 26,
  "textWords": 5,
  "hasAssistantText": true,
  "assistantTextChars": 26,
  "assistantTextWords": 5,
  "narration": {
    "source": "assistantText",
    "text": "I will write the artifact.",
    "totalChars": 26,
    "totalWords": 5,
    "truncated": false
  },
  "hasReasoning": true,
  "reasoningChars": 30,
  "reasoningWords": 6
}

narration 是可选字段,仅在模型回合包含工具调用且存在可展示 assistant visible text 时写入;它是模型轮次的展示投影,不是新的 run status,也不从 reasoning / thinking / thought 提取。事件中只保存短 preview;完整文本仍通过 readModelTurn({ runId, round }) 的白名单 DTO 读取。

前端读取详情时使用 Host ABI readModelTurn({ runId, round }),不直接解析 modelResponsePath 指向的 raw 文件。

4.5 Tool

tool_call_requested
tool_call_awaiting_approval
tool_call_approved
tool_call_denied
tool_call_started
tool_call_completed
tool_call_failed

tool_call_requested:

{
  "round": 1,
  "invocationId": "inv_root",
  "callId": "call_...",
  "toolId": "builtin:workspace.apply_patch",
  "snapshotId": "inv_root",
  "name": "workspace.apply_patch",
  "argumentsRef": "tool-args/call_....json",
  "providerMetadata": {}
}

tool_call_completed:

{
  "round": 1,
  "invocationId": "inv_root",
  "callId": "call_...",
  "toolId": "builtin:workspace.apply_patch",
  "snapshotId": "inv_root",
  "name": "workspace.apply_patch",
  "isError": false,
  "errorCode": null,
  "message": null,
  "elapsedMs": 120,
  "resourceRefs": ["output/main.md"]
}

tool_call_started、tool_call_completed 与 tool_call_failed 保持 tool_call_requested 之后的既有顺序,并携带同一 canonical toolId / snapshotId。预算耗尽属于可恢复的 tool_call_failed;turn/snapshot/choice violation 在 requested 后 fail-fast,不会发出 started。tool_result_stored 会携带同一 round、toolId 与 path,用于 UI 读取工具结果详情。

4.6 Plan / Profile

plan_created
plan_updated
plan_node_started
plan_node_completed
plan_policy_violation
profile_selected
profile_switch_requested
profile_switched
profile_switch_denied

Locked plan violation 必须失败或等待用户决策,不能静默忽略。

4.7 Artifact / Commit

artifact_assembly_started
artifact_assembled
artifact_assembly_failed
chat_commit_requested
chat_commit_completed
chat_commit_failed
committed_message_rollback_completed

自动与显式 commit 共用同一事件序列。自动 commit 在一轮 tool calls 全部处理后最多发起一次。chat_commit_requested 记录 chat ref、artifact path 与预期 SHA;Host 重读当前 workspace,SHA 不一致时拒绝提交,并在确认后记录 message id。chat_commit_completed.messageId 是提交时结果,不是对当前聊天消息位置的反向查询。Host 返回未确认时写 warning 级 chat_commit_failed;它不会终止 run,也不会产生 chat_commit_recorded。自动提交跳过本次,显式提交另写模型可见的 tool_call_failed。

5. 事件与副作用的顺序

推荐顺序:

意图事件
  -> 执行副作用
  -> 结果事件

例如 tool call:

tool_call_requested
tool_call_started
tool dispatch
tool_call_completed / tool_call_failed

例如 workspace patch:

workspace_patch_requested
apply patch
workspace_patch_applied

当前 workspace_file_written 同时覆盖 workspace.write_file 的 replace 与 append 成功结果,payload 必须携带 mode,便于 timeline 与恢复逻辑区分完整替换和原样追加。

如果副作用前需要保证恢复后不会重复执行,可以先写 pending event,再由恢复逻辑检查 pending 状态。这一点对 MCP/外部副作用尤其重要。

6. 实时订阅

前端订阅 API:

const unsubscribe = await window.__TAURITAVERN__.api.agent.subscribe(runId, event => {});

要求:

  • subscribe 不复播全部历史,除非 options 指定。
  • UI 首次进入 run 页面可以读取最新页 readEvents(runId, { beforeSeq, limit }),再通过 subscribe / afterSeq 追新;需要回看更早轮次时继续用 beforeSeq 分页补拉。
  • unsubscribe 必须幂等。
  • 事件丢失时,UI 可通过 afterSeq 补拉。

7. 分页读取

Journal 读取需要支持:

readEvents(runId, { afterSeq, limit })
readEvents(runId, { beforeSeq, limit })

移动端 timeline 不应该一次读取巨大 journal。 默认 timeline UI 应把 raw journal 投影成用户可见操作流,并使用窗口化渲染;状态机、provider state 等审计事件仍保留在 journal 中,但不应因为 UI 容量限制挤掉早期操作轮次。

8. Cancel

Cancel 是用户意图,不是 failure。

流程:

cancel_agent_run(runId)
  -> run_cancel_requested
  -> signal cancellation token
  -> 当前可取消操作停止
  -> run_cancelled

约束:

  • LLM call 必须尽量复用现有 cancellation registry 或等价 watch channel。
  • Tool dispatch 需要声明是否 cancellable。
  • Cancel 后不能 commit。
  • Cancel 后 workspace 保留。

9. Approval

危险工具、MCP tool、commit、profile switch 可以要求审批。

流程:

tool_call_requested
tool_call_awaiting_approval
approveToolCall({ approved: true/false })
tool_call_approved / tool_call_denied

审批必须记录:

  • requested tool
  • arguments summary/ref
  • policy reason
  • user decision
  • decision timestamp

审批拒绝不是系统错误。它可以进入模型可见 tool error,或让 run 暂停/失败,取决于 plan policy。

10. Resume

第一期可以不实现自动 resume,但 journal 设计必须支持。

Resume 的基本策略:

  • 读取最后状态。
  • 如果终态,拒绝 resume。
  • 如果 pending model call,没有 result event,标记 previous attempt interrupted,重新发起或让用户选择。
  • 如果 pending external tool call,默认不重复执行,要求人工确认。
  • 如果 pending workspace patch,检查 patch result/ref 决定是否重放。

外部副作用必须谨慎,不能因为恢复而重复调用付费 API 或危险工具。

11. Error 分类

建议错误 code 分层:

agent.invalid_intent
agent.invalid_profile
agent.policy_violation
agent.cancelled
workspace.path_denied
workspace.required_artifact_missing
workspace.patch_failed
context.budget_exceeded
model.provider_denied
model.request_failed
tool.not_found
tool.policy_denied
tool.execution_failed
mcp.server_denied
mcp.tool_denied
commit.cursor_integrity
commit.save_failed
journal.append_failed

journal.append_failed 是严重错误。没有 journal 就不能继续执行副作用。

12. 当前核心 Event Set

当前至少应保持:

run_created
generation_intent_recorded
status_changed
workspace_initialized
persistent_projection_initialized
context_assembled
model_request_created
model_response_stored
provider_state_updated
model_completed
direct_output_captured
tool_call_requested
tool_call_started
tool_result_stored
tool_call_completed
tool_call_failed
workspace_file_written
workspace_patch_applied
agent_loop_finished
artifact_assembled
commit_started
commit_draft_created
persistent_changes_committed
persistent_state_metadata_update_requested
persistent_state_metadata_updated
persistent_state_metadata_update_failed
run_committed
run_completed
run_partial_success
run_cancel_requested
run_cancelled
run_failed

这套事件已经足够支撑当前 tool loop、timeline、cancel、debug、commit、partial-success 和 diff 基础。run_rollback_targets 作为显式丢弃 / 旧版本清理事件形状保留,但不再是 committed drift 的默认终态路径。