目标系统与自动模式
Goal 系统
Goal(目标)系统允许用户通过 /goal <objective> 命令设定一个长期目标,让 Agent 在多个对话轮次中持续追寻该目标,直至完成、受阻或暂停。
状态机
Goal 的生命周期由以下状态构成:
pursuing → paused → achieved / blocked
→ usage-limited / budget-limited
- pursuing(追寻中):初始状态,Agent 正在积极地朝目标推进。
- paused(已暂停):用户使用
/goal pause暂停自动续跑,可用/goal resume恢复。 - achieved(已完成):Agent 认定目标已完全达成。需经过严格的完成审计(Completion Audit)。
- blocked(已阻塞):Agent 遇到无法继续的障碍,需要用户介入。
- usage-limited / budget-limited:因 token 预算或费用限制而停止。
核心文件
| 文件 | 路径 | 用途 |
|---|---|---|
| goal.tsx | src/commands/goal/goal.tsx |
/goal 命令的 JSX 实现(React Ink 渲染) |
| index.ts | src/commands/goal/index.ts |
命令注册入口 |
| goal.ts | src/utils/goal.ts |
工具函数:构建续跑提示、状态格式化 |
| useGoalAutoContinue.ts | src/hooks/useGoalAutoContinue.ts |
React Hook:在每轮对话结束后自动注入续跑提示 |
持续提示
buildContinuationPrompt()(定义于 src/utils/goal.ts)在每轮自动续跑时注入完整的上下文提示,包含:
- 目标描述(
<objective>XML 标签包裹) - 续跑行为指导:保持目标完整性,不允许缩小范围
- 基于证据的工作原则:依赖当前工作区状态而非对话记忆
- 完成审计要求:必须逐项验证所有需求
- 阻塞审计规则:连续 3 轮相同阻塞条件才允许标记 blocked
useGoalAutoContinue hook(src/hooks/useGoalAutoContinue.ts)监听 QueryGuard 状态:
- 当查询从活跃(running)转为空闲(idle)时触发
- 仅在 goal 状态为
pursuing且非 plan 模式时注入 - 自动清理旧目标的排队续跑,防止冲突
- 每次续跑递增
continuationCount
完成审计(Completion Audit)
Agent 在决定标记目标为 achieved 前,必须执行严格的完成审计:
- 从目标描述中推导具体的、可验证的需求
- 不重新定义成功标准——保留原始范围
- 对每个需求,检查当前状态中的权威证据
- 确认证据足以证明完成,而非仅未发现未完成的工作
- 只有在当前证据能够经受逐项审查时,才标记为已达成
Blocked 审计
防止 Agent 过早放弃的机制(src/utils/goal.ts 中 BLOCKED_AUDIT_TURNS = 3):
- 阻塞条件必须连续出现 至少 3 轮 才能标记 blocked
- 用户恢复已阻塞的目标后,阻塞审计重置
- 仅在确实无法推进、需要用户输入或外部状态变更时才使用
- 不因任务困难、缓慢或需要澄清而标记 blocked
预算跟踪
checkTokenBudget()(定义于 src/query/tokenBudget.ts)根据 token 消耗决定是否继续自动续跑:
- 使用
createBudgetTracker()跟踪每次续跑的 token 消耗 COMPLETION_THRESHOLD = 0.9:token 消耗达到预算的 90% 时停止DIMINISHING_THRESHOLD = 500:连续 3 次续跑且每次增量 < 500 token 时视为收益递减- 返回
continue或stop决策
Auto Mode(自动模式)
激活方式
- 用户通过
/goal <objective>设置目标后自动进入自动模式 - Plan 模式下自动模式被禁用(
inPlanMode检查)
自动续跑机制
每次 Agent 响应完成后,useGoalAutoContinue hook 自动向命令队列注入 [goal] Continue 提示:
const prompt = buildContinuationPrompt(goal, now)
enqueue({
mode: 'prompt',
value: prompt,
priority: 'later',
isMeta: true,
})
- 只在
pursuing状态下触发 - 跳过 plan 模式
- 用户输入优先于自动续跑(有阻塞性用户输入时不注入)
- 避免为同一目标重复排队
update_goal 工具
Agent 可通过 update_goal 工具报告进度:
status='complete':目标达成,停止续跑status='blocked':受阻,需用户介入- 调用时必须包含
goal_id(与目标 ID 匹配)
目标管理命令
| 命令 | 功能 |
|---|---|
/goal <objective> |
设置新目标(若已有活跃目标则提示覆盖确认) |
/goal set <objective> |
同上 |
/goal pause |
暂停当前目标 |
/goal resume |
恢复被暂停的目标(重置续跑计数和预算窗口) |
/goal edit <new objective> |
编辑目标描述 |
/goal clear |
清除当前目标 |
/goal(无参数) |
查看当前目标状态 |
查询循环(query.ts)
queryLoop() 主循环
src/query.ts 中的 queryLoop() 是 Agent 的核心执行循环,在 QueryEngine.ts 中被调用。每次迭代处理一个完整的模型调用周期:
模型调用 → 流式响应处理 → 工具执行 → stop hooks → 继续决策
- 模型调用:
queryModelWithStreaming()发送消息到 API - 流处理:处理
content_block_start/delta事件 - 工具执行:检测
tool_use并执行 - stop hooks:执行
handleStopHooks()(记忆提取、自动 dream、prompt 建议等) - 继续决策:检查是否需要继续循环(工具调用、max_output_tokens 恢复、goal 续跑)
max_output_tokens 恢复
当模型响应被 max_output_tokens 截断时,queryLoop 提供最多 3 次 恢复尝试(MAX_OUTPUT_TOKENS_RECOVERY_LIMIT):
- 注入恢复提示,指导模型从中断处继续
- 若 8K 默认输出限制触发,自动升级到 64K(一次,
tengu_otk_slot_v1功能门控) - 恢复尝试耗尽后,将错误信息输出给用户
相关变量(src/query.ts):
maxOutputTokensRecoveryCount:当前恢复尝试次数MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3:最大恢复次数ESCALATED_MAX_TOKENS:升级后的输出 token 上限
Reactive Compaction
当上下文超出 API 限制时自动触发压缩:
hasAttemptedReactiveCompact:防止在同一轮迭代中重复压缩- 上下文超时(prompt-too-long)触发的压缩流程
- 压缩后保持 stop hooks 的阻塞错误处理
- 保留反应式压缩保护(不因 stop hook 错误而重置,防止无限循环)
Task 系统
Task(任务)系统管理 Agent 派发的异步子任务。
Task 类型
定义于 src/Task.ts:
| 类型 | 标识 | 用途 |
|---|---|---|
local_bash |
b |
本地 Shell 命令执行 |
local_agent |
a |
本地子 Agent |
remote_agent |
r |
远程 Agent |
in_process_teammate |
t |
进程内队友 |
local_workflow |
w |
本地工作流脚本 |
dream |
d |
记忆整理任务 |
monitor_mcp |
m |
MCP 监控 |
状态
pending → running → completed / failed / killed
pending:等待分配running:正在执行completed:正常完成failed:执行失败killed:被终止
后台/前台任务
任务系统区分前台任务(阻塞用户交互)和后台任务(在后台运行),但 Task 类型本身不区分配置字段;区分体现在调用上下文中。
任务 ID 生成
generateTaskId()(src/Task.ts)生成格式为 {类型前缀}{8位随机字母数字} 的 ID:
- 使用
randomBytes(8)生成安全随机数 - 36 进制字母表(数字 + 小写字母)
- 36^8 ≈ 2.8 万亿种组合,抗暴力枚举
stopTask(优雅终止)
Task 的 kill() 方法实现优雅终止,每个 Task 类型提供独立的 kill 实现(src/tasks.ts):
export type Task = {
name: string
type: TaskType
kill(taskId: string, setAppState: SetAppState): Promise<void>
}
任务注册
src/tasks.ts 中的 getAllTasks() 收集所有可用 Task:
- 始终注册:
LocalShellTask,LocalAgentTask,RemoteAgentTask,DreamTask - 条件注册(通过 feature gate):
LocalWorkflowTask,MonitorMcpTask