本文定义 ExecGo 执行层的统一运行时语义,用于指导后续调度器、执行器、HTTP/gRPC API 和持久化结构的演进。
这份文档的目标不是一次性推翻现有实现,而是先把未来应收敛到的 runtime 契约固定下来。
设计目标
ExecGo 的执行层应当被视为一个面向 AI Agent 的 action harness,而不是一组零散工具调用的拼装。
因此,运行时语义需要满足:
- 对上层 agent 来说状态可判定
- 对长任务来说句柄可轮询
- 对失败来说错误可机器处理
- 对运维和调试来说事件可追踪
- 对未来权限控制和人工介入来说语义可扩展
一、统一状态机
1. 调度状态与运行状态分层
建议继续保留两层语义:
Task.Status面向工作流调度层,表达该任务在 DAG 中的总体状态RuntimeResult.Status面向执行运行时,表达一次实际 task run 的生命周期状态
这样可以避免把“DAG 语义”和“执行语义”混成一层。
2. 调度状态
当前调度状态可继续沿用:
pendingrunningsuccessfailedskipped
含义:
pending已提交,等待依赖满足和调度running已进入执行阶段success最终执行成功failed最终执行失败skipped因依赖失败或取消而未执行
3. 运行时状态
建议统一为:
acceptedrunningsuccessfailedcancelled
含义:
accepted任务已被执行器接收,但尚未进入明确运行态 适用于异步执行器、远程执行器、队列型执行器running正在执行success执行完成且成功failed执行完成但失败cancelled执行被显式取消,或被运行时中止并确认取消成功
4. 状态转移规则
建议允许的主路径如下:
submitted -> pending -> running -> success
submitted -> pending -> running -> failed
submitted -> pending -> skipped
submitted -> pending -> running -> cancelled
runtime:
accepted -> running -> success
accepted -> running -> failed
accepted -> running -> cancelled
accepted -> failed
约束:
success、failed、cancelled、skipped都是终态skipped只属于调度层,不属于一次真实执行cancelled不应再回到runningaccepted主要用于异步执行器,不要求同步执行器必须暴露停留在该状态
二、句柄语义
1. 为什么需要 handle
只要执行器支持以下任一场景,就应该支持句柄:
- 长任务
- 远程任务
- 需要轮询状态
- 需要取消
- 需要持续获取 progress
2. 统一字段
建议统一使用:
handle_idstatusprogressstarted_atfinished_at
3. 行为约定
- 同步执行器可以不返回
handle_id - 异步执行器必须返回稳定的
handle_id handle_id应足以查询一次 run 的当前状态- 后续
cancel、poll、resume等能力都应以handle_id为核心锚点
三、统一结果结构
建议标准结果包络如下:
{
"status": "success",
"handle_id": "run-123",
"output": {},
"started_at": "2026-03-30T12:00:00Z",
"finished_at": "2026-03-30T12:00:03Z",
"duration_ms": 3000,
"attempt": 1,
"details": {},
"error": null
}
字段建议:
status运行时状态handle_id任务句柄;同步执行器可省略output执行器主输出,供上层 agent 消费started_at实际开始执行时间finished_at实际结束时间;未结束时为空duration_ms总执行时长attempt当前成功或失败对应的尝试次数details执行器私有补充信息,例如:- shell 的
exit_code/stdout/stderr - http 的
status_code/headers - file 的
path/size
- shell 的
error结构化错误;成功时为空
输出约束
建议把输出分成两层:
output上层最关心的标准业务结果details调试和执行器专属细节
这能降低上层 agent 对执行器细节的耦合。
四、统一错误结构
建议标准错误包络如下:
{
"code": "timeout",
"message": "task exceeded timeout 5000ms",
"retryable": true,
"source": "scheduler",
"details": {
"timeout_ms": 5000
}
}
建议错误码
unknowninvalid_inputtimeoutcancelleddeniednot_foundexternal_failureinternal
字段含义
code机器可判定的稳定错误分类message人类可读的错误描述retryable是否建议继续重试source错误来源,例如:schedulerexecutorpolicystore
details附加上下文
错误语义建议
invalid_input通常不可重试timeout视执行器类型,一般可配置为可重试denied默认不可重试external_failure可由执行器决定是否可重试internal默认可视情况重试,但应重点报警
五、事件流语义
建议引入统一事件模型,为后续日志、审计、Web UI 和流式订阅做准备。
建议事件类型:
task_submittedtask_acceptedtask_startedtask_progressedtask_retriedtask_succeededtask_failedtask_cancelled
事件至少包含:
typetask_idhandle_idtimestampmessagedata
六、当前结构到目标结构的映射建议
当前已有字段:
Task.StatusTask.RunStatusTask.HandleIDTask.ProgressTask.ResultTask.Errorexecutor.Result
建议演进方式:
阶段 1:兼容式补强
- 保留现有
Task.Status - 让
Task.RunStatus明确只承载 runtime status - 让
Task.Result逐步收敛为标准RuntimeResult的 JSON 表示 - 让
Task.Error作为兼容字段保留,同时逐步引入结构化错误
阶段 2:执行器统一返回结构化结果
executor.Result应逐步对齐到统一运行时结果模型- 所有执行器都尽量返回一致的
status/output/error/progress
阶段 3:API 正式暴露统一契约
GET /tasks/{id}返回的任务对象中,运行时字段具备稳定含义- 新增句柄查询或取消接口时,以统一结构输出
七、对未来功能的约束
这套语义是为以下能力预留空间:
- cancel
- pause / resume
- human-in-the-loop
- 远程 MCP
- process 类长任务
- browser / computer-use bridge
- 策略控制与审批
因此后续新增字段时,应优先复用统一结果、错误和事件模型,而不是为每个执行器单独发明一套状态表达。
八、建议的第一批落地项
在代码层,建议优先做下面几件事:
- 把运行时状态枚举固定下来
- 引入统一
RuntimeResult - 引入统一
RuntimeError - 让调度器在写回
Task.Result时遵循统一包络 - 为后续
cancel预留句柄语义
在此基础上,ExecGo 的执行层才真正具备清晰的 agent runtime 语义。