第16章 目标、计划、技能与定时任务:长程上下文治理
从一次对话走向长期任务:目标状态、计划模式、技能复用与未来触发
16.1 本章导读
一个能调用工具的 Agent 只能解决“眼前这一步”;一个可长期协作的 Agent 还需要知道自己在追什么目标、下一步计划是什么、哪些能力可以复用、未来什么时间该继续工作。本章聚焦四组平台能力:goal、plan、skill 与 schedule。它们共同回答一个问题:Harness 如何把一次性对话扩展成可持续推进的任务系统。
16.2 目标状态机:GoalService
GoalService 同时实现 IGoalQueryService 与 IGoalCommandService。它的工作流很直接:读取当前会话目标,规范化命令,交给 GoalAggregate 做状态转移,再通过 IGoalRepository 保存快照。目标是跨回合存在的状态,不是某一条模型消息的临时注释。
| 状态/动作 | 取值 | 含义 |
|---|---|---|
| 目标状态 | ACTIVE、PAUSED、BLOCKED、COMPLETED、CLEARED | 描述长期目标当前是否可继续、暂停、阻塞、完成或清除 |
| 目标动作 | CREATE、UPDATE、PAUSE、RESUME、COMPLETE、BLOCK、CLEAR | 驱动聚合状态机变更 |
| 终态判断 | COMPLETED、CLEARED | 只有终态后才允许创建新的目标 |
目标层还对应一组模型可调用工具:goal_get 读取当前目标,goal_create 创建长期目标,goal_update 推进、暂停、恢复、完成、阻塞或清除目标。这样模型不必直接理解数据库,只需要通过工具协议改变目标状态。
16.3 目标与 HTTP/API 的关系
目标既可以被模型通过工具使用,也可以由外部系统通过 REST API 管理。GoalQueryController 暴露 GET /api/harness/goals/{sessionId},GoalCommandController 暴露 POST /api/harness/goals/{sessionId}。这种双入口设计很重要:模型能自我管理长期任务,控制台或运维系统也能人工介入。
{
"action": "CREATE",
"title": "完成插件开发教程",
"description": "补齐示例、验证命令和常见错误",
"maxRounds": 8,
"continuationEnabled": true
}
边界:目标状态依赖 sessionId。如果未来做多租户或多实例,目标仓储必须与认证身份、会话亲和性和任务锁一起设计,不能只按字符串会话号更新。
16.4 计划模式:PlanModeService 与 exit_plan_mode
计划模式用于让 Agent 先规划、再执行。源码中的 PlanModeService 管理每个会话的 PlanModeState,ExitPlanModeTool 则让模型通过工具显式退出计划模式。它的价值不在“生成一段计划文本”,而在把“是否还处于规划阶段”变成可检查的运行状态。
| 元素 | 职责 | 维护建议 |
|---|---|---|
PlanModeState | 记录计划模式是否开启、计划内容或相关状态 | 作为会话级上下文,不要混入工具返回文本 |
PlanModeService | 维护内存中的会话计划状态 | 长期运行前评估持久化和重启恢复 |
exit_plan_mode | 让模型声明计划完成并切换执行阶段 | 与 UI 展示和策略树节点保持语义一致 |
当你新增“强制先计划”或“计划需审批”的能力时,推荐把策略放在用例编排层,而不是把判断写进工具或前端。工具只表达动作,策略树决定什么时候允许动作发生。
16.5 技能工具:SkillTool
SkillTool 是把“可复用能力”塞进工具体系的入口。它实现 ToolDefinition,因此和文件、Shell、Web、插件、MCP 工具一样,会经过工具注册、模型 schema、执行上下文和结果回填。技能的工程价值是把一组稳定做法沉淀成可调用单元,而不是让模型每次都从零推理。
v0.1.3 后技能来源统一由 harness.extensions.skills 管理:roots、home、bundled-dir 和项目目录都会被发现;文件可以是 <skill-name>/SKILL.md 或 <skill-name>.md。同名技能按 rank 仲裁,技能名要求 kebab-case,降低多来源注册时的歧义。
| 维度 | 技能工具关注点 |
|---|---|
| 输入 | 应明确任务、上下文和约束,避免无边界自由文本 |
| 输出 | 应返回结构化或可审计内容,便于写入会话事件 |
| 治理 | 技能内部若会调用高风险工具,仍应经过审批、Hook 和沙箱策略 |
| 版本 | 技能更新应考虑兼容旧会话和历史事件回放 |
16.6 定时任务:ScheduleService
ScheduleService 提供未来触发能力,领域模型包括 ScheduleRecord、ScheduleKind、ScheduleState 和 ScheduleView。支持三类计划:AFTER 表示若干秒后触发,AT 表示指定时间触发,EVERY 表示按间隔重复触发。
| 工具 | 能力 | 说明 |
|---|---|---|
schedule_create | 创建一次性或周期性计划 | 参数可表达 after、at、every 等调度语义 |
schedule_list | 列出当前会话计划 | 返回 SCHEDULED、OVERDUE 等视图状态 |
schedule_delete | 删除计划 | 按计划 id 删除 |
drive(sessionId, now) 是调度推进方法:它查找到期记录;对 EVERY 任务会推进到下一个锚点之后;对一次性任务会删除记录,表示已派发。这个设计把“找出该触发什么”和“真正向 Agent 投递任务”分开,方便未来替换调度器。
当前边界:README 已明确指出 ScheduleService 尚无 @Scheduled 或 Quartz 驱动方,且仓储偏内存形态;它是领域能力雏形,不是完整生产级调度系统。上线前必须补驱动、持久化、幂等和失败重试。
16.7 Typert:扩展贡献的注册表
typert 域不是普通业务功能,它更像“扩展贡献目录”。TypertContribution 可以携带多个 InvocationDescriptor,TypertService.registerContribution() 会把这些调用描述同时写入 remote 与 local registry。后续调用方可以通过 endpoint 解析 invocation,而不需要知道能力来自插件、远端协议还是本地适配器。
| 对象 | 职责 | 价值 |
|---|---|---|
InvocationDescriptor | 描述 endpoint、schema、调用方式与元信息 | 让能力可发现、可解析、可治理 |
TypertContribution | 把多个 invocation 作为一次贡献注册 | 适合插件或协议加载时批量导入 |
ITypertRegistryContract | 聚合 context、lookup、local、remote 四类注册表 | 统一本地与远端能力目录 |
InMemoryTypertRegistry | 当前内存实现 | 适合启动期装配和本地演示 |
TypertProtocolLoader | 基础设施侧协议加载入口 | 把外部描述导入注册表 |
如果把第8章的工具系统看成“能执行什么动作”,Typert 更像“动作目录与协议描述”。它给插件、MCP、远端工具和未来 UI 动态表单之间留了一个统一发现层。
16.8 Credentials:密钥引用,而不是明文散落
Agent Harness 需要调用模型、插件、MCP Server、子代理和外部 API,密钥如果散落在脚本、工具参数或日志里,会直接破坏安全边界。项目中的 credentials 域用 CredentialRef 表示引用,用 ResolvedCredentialVO 表示解析后的值,用 CredentialInfoVO 表示可展示但不泄密的摘要。
基础设施层的 CredentialsYamlStore 与 LocalCredentialProviderPort 提供本地实现;生产环境可以替换为 Vault、KMS、云密钥服务或企业凭据平台。关键是领域层只依赖 ICredentialProviderPort,不会把密钥存储方式扩散到工具系统。
| 原则 | 落地方式 |
|---|---|
| 引用优先 | 工具参数传 CredentialRef,不要直接传明文密钥 |
| 最小展示 | 前端只展示 CredentialInfoVO 中的名称、类型和摘要信息 |
| 运行期解析 | 真正执行外部调用前才解析成 ResolvedCredentialVO |
| 实现可替换 | 本地 YAML 是开发便利,不应成为生产密钥体系 |
16.9 六类能力如何组合
目标、计划、技能、定时任务、Typert 和凭据各自独立,但组合后才能支撑长程 Agent。一个典型流程是:用户创建目标;Agent 进入计划模式,拆解步骤;模型调用技能处理可复用动作;插件通过 Typert 贡献可调用端点;需要外部 API 时通过 CredentialRef 解析密钥;遇到需要等待外部条件的步骤时创建定时任务;未来调度触发后,再回到同一个 session 推进目标状态。
16.10 长程上下文的风险
长程能力最怕“看起来能续跑,实际不可审计”。建议重点关注七个风险:目标状态被并发覆盖;计划只在内存中,重启丢失;技能版本变化导致旧会话语义漂移;定时任务重复派发;Typert 注册表重启后丢失;凭据被日志或工具结果泄漏;模型在没有人工确认时长期自动调用高风险工具。解决这些问题,需要把状态、事件、审批、幂等、版本和密钥最小暴露一起设计。
| 风险 | 建议 |
|---|---|
| 目标并发更新 | 使用 revision 或乐观锁,保证状态机转移可追踪 |
| 计划丢失 | 把计划模式状态持久化,并写入事件日志 |
| 技能漂移 | 记录技能版本,避免历史回放使用新语义解释旧结果 |
| 重复调度 | 为派发引入幂等 key 和状态转移日志 |
| 注册表漂移 | Typert contribution 需要版本、来源和健康检查 |
| 密钥泄漏 | 日志、事件和工具结果默认脱敏,只保存引用和摘要 |
| 长期自动执行 | 周期任务默认低权限,高风险工具仍需运行期审批 |
16.11 小结
本章要点:目标让 Agent 知道长期追求什么;计划模式让它在执行前先形成结构化路径;技能把稳定能力沉淀成工具;定时任务让系统能在未来继续推进;Typert 负责贡献发现;Credentials 负责密钥引用与解析。它们共同构成“长程上下文治理”的基础,但当前仍有明显生产化缺口:持久化、调度驱动、幂等、版本、密钥托管和审批联动都需要补强。
至此,学习手册完成从基础启动、架构机制、扩展治理,到工程接手、协同执行和长期任务的完整闭环。下一步可以回到第 14 章的协作清单,挑一个真实改动走完整工程流程;也可以沿第 15 章的工作流/子代理方向,把 Harness 从单 Agent 演进成多执行者协作平台。