Skip to content
预计 1 分钟右侧目录可快速定位

Runtime 事实怎么落表:Run、Node Execution 与 Attempt Identity ​

前置:数据库并发边界:Transaction、Isolation、Optimistic Concurrency 与 Pool
核心问题:为了 crash/retry/resume,数据库至少要保存哪些 Run、Node、Attempt 事实,execution identity 应该落在哪里?

为什么一个 runs 表不够 ​

UI 希望快速回答:

text
这个 Run 现在 RUNNING / WAITING / FAILED / SUCCEEDED?
下一步是什么?
当前 checkpoint/version 是多少?

故障分析则要回答:

text
node-7 执行过几次?
attempt-2 为什么 timeout?
attempt-3 是哪个 worker 执行?
Tool side effect 的 operation_id 是什么?

两个问题粒度不同。把全部 attempt history 塞进 runs.json,每次状态变化都重写巨大对象,并发控制也难以表达。

Run Table:保存当前 Materialized State ​

最小概念字段可以是:

text
runs
- run_id PK
- tenant_id
- status
- state_version
- plan_version
- current_checkpoint_id
- deadline_at
- cancel_requested_at
- created_at / updated_at
- terminal_error_code

Run row 是“当前事实的快速视图”,适合 API 查询和 optimistic concurrency:

sql
UPDATE runs
SET status = ?, state_version = state_version + 1
WHERE run_id = ? AND state_version = ?;

更新 0 行意味着调用方拿的是 stale state,而不是应该无条件覆盖。

Node 与 Execution Attempt 要分清 ​

逻辑 Node:

text
node_id = search-code
state = SUCCEEDED

但它可能经历:

text
attempt 1 → TIMEOUT
attempt 2 → RATE_LIMITED
attempt 3 → SUCCEEDED

因此可使用:

text
node_executions
- run_id
- node_id
- attempt_no
- operation_id
- worker_id / lease_epoch
- status
- started_at / finished_at
- input_ref / output_ref
- normalized_error
- trace_id
UNIQUE(run_id, node_id, attempt_no)
UNIQUE(operation_id)

不要覆盖失败 attempt;它们是 retry、cost、incident analysis 的真实证据。

attempt_no 与 operation_id 不是一回事 ​

Retry attempt 可以增加,但某些外部副作用的 logical operation identity 必须稳定。例如远端 API 的 idempotency key 可能跨 retry 共用同一个 operation_id。

text
logical operation
  operation_id = op-88

attempt 1 → network timeout
attempt 2 → reconciliation / retry

如果每次 attempt 都生成新的业务 operation id,下游无法判断这是一次动作的重试还是两次新动作。

大 Payload 不一定直接塞数据库行 ​

Model raw response、大 Tool output、Artifact 可以进入 object/artifact store,表里保存:

text
content_ref
hash
type
size
schema_version

数据库保留 identity、状态和索引友好的事实。否则大 JSON 频繁更新会放大锁、I/O 和备份成本。

与 Checkpoint / Event Log 的关系 ​

text
Run table
→ current materialized view

Node execution table
→ per-node/per-attempt execution facts

Checkpoint
→ 可恢复 snapshot/boundary

Event log
→ append-only history / reconstruction evidence

一个系统不一定四种都需要完整实现,但概念不能混成“一张 history JSON”。Shadow Harness 可以先简化存储,接口仍保持这些责任边界。

Failure Modes ​

Retry 覆盖上一条 Node 记录 ​

后果: 看不到 attempt history,也无法解释成本和失败链。

处理: logical Node 与 attempt record 分开;attempt append,Node materialized status 更新。

API 直接写 status='SUCCEEDED' ​

后果: 绕过 Runtime invariant 和 execution evidence。

处理: 状态迁移通过 Runtime command/reducer;DB 是 durable representation,不发明第二套状态机。

如何验证 ​

场景必须成立
Node timeout 两次后成功3 条 attempt 可审计,Node 最终 SUCCEEDED
两个 writer 用同一旧 state_version 更新 Run只有一个成功
大 Tool Result 10MB主状态表不需要每次复制完整 payload

自测 ​

  1. 为什么只存 run.status 无法回答“某个 Node 第几次 Attempt 做过什么”?Runtime tables 还需要哪些 identity?
  2. 同一 Node 重试三次时,Node Execution 和 Attempt 应怎样分层,才能保留历史又得到当前状态?

Sources ​

AI Engineering · Agent · Harness · Backend Systems