Agent 工作流状态机
Magent 现在把 agent loop 建模为显式的 thread -> turn -> item ledger。这个 ledger 是 agent workflow state 的 source of truth。旧 session messages 和 context-items 仍然保留,但只作为 gptel prompt construction、迁移和旧测试的 derived projections。
Codex 对齐
Codex 在 SDK/app-server 边界暴露 thread event stream:
thread.startedturn.queuedturn.starteditem.starteditem.updateditem.completedturn.completed或turn.failed
Magent 保留同类工作流边界,但适配 Emacs:
- Provider transport 仍然走
gptel-request。 - Magent loop 自己负责 tool dispatch、continuation outcome、abort 和 Emacs UI rendering;turn layer 遵循 Codex-style continuation:tool output 触发下一次 sampling,assistant completion 结束 turn,即使 assistant text 为空。
- 不引入 Codex sandbox、seatbelt、bubblewrap 或 shell isolation parity。
Codex 中最接近的参考点是 SDK event names、core sampling loop、app-server event conversion 和 thread status shape。Magent 借鉴的是事件边界和 turn/item 生命周期,不复制 Codex 的隔离和 app-server runtime。
状态对象
magent-ledger.el 定义 canonical ledger:
magent-thread- 状态:
not-loaded、idle、active、system-error、closed
- 状态:
magent-thread-turn- 状态:
queued、in-progress、completed、interrupted、failed、dropped
- 状态:
magent-thread-item- 状态:
pending、in-progress、completed、failed、cancelled
- 状态:
每个用户 prompt 创建一个 turn。Assistant message、reasoning block、tool invocation 和 tool output 都是这个 turn 下的 item。Reasoning item 永远不会提升成 assistant message text,即使 provider 没有返回可见 content。
| 对象 | 运行态 | 终态 |
|---|---|---|
| thread | not-loaded, idle, active, system-error, closed |
closed |
| turn | queued, in-progress |
completed, interrupted, failed, dropped |
| item | pending, in-progress |
completed, failed, cancelled |
Turn lifecycle:
queued -> in-progress -> completed
-> interrupted
-> failed
-> dropped
Item lifecycle:
pending -> in-progress -> completed
-> failed
-> cancelled
Tool Items
Tool call 和 tool result 是同一个 item lifecycle,不是两条持久记录。
模型请求工具时,item 开始:
(:type tool :status in-progress :name "grep" :input (:pattern "..."))
工具结果可用后,同一个 item 完成或失败:
(:type tool :status completed :output "...")
这和旧的 tool-call + tool-output 分裂不同。为了 prompt reuse,Magent 仍会把 completed tool item 投影成 gptel historical (tool . PLIST) 形状。
如果模型请求工具而结果稍后到达,Magent 按 call-id 更新同一个 item。如果旧路径只记录了 tool result,loop 会创建 synthetic turn,让 item 仍然有 durable turn parent。
持久化
持久化格式是 snapshot + journal 。
journal:内存中的 append-only event log,记录turn-queued、turn-started、item-started、item-completed、turn-completed等生命周期事件。Session 文件只保留由magent-session-journal-max-events控制的最近有界尾部;tool/permission 审计由magent-audit.el单独负责。snapshot:materialized full thread state。它保存当前 thread、turns 和 items,避免每次 resume 都从头 replay。
Session JSON 同时写入两者:
{
"snapshot": { "id": "...", "turns": [] },
"journal": [{ "type": "turn-started", "...": "..." }]
}
旧 messages 和 context-items 是 prompt reuse 和迁移用的 projections。加载时,Magent 优先 replay snapshot + journal ;若不存在则把 legacy messages 迁移成 thread ledger。
Replay 语义:
- 把
snapshot载入 materialized thread state。 - 附加持久化保留的
journal尾部,供恢复和 history 检查。 - 只应用
seq > snapshot.last-event-seq的事件。
因此 snapshot 是快速恢复点,持久化 journal 是有界恢复尾部,两者不可互换。
Loop Flow
- UI submission 进入
magent-runtime-api.el。 magent-runtime-api.el创建 queued ledger turn,并立即记录 completed user message item。- submission 真正开始时,
magent-runtime-api.el把 turn 转成in-progress。 magent-agent-process幂等复用该 turn/user item,不重复写用户消息。magent-agent-loop消费 normalized LLM events。- Text 和 reasoning deltas 更新 snapshot 中 materialized in-progress items,但不会为每个 chunk 追加 journal event;terminal item event 携带最终内容。
- Tool-call events 累积到 provider-neutral
tool-call-batch-end。 - Tool dispatch 启动
toolitems,记录 approval metadata,并把同一 item 更新为completed或failed。 - Tool output 返回
tool-output等 continuation outcome;magent-agent-process决定是否从 session history 重建 prompt 并继续 sampling。 - 如果 post-tool continuation 返回空 assistant text,Magent 会进行一次禁用 provider tools 的 final-response retry。若该 retry 只有 reasoning 而没有 text,再进行一次更严格且禁用 reasoning 的 retry;严格 retry 仍为空时 turn 失败。
- Assistant completion 记录 assistant message item,并完成 turn。
- Abort、failure 和 dropped queued submissions 分别把 turn 转成
interrupted、failed或dropped;aborted turn 下的 in-progress items 标记为cancelled。 magent-max-sampling-requests是可选兼容 safety guard,默认关闭。Codex 没有 per-turn sampling cap;用户显式启用时,Magent 在到达上限时用一次禁用 provider tools 的 final request 替代后续 continuation。
Prompt reconstruction 是 ledger-driven。知道 current turn id 时,Magent 只包含 completed history 和该 turn,避免后续 queued user submissions 泄漏进 active model request。
UI 投影
受支持的 frontend 是 agent-shell。 magent-agent-shell.el 创建由 magent-acp.el 实现的 in-process ACP client。ACP session/prompt request 提交到 magent-runtime-api.el ,并保持 pending 直到对应 runtime turn completed、failed 或 cancelled。Runtime 发出 Magent-native observer events; magent-acp.el 转换为 ACP session/update 消息。
magent-runtime-queue.el 负责 queued/active turn state。当前实现一次只有一个全局 active turn,但 submission 都带 runtime session id,所以 cancellation 是 session-scoped:取消一个 ACP session 会移除该 session 的 queued work 并 abort 该 session 的 active turn,不会丢掉其他 session 的 queued turns。
magent-ui.el 、 magent-ui-legacy.el 以及 docs/UI_BACKENDS.org 覆盖的其他
legacy UI 文件是保留但不受支持的代码,不属于受支持的 UI projection 或公共命令面。
保留的 Codex 差异
Magent 刻意保留以下差异:
- UI 是 Emacs-native;受支持的交互是 agent-shell + in-process ACP。
- Provider streaming 经
magent-llm-gptel.el规范化,但 transport 仍是 gptel。 - Codex core 有 app-server mailbox、steering queue 和更丰富的 intra-turn input 语义;Magent 把 request serialization 放在 runtime queue,用 Codex-style tool continuation 处理工具结果:工具执行记录 model-visible output,然后 turn layer 继续 sampling,直到模型返回 assistant completion。Magent 仍不实现 Codex 的 app-server mailbox、steering queue、stop hooks 或 mid-turn auto-compaction。
- Magent 用自己的 session JSON
snapshot + journal,不直接使用 Codex rollout files。 - Tool call 和 output 在 Magent 中是一个 source-of-truth item 的状态更新。
- Tool execution 当前由 Magent tool queue 串行化;Codex 更丰富的 per-tool runtime、approval、MCP 和 process execution 机制不在当前范围。
- Child agents 是
AGENT_JOBS.org中描述的 durable Magent jobs,不是 Codex app-server threads。
Backlog / TODO
Agent-workflow/UI refactor backlog 已无开放 TODO。未来 hardening 候选:
- 为更多结构化输出增加 per-tool renderer plugins。
- 增加超大 transcript 和长 streaming session 的 live visual smoke coverage。
设计评审摘要
当前主 agent-loop 相比 Codex 的主要差距已经不再是缺少显式 thread/turn/item lifecycle;Magent 已经拥有并持久化该 lifecycle。
剩余差异是有意的产品/运行时边界:
- 不提供 Codex app-server lifecycle manager、subscriber model、unloaded thread cache 或 active flags。
- 不复制 Codex 的 intra-turn input queue/mailbox semantics。
- 不直接使用 Codex rollout files。
- 不把 tool call 和 tool output 分成两条 source-of-truth records。