OpenClaw Tasks 模块超深度架构分析
分析版本:2026-04-20 | 代码目录:
src/tasks/| 风格:Dark Terminal | 源码行数:4,717 行(27 文件,不含测试)
一、模块定位
1.1 业务职责
src/tasks/ 是 OpenClaw 的任务与流程编排引擎(Task & Flow Orchestration Engine)。它为 AI Agent 的异步操作提供完整的生命周期管理------从创建、追踪、进度更新、完成通知到自动清理。业务职责精确定义为:
- 任务注册表 (
task-registry.ts)--- 2017 行核心引擎:内存索引 + SQLite 持久化 + 多维查询 + 自动生命周期监听 + 投递通知 - 任务执行器 (
task-executor.ts)--- 高级编排 API:创建/启动/完成/失败/重试/取消 + Flow 集成 - 流程注册表 (
task-flow-registry.ts)--- 695 行流程引擎:流程 CRUD + 状态机 + revision 乐观锁 + 两种同步模式 - 任务状态快照 (
task-status.ts)--- 状态聚合 + 可见性过滤 + 文本脱敏 + 用户面输出格式化 - 投递策略 (
task-executor-policy.ts)--- 通知策略决策:done_only / state_changes / silent + 自动投递判定 - 所有权访问控制 (
task-owner-access.ts+task-flow-owner-access.ts)--- ownerKey 隔离的 CRUD + 查找 - 领域视图 (
task-domain-views.ts)--- 内部记录→插件 API DTO 映射 - 持久化层 (
task-registry.store.*+task-flow-registry.store.*)--- SQLite 存储 + 快照/增量双模式 - 审计与维护 (
task-registry.audit.ts+task-registry.maintenance.ts+task-flow-registry.*.ts)--- 过期清理 + 孤儿检测 + 状态修复
1.2 在系统中的位置

┌─────────────────────────────────────────────────────────────────────┐
│ AI Agent / Gateway / CLI │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────────────┐ │
│ │ Sessions │ │ Cron │ │ Subagent │ │ ACP Harness │ │
│ │ API │ │ Scheduler│ │ Spawner │ │ (Codex/Claude Code) │ │
│ └─────┬────┘ └─────┬────┘ └─────┬─────┘ └──────────┬───────────┘ │
│ │ │ │ │ │
│ ┌──────┴────────────┴────────────┴───────────────────┴───────────┐ │
│ │ tasks/ (本模块) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │ │
│ │ │ Task Registry │ │ Flow Registry │ │ Delivery │ │ │
│ │ │ (2017L core) │ │ (695L engine) │ │ Engine │ │ │
│ │ │ + 5 indices │ │ + revision lock │ │ (policy+msg) │ │ │
│ │ │ + lifecycle │ │ + 2 sync modes │ │ │ │ │
│ │ │ + agent events │ │ + state/wait │ │ │ │ │
│ │ └────────┬─────────┘ └────────┬─────────┘ └───────┬───────┘ │ │
│ │ └───────────┬────────┘ │ │ │
│ │ ┌────────────────────┴────────────────────────────┐│ │ │
│ │ │ Store Layer (SQLite) ││ │ │
│ │ │ runs.sqlite + flows.sqlite ││ │ │
│ │ └─────────────────────────────────────────────────┘│ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ┌──────┴────────────┴────────────┴─────────────────────────────┐ │
│ │ Agent Events · System Events · Heartbeat · Channel Send │ │
│ └──────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
tasks/ 是异步操作的统一追踪层------所有需要后台执行的 AI Agent 操作(子代理、ACP 会话、Cron 任务、CLI 命令)都通过此模块注册、追踪和通知。
1.3 核心业务价值
| 价值维度 | 具体体现 | 量化指标 |
|---|---|---|
| 生命周期完整性 | 7 种任务状态 × 8 种流程状态 + 自动状态转换 | queued→running→succeeded/failed/timed_out/cancelled/lost |
| 持久化可靠性 | SQLite 增量写入 + 快照备份 | 进程崩溃后自动恢复 |
| 多维索引 | runId / ownerKey / parentFlowId / relatedSessionKey 4 个索引 | O(1) 查找 vs O(n) 扫描 |
| 投递通知 | 3 种策略 + 2 层 fallback + 幂等键 | done_only / state_changes / silent |
| 所有权隔离 | ownerKey 匹配 + scopeKind(session/system) | 安全的多租户任务访问 |
| 乐观并发 | revision 字段 + expectedRevision 检查 | 防止 Lost Update |
| 自动清理 | cleanupAfter + 7 天保留期 + 过期过滤 | 防止无限增长 |
| 流程编排 | managed / task_mirrored 双模式 + 等待/阻塞/恢复 | 复杂多步骤编排 |
二、模块整体结构
2.1 文件清单与职责矩阵
| 文件 | 行数 | 导出数 | 职责层 | 核心职责 |
|---|---|---|---|---|
task-registry.ts |
2017 | 30+ | Core | 任务注册表主引擎 |
task-executor.ts |
738 | 14 | API | 高级编排 API |
task-flow-registry.ts |
695 | 20+ | Core | 流程注册表引擎 |
task-registry.store.sqlite.ts |
546 | 8 | Store | SQLite 持久化实现 |
task-registry.maintenance.ts |
392 | 6 | Ops | 过期清理 + 孤儿修复 |
task-flow-registry.audit.ts |
286 | 4 | Ops | 流程审计 |
task-status.ts |
183 | 8 | View | 状态快照 + 文本脱敏 |
task-registry.audit.ts |
187 | 4 | Ops | 任务审计 |
task-executor-policy.ts |
121 | 7 | Policy | 通知策略 + 消息格式化 |
task-flow-registry.maintenance.ts |
131 | 3 | Ops | 流程维护 |
task-owner-access.ts |
132 | 7 | Access | 任务所有权网关 |
task-domain-views.ts |
95 | 4 | View | DTO 映射 |
task-registry.store.ts |
92 | 7 | Store | Store 抽象 + 配置 |
task-registry.types.ts |
85 | 13 | Types | 核心类型定义 |
task-flow-registry.types.ts |
43 | 3 | Types | 流程类型定义 |
task-registry.summary.ts |
56 | 3 | Util | 任务汇总统计 |
task-flow-owner-access.ts |
49 | 4 | Access | 流程所有权网关 |
task-registry.audit.shared.ts |
41 | 2 | Ops | 审计共享逻辑 |
runtime-internal.ts |
36 | 30+ | Bridge | Task Registry 内部 API 重导出 |
task-flow-runtime-internal.ts |
21 | 15+ | Bridge | Flow Registry 内部 API 重导出 |
task-registry.paths.ts |
30 | 3 | Util | 状态目录路径解析 |
task-registry-control.types.ts |
28 | 1 | Types | Control runtime 类型 |
task-status-access.ts |
10 | 1 | Bridge | 状态快照访问重导出 |
task-registry.reconcile.ts |
5 | 1 | Ops | Reconcile 入口 |
task-flow-registry.paths.ts |
10 | 1 | Util | Flow 状态目录路径 |
task-registry-control.runtime.ts |
2 | 1 | Bridge | Control runtime 懒加载 |
task-registry-delivery-runtime.ts |
1 | 1 | Bridge | Delivery runtime 懒加载 |
task-registry.store.types.ts |
6 | 1 | Types | Store 快照类型 |
task-flow-registry.store.types.ts |
5 | 1 | Types | Flow Store 快照类型 |
task-flow-registry.store.ts |
77 | 5 | Store | Flow Store 抽象 + 配置 |
总计:4,717 行有效代码(27 文件,不含 7 个测试文件 2,352 行)。
2.2 四层架构
┌─────────────────────────────────────────────────────────┐
│ API Layer (task-executor.ts) │
│ createQueuedTaskRun · createRunningTaskRun · │
│ completeTaskRunByRunId · failTaskRunByRunId · │
│ runTaskInFlow · cancelFlowById · retryBlockedFlow │
│ → 面向调用者的高级编排 API │
├─────────────────────────────────────────────────────────┤
│ Core Layer (task-registry + flow-registry) │
│ Task Registry: 5 indices + lifecycle listener + │
│ delivery engine + idempotent send │
│ Flow Registry: revision lock + 2 sync modes + │
│ state/wait/blocked management │
│ → 面向内部的低级 CRUD + 状态机 │
├─────────────────────────────────────────────────────────┤
│ Policy Layer (executor-policy + status) │
│ shouldAutoDeliver · formatTerminalMessage · │
│ sanitizeTaskStatus · buildTaskStatusSnapshot │
│ → 策略决策 + 用户面格式化 │
├─────────────────────────────────────────────────────────┤
│ Store Layer (store.ts + store.sqlite.ts) │
│ SQLite 增量写入 (upsertTask/deleteTask) + │
│ 快照备份 (saveSnapshot/loadSnapshot) │
│ → 持久化抽象 + 具体实现 │
└─────────────────────────────────────────────────────────┘
2.3 核心类型体系
TaskRecord --- 任务记录(22 个字段)
typescript
export type TaskRecord = {
taskId: string; // 全局唯一 ID (crypto.randomUUID())
runtime: TaskRuntime; // 执行运行时: subagent|acp|cli|cron
taskKind?: string; // 任务类别标签
sourceId?: string; // 来源 ID (如 cron job ID)
requesterSessionKey: string; // 请求者会话密钥
ownerKey: string; // 所有者密钥 (权限隔离)
scopeKind: TaskScopeKind; // 作用域: session|system
childSessionKey?: string; // 子会话密钥 (subagent/acp)
parentFlowId?: string; // 父流程 ID
parentTaskId?: string; // 父任务 ID
agentId?: string; // Agent ID
runId?: string; // 运行 ID (多任务可共享)
label?: string; // 人类可读标签
task: string; // 任务描述 (必填)
status: TaskStatus; // 当前状态
deliveryStatus: TaskDeliveryStatus; // 通知投递状态
notifyPolicy: TaskNotifyPolicy; // 通知策略
createdAt: number; // 创建时间戳
startedAt?: number; // 启动时间戳
endedAt?: number; // 结束时间戳
lastEventAt?: number; // 最后事件时间戳
cleanupAfter?: number; // 清理时间戳 (自动计算)
error?: string; // 错误信息
progressSummary?: string; // 进度摘要
terminalSummary?: string; // 终端摘要
terminalOutcome?: TaskTerminalOutcome; // 终端结果: succeeded|blocked
};
TaskStatus --- 七态状态机
queued ──→ running ──→ succeeded ──→ (blocked?)
│ │ │
│ ├──→ failed └──→ (normal success)
│ ├──→ timed_out
│ └──→ cancelled
└──→ lost (backing session disappeared)
TaskFlowRecord --- 流程记录(16 个字段)
typescript
export type TaskFlowRecord = {
flowId: string; // 全局唯一 ID
syncMode: TaskFlowSyncMode; // task_mirrored|managed
ownerKey: string; // 所有者密钥
requesterOrigin?: DeliveryContext; // 请求来源 (频道+to+accountId)
controllerId?: string; // 控制器 ID (managed 模式必填)
revision: number; // 乐观锁版本号
status: TaskFlowStatus; // 当前状态
notifyPolicy: TaskNotifyPolicy; // 通知策略
goal: string; // 流程目标描述
currentStep?: string; // 当前步骤
blockedTaskId?: string; // 阻塞的任务 ID
blockedSummary?: string; // 阻塞摘要
stateJson?: JsonValue; // 任意状态数据
waitJson?: JsonValue; // 等待条件数据
cancelRequestedAt?: number; // 取消请求时间
createdAt: number; // 创建时间
updatedAt: number; // 更新时间
endedAt?: number; // 结束时间
};
TaskFlowStatus --- 八态状态机
queued ──→ running ──→ waiting ──→ running (loop)
│ │ │
│ │ ┌─────┘
│ │ blocked ──→ running (retry)
│ │
│ ├──→ succeeded
│ ├──→ failed
│ ├──→ cancelled (cancelRequestedAt set first)
│ └──→ lost
└──→ cancelled
三、核心业务逻辑深度解析
3.1 Task Registry(task-registry.ts)--- 核心引擎

5 个内存索引
typescript
const tasks = new Map<string, TaskRecord>(); // 主表: taskId → TaskRecord
const taskDeliveryStates = new Map<string, TaskDeliveryState>(); // 投递状态: taskId → DeliveryState
const taskIdsByRunId = new Map<string, Set<string>>(); // runId → taskId[]
const taskIdsByOwnerKey = new Map<string, Set<string>>(); // ownerKey → taskId[]
const taskIdsByParentFlowId = new Map<string, Set<string>>(); // parentFlowId → taskId[]
const taskIdsByRelatedSessionKey = new Map<string, Set<string>>(); // sessionKey → taskId[]
索引维护 :每次 updateTask() 调用时,检测 ownerKey/childSessionKey/parentFlowId 是否变化,增量更新索引。避免全量重建(O(n)),改为精确的 add/delete 操作(O(1))。
createTaskRecord() --- 创建任务(完整流程)
1. ensureTaskRegistryReady() → 首次调用时从 SQLite 恢复 + 启动 Agent Event 监听器
2. resolveTaskRequesterSessionKey() → 确定 requesterSessionKey
3. resolveTaskScopeKind() → 确定 session/system 作用域
4. resolveTaskOwnerKey() → 确定 ownerKey (优先 params.ownerKey > requesterSessionKey)
5. assertTaskOwner() → 验证 ownerKey 非空 (system 除外)
6. assertParentFlowLinkAllowed() → 验证父流程链接合法性:
- scopeKind 必须是 session
- flow 必须存在
- ownerKey 必须匹配
- flow 不能已取消
- flow 不能已终止
7. findExistingTaskForCreate() → 去重检查:
- 同 runId + runtime + scopeKind + ownerKey + childSessionKey + parentFlowId + label + task → 精确匹配
- ACP: 同 runId + scope 匹配 → 选择优先级最高的 (cli > 其他)
8. 如有 existing → mergeExistingTaskForCreate() → 增量补全空字段
9. 如无 existing → 创建新记录:
- taskId = crypto.randomUUID()
- deliveryStatus = ensureDeliveryStatus() → system→not_applicable, 有ownerKey→pending, 无→parent_missing
- notifyPolicy = ensureNotifyPolicy() → silent(不可投递) / done_only(默认)
- cleanupAfter = 终态时自动计算 (endedAt + 7天)
10. 写入主表 + 更新 5 个索引 + 持久化
11. syncFlowFromTask() → 同步父流程状态 (task_mirrored 模式)
12. emitTaskRegistryObserverEvent("upserted")
13. 如已终态 → maybeDeliverTaskTerminalUpdate()
mergeExistingTaskForCreate() --- 幂等合并
设计目的:同一次 ACP 运行可能多次调用 createTaskRecord(每次 LLM turn 都可能报告状态)。幂等合并确保不创建重复记录,而是补全空字段。
合并规则:
- 源数据(sourceId/parentFlowId/parentTaskId/agentId/label/task)只在原记录为空时才填充
preferMetadata=true时允许覆盖已有 label/task- deliveryStatus 只能从非 pending → pending(不能回退)
- notifyPolicy 只能从 silent → 非 silent(不能变回 silent)
updateTask() --- 更新任务(核心写入路径)
1. 获取 current record
2. merge patch → next
3. 如果 next 是终态且无 cleanupAfter → 自动设置 (endedAt + 7天)
4. 检测索引变化 → 增量更新 5 个索引
5. persistTaskUpsert() → SQLite 增量写入
6. syncFlowFromTask() → 同步父流程 (task_mirrored)
7. syncManagedFlowCancellationFromTask() → 检查 managed 流程是否所有子任务已终态
8. emitTaskRegistryObserverEvent("upserted")
9. return cloneTaskRecord(next)
ensureListener() --- Agent Event 自动状态转换
typescript
listenerStop = onAgentEvent((evt) => {
// 按 runId + sessionKey 查找匹配的任务
const scopedTasks = getTasksByRunScope({ runId: evt.runId, sessionKey: evt.sessionKey });
for (const current of scopedTasks) {
if (isTerminalTaskStatus(current.status)) continue; // 终态任务忽略
const patch: Partial<TaskRecord> = { lastEventAt: now };
if (evt.stream === "lifecycle") {
if (phase === "start") → patch.status = "running"
if (phase === "end") → patch.status = "succeeded" / "timed_out" (aborted)
if (phase === "error") → patch.status = "failed"
}
updateTask(current.taskId, patch);
maybeDeliverTaskStateChangeUpdate(); // state_changes 策略通知
maybeDeliverTaskTerminalUpdate(); // done_only 策略通知
}
});
设计目的:自动监听 Agent 生命周期事件(start/end/error),无需手动调用 start/complete/fail。当 ACP 或 subagent 运行状态变化时,任务记录自动更新。
3.2 Delivery Engine --- 投递通知

maybeDeliverTaskTerminalUpdate() --- 终态投递
1. shouldAutoDeliverTaskTerminalUpdate() 检查:
- notifyPolicy ≠ silent
- runtime ≠ subagent (subagent 由调用者直接处理)
- 是终态
- deliveryStatus = pending
2. tasksWithPendingDelivery 去重 → 防止并发投递
3. shouldSuppressDuplicateTerminalDelivery() → ACP 多 runId 去重
4. resolveTaskDeliveryOwner() → 确定投递目标:
- 有父流程 → 用流程的 ownerKey + requesterOrigin
- session 作用域 → 用任务的 ownerKey
- system 作用域 → 无投递目标
5. canDeliverTaskToRequesterOrigin() → 检查是否有可投递的频道
6. 投递路径:
├─ 可投递频道 → sendMessage() 直接发送
│ ├─ 成功 → deliveryStatus = "delivered"
│ └─ 失败 → fallback 到 queueTaskSystemEvent()
│ ├─ 成功 → deliveryStatus = "session_queued"
│ └─ 失败 → deliveryStatus = "failed"
└─ 不可投递频道 → queueTaskSystemEvent() 直接走系统事件
├─ 成功 → deliveryStatus = "session_queued"
└─ 失败 → deliveryStatus = "failed"
7. blocked 后续通知:
- terminalOutcome = "blocked" → 额外 queueBlockedTaskFollowup()
- 使用不同 contextKey (task:<id>:blocked-followup)
幂等键设计 :task-terminal:<taskId>:<status>:<outcome> / flow-terminal:<flowId>:<taskId>:<status>:<outcome>。确保同一条消息不会重复发送。
maybeDeliverTaskStateChangeUpdate() --- 状态变更投递
1. shouldAutoDeliverTaskStateChange() 检查:
- notifyPolicy = state_changes
- deliveryStatus = pending
- 非终态
2. deliveryState.lastNotifiedEventAt 检查 → 防止重复通知旧事件
3. formatTaskStateChangeMessage() → 生成消息:
- running → "Background task started: <title>."
- progress → "Background task update: <title>. <summary>"
- 其他 → null (不发送)
4. 投递路径同终端投递,但使用不同的幂等键格式:
task-event:<taskId>:<eventAt>:<eventKind>
flow-event:<flowId>:<taskId>:<eventAt>:<eventKind>
3.3 Flow Registry(task-flow-registry.ts)--- 流程引擎

两种同步模式
| 模式 | 用途 | 状态同步 | 控制器 |
|---|---|---|---|
task_mirrored |
单任务流程(自动创建) | 流程状态 = 任务状态镜像 | 无需 controllerId |
managed |
多步骤编排流程 | 控制器手动设置状态 | 需要 controllerId |
task_mirrored :createTaskFlowForTask() 自动从任务创建,syncFlowFromTask() 在每次任务更新时自动同步流程状态。一对一无缝映射。
managed :控制器(如 TaskFlow skill)通过 setFlowWaiting()/resumeFlow()/finishFlow()/failFlow() 手动管理流程状态。支持等待(wait)、阻塞(blocked)、恢复(resume)等复杂语义。
Revision 乐观锁
typescript
updateFlowRecordByIdExpectedRevision(params: {
flowId: string;
expectedRevision: number; // 调用者持有的 revision
patch: FlowRecordPatch;
}): TaskFlowUpdateResult {
const current = flows.get(params.flowId);
if (!current) return { applied: false, reason: "not_found" };
if (current.revision !== params.expectedRevision) {
return { applied: false, reason: "revision_conflict", current: cloneFlowRecord(current) };
}
// revision 匹配 → 应用 patch,revision += 1
return { applied: true, flow: writeFlowRecord(applyFlowPatch(current, params.patch), current) };
}
设计目的:多个子任务可能同时更新同一个流程。revision 确保更新基于最新状态,防止 Lost Update。调用者需要重新读取 flow 并重试。
deriveTaskFlowStatusFromTask() --- 状态映射
typescript
task.queued → flow.queued
task.running → flow.running
task.succeeded + terminalOutcome="blocked" → flow.blocked
task.succeeded + terminalOutcome=undefined/"succeeded" → flow.succeeded
task.cancelled → flow.cancelled
task.lost → flow.lost
task.failed/timed_out → flow.failed
3.4 Task Executor(task-executor.ts)--- 高级编排

ensureSingleTaskFlow() --- 自动流程包装
typescript
function isOneTaskFlowEligible(task: TaskRecord): boolean {
// 条件:session 作用域 + 无父流程 + 可投递 + acp/subagent 运行时
if (task.parentFlowId?.trim() || task.scopeKind !== "session") return false;
if (task.deliveryStatus === "not_applicable") return false;
return task.runtime === "acp" || task.runtime === "subagent";
}
设计目的:ACP/subagent 任务如果没有显式父流程,自动包装为 task_mirrored 流程。这样即使单个任务也有 Flow 的投递和取消能力。
retryBlockedFlowTask() --- 阻塞重试
1. resolveRetryableBlockedFlowTask():
- flow 存在 + status = "blocked"
- latestTask.status = "succeeded" + terminalOutcome = "blocked"
2. 创建新任务:
- 继承原任务的 runtime/ownerKey/task/label/notifyPolicy
- parentFlowId = flow.flowId
- parentTaskId = 原任务的 taskId
- status = queued/running (由调用者选择)
3. 返回 RetryBlockedFlowResult:
{ found, retried, previousTask, task }
cancelFlowById() --- 流程取消(5 步)
1. 验证流程存在 + 非终态
2. markFlowCancelRequested() → 设置 cancelRequestedAt (revision 保护)
3. 取消所有活动子任务:
for each active task → cancelTaskById()
4. 检查是否还有活动子任务:
- 有 → 返回 "One or more child tasks are still active."
- 无 → 继续
5. cancelManagedFlowAfterChildrenSettle():
revision 保护更新 flow.status = "cancelled"
3.5 Task Status(task-status.ts)--- 状态快照

buildTaskStatusSnapshot()
typescript
function buildTaskStatusSnapshot(tasks, opts?): TaskStatusSnapshot {
const now = Date.now();
const visibleCandidates = tasks.filter(t => !isExpiredTask(t, now)); // 排除已过期
const active = visibleCandidates.filter(isActiveTask); // queued + running
const recentTerminal = visibleCandidates.filter(t => isRecentTerminalTask(t, now)); // 5 分钟内终态
const visible = active.length > 0
? [...active, ...recentTerminal] // 有活动任务时显示全部
: recentTerminal; // 只显示最近终态
const focus = active[0] // 第一个活动任务
?? recentTerminal.find(t => isFailureTask(t)) // 或最近的失败任务
?? recentTerminal[0]; // 或最近的终态任务
return { latest, focus, visible, active, recentTerminal,
activeCount, totalCount, recentFailureCount };
}
visible 逻辑:有活动任务时,活动+最近终态都显示;无活动任务时,只显示最近 5 分钟的终态。过期任务(cleanupAfter <= now)始终不可见。
文本脱敏管线
原始文本 → stripInlineLeakedInternalContext() // 移除内部运行时上下文
→ sanitizeUserFacingText() // 移除敏感信息
→ replace(/\s+/g, " ").trim() // 折叠空白
→ truncateUtf16Safe(maxChars) // 截断 + "..." 后缀
stripInlineLeakedInternalContext :检测 INTERNAL_RUNTIME_CONTEXT_BEGIN 标记和 legacy 格式,截断内部上下文泄漏。防止 AI Agent 的内部运行时提示泄漏到用户可见的通知中。
3.6 Executor Policy(task-executor-policy.ts)--- 策略决策

通知策略矩阵
| notifyPolicy | 终态通知 | 运行中通知 | 适用场景 |
|---|---|---|---|
done_only |
✓ | ✗ | 默认策略------只在完成/失败时通知 |
state_changes |
✓ | ✓ | 需要进度更新的场景 |
silent |
✗ | ✗ | 系统内部任务,不需要通知 |
shouldAutoDeliverTaskTerminalUpdate() 细化规则
typescript
- notifyPolicy = "silent" → 不投递
- runtime = "subagent" + status ≠ "cancelled" → 不投递 (subagent 由父会话处理)
- 非终态 → 不投递
- deliveryStatus ≠ "pending" → 不投递 (已投递过)
subagent 例外:subagent 完成时,其结果由父会话的 Agent 直接处理,不需要额外通知。但 cancelled 状态需要通知(用户可能主动取消了)。
消息格式
| 状态 | 格式 |
|---|---|
| succeeded | "Background task done: {title} (run {id}). {summary}" |
| succeeded+blocked | "Background task blocked: {title} (run {id}). {summary}" |
| failed | "Background task failed: {title} (run {id}). {error}" |
| timed_out | "Background task timed out: {title} (run {id})." |
| cancelled | "Background task cancelled: {title} (run {id})." |
| lost | "Background task lost: {title} (run {id}). {error/fallback}" |
四、关键设计决策
4.1 为什么使用 5 个内存索引而非数据库查询?
问题:任务查找可能按 runId、ownerKey、parentFlowId、sessionKey 进行,纯 Map 扫描是 O(n)。
方案:维护 5 个倒排索引(Map<string, Set>),每个索引将一个键映射到 taskId 集合。
权衡:
- 优点:O(1) 查找,无需 SQL 查询
- 缺点:每次更新需维护索引(增量更新,非全量重建)
- 持久化:索引不持久化,重启时从 SQLite 全量重建
4.2 为什么使用 Agent Event 监听器自动转换状态?
问题:ACP/subagent 运行时的状态变化(start/end/error)需要同步到任务记录。
方案 :onAgentEvent() 监听全局事件流,按 runId+sessionKey 匹配任务,自动应用状态 patch。
好处:
- 调用者无需手动调用 start/complete/fail------事件驱动自动同步
- 即使调用者遗漏了状态更新,事件监听器也能补全
- 解耦------任务注册表不依赖具体运行时的实现
4.3 为什么投递使用两层 fallback?
问题:直接消息发送可能失败(频道不可用、API 限流等)。
方案:
- 首选:通过频道直接发送(sendMessage)------即时送达
- 回退:通过系统事件队列(enqueueSystemEvent + requestHeartbeatNow)------延迟送达
requestHeartbeatNow:确保主会话尽快被唤醒处理系统事件。没有心跳请求,系统事件可能在下一个心跳周期(30 分钟)才被处理。
4.4 为什么 Flow 使用 revision 乐观锁?
问题:多个子任务可能并发更新同一个流程(如 ACP 多步骤流程)。
方案 :expectedRevision 参数。调用者读取 flow 时获得 revision,更新时必须传入相同 revision。如果中间有其他更新,revision 不匹配 → 返回 revision_conflict → 调用者重试。
vs 悲观锁:无锁等待,适合低冲突场景。任务更新频率低(秒级),冲突概率低。
4.5 为什么 task_mirrored 流程自动创建?
问题:单个 ACP/subagent 任务没有显式父流程,但需要流程级别的投递和取消能力。
方案 :ensureSingleTaskFlow() 在创建任务时自动包装为 task_mirrored 流程。流程状态自动跟随任务状态。
好处:所有任务都有统一的"流程"视图------无论是否显式编排。UI 和 API 可以统一通过 flowId 访问。
4.6 为什么 cleanupAfter 自动计算?
问题:任务记录无限增长会消耗内存和磁盘。
方案 :任务进入终态时,自动计算 cleanupAfter = endedAt + 7天。维护任务定期扫描并删除过期记录。
7 天保留期:足够用户回顾最近任务历史,但不会无限堆积。系统任务(scopeKind=system)也有相同的保留期。