-
Notifications
You must be signed in to change notification settings - Fork 2
Task Runtime
📑 本页目录
直连引擎与编排器负责"跑一轮",TaskRuntime 负责"跑一个跨进程重启仍能续的长任务": 持久化 Task 模型、Checkpoint、暂停/恢复/取消/重试、崩溃恢复、工具幂等。 来源:
docs/task-execution-architecture.md、docs/T76_ADR.md、docs/T76_ARCHITECTURE_AUDIT.md。
审计发现的三件真事:
| 事实 | 后果 |
|---|---|
SharedPrefsConversationMemory 是 O(n) 读-改-写、apply() 异步落盘、无 schema 版本号 |
崩溃即丢/解码失败即清空 |
引擎写入顺序是 先持久化 Assistant(toolCalls) → 执行工具 → 再写 ToolResult
|
进程死于工具执行期间留下悬空 toolCall → 重启后 LLM 请求被 400 拒绝 |
abort() 只置标志位,无持久化、无 Pause 语义 |
长任务无法断点续跑 |
| # | 决策 | 理由 / 否决项 |
|---|---|---|
| D-1 |
文件式 JSON + 原子 rename:filesDir/taskstore/<taskId>.json,temp 写 → fsync → rename;schema 带 version + ignoreUnknownKeys;损坏文件移入 corrupt/(隔离而非删除) |
checkpoint 频率(每个 ToolCall 边界)远超 SharedPreferences 的 O(n) 模型;否决 SharedPreferences(非原子)、Room(第二数据库)、DataStore |
| D-2 |
TaskRuntime 作为叠加层(组合而非替换):AgentEngine 绑定保持 ApexAgentEngine 不动 |
审计修正:VM 有 15 处 (agentEngine as? ApexAgentEngine) cast,替换绑定会让全部 cast 静默失效;最终改由 AgentTaskStatusController 转发(3 处一行改动) |
| D-3 | 恢复触发 = ViewModel init 时确定性发现(纯文件扫描) | 不复活 platform:persistence 死代码;不依赖后台任务 |
| D-4 | taskId 一等公民 + v1 单活跃执行 | 不先造第二套 Session 系统 |
| 层 | 类型 | 说明 |
|---|---|---|
| Task | AgentTask |
单文件持久化:状态 / 步骤 / journal / 时间戳 / 配置快照 |
| Execution | 执行流 | 一次 execute = 一次执行,Channel + 镜像协程,单活跃互斥
|
| Step | TaskStepModel |
Plan / Spec 的步骤(状态 PENDING/RUNNING/DONE/FAILED/SKIPPED) |
| Operation | ToolOperationRecord |
journal 条目(NOT_STARTED/RUNNING/SUCCEEDED/FAILED/UNKNOWN) |
-
journal 上限:最近 200 条滚动保留(
TaskStoreLimits.MAX_JOURNAL_SIZE); - operationId 格式
<taskId>-op<seq>,与 LLM callId 建立映射(恢复时判幂等 + 修补悬空历史); - 日志脱敏:journal 只存参数摘要(截断 512 字符)与输出摘要(截断 256 字符),不落 prompt 全文。
PENDING / PLANNING / RUNNING / WAITING_USER / PAUSED / CANCELLING / RECOVERING / RETRYING / FAILED / COMPLETED / CANCELLED
迁移表(v1,逐条):
| From | 允许的 To |
|---|---|
| PENDING | PLANNING, RUNNING, FAILED, CANCELLED |
| PLANNING | RUNNING, WAITING_USER, PAUSED, FAILED, CANCELLED |
| RUNNING | PLANNING, WAITING_USER, PAUSED, CANCELLING, RECOVERING, COMPLETED, FAILED |
| WAITING_USER | RUNNING, PAUSED, CANCELLING, RECOVERING, FAILED |
| PAUSED | RUNNING, CANCELLING, RECOVERING, FAILED |
| CANCELLING | CANCELLED, FAILED |
| RECOVERING | RUNNING, CANCELLING, FAILED |
| RETRYING | RUNNING, CANCELLING, FAILED |
| FAILED | RETRYING, CANCELLED |
| COMPLETED / CANCELLED | 无出边(绝对终态) |
三条不变量(有全矩阵测试锁定):
-
自环一律非法(
isLegal(s, s) == false对全部状态成立); -
RUNNING → CANCELLED 直达非法:取消必须经
CANCELLING瞬态(abort 是协作式的,请求取消到收尾之间存在窗口);唯一例外是FAILED → CANCELLED(放弃失败任务); - PAUSED ≠ CANCELLED:PAUSED 可 resume 回 RUNNING,CANCELLED 重启后不自动继续。
与运行时状态机(编排器)的映射:
Planning → PLANNING
Acting / Observing / Responding → RUNNING
AwaitingUserInput / AwaitingPlanConfirmation / AwaitingSpecConfirmation → WAITING_USER
Finished.Completed → COMPLETED;Finished.Failed → FAILED;Finished.Aborted → PAUSED | CANCELLED(按用户意图)
CheckpointBoundary 共 14 类:TASK_CREATED / PLAN_CONFIRMED / STEP_STARTED / TOOL_CALL_STARTED /
TOOL_CALL_FINISHED / STEP_FINISHED / CONTEXT_COMPRESSED / WAITING_USER / ERROR / PAUSED / CANCELLED /
COMPLETED / RECOVERED / RETRY_STARTED。
| 边界 | 落盘内容 |
|---|---|
TOOL_CALL_STARTED |
journal 追加 RUNNING + 幂等分类快照 |
TOOL_CALL_FINISHED |
journal 更新 SUCCEEDED / FAILED + 输出摘要 |
CONTEXT_COMPRESSED |
压缩计数 + historyAnchor + Task 状态重注入
|
STEP_FINISHED |
由步骤切换推导 |
Important
镜像 collector 跑在 TaskRuntime 自己的 scope(与 UI collect 生命周期解耦): 用户点取消、VM job 被取消,checkpoint 落盘仍然完整;abort/pause 的收尾事件不会因为 UI 停止消费而丢失。
引擎 finally 会无条件发 Complete(含失败场景),TaskRuntime 不信它,
由 finalizeStream 按标志统一裁决,优先级:
pause > cancel > failed > completed
finalize 的异常安全:finalize 失败不能吞掉 done.complete / tap.close,否则 pause/cancel 会永久挂起。
| 操作 | 语义 |
|---|---|
pause() |
置 pauseRequested → engine.abort()(协作式)→ 等流收尾 → PAUSED 落盘;对话历史不动。重启发现 PAUSED → 等用户显式续(不自动跑) |
resume() |
注入 [RESUME] 提示(告知中断点进度、勿重复已完成操作)→ 续跑;同一引擎 + 同一 history,上下文天然连续 |
cancel() |
置 cancelRequested → abort → CANCELLING → CANCELLED 落盘;绝不自动继续 |
retry() |
FAILED → RETRYING → RUNNING,retryCount+1,上限 3(DEFAULT_RETRY_LIMIT);注入 [RETRY] 提示 |
并发互斥:AtomicBoolean.compareAndSet 占位 —— 并发两次 resume/retry/execute 只有一个成功,
另一个立即拒绝(无 check-then-act 竞态)。
flowchart TD
A["App 启动 → VM init"] --> B["TaskRuntime.discoverRecoverableTasks()"]
B --> C["store.loadActiveTasks()<br/>(顺带清理 temp + 隔离损坏文件)"]
C --> D["RUNNING/WAITING/PLANNING/RECOVERING → RECOVERING 并落盘<br/>PAUSED 保持不变"]
D --> E["修补悬空 toolCall 历史"]
E --> F["UI 横幅:任务标题 + 中断步骤"]
F -->|继续| G["resumeFromCrash():RecoveryPolicy.planForTask<br/>→ 构造恢复提示 → RECOVERING→RUNNING"]
F -->|取消| H["CANCELLED 终态"]
悬空 toolCall 修复(R-5):为"有 callId 但无配对 ToolResult"的调用追加合成结果:
⚠ Interrupted: outcome UNKNOWN ... Verify ...
既满足 OpenAI 兼容 API 的配对校验(否则整条历史 400 不可用),又明确告知 LLM"结果未知,先验证"。
Note
诚实声明:进程死亡用确定性模拟 TaskRuntime.simulateCrash()(SIGKILL 语义)——
真机 E2E 未做(CI 无法复现真机进程死亡),不伪造 PASS。
压缩(P7 三级)不会抹掉任务状态,靠三件事:
- Task 状态住在 TaskStore 独立文件,不在对话历史里;
-
ContextCompressed边界触发重注入:contextInjector向引擎 history 追加[TASK STATE]system 消息(标题 / 步骤进度 / 未完成步骤); - checkpoint 记录
historyAnchor(压缩后持久化消息数)与compressionCount。
TaskConfigSnapshot 在任务创建时刻从引擎快照:mode / thinkingLevel / maxIterations / maxContextTokens /
压缩参数 / temperature / reflectionRounds / enabledToolIds。
执行中途改设置不影响运行中任务;恢复与重试以快照为参照。
-
LlmRequestContext.taskId/stepId(此前恒 null)现在真实填充:execute 入口设 taskId、StepStart 设 stepId、finalize 清空;引擎全部 7 个构造点经tagged()包裹; -
TaskRuntimeEvent(低频通道):StatusChanged / CheckpointSaved / Finished / RetryExhausted / RecoverableDiscovered; -
AgentEventsealed 层级冻结不扩展。
-
MCP 生态总览
新 - (沙箱 MCP · 官方 Hub · 逆向 Host · 门控语义)
- 终端运行时
- 终端 API 契约
- SDK 边界
- Termux 能力矩阵
- Ubuntu rootfs 供给
- Ubuntu 生命周期
- PRoot 二进制溯源
- VT100/ANSI 模拟器
- 终端性能
- 终端迁移
- 原生层 C++/JNI