上一课学完 ReAct、Plan-and-Solve 和 Reflection 以后,我们留下了一个新的问题:
如果 Planner 只是负责判断下一步,那一个真正的 Agent Framework,到底还需要谁?
最开始这个问题看起来像名词辨析:
Agent
LLM
Tool
Memory
Context
Protocol
Runtime
Harness谁包含谁?谁调用谁?谁拥有谁?
但真正聊下去以后,我们发现这不是一棵简单的 ownership tree。
更准确的问题是:
谁负责理解意义,谁负责把这个意义变成真实、可控、可结束的运行。
这一课,我们从这里开始。
学习主线继续参考 Datawhale 的 Hello-Agents 第七章《构建你的智能体框架》,同时把问题放回 Mira 当前真实的 Agent Runtime。
Agent 可以说“我想结束”,但不能自己掐掉 Runtime#
假设 Agent 已经判断任务完成,但 Runtime 发现还有一个 Tool Call 没回来。
课堂里的第一反应是:
Agent 等 Runtime,或者终止 Runtime?
我们最后把这句话拆得更严格一点:
Agent
→ 我不再需要这个结果了
Runtime
→ 尝试 cancel
→ 等待真实终态
→ cleanup
→ 再决定这个 Run 能不能结束Agent 可以表达 WAIT、CANCEL(action)、COMPLETE,但它不应该直接拥有 runtime.kill()。
因为 Tool 可能仍然在写文件、跑进程、发网络请求、修改外部系统。
如果 Agent 逻辑上已经结束,而它创建的副作用还偷偷留在后台,系统就出现了一个很脏的状态:
“任务说自己结束了,但现实还没有结束。”
所以这一课最早留下的一句话是:
Agent 决定意图,Runtime 决定生命周期。
这也自然碰到 structured concurrency 的直觉:
一个 Run 结束以前,它拥有的子任务应该进入可确认的终态。
可以有真正独立的后台 Job,但它应该从一开始就是独立生命周期,而不是从当前 Run 里偷偷掉出去。
Runtime 可以限制 Agent,但不应该替 Agent 思考#
接着我们问:
如果 Agent 连续 20 次调用同一个 Tool,看起来已经陷入循环,Runtime 能不能直接判断“你错了”,然后打断?
答案是:不能仅凭“看起来重复”就替 Agent 做语义判断。
连续调用同一个 Tool 20 次可能是死循环,也可能真的是任务需要。
Runtime 不应该做:
“你重复太多了,我认为你思考错了。”但它完全可以做:
max_steps reached
timeout reached
cost budget exhausted
retry budget exhausted前者是在替 Agent 思考。
后者是在执行既定运行合同。
所以第二句话变成:
Runtime 可以限制 Agent,但不应该替 Agent 思考。
我们课堂里借用了两个工作词:
Semantic ownership
→ 谁定义意义、策略和当前需要
Operational ownership
→ 谁负责执行、资源、持久化和生命周期它们不是必须背下来的标准术语,但非常适合拿来拆责任。
Memory、Context、Tool 都不是简单的二选一#
Memory#
Agent 更应该拥有:
什么值得记
什么时候想起
哪条记忆与当前 Goal 有关
记忆冲突以后意味着什么而 Runtime / Infrastructure 更适合负责:
持久化
索引
隔离
并发
容量
读取所以:
Memory 在语义上更接近 Agent,在运行机制上由 Runtime / Infra 承载。
而且长期 Memory 不能因为“没有更强证据”就自动升级成事实。
这一课我们最终把它压成一句:
Memory 是 prior,不是 proof。
它可以告诉 Agent:“过去我们好像这样做过。”
但真正的当前事实,仍然应该尽可能回到 Evidence。
Context#
Context 是:
Agent 此刻真正看得见、能拿来做当前判断的东西。
Memory 是仓库。
Context 是桌面。
Prompt 是这一次真正递给模型看的那一叠纸。
Agent 更适合决定:
我现在需要看什么?
Harness / Runtime 则负责:
怎么召回
怎么压缩
怎么组装
怎么守 context window
怎么把最终内容真的送进 model call所以:
Context 的语义优先级更接近 Agent,物理边界归 Runtime。
Tool#
Tool 也一样。
Agent 决定:
为什么现在要调用这个能力?
它服务哪个子目标?
结果回来以后意味着什么?Runtime / Harness 负责:
schema
availability
approval
workspace
timeout
invocation
result projection因此可以记成:
Agent owns tool intent;Runtime / Harness owns tool execution conditions。
Tool 自己最好保持克制:
给我合法输入,我按照自己的 contract 执行,再返回结果。
如果一个所谓 Tool 开始自己做全局规划、自己决定调用别的 Tool、自己判断用户目标是否完成,它其实已经在向 Agent / SubAgent 漂移。
Harness:Mira 现在这个词可能用窄了#
这堂课最有意思的转折发生在 Harness。
Mira 当前把 Harness 定义成:
concrete tool 的控制平面。
它现在主要负责:
Tool Registry
Tool Exposure
Schema / Metadata
Risk / Approval
Workspace Boundary
Invocation
External MCP Projection
Trace / Audit
Result Projection这套职责本身没有问题。
问题是,当我们去看更宽口径的 Agent Harness 时,会发现 Harness 通常还会碰到:
Context assembly
Memory recall / projection
Instructions
Skills
MCP
Tool lifecycle
Sandbox
Before / after model hooks
Before / after tool hooks
Result feedback
Compaction
SubAgent environmentLangChain 现在会把 Harness 描述成围绕模型、把模型连接到环境、数据、Memory 和 Tool 的脚手架,并通过 middleware 在 model call 和 tool call 前后插入可组合控制逻辑。
Anthropic 最近关于 long-running agent 的 Harness 工作也不断强调:Context continuity、结构化 handoff、环境设计和 evaluator 都会显著改变同一个模型的实际表现。
所以课堂里我们重新给 Harness 留了一句更宽的定义:
Harness 是模型 / Agent 与现实环境之间的行为脚手架。
如果这样看,Mira 现在叫 Harness 的模块,更像完整 Agent Harness 里的:
Capability Harness
+
Action Governance也就是说:
Mira 不是 Harness 做得太少,而是把 Harness 的一个子域命名成了整个 Harness。
这暂时不意味着要立刻改代码。
但脑子里的地图应该先摆正。
Agent 想,Harness 装备,Runtime 让它活着#
我们最后把三个最容易混的角色压成:
Agent
→ 我下一步想做什么?
Harness
→ 你现在看得到什么、能使用什么,
这些能力和信息怎样进入你的认知与行动空间?
Runtime
→ 这一轮真实怎么启动、等待、恢复、
限制、取消、清理和结束?再短一点:
Agent 想。
Harness 装备。
Runtime 让它活着并推进。
Tool Exposure 其实也是 Context Retrieval#
然后我们碰到了 Mira 当前设计里一个值得重新审视的问题。
Mira 现在工具很多时,会走:
eligible tools > 20
→ capability profile
→ embedding / rerank
→ top 20
→ 暴露给 Planner过去我们很自然地觉得:
工具多,那就用语义排序模型挑一批最相关的。
但这一课继续拆 semantic ownership 后,我们开始怀疑:
Harness 是不是在 Agent 还没有表达能力需求以前,就替 Agent 定义了它能看到的整个行动世界?
如果用户只说:
“帮我处理一下这个项目。”
Harness 先语义排序出 read、grep、terminal、git,看起来很聪明。
但 Agent 可能执行到第六步才发现:
我现在需要 Browser。
如果 Browser 当初排第 73,它甚至不知道自己还有这条路。
这就不只是一次 retrieval miss。
而是:
Harness 构造了一个不完整的行动世界,然后让 Agent 在里面自以为掌握全部可能性。
课堂里 Tomz 提出了更自然的一层:
第一回合先选“能力”
↓
再展开这一类具体 Tool
↓
找不到 / 选错
↓
允许重新选择能力例如先暴露粗粒度能力目录:
感知 / 查询
操作 / 修改
领域技能
外部服务 / MCP
验证 / 检查然后:
Agent 选择 capability domain
→ Harness progressive disclosure
→ 具体 Tool这就是 Tool 侧的 Progressive Disclosure(渐进式披露)。
它和 Memory retrieval 很像:
Memory
不是把全部记忆塞进 Context
而是按需 recall
Tool
不是把全部 schema 塞进 Context
而是按需 disclose两者本质上都属于 Context Engineering:
这一刻,到底应该让 Agent 看见什么?
这里也出现了这堂课一个很重要的自我修正:
Ranking 可以保留,但不应该天然拥有构造 Agent 行动世界的最高权力。
更合理的关系可能是:
Agent intent
→ capability navigation
→ progressive disclosure
→ optional ranking
→ concrete toolRanking 更像 retrieval implementation,而不是 architecture。
甚至进入一个已经缩小过的 Tool domain 以后,未必还需要 embedding + reranker。关键词、Tag、BM25、metadata filter,可能就已经足够。
Progressive Disclosure 不等于 Authorization#
Agent 没看到某个 Tool,至少可能有几种完全不同的原因:
internal
forbidden
not relevant yet
temporarily unavailable
recoverable所以:
Disclosure
≠
Authorization有些能力,Agent 可以知道它存在,但现在不能执行。
比如:
browser_search
status: unavailable
reason: browser_disconnected
recoverable_by: connect_browser这比让 Agent 先调用失败,再从错误里猜“原来要连接浏览器”更好。
但如果某个能力因为权限本身就不允许这个 Agent 知道,那么它连名字也不应该被暴露。
于是 Harness 不只是过滤 Tool。
它实际上在塑造:
Agent 当前对可行动世界的认知。
Policy 是 Runtime 里的门禁层#
Policy 放哪,我们也重新收了一次。
课堂里最终采用的是:
Policy 是 Runtime 的门禁层,规则由人类通过程序定义。
也就是:
Agent
→ propose action
Runtime / Policy Gate
→ allow
→ deny
→ request approval
Runtime
→ executeAgent 可以解释:“我认为这次删除是合理的。”
但它不能自己放行。
Policy 的规则可能来自产品、企业、workspace、用户长期授权、风险等级和 side effect 分类。
它们的共同点是:
不能只靠 Prompt 里一句“请小心”。
所以更像:
Runtime
├── Lifecycle
├── State / Checkpoint
├── Resource Control
├── Policy Gate
├── Approval
└── ExecutionPolicy 并不因为在 Runtime 里,就变成 Runtime 自己“思考出来的规则”。
它仍然是:
人类通过程序施加给 Agent 行动世界的硬边界。
Protocol:不要让每一层都靠猜#
Framework 最后一个重要角色是 Protocol。
Agent 想调用 Tool,最脆弱的方式是:
“帮我读一下 foo.ts。”
然后让系统猜它到底想干什么。
更稳定的是:
Action {
type: use_tool
toolId: read_file
args: ...
}Tool 返回结果也一样。
我们最后更倾向一种:
薄 envelope + domain payload
例如:
ToolResult {
status
summary
warnings?
error?
artifacts?
payload
}通用 envelope 只保留跨领域稳定成立的语义。
领域细节留在 payload。
否则 Framework 很容易长出一座状态动物园。
更克制一点:
success
partial
failure已经能覆盖很多通用执行语义。
真正值得争论的是:
success 到底相对于什么成立?
如果 Tool 叫 browser_click(selector),那“点击成功”就可以是 success。支付最终失败,是另一个业务事实。
但如果 Tool 叫 submit_payment(...),它的 contract 本身就是“完成支付”,那业务失败就应该返回 failure。
所以我们最后留下:
ToolResult 的 status 应该绑定 Tool 自己声明的 contract,而不是绑定用户的全局 Goal。
同时还有一层产品判断:
好的 Agent-facing Tool contract,往往应该尽量接近可验证的业务结果,而不是永远暴露底层 primitive。
这也和我们最近讨论的微应用很像。
Agent 更适合看到:
整理录音
分析表格
编辑图片
提交报销而不是永远自己拼:
click
read
write
wait
click
parse
...Tool Success、Goal Complete、Run Completed 必须拆开#
Protocol 一旦干净,几个“成功”也必须拆开:
Invocation Success
→ 这一次 Tool contract 成功
Evidence Sufficient
→ 当前证据足够支撑某个判断
Goal Complete
→ 用户目标的 acceptance criteria 已覆盖
Run Completed
→ Runtime 确认生命周期可以安全结束它们不是同一个 Boolean。
比如:
write_file → success
run_tests → timeout
read_diff → success只能证明:
代码确实改了。
它不能证明:
Bug 已经修好。
而 timeout 自己也首先只是一个执行事实:
status = failure
reason = timeout它到底是环境问题、网络问题、测试框架问题、新代码死循环,还是 timeout 配置太短,应该继续由 Agent 根据 Evidence 判断。
这也是中高级面试里一个很明显的扣分点:
Observed Fact 和 Root Cause 不能混。
Tool 报事实。
Agent 解释事实。
异步任务还有一个 Settlement Window#
测试里还有一道很像真实工程的问题。
邮件服务 HTTP 200:服务商已经接受发送请求。
五秒以后又收到 bounce:收件人地址无效,最终投递失败。
这和我们平时做 GitHub 工作很像:
PR 已经提交,不等于 AI Review 已经结束。
所以有些 Tool contract 不是一次 request-response 就能 settle。
它可能需要:
awaiting_confirmation这样的中间状态。
如果 Tool contract 是 submit_email,HTTP 200 可能就已经 success,bounce 是后续异步事件。
如果 contract 是 deliver_email,那么它就应该继续等待 settlement。
于是:
“完成”不一定是一个瞬时点,有些能力存在 settlement window。
但同样不能把所有外部长期状态都无限绑在一个 Run 上。
真正独立的 CI、后台索引、长周期监控,可以成为独立 Job / Watch。
原则仍然是:
可以异步,但不能偷偷脱离生命周期。
最后把 Framework 总装一次#
这一课最后,我们得到了一张比“Agent = LLM + Tool”稍微完整一点的图:
User Goal
│
▼
Agent Core
- Goal understanding
- Planning
- Semantic decisions
- Completion judgment
- Memory / Context intent
│
▼
Agent Harness
- Context assembly
- Capability discovery
- Progressive disclosure
- Skill / MCP projection
- Environment projection
- Result projection
- Model / Tool hooks
│
▼
Agent Runtime
- Run lifecycle
- State / Checkpoint
- Pause / Resume
- Cancellation
- Resource budgets
- Policy Gate / Approval
- Child ownership
- Cleanup
│
▼
Capability Layer
- Tool
- Skill
- MCP
- SubAgent service
│
▼
Environment
- Filesystem
- Browser
- OS
- Network
- External services还有三个东西横穿整个结构:
Memory:过去保存下来的经验和信息。语义判断偏 Agent,存储机制偏 Runtime / Infra。
Context:Agent 此刻真正看得到的世界。Agent 决定需要什么,Harness 负责组织与投影,Runtime 守物理边界。
Protocol:所有组件之间允许怎样讲话,以及这些话具有怎样的确定语义。它不是一层,它是整套 Framework 的合同。
回头看 Mira:真正值得改的是哪几刀?#
这一课没有直接开施工卡。
因为学架构时最危险的事情之一,就是刚听懂一个新概念,就立刻把旧系统重构一遍。
但我们确实留下了几个新的判断。
1. Mira 当前 Harness 更像 Capability / Action Harness#
现在的 Harness 已经把 registry、exposure、approval、workspace、invocation、MCP projection、trace、result projection 做得比较清楚。
这部分不用因为概念变宽就推翻。
但以后继续讨论 Harness 时,要知道:
它还不是完整的 Agent Harness。
2. Tool semantic rerank 不再视为 settled design#
当前的:
>20 tools
→ semantic rank
→ top 20 exposure以前看起来很自然。
现在我们会把它降级成:
一个待验证的设计假设。
更值得探索的是:
capability directory
→ Agent selects domain
→ progressive disclosure
→ optional ranking inside domainRanking 可以留下。
但它服务 disclosure,不应该天然统治 disclosure。
3. Policy Gate 更应该回到 Runtime ownership#
Harness 可以描述 risk metadata、requires approval、workspace condition、availability。
但真正的 allow / deny / prompt 应该由 Runtime 的 Policy Gate 落实。
4. Completion Contract 要继续磨硬#
这是整套 Framework 里我们目前还最容易犯错的地方。
canAnswer
taskComplete
accepted
runCompleted不能再互相代替。
课堂测试:83 分,比满分更有用#
学完以后,我们做了一轮中高级 Agent / Runtime / Harness 架构面试。
最终:
83 / 100
没有往上抹。
整体判断是:
Strong Mid,已经摸到 Senior 门槛;系统设计直觉偏强,但 Protocol 严谨性和 Runtime hard boundary 还需要继续磨。
这次测试里比较稳定的地方包括:
- 能分清 Agent semantic decision 和 Runtime operational control;
- 能意识到 Tool success 不等于 Goal complete;
- Evidence 不足时不会用 read diff 代替真正测试;
- 接受 progressive disclosure,而不是全量 Tool 注入;
- SubAgent 有局部施工权,Parent 保留验收权;
- Memory 是参考,不是事实真相;
- Runtime budget 耗尽以后不能让 Planner 靠“我还想试”无限续命。
而真正扣分的地方主要有三个。
第一:状态事实与原因偶尔混在一起#
例如 timeout 很容易直接被说成“环境失败”。
更严谨的是:
timeout = observed fact
root cause = needs diagnosis第二:Runtime hard boundary 偶尔会被让回 Agent#
例如预算。
Agent 可以决定:
这一刀还值不值得继续。
Runtime 必须决定:
你还有没有资格继续。
所以:
soft budget
→ 提醒 Agent
hard budget
→ Runtime 拒绝新 Action两层都需要。
第三:Completion Contract 还要更机械#
真实工作经验很容易让人说:
“差不多到这里就算做完了。”
但 Runtime 不能靠“差不多”。
它需要明确知道:
用户到底要求什么
哪些 acceptance predicates 已满足
哪些 pending
哪些 impossible
哪些只能 guarded answer这也是下一阶段最值得继续磨的地方。
这一课真正应该留下什么#
如果以后把这篇忘得只剩几句话,我们希望留下的是:
Agent 负责意义和决策。
Harness 负责把正确的 Context、Capability 和 Environment 组织给 Agent。
Runtime 负责让执行真实、安全、有限、可恢复、可结束。
Policy 是 Runtime 的人类门禁。
Memory 是 prior,不是 proof。
Tool success 只相对于自己的 contract。
Protocol 不负责让系统更聪明,它负责让每一层不要靠猜。
还有今天最值得继续带回 Mira 的一句:
Capability Exposure 也是 Context Engineering。
Tool 不是越多越好。
Memory 也不是越多越好。
真正困难的始终是:
在这一刻,让 Agent 看见正确的世界。
下一站:Harness Engineering#
Framework 的基础边界到这里先收住。
下一课不再继续画组件图。
更值得往下问的是:
为什么同一个模型,换一套 Harness,Agent 的实际能力会差一个档次?
我们会继续看 Context、Tool disclosure、Skill loading、Hooks / Middleware、Feedback loop、Environment、Evaluation,到底怎样一起改变模型在真实任务里的表现。
到那时,Harness 就不只是“应该放哪一层”的架构名词了。
它会开始变成真正影响 Agent 智力表现的工程变量。
参考阅读#
- Datawhale Hello-Agents:第七章 构建你的智能体框架
- LangChain:How to Build a Custom Agent Harness
- LangChain:How Middleware Lets You Customize Your Agent Harness
- LangChain:Progressive Tool Disclosure with Deep Agents
- Anthropic:Effective harnesses for long-running agents
- Anthropic:Harness design for long-running application development