Claude Code Agent Wakeup:模型如何把未来事件交给 Harness
面向 agent / harness 工程师,拆解 Claude Code 2.1.193 的 ScheduleWakeup、Cron、Monitor、后台任务、task-notification、Stop hook rewake 与 remote trigger 机制。
这篇文章面向 agent / harness 工程师,讨论一个容易被产品语言压扁的问题:Claude Code 里的 agent 能不能“自己决定下一次什么时候醒来,或者被什么事件触发”?
短答案:能。更准确的说法是:模型没有长期运行权,它把下一次 continuation 的意图登记给 harness。模型负责选择“等多久”“监听什么”“收到事件后做什么”;harness 负责计时、监听、落盘、恢复、入队,并在未来把事件重新投影成一个 model turn。
以下分析基于本机安装的 Claude Code 2.1.193,安装路径为 /opt/homebrew/Caskroom/claude-code/2.1.193/claude,build time 2026-06-25T18:18:11Z。信息来源是本机 binary 中可观察的工具说明、schema 字符串、混淆后的 runtime 片段和本机 .claude 状态,不依赖过时源码。
Executive Summary
- Claude Code 有一组 wake / continuation 机制,单一 sleep API 无法覆盖这套设计。
ScheduleWakeup是/loop dynamic的一次性自节奏工具:模型选择delaySeconds、reason、prompt,harness 在未来重新提交 prompt。CronCreate是更通用的 prompt scheduler:支持 recurring / one-shot,默认 session-only,durable: true时写入.claude/scheduled_tasks.json。Monitor是事件驱动 wake source:后台脚本 stdout 每一行都是事件,harness 把它们变成<task-notification>。- Bash / Agent / Workflow / MCP background task 完成后也会以
<task-notification>重新唤醒主 agent。 - Stop / SubagentStop hook 看到
background_tasks和session_crons,可以区分“会话真的结束了”与“会话正在等未来 wakeup”。 - Hook schema 里有
asyncRewake:后台 hook exit code2时可以把 hook 输出作为 feedback 重新喂给模型。 RemoteTrigger暴露出远端/cloud session trigger 面,但受allow_remote_sessionspolicy gate 控制,不能当作普通本地 loop 能力来理解。- 设计抽象是:model declares future intent, harness owns time and events, continuation re-enters as queued user-role input。
- 对自研 harness 的核心借鉴:统一所有异步来源到一个 provenance-aware command queue,并让模型看到结构化、可归因、受限大小的 continuation envelope。
Mental Model
把 Claude Code 的 wakeup 机制想成三层。
- Model-visible layer:模型看到工具、tool prompt、loop reminder、
<task-notification>。 - Harness runtime layer:harness 管 timer、cron、watcher、background process、task registry、locks、persistent files。
- Projection layer:未来事件发生时,harness 把事件投影成 queued input,再启动一次主循环。
这个图里最重要的是 Queue -> NextTurn。模型并没有在后台保持一个 JavaScript closure,也没有跨 turn 持有 stack。continuation 的形态是新的输入:可能是一段 scheduled prompt,可能是 <task-notification>,可能是 hook feedback,可能是远端 inbox message。
Wake Source Taxonomy
Claude Code 现在至少有六类 wake source。
| Source | Model-visible? | 触发条件 | 典型用途 | 生命周期 |
|---|---|---|---|---|
ScheduleWakeup | 是 | delay 到期 | /loop dynamic 自节奏 | 一次性,必须每轮 re-arm |
CronCreate | 是 | cron match | 定时 prompt / recurring task | session-only 或 durable |
Monitor | 是 | stdout line event | 日志、CI、PR comment、文件变化 | task registry 管理 |
run_in_background | 是 | shell / agent 退出 | 长命令、异步 subagent | task registry 管理 |
asyncRewake hook | 配置可见,非普通 tool | hook exit code 2 | Stop hook feedback / verifier | hook runtime 管理 |
RemoteTrigger | 条件暴露 | 远端 trigger API | cloud / CCR session | policy gated |
另外还有 PushNotification 和 SendUserMessage(status: proactive)。它们会拉用户注意力,甚至推到手机;它们解决的是“通知人”,模型 re-entry 仍然依赖 queue / task-notification / scheduled prompt 这类 continuation 通道。
Model-Visible Tool Surface
如果把这套能力当成 harness API,而不是产品功能,可以先按“谁能调用、输入是什么、结果怎样回到模型”来读。
| Surface | Tool / API | 调用方 | 核心输入 | 直接输出 | 未来 continuation |
|---|---|---|---|---|---|
| Dynamic delay | ScheduleWakeup | 主模型 | delaySeconds, reason, prompt | next wakeup timestamp, clamped delay | scheduled prompt 重新入队 |
| Cron scheduler | CronCreate | 主模型 | cron, prompt, recurring, durable | job id, persisted/session-only 状态 | cron fire 后 prompt 重新入队 |
| Cron control | CronDelete / CronList | 主模型 | jobId 或空输入 | cancel 结果 / job list | 无;用于管理已登记 jobs |
| Event watcher | Monitor | 主模型 | command, description, timeout_ms, persistent | background task id / output file | stdout event 变成 <task-notification> |
| Background shell | Bash(..., run_in_background) | 主模型 | shell command, background flag | task id / output file | process exit 变成 <task-notification> |
| Background agent | Agent(..., run_in_background) | 主模型 | prompt, description, subagent_type, isolation | agent id / launched status | agent stop 变成 <task-notification> |
| Background workflow | Workflow | 主模型 | script, name, args, scriptPath, resumeFromRunId | workflow task id / run id | workflow complete 变成 <task-notification> |
| Task management | TaskList | 主模型 | 通常空输入 | running / completed task summaries | 无;用于发现 monitor/background task |
| Task cancellation | TaskStop | 主模型 | task_id | stopped / failed status | 可能生成 stop notification |
| Task output | TaskOutput | 主模型 | task_id, block, timeout | status + output | 弱化的兼容面;优先读 output file |
| Hook rewake | asyncRewake | settings / plugin hook 配置 | hook command, rewakeMessage, rewakeSummary | hook backgrounded | hook exit code 2 变成 feedback continuation |
| Remote trigger | RemoteTrigger | 条件暴露的模型工具 / remote surface | action, trigger_id, body | remote API result | 取决于 cloud / CCR trigger |
| User attention | PushNotification | 主模型 | short message | terminal / mobile notification result | 不直接唤醒模型 |
这张表里有三个边界要注意。
第一,ScheduleWakeup 和 CronCreate 都能让 prompt 在未来重新进入会话,但生命周期完全不同。ScheduleWakeup 是 /loop dynamic 的一次性 self-pacing continuation;CronCreate 是 cron job,默认 session-only,显式 durable: true 才会写 .claude/scheduled_tasks.json。
第二,Monitor、background Bash、background Agent、Workflow 的共同点不是“都能后台运行”,而是都会把完成或事件投影成 <task-notification>。模型真正消费的是 notification envelope,而不是后台进程本身。
第三,asyncRewake 和 RemoteTrigger 不应被塞进普通 model-callable tools 一类。asyncRewake 是 hook 配置面,触发权在 hook runtime;RemoteTrigger 是 gated remote surface,受登录、policy、remote session 能力控制。它们证明 harness 有更多 wake source,但不能直接当成本地 agent 的通用自唤醒工具。
用伪类型表达,模型可直接调用的 wake-related subset 更接近这样:
type WakeTool =
| {
name: "ScheduleWakeup"
input: { delaySeconds: number; reason: string; prompt: string }
continuation: { mode: "prompt"; source: "scheduler" }
}
| {
name: "CronCreate"
input: { cron: string; prompt: string; recurring?: boolean; durable?: boolean }
continuation: { mode: "prompt"; source: "cron" }
}
| {
name: "Monitor"
input: { command: string; description: string; timeout_ms?: number; persistent?: boolean }
continuation: { mode: "task-notification"; source: "stdout" }
}
| {
name: "Bash"
input: { command: string; run_in_background?: boolean }
continuation: { mode: "task-notification"; source: "process_exit" }
}
| {
name: "Agent"
input: {
prompt: string
description: string
subagent_type?: string
run_in_background?: boolean
isolation?: string
}
continuation: { mode: "task-notification"; source: "agent_stop" }
}
这也解释了为什么 Claude Code 的 wakeup 机制看起来分散:模型看到的是多个领域工具,harness 看到的是同一种 continuation plumbing。
ScheduleWakeup: Dynamic Loop 的自节奏工具
ScheduleWakeup 的工具说明非常明确:它用于 /loop dynamic mode。用户调用 /loop 但不指定 interval 时,模型需要自己决定下一轮什么时候值得跑。
简化后的 tool contract 是:
type ScheduleWakeupInput = {
delaySeconds: number
reason: string
prompt: string
}
type ScheduleWakeupResult = {
scheduledFor: number
clampedDelaySeconds: number
wasClamped: boolean
}
关键运行语义:
delaySeconds会被 runtime clamp 到[60, 3600]。- runtime 会把 wakeup 对齐到分钟级 fire time。
- 5 分钟 prompt cache TTL 是 pacing 设计的一部分。
60s-270s适合 cache warm 的积极轮询。300s被明确标成差选择:刚好跨过 cache TTL,又没有换来足够长的等待。1200s-1800s是 idle / fallback heartbeat 的推荐区间。- 这个 wakeup 只 fire 一次。下一轮还要继续,模型必须再次调用
ScheduleWakeup。 - 如果模型没有 re-arm,loop 会结束;如果 keepalive gate 打开,runtime 可以给一次 fallback keepalive,但预算很小。
模型在 dynamic loop 里收到的指导大概是:
Run the task now.
If the next run is gated on an observable event, arm Monitor.
Confirm what you did and what fallback delay you chose.
As the last action, call ScheduleWakeup.
If woken by task-notification, handle the event, then re-arm ScheduleWakeup.
To stop, omit ScheduleWakeup and stop any Monitor you armed.
设计上,ScheduleWakeup 的适用面很窄:它服务 /loop dynamic,不承担通用 sleep 或后台任务轮询器角色。工具说明专门提醒:不要对 harness 已经能跟踪的后台工作做短间隔 polling。后台 Bash / Agent / Workflow 完成时会自动发 <task-notification>;如果还要兜底,设一个长 heartbeat。
CronCreate: Prompt Scheduler
CronCreate 是更普通的 scheduler。它把一个 prompt 绑定到 cron 表达式上。
简化 schema:
type CronCreateInput = {
cron: string
prompt: string
recurring?: boolean
durable?: boolean
}
运行语义:
- cron 是标准 5-field cron,按用户本地时区解释。
recurring: false表示 one-shot,在下一次 match 后 fire 一次并自动删除。recurring: true是默认值,直到删除或 auto-expire。durable: false是默认值:任务只在当前 Claude session 内存里,Claude 退出就消失。durable: true会写入.claude/scheduled_tasks.json,重启后恢复。- durable one-shot 如果在 Claude 关闭期间错过,下一次启动时会被 surfaced for catch-up,然后删除。
- scheduled jobs 只在 REPL idle 时 fire,不会在 mid-query 中打断模型。
- recurring job 默认最多活 7 天;到期时最后 fire 一次,然后删除。
- recurring fire 会有 deterministic jitter:最多 10% period,cap 15 分钟。
- one-shot 如果落在
:00/:30,可能提前最多 90 秒,目的是打散 fleet spike。
本机 binary 里能看到默认 jitter config:
{
recurringFrac: 0.5,
recurringCapMs: 1800000,
oneShotMaxMs: 90000,
oneShotFloorMs: 0,
oneShotMinuteMod: 30,
recurringMaxAgeMs: 604800000,
cacheLeadMs: 15000
}
604800000ms 正好是 7 天。这解释了为什么 recurring task 文案会提示用户:Recurring tasks auto-expire after 7 days。
Durable Store Shape
持久化文件位于工作目录链路下的 .claude/scheduled_tasks.json,旁边有 .claude/scheduled_tasks.lock。
可观察到的 task shape 大致是:
type ScheduledTask = {
id: string
cron: string
prompt: string
createdAt: number
lastFiredAt?: number
recurring?: true
permanent?: true
createdBySessionId?: string
createdByPid?: number
createdByProcStart?: string
}
type SchedulerLock = {
sessionId: string
pid: number
procStart?: string
acquiredAt: number
}
这个 lock 很关键。durable scheduler 需要先确认当前进程拥有 fire 权。runtime 会尝试 acquire .claude/scheduled_tasks.lock,判断 PID 是否仍然活着,必要时 recover stale lock。task 自身还带 createdBySessionId / createdByPid / createdByProcStart,用于区分当前 session、同一项目里其他 session、以及已死进程留下的任务。
这个设计把三个问题拆开了:
- 谁有资格 fire durable task。
- 关闭期间错过的 one-shot 怎么补偿。
- recurring task 如何更新
lastFiredAt并 bounded lifetime。
Monitor: Event Stream To Conversation
Monitor 是事件驱动 continuation。它启动一个后台命令,命令的 stdout 每一行都被当成一个 event。
简化 schema:
type MonitorInput = {
command: string
description: string
timeout_ms?: number
persistent?: boolean
}
工具说明强调了三个使用模型:
| 需求 | 推荐方式 |
|---|---|
| 只要一个通知,例如“server ready” | Bash run_in_background,命令在条件满足时退出 |
| 每次事件都通知,例如每条 ERROR | Monitor + unbounded command |
| 直到自然结束的多事件流,例如 CI step | Monitor + 会退出的 poll loop |
Monitor 的工程约束很多:
- stdout 是事件流;stderr 默认只进 output file,不触发通知。
- 如果要让 stderr 触发,需要在脚本里
2>&1再过滤。 - 每个 pipe stage 要 line-buffered;
grep --line-buffered、awk要fflush()。 - 监听 job outcome 时,filter 必须覆盖 failure / cancelled / timeout,不只匹配 success。
- 每一行都会变成 conversation message,所以必须过滤到“值得模型行动”的信号。
- 200ms 内多行会 batch 成一个 notification。
- 输出过多会自动停止 monitor。
persistent: true表示 session-length watch,典型用于 PR monitoring / log tail。- 取消用
TaskStop,查 task id 用TaskList。
Monitor 的价值不只是跑一个后台命令。普通 shell 也能后台跑。它真正提供的是 event projection:harness 把 stdout line 变成有 provenance 的 conversation event,并立即唤醒 loop,绕过 ScheduleWakeup 的 fallback deadline。
Background Tasks: Completion As Wake Signal
Bash、Agent、Workflow、MCP background task 都走相似模式:任务先在 task registry 里注册,工具调用可以先返回“已后台运行”,未来任务完成时发 <task-notification>。
Bash 的 run_in_background 文案说明得很直:命令 detached 运行,跨 turn 保持运行,退出时重新调用你。模型不需要在命令后加 &;裸 & 反而绕开 harness 追踪,通知永远不会来。
Agent 工具也有 background 语义:
type AgentInput = {
prompt: string
description: string
subagent_type?: string
run_in_background?: boolean
isolation?: "worktree" | "remote" | string
}
几个关键点:
run_in_background: true会异步运行 subagent,完成后通知主 agent。isolation: "remote"总是 background。- subagent 的结果不直接给用户看;主 agent 收到后需要总结给用户。
- coordinator prompt 明确要求:不要用一个 worker 去检查另一个 worker,worker 完成会自动通知。
- 继续一个完成的 worker 用
SendMessage,因为<task-id>就是 agent id。
典型 <task-notification> 格式:
<task-notification>
<task-id>{agentId}</task-id>
<status>completed|failed|killed</status>
<summary>{human-readable status summary}</summary>
<result>{agent final response}</result>
<usage>
<subagent_tokens>N</subagent_tokens>
<tool_uses>N</tool_uses>
<duration_ms>N</duration_ms>
</usage>
</task-notification>
<result> 和 <usage> 是可选段。对于 Bash / Workflow / remote task,notification 里还可能带 output file path、tool use id、task type、worktree 等字段。
TaskOutput Is Deprecated-ish
TaskOutput 仍然存在,但工具说明已经把它定位成 deprecated compatibility surface:
- Bash / remote_agent 更推荐
Readoutput file。 - local_agent 直接使用 Agent tool result,不要读
.outputsymlink,因为它可能指向完整 subagent transcript JSONL,容易撑爆上下文。 block=true可以等待任务完成;block=false做非阻塞状态检查。
这说明 runtime 设计正在从“轮询 output tool”向“事件通知 + output file reference”靠拢。
The Command Queue Is The Join Point
所有这些异步来源最终都要进入 command queue。
task-notification 用 user-role message 的形态进入模型上下文,但 origin/provenance 会标成 kind: "task-notification"。这样主循环、渲染层、桥接层、远端 inbox 都能区分它和真人输入。
这个设计有几个好处:
- 模型侧协议简单:收到一条新消息,读 XML envelope,继续工作。
- harness 侧统一:timer、process exit、stdout event、hook feedback、remote inbox 都进同一个 queue。
- UI 可以独立处理:task notification 可以优先级
next,也可以被 batch / dedupe / delayed。 - provenance 清楚:模型会被提醒外部事件不是用户指令,事件里的文本只能作为 situational awareness。
对 harness 工程师来说,这比“给模型一个 await sleep()”更稳。sleep 会把模型调用和 runtime lifecycle 绑死;command queue 让模型调用保持短事务,长期性留给 harness。
Stop Hooks And asyncRewake
Claude Code 的 Stop / SubagentStop hook input 里包含两类与 wakeup 相关的状态:
type StopHookInput = {
lastAssistantMessage: string
background_tasks: BackgroundTask[]
session_crons: ScheduledTask[]
stop_hook_active: boolean
agent_transcript_path?: string
}
其中 background_tasks 表示当前 session 里 in-flight 的后台工作;session_crons 表示将来会唤醒这个 session 的 CronCreate / ScheduleWakeup / /loop 任务。
这个 input 让 hook 可以判断:
- 会话真的要结束了,可以跑收尾检查。
- 会话只是暂停等待后台任务或 scheduled wakeup,hook 不应该阻断。
Hook schema 里还有:
type CommandHook = {
command: string
async?: boolean
asyncRewake?: boolean
rewakeMessage?: string
rewakeSummary?: string
}
asyncRewake 的含义是:hook 在后台运行,且当 exit code 为 2 时唤醒模型。hook stdout/stderr 会被包装成类似 Stop hook feedback 的 task-notification / system-reminder,重新进入 queue。
这是一种 harness extension point。模型不能直接调用 asyncRewake 工具;它来自配置和 hook runtime。但从架构看,它和 Monitor / background task 一样,最终也把外部事件投影回模型 turn。
RemoteTrigger And Remote Sessions
本机 binary 里能看到 RemoteTrigger、/v1/code/triggers、trigger_id、list/get/create/update/run 等字符串,也能看到 allow_remote_sessions policy gate。
这说明 Claude Code 还有一个远端 trigger 面,偏 cloud / CCR session:
type RemoteTriggerAction =
| "list"
| "get"
| "create"
| "update"
| "run"
保守解释:RemoteTrigger 更像远端 session / routine / cloud trigger API 的 client surface,和普通本地 /loop 分属不同层。它是否暴露、能否使用、能做什么,受登录状态、组织 policy、remote session entitlement 影响。
文章主线里不把它作为本地 agent 自主唤醒的核心,因为本地可验证路径已经由 ScheduleWakeup、CronCreate、Monitor、background task 覆盖。
Headless Mode Boundary
一个非常重要的边界:headless / -p 场景没有 interactive REPL 的 task-notification re-invocation。
这意味着:
- 交互式 session 里,
run_in_background很自然:任务完成后会叫醒模型。 - headless session 里,background task 完成后没有同样的 re-entry path;长任务应该同步跑,或者由外部 orchestrator 管理。
- 任何依赖“稍后通知我”的工具,在 headless harness 里都需要明确的 event pump / callback transport。
这条边界很容易被忽略。如果你在自己的 SDK / CI / GitHub Action agent 里照搬 interactive CLI 的 background 语义,却没有实现 queue re-entry,结果就是任务真的跑完了,但模型永远不会接结果。
What The Model Actually Sees
模型看到的是有限投影,而非 scheduler 全状态或 .claude/scheduled_tasks.json 原始内容。
| 来源 | 模型看到什么 |
|---|---|
ScheduleWakeup | tool schema、tool prompt、tool result 中的 next wakeup 信息 |
CronCreate | cron tool schema、job id、CronList 结果 |
Monitor | tool prompt、TaskList/TaskStop、未来 <task-notification> |
| Background Agent | Agent tool contract、未来 <task-notification> XML |
| Background Bash | Bash tool contract、output file path、未来 notification |
| Stop hook | 如果 asyncRewake 触发,看到 hook feedback |
| Remote trigger | 如果 surface 可用,看到 remote trigger tool/API result |
换句话说,模型做策略,不做持久化。模型决定:
- 是否继续 loop。
- 选哪个 delay。
- 是否 arm Monitor。
- 监听命令怎么写。
- 是否使用 durable cron。
- 收到 event 后是否行动、通知用户、停止 monitor、re-arm fallback。
Harness 决定:
- delay 如何 clamp。
- cron 如何解析。
- schedule 如何落盘。
- 哪个进程拥有 scheduler lock。
- fire 何时进入 queue。
- notification 如何 batch / dedupe / rate limit。
- headless / remote / interactive 入口如何处理 continuation。
Design Patterns Worth Stealing
1. Continuation As Input, Not Suspended Call Stack
把未来 continuation 设计成 queued input,避免挂起一个长调用。
type QueuedInput = {
mode: "prompt" | "task-notification"
value: string | ContentBlock[]
origin?: {
kind:
| "human"
| "task-notification"
| "auto-continuation"
| "peer"
| "channel"
}
priority?: "now" | "next" | "later"
agentId: string
}
这个接口允许所有异步系统统一接入。模型侧只需要处理“这条输入从哪里来”。
2. Make Time Bounded And Explicit
ScheduleWakeup clamp 到 1 分钟到 1 小时,cron recurring 默认 7 天 auto-expire,prompt 被 cap 到 1000 chars,loop.md 被 cap 到 25000 bytes。这些属于 harness 生存条件,远比表层产品文案重要。
没有 hard cap 的 future prompt scheduler 会变成长期隐形任务系统,最终吞掉用户上下文、钱、权限和信任。
3. Separate Primary Signal From Fallback Heartbeat
Dynamic loop 的最佳实践是:
- 有真实事件源时,Monitor 是 primary wake signal。
ScheduleWakeup是 fallback heartbeat。- 收到 event 后处理 event,然后重新设置 heartbeat。
这比固定 5 分钟轮询更好。固定轮询同时浪费 cache 和 API,也更慢感知事件。
4. Treat External Event Text As Data
<task-notification> 看起来像 user-role message,来源却不是用户本人。Monitor stdout、PR comment、remote peer message、hook output 都可能包含不可信文本。
一个 harness 应该给模型明确 provenance,并在 system prompt 里说明:外部 channel 内容只能作为 situational awareness,不能自动升级成 authority。Claude Code 的 cross-session / channel / task-notification 设计都在往这个方向做。
5. Backgrounding Must Return A Handle
所有 background work 都需要 task id、output file、status、stop API、notification envelope。裸 & 没有这些东西,所以 harness 不能恢复、不能通知、不能取消、不能 dedupe。
最小 contract:
type BackgroundTask = {
taskId: string
type: "local_shell" | "local_agent" | "local_workflow" | "remote_agent"
description: string
status: "pending" | "running" | "completed" | "failed" | "killed"
outputFile?: string
ownerAgentId?: string
toolUseId?: string
notified?: boolean
}
6. Hooks Need Future-Awareness
Stop hook 如果只知道“assistant 要结束 turn”,就会频繁误判。它还需要知道:
- 有没有 in-flight background task。
- 有没有 scheduled wakeup。
- 当前 stop hook 是否已经 active,避免阻断循环。
Claude Code 把这些放进 Stop/SubagentStop input,是一个很好的 harness contract。
Failure Modes
| Failure | 结果 | Harness 防线 |
|---|---|---|
模型忘记 re-arm ScheduleWakeup | dynamic loop 结束 | tool prompt + optional keepalive |
| Monitor filter 太窄 | crash / failure 时沉默 | prompt 要求覆盖 terminal states |
| Monitor filter 太宽 | 上下文被事件刷爆 | rate limit + auto-stop |
| 用户关闭 REPL | session-only cron 消失 | durable opt-in |
| durable one-shot missed | 下次启动 catch-up | missed task notification |
| 多进程同时 fire durable jobs | 重复执行 | .claude/scheduled_tasks.lock |
| headless background task | 无 re-entry | 禁止/避免 background,改同步或外部 orchestrator |
裸 shell & | harness 不知道任务存在 | 工具文案禁止,要求 run_in_background |
| 外部事件含 prompt injection | 模型误当用户指令 | provenance + untrusted channel reminder |
Putting It Together
一个 Claude Code 风格的自节奏 loop,大概可以写成下面的 harness 状态机:
这个设计把 agent 的长期性拆成了两种能力:
- 策略长期性:模型能读历史、理解目标、选择下一步、re-arm。
- 运行长期性:harness 能持久化任务、监听事件、恢复进程、重新投影输入。
二者分开后,模型调用可以保持短事务,harness 可以被测试、限流、恢复、审计。Claude Code 的 wakeup 机制最值得借鉴的地方就在这里:它没有让模型假装自己是 daemon;它让 harness 成为 daemon,模型成为每次事件到达时重新规划的 planner / actor。
Practical Checklist For Harness Builders
如果你在设计自己的 agent runtime,可以直接拿这份清单对照:
- 是否有统一 command queue,避免每类异步事件各走一套模型入口?
- queued input 是否带 provenance,并能区分 human / task-notification / peer / channel?
- background task 是否都有 task id、output file、status、stop API?
- event watcher 是否有 output rate limit、batch、timeout、persistent flag?
- time scheduler 是否有 max age、jitter、idle-only fire、durable opt-in?
- durable scheduler 是否有 lock 和 stale lock recovery?
- missed one-shot 是否会 catch-up,避免静默丢失?
- headless mode 是否明确禁用或替代 interactive background continuation?
- Stop hook 是否能看到 pending background tasks 和 scheduled wakeups?
- 外部事件文本是否被标成 untrusted data?
- 模型是否需要每轮显式 re-arm,从而保留停止权?
如果这些答案都清楚,你的 harness 就已经越过了“能跑后台任务”的阶段,进入了“能安全托管 agent 的未来”的阶段。