目标会漂
"完成整个迁移" 跑了几轮就缩成 "核心路径已迁移"。局部证据顺势变成新中心。
系统开始把自主性装进工程结构里。
"完成整个迁移" 跑了几轮就缩成 "核心路径已迁移"。局部证据顺势变成新中心。
做完的、试过的、卡在环境上的——全散在聊天里。下一个 Agent 拿到的是碎片。
短任务里没事,长任务里这些话会被当成「完成」。
和 thread · protocol · tools · usage · resume 都打通。好处是稳,代价是重。
// 6 slash variants /goal → open menu /goal <objective> → set / replace /goal clear → delete row /goal edit → prefill editor /goal pause/resume → flip status // 3 RPCs over app-server thread/goal/set // write thread/goal/get // read thread/goal/clear // delete
CREATE TABLE thread_goals ( thread_id TEXT PRIMARY KEY, goal_id TEXT NOT NULL, objective TEXT NOT NULL, status TEXT NOT NULL CHECK status IN ( 'active', 'paused', 'budget_limited', 'complete'), token_budget INTEGER, tokens_used INTEGER DEFAULT 0, ... );
budget_limited。
放行后,胶囊被包进 <goal_context>,下一轮 prompt 就带着它走。
fn continuation_candidate_if_idle(): if !Feature::Goals return None if status != Active return None if turn_in_progress return None if queued_user_input return None if mode == Plan return None // kill return Some(Candidate) // hidden injection: <goal_context> { continuation.md | budget_limit.md | objective_updated.md }
{"name": "get_goal", // read
"parameters": {}}
{"name": "create_goal", // guarded
"parameters": {
objective: str,
token_budget?: int+ }}
{"name": "update_goal", // SCHEMA-LOCK
"parameters": {
"status": { "enum": ["complete"] }
}}
// 单值 enum:写啥都只能是 "complete"
update_goal 不是 SQL UPDATE 直发——
要经 app-server 校验、runtime 授权才写库,最后 emit 一个 ThreadGoalUpdatedEvent。
状态 LED 由这条环的回信触发,不是模型自己点亮的。这才是它"complete"。
// model side update_goal({status: "complete"}) ↓ tool_call // app-server side validate · authorize ↓ state_db.update_thread_goal( status = Complete, updated_at_ms = now()) ↓ // emit ThreadGoalUpdatedEvent
PRIMARY KEY = thread_id —— 一条线程只有一个 goal。
用户想完成的事 —— 不再散落在聊天记录里。
{ active · paused · budget_limited · complete }
超了就 budget_limited · 注 budget_limit.md。
由运行时统计,不靠模型自己汇报。
目标自己的生命周期,不依赖聊天窗口。
读当前目标是什么、做到哪一步、已经消耗多少。无副作用,谁都能调。
用户明确要长期推进时才用。模型不该自己立目标——看什么都自启目标就乱了。
模型能写的只有 "complete"。 想暂停 · 想恢复 · 想说"放弃" —— 写不出来。
runtime 主动翻 status = budget_limited,注 budget_limit.md 让模型自己写一份收束。
下一轮 prompt 自带 continuation.md / budget_limit.md / objective_updated.md 之一 —— 续跑是调度,不是 prompt。
挂在每轮结束前。评估器说没完成就不让这轮结束,继续干。
// ~/.claude/commands/goal.md (一个 markdown) allowed-tools: [Bash, Edit, Read] description: "set the goal-until-met condition" // 用户敲 /goal "直到 README 写完..." session.activeGoal = "直到 README 写完 3 节 + 介绍" session.activeGoal.iteration = 0 // stop hook 在 settings.json 里: "hooks": { "Stop": [ { matcher: "*", command: "evaluator-claude-haiku" }] }
// turn 1 · worker Edit README.md + ## Quick Start Edit README.md + ## Installation // worker 自评:已完成大部分内容 worker → stop
// stop hook fires stop-hook eval (default haiku) condition: "直到 README 写完 3 节 + 介绍" input: transcript + file diffs // deliberating ...
// evaluator → block { "decision": "block", "reason": "README 只有 2 节, 还缺 1 节 + 介绍段落" } → append GS-01 unmet to transcript → next worker turn 自动加上 prefix: "上一轮被 block, 原因: README 只有 2 节, 还缺 1 节 + 介绍"
// turn 3 · worker Edit README.md + Project intro paragraph worker → stop // evaluator { "decision": "met", "reason": "✓ 3 sections + intro" } // session activeGoal = null Stop hook removed → session 安静结束 · 总 3 turn
一个 markdown 文件即一条 slash。把 activeGoal 写入 session,再注册 hook。
每次 worker 想 stop,hook 先拦下来 —— stop event 不能直接落地。
看 transcript + activeGoal,只 met / block + reason,不写代码。
npm test · 不能 grepSessionDB.state_meta 存着,可以跨会话 · 跨 gateway 恢复。今天 CLI 起的,明天 Telegram 接着看。
// telegram bot · /goal command gateway_event = { origin: "telegram", user_id: "@nik", text: "ship payments module by Fri with tests" } // 同样从 cli/discord/web 也能发 ↑ hermes.set_goal(gateway_event)
// SessionDB.state_meta(agent_id) { goal_id: "g_8a3f", condition: "ship payments module...", origin: "telegram", turn_count: 0, budget: 12, last_judge: null } // 多 gateway 共同读写这一行
// turn N · goal_judge verdict = goal_judge(transcript, condition) try: if verdict.met: // ✓ stop continuation finalize_goal() else: continue_next_turn(reason=verdict.reason) except JudgeError: // fail-open continue_next_turn(reason="judge unreachable") // 不让 judge 自己挂掉 = 整段断
budget_exhausted,
写一条用户能看见的 reason 回到 origin gateway。
真正的兜底从来不是 judge,是预算和可见性。
// each turn state_meta.turn_count += 1 if turn_count >= budget: state_meta.status = "budget_exhausted" gateway.send(origin, "⚠ 跑满 12 轮还没 met, 暂停在 'tests 还差 1 个' ——要我接着干吗?") // 用户看得见,可以决定续 / 改 / 停
// next morning · web dashboard agent = hermes.connect(agent_id) meta = SessionDB.state_meta(agent_id) // "tests 还差 1 个" 等在那儿 print(meta.last_judge.reason) // 用户敲个 "go" · agent 继续 hermes.resume(agent, gateway="web") → turn 9 / 12 picks up
"不让评估器自己挂掉就把整个过程打断"。不挂死整段流程,继续下一轮。
默认 turn budget 写得很短。可以续,但不能无限续。
budget 用完不是默默退出,是把 reason 推回 origin gateway给用户:要不要续 / 改 / 停。
这次到底做哪个目录 · 哪个模块 · 哪条用户路径。范围越糊,模型越容易"顺手优化"。
哪些 public API 不能改。 哪些测试不能为了让它过就删断言。模型很擅长用局部成功换全局风险。
不是"测试通过"四个字。要写哪条命令 · 哪段日志 · 哪个 diff · 哪个 CI 状态能把"通过"钉死。
最多 N 轮 · N 分钟 · N token。到点不是再硬跑,是停掉新增、把现场整理一遍。
哪怕没做完,也要留下已完成项 / 未完成项 / 失败命令 / 当前假设 / 下一步建议。失败也得能被接得住。
"补完整 auth 测试" → 五字段版:范围 src/auth · 不变量 不动 public · 验证 npm test test/auth · 预算 20 轮 · 收束 fail/pass 都给清单
"循环本身不是目的,目标、反馈信号和验证才是。" 让 Agent 自动测试 + 修认证 + 验证。
"防 early exits"。让 agent 把任务老实做完,省得人一直盯着。Codex 0.128 内置 ralph loop++。
"关键能力是把任务所需的上下文,提供到 LLM 有可能把它解出来的程度。" 这上下文也包括目标、状态、预算、验证。
让 Agent 在小训练 loop 里自主实验 · 5 分钟一轮 · 看 val_bpb 改没改善。自主循环装进可度量、可回滚框架。
一个 Markdown 文件,写下"这类事怎么做"。
SKILL.md + Hooks + AGENTS.md + 子代理。"过程资产" 浮现。
可分发的工作流包 · 带依赖声明 · 仓库工程化。
从一句指令,长成运行时任务合同:有状态、有 schema、有 budget、有恢复。
仓库规则 · 持续生效的行为契约
这类任务的固定流程 · 过程资产
当前目标 + 停止条件 + 预算
关键事件上的确定性检查
阶段性事实 · 探索隔离
最终交付的外部验证
Codex 状态机 · Claude evaluator · Hermes 跨入口 —— 三种实现,同一个接口。先认接口,再谈谁好谁坏。
范围 / 不变量 / 验证证据 / 预算 / 收束输出。少一件,长任务都接不住。
不是 Agent 更自主,是系统开始把自主性装进可观察、可回滚、可接管的工程结构里。