Skip to content

P0: 定义 task result settlement 状态机,保证 existing-agent invocation 回复与端到端幂等 #2969

Description

@jolestar

背景

Holon 已经分别具备:

  • TaskRecord 的单调生命周期与 terminal result 持久化;
  • WaitFor(wake=task_result) / exact task rejoin;
  • InvokeAgent(new_subagent | existing_agent)
  • agent message delivery ledger、activation / turn / WorkItem continuation;
  • stale / duplicate rejoin 的局部拒绝逻辑。

但这些机制之间尚缺少一个统一、持久、可恢复的 task result settlement 状态机。当前尤其容易出问题的是 InvokeAgent(existing_agent):调用方创建 invocation task,目标侧接收一条带 delivery / task correlation 的消息,monitor 再从目标 agent 的 activation、turn 和后续 continuation 中推断“这次调用是否已经回复”。

这仍然是在用目标 agent 的执行终局推断调用结果,而不是用一次 invocation 独占的 result identity 明确结算。目标 agent 是长期存在、可并发接收消息、可休眠/离线/重启、也可进入 WorkItem continuation 的主体,因此“目标出现了一个 terminal/replied turn”不必然等于“它回复了这次调用”。

P0 问题

需要定义 task result settlement 的 canonical 状态机,至少覆盖以下路径:

  1. inline:结果在 requester 当前 execution/turn 仍可安全接纳时产生;
  2. late:requester 已结束当前 turn,或已注册/尚未注册 task wait 后结果到达;
  3. offline:requester 或 target 不在内存中,经历 daemon restart / recovery 后仍能完成;
  4. caller-admitted:结果已经通过 requester 侧 canonical scheduler admission,明确绑定到唯一 caller execution;
  5. duplicate-suppressed:同一结果因 producer retry、delivery retry、recovery replay 或重复 wake 再次出现时,只返回既有 settlement,不再创建 turn、重开 task 或重复交付。

没有这层状态机时,会出现以下语义风险:

  • existing agent 同时处理其他消息时,错误的 terminal turn/brief 被识别为本次 invocation reply;
  • 同一 target 上的多个 invocation 并发或乱序完成时,结果串线;
  • result 先于 WaitFor 注册到达,inline 与 late 路径产生不同真相;
  • target 已接受 delivery 但随后休眠、离线、重启或进入 continuation,monitor 永久轮询或错误结算;
  • requester 已完成/切换 WorkItem 后,结果被静默丢弃、错误重入或重开 terminal task;
  • producer、delivery、scheduler 各自做局部去重,但缺少端到端幂等身份。

需要先定义的身份

1. Requester key

必须稳定标识“谁在等待这个结果”,不能仅依赖当前内存态或模糊的 WorkItem 归属。建议至少包含:

  • requester agent identity;
  • requester-owned task id;
  • 创建该 task 的 execution/turn identity(若有);
  • requester WorkItem binding + generation(若有)。

WorkItem 可以参与路由,但不能单独充当 requester identity。

2. Spawn / invocation lineage

每次调用都需要不可变 lineage,贯穿:

  • invocation task;
  • target agent;
  • initial delivery;
  • target activation / turn;
  • recovery attempt;
  • WorkItem / task continuation;
  • terminal result publication。

existing_agent,lineage 表示“这次调用关系”,不能等同于 agent 的创建 parentage,也不能被目标 agent 的其他消息或既有 WorkItem 继承/覆盖。

3. Result identity

每个逻辑结果必须有稳定、可重放的 identity。它应由 requester task + invocation lineage + producer terminal generation/outcome 派生,而不是由“最近一个 turn”或随机 message id 临时决定。

同一逻辑结果的 producer retry、delivery retry、daemon recovery 与 scheduler replay必须复用该 identity;不同 invocation 即使 target、文本和 terminal turn 相似,也不得共享 identity。

建议状态机边界

具体命名可在 RFC 中调整,但至少需要表达以下事实,而不是只用 TaskStatus 代替:

Pending
  -> ResultProduced(result_identity, lineage)
  -> ResultPersisted
  -> InlineEligible | LateQueued | OfflineQueued
  -> CallerAdmitted(requester_key, activation_id)
  -> Settled(settlement_id)

任意已见 result_identity 的重复输入
  -> DuplicateSuppressed(existing settlement)

附加要求:

  • TaskStatus::Completed/Failed/... 描述 producer/task 终态;settlement 描述结果是否以及如何被 requester 接纳,两者不能混为一个字段。
  • terminal task + result publication 必须保持原子或具备明确可恢复的 intent。
  • caller admission 必须有 durable fence;仅 enqueue 成功或 target turn terminal 不等于 requester 已接纳。
  • requester 已不再允许重入时,结果必须进入可查询的 terminal disposition,而不是无限等待或静默消失。
  • duplicate suppression 是正常幂等结果,应有结构化 outcome / audit event,不应只记录 generic invariant violation。

InvokeAgent(existing_agent) 的回复契约

必须明确:

  1. target 收到 invocation 时,delivery、invocation task 和 lineage 被写入 canonical execution source;
  2. lineage 必须跨 target 的 yield、wait、task rejoin、WorkItem continuation 与 restart 传播;
  3. 只有携带该 lineage 的 terminal result publication 才能结算对应 invocation task;
  4. target 的无关 operator input、peer message、timer、其他 invocation 或已有 WorkItem 的 terminal turn 不得被识别为本次回复;
  5. target 可以显式产生“成功但无附加文本”“失败”“取消”“拒绝”“中断”等结果,不能靠抓取最近 assistant message 猜测;
  6. target 不在线时,delivery 与 monitor 可恢复;恢复后不能丢失 requester/result identity;
  7. 多个 caller 并发调用同一个 existing agent 时,允许乱序完成,但必须一一对应。

验收标准

  • 增加 RFC:定义 requester key、invocation/spawn lineage、result identity、settlement states、durable facts 与 reducer ownership。
  • 所有 task result producer(至少 command task、new subagent、existing agent invocation)通过同一个 settlement contract 发布结果。
  • 覆盖 inline result:结果先于 requester 注册 wait 到达,仍只结算一次。
  • 覆盖 late result:requester 已 yield/wait 后精确 rejoin。
  • 覆盖 offline/restart:producer terminal、result persist、caller admission 前后分别注入重启,结果不丢、不重。
  • 覆盖 caller-admitted fence:只有 admission commit 成功后才进入 settled;失败可重试。
  • 覆盖 duplicate-suppressed:重复 publish、enqueue、wake、recovery replay 均返回同一 settlement identity,不创建第二个 turn。
  • 覆盖两个 caller 并发调用同一个 existing agent,并以相反顺序完成,结果不串线。
  • 覆盖 existing agent 在 invocation 期间处理无关 operator/timer/task-result 消息;这些 terminal turn 不得提前结算 invocation。
  • 覆盖 target yield/wait/WorkItem continuation 后再回复,lineage 不丢失。
  • terminal task 不会被结果重开;无法再 admit 的结果具有显式 terminal disposition 和可查询证据。
  • observability 能按 requester key、lineage、result identity、settlement id 串起端到端链路。

与现有 issue 的关系

以下 issue 修复或记录了相邻的单点故障,但不替代本 issue 的统一 settlement contract:

这些问题共同说明:局部修复 delivery、turn id、WorkItem binding 或 stale 判定仍不足以保证端到端结果结算。

当前实现定位(2026-09-14,基于 4de34d6f

  • src/runtime/agent_services.rs:创建 requester-owned invocation task,并启动 monitor;
  • src/host.rs::invoke_existing_agent:创建 durable delivery,记录 current_task_id / correlation 与 pre-delivery turn baseline;
  • src/host.rs::invocation_delivery_terminal_evidence:沿 delivery activation、attempt、WorkItem/task continuation 推断 terminal evidence;
  • src/runtime/tasks.rs::monitor_spawned_child_agent_task:把推断结果提交为 terminal task result;
  • src/runtime/scheduler_executor.rs::provably_stale_task_rejoin:在 requester 侧独立判断 exact task rejoin 是否 stale。

需要把上述分散推断收敛为一个 canonical settlement reducer / ledger,而不是继续在 monitor 和 scheduler 两端增加启发式判断。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:criticalCritical priority - blocks release or productionscheduler

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions