PT Platform terminal fallback
2026-05-29 · Mai / MB / Tobatsu
CURRENT ALIGNMENT

失败任务必须有人收口。

`platform_terminal_callback` 不再只是 runner 崩掉时的报警。 它要变成控制面终态通知:Tobatsu 看到任务 `failed/cancelled`,上游就必须知道。

THE 63E5 HOLE

旧规则在哪里断了

这不是 Mai 的状态判断问题。63e5 的 accepted envelope 里有 `platform_terminal_callback`。 真正的问题在 Tobatsu 的触发条件和 suppress guard。

任务创建

Mai 带上 `platform_terminal_callback`。

configured=true · host=tool-test.fpai.io

runner 自己报错

临时脚本 import helper 时触发 `AttributeError`。

'NoneType' object has no attribute '__dict__'

agent 自己 fail

脚本调用 `tobatsu-agent fail`。

transition.fail · actor=agent

外层 run 是成功

进程退出码是 `0`,worker 写 `job.succeeded`。

run.status=success · exit_code=0

兜底没发

worker 没进 notifier;同 run agent fail 也会被 suppress。

platform_terminal_notification.* = 0
MAI / MB / TOBATSU

三边现在各自是什么状态

Mai 和 MB 都接受 always-send。差异在业务 callback 模型: Mai 有 `delivery_update`,MB 没有这层。

Mai

已确认
  • `delivery_update` 可以发很多次,但它不是终态。
  • 只有 `delivery_result` / business failed 才算 business terminal。
  • `business_update + platform_terminal` 仍然要收口 failed。
  • 已 terminal 后再来 callback,只记 event。
  • 后续会补强 `writeBusinessDelivery` 的对称去重测试。

MB

不用先改
  • MB 没有 update / terminal 两段式。
  • `/result` 第一次就把 run 变成 `done`。
  • `/failed` 第一次就把 run 变成 `failed`。
  • `platform_terminal` 只看 `run.status`。
  • 红线:不要先发新 `terminal_reason_kind=agent_failed`。

Tobatsu

暂不动代码
  • 当前只在 `job failed/cancelled` 时触发 platform terminal。
  • 当前同 run 的 agent terminal 会 suppress。
  • 计划改成按最终 task state 发。
  • 过渡期继续用 `terminal_without_agent_completion`。
  • 精确原因放诊断字段,不改 shared enum。
STATE RULES

去重只看终态,不看普通更新

这是这次最重要的修正。不能写成“只要业务 callback 到过,就忽略 platform terminal”。 普通更新不算收口。

Mai 的规则

running + zero/many
business_update
still running / partial
running +
platform_terminal
terminal failed
running +
business_terminal
terminal result
terminal +
later callback
event only

MB 的规则

running +
/result
done
running +
/failed
failed
running +
platform_terminal
failed + close turn
done/failed +
platform_terminal
secondary / idempotent
业务终态 平台收口 只记记录
TOBATSU PLAN

Tobatsu 后面要改什么

现在还没动代码。下面是已经和 Mai、MB 对齐过的待排期改动。 第一刀可以不等 MB 发 enum。

  1. 按 task state 触发

    `job.succeeded + task.failed` 也要发 platform terminal。63e5 就是这个洞。

  2. 删掉 suppress gate

    同 run 有 `transition.fail actor=agent`,只能当诊断,不能阻止发送。

  3. 补诊断字段

    发 `terminal_actor`、`terminal_event_type`、`terminal_summary`、`runner_status`。

  4. 保守 reason kind

    先用 `terminal_without_agent_completion`。不要直接发 `agent_failed`。

  5. HTTP 请求加 UA

    platform terminal 也是外部请求,要带正常 `User-Agent` 和现有 retry。

RED LINES

不能踩的 4 条线

这些是 MB 明确指出的 hard constraints。踩了就会变成 422、409 或 401。

422 `terminal_reason_kind` 闭集

不要先发 `agent_failed`。先用已有 `terminal_without_agent_completion`。

422 `state` 只能两种

只允许 `failed` / `cancelled`。不要发 `done` 或 `succeeded`。

409 `task_id` 必须是真 task

不能塞 parent id 或 job id。MB 会和 `agent_runs.tobatsu_task_id` 对比。

401 Bearer token 原样回传

用上游 mint 的 callback token。不要换成 Tobatsu service token。

TARGET PAYLOAD

过渡期推荐 body

不新增 enum,也能把 63e5 这类问题讲清楚。 业务端拿状态收口,ops 看诊断字段。

{
  "type": "tobatsu.platform_terminal",
  "task_id": "63e5e8a8-...",
  "run_id": "f89ac0c3-...",
  "state": "failed",
  "terminal_reason_kind": "terminal_without_agent_completion",
  "terminal_actor": "agent",
  "terminal_event_type": "transition.fail",
  "terminal_summary": "internal_error: ...",
  "runner_status": "success",
  "callback_kind": "platform_terminal",
  "caller_run_id": "gwr_ac58..."
}
WHAT IS DONE

今天的状态

目前只完成协议对齐和可执行计划。Tobatsu 代码还没有动。

Mai 现状

ACK
  • 同意 always-send。
  • 同意 update 不参与去重。
  • 同意不用 `agent_failed`。
  • 后续补 receiver 测试。

MB 现状

ACK
  • 当前代码已支持。
  • 无需先改接收状态。
  • 如果要新 enum,再起 PR。
  • 过渡方案已确认。

Tobatsu 现状

WAITING
  • 已写计划文档。
  • 已识别 63e5 回归测试。
  • 代码未改。
  • 等用户排期。