---
title: "yoagent 技术笔记：从 Agent Loop 到共享 Harness"
description: "理解 Agent Loop、Extension、多个 Agent 共享 Harness，以及子 Agent 与业务 Workflow 的适用边界。"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.chengchencode.com/llms.txt
> Use this file to discover all available pages before exploring further.

# yoagent 技术笔记：从 Agent Loop 到共享 Harness

> **阅读目标**
>
> <p>读完本文，你将能清晰回答三个核心问题：</p>
>
> <ol>
>   <li>Agent 底层是如何循环执行的？</li>
>   <li>为什么多个不同角色的 Agent 可以共用同一套 Harness（执行底座）？</li>
>   <li>什么时候该使用子 Agent（Sub-Agent），什么时候该使用业务工作流（Workflow）？</li>
> </ol>

官方资料：[Design Philosophy](https://yologdev.github.io/yoagent/design-philosophy.html) · [Agent Loop](https://yologdev.github.io/yoagent/concepts/agent-loop.html) · [Extensions](https://yologdev.github.io/yoagent/concepts/extensions.html) · [Sub-Agents](https://yologdev.github.io/yoagent/concepts/sub-agents.html) · [源码](https://github.com/yologdev/yoagent)

注：本文基于 2026-10 的官方文档整理；文中涉及 English Studio 的部分均为架构演进与工程落地推演，非已上线实现。

## 先建立心智模型

yoagent 是一个用 Rust 编写的轻量级 Agent 执行底座（Harness）。它的核心定位是跑通并管好单次 Agent Loop，而非提供开箱即用的聊天产品或复杂的多 Agent 业务编排框架。

用一句话概括 Agent 的核心机制：在“模型推理 → 工具执行 → 结果回填”的闭环中持续迭代，直到模型不再发起工具调用并给出最终答案。

把上面这个 `while` 循环写出来并不难，真正棘手的是生产环境下的工程边界处理：流式响应中断、工具执行超时、重试引发的重复输出、上下文 Token 超限、中途取消任务、成本统计以及异常终止判定。

yoagent 的设计哲学非常纯粹：把这套通用的运行时机制（Harness）做深、做透，且<mark>只实现一次</mark>。

### 一张表看清职责边界

在实际工程中，我们需要将 yoagent（底层执行层）与 English Studio（业务应用层）清晰解耦：

| 核心能力 | yoagent 执行底座（Harness） | English Studio 应用层（App） |
| --- | --- | --- |
| 模型交互 | 封装请求、流式解析、指数退避重试 | 选择具体模型、管理 API 凭据 |
| 工具调用与循环 | 驱动 Tool Call 循环与结果回填 | 定义英语教学业务工具与调用权限 |
| 上下文与限流 | 提供分层上下文裁剪与执行配额限制 | 决定哪些核心学习事实需要长期保留 |
| 扩展与可观测性 | 暴露统一的生命周期 Hook 与事件流 | 挂载业务扩展、向前端推送学习进度 |
| 教学策略与计划 | 不介入具体业务 | 制定学习计划、编排教学策略 |
| 产品形态与交互 | 不包含 UI 与会话持久化实现 | 管理 UI 渲染、会话列表、复习排期表 |
| 长期记忆（Memory） | 不内置特定记忆存储服务 | 对接 OpenViking 等外部记忆服务 |

> **记忆口诀**
>
> <ul>
>   <li><strong>Harness（底座）</strong> 管“怎么可靠地跑完循环”；</li>
>   <li><strong>Agent 配置</strong> 管“以什么角色、带哪些工具去跑”；</li>
>   <li><strong>应用服务</strong> 管“何时触发运行、结果归谁管理”。</li>
> </ul>

---

## 一、Agent Loop 是如何运行的？

在 API 设计上，yoagent 分为高低两层：

- <strong>高层有状态封装（Agent）</strong>：内部维护会话消息、工具注册表与消息队列，适合快速开箱使用。
- <strong>底层无状态入口（函数式 API）</strong>：由调用方显式传入上下文 `AgentContext` 与配置 `AgentLoopConfig`，官方提供两个核心函数：
  - `agent_loop()`：将新输入追加到上下文，启动一次全新的 Run。
  - `agent_loop_continue()`：基于当前已有的上下文状态，继续向后推进执行。

一次完整 Run 的内部生命周期可以拆解如下：

### 厘清四个粒度：Turn、Run、Session 与 Workflow

在设计应用层架构时，必须严格区分以下四个由小到大的执行粒度：

| 概念 | 粒度定义与职责边界 |
| --- | --- |
| Turn（轮次） | 最小循环单元。包含一次模型响应，以及该次响应触发的所有关联工具执行。 |
| Run（单次运行） | 单次执行闭环。从接收一条 `prompt` 开始，历经 1 到多个 Turn，直到 Agent 停止输出。 |
| Session（会话） | 跨 Run 的状态容器。由多次连续的 Run 组成，需由应用层管理身份绑定、持久化与状态恢复。 |
| Workflow（业务流） | 面向业务目标的编排。为完成特定业务目标组织的一连串步骤，可能包含 0 次、1 次或多次 Agent Run。 |

<strong>切记：<mark>Run 不等于 Workflow</mark>。</strong> 一次简单的日常答疑只需一个 Run 即可完成；而“语法薄弱点专项训练”则可能是跨越数天、包含多个阶段（诊断 → 讲练 → 复测）的业务 Workflow。

---

## 二、Extension：如何在不侵入循环代码的前提下扩展能力？

为了保持核心 Loop 的精简，yoagent 通过 Extension（统一扩展契约）与 RunHooks（运行期介入点）对外提供切面扩展能力：

| Hook 名称 | 触发时机 | 典型工程用途 |
| --- | --- | --- |
| `on_input` | 用户输入刚进入本次 Run 时 | 拦截并拒绝不合规输入 |
| `before_model` | 每次向大模型发起请求之前 | 动态补充临时学习上下文、限制模型调用 |
| `before_tool` | 具体工具执行之前 | 权限校验、拒绝调用或修正入参 |
| `after_tool` | 工具执行完毕返回结果之后 | 敏感数据过滤、结果清洗与格式修正 |
| `on_stop` | 模型准备自然结束输出时 | 校验答案质量，若不达标则打回要求继续推理 |
| `on_event` | 运行期产生各类状态事件时 | 日志采集、监控指标上报与前端 UI 事件推送 |
| `finish` | 整个 Run 彻底结束时 | 记录最终执行状态、清理本次运行占用的资源 |

两者的生命周期关系如下：

<strong>为什么 RunHooks 要每次重新创建？</strong> 核心原因是<mark>单次运行的状态隔离</mark>。两个并发的学习任务即使复用同一个 Extension 类型，也绝不能意外共享本次的执行轮数、临时权限和验证次数。真正需要跨 Run 共享的全局预算或计数器，应放在 Extension 持有的共享状态中，并做好并发同步保护。

### Fail-Closed：异常时宁可拒绝，也绝不默认放行

在涉及安全与权限的拦截点上，必须遵循 <strong>Fail-Closed（故障关闭）</strong> 原则。例如 AI 计划调用高危工具 `delete_learning_records`：

1. `before_tool` 确认用户已授权 → 允许执行；
2. 明确判定没有授权 → 拦截拒绝；
3. 权限扩展超时、崩溃或无法判定 → 仍然拒绝（Fail-Closed）。

同理，负责敏感输出过滤的 `after_tool` 一旦执行失败，绝不能原样泄露未经脱敏处理的工具结果。

> **失败语义区分**
>
> Extension 分为<strong>建议型（Advisory）</strong>与<strong>必需型（Required）</strong>两种失败语义。并非所有建议型扩展失败都会终止整个 Run，但涉及权限判定与结果过滤的关键节点，实现时仍需做保护性兜底处理。

### 架构边界：不是所有逻辑都应该写成 Hook

在工程实践中，应当按职责归属拆分逻辑：

- <strong>Tool（工具）</strong>：供模型调用的明确动作，例如 `find_grammar_knowledge`。
- <strong>Extension（扩展）</strong>：与业务解耦的横切关注点，例如安全检查、预算控制、可观测性和临时上下文补充。
- <strong>App Service（应用服务）</strong>：管理会话生命周期、长期业务状态、学习计划与 Workflow。

> **经验法则**
>
> 如果一项能力需要跨越很多次 Run，并长期维护学习状态，它通常不只是一个 Extension，而应沉淀为应用层服务。

---

## 三、为什么多个 Agent 能共享同一套 Harness？

多 Agent 复用底座的核心在于：共享的是底层执行机制和基础设施，而<mark>不是共享同一份可变的消息历史</mark>。

可以用一个公式直观理解：

以 English Studio 中的三个专项教练 Agent 为例：

| Agent | 角色定位 | 特有工具 | 维护的当前上下文 |
| --- | --- | --- | --- |
| Listening | 听力教练 | 音频转写、听力错误分析 | 当前音频、听写结果与批改记录 |
| Grammar | 语法私教 | 查语法知识、生成专项题 | 语法薄弱点、历史错题 |
| Vocabulary | 词汇复习教练 | 查询到期卡片、记录复习结果 | 生词表、掌握度、复习计划 |

在运行时，三者可以调用相同的模型 Provider，也可以按需选用不同模型；它们还能共享日志管道、统一工具运行能力、记忆网关（OpenViking）和 Session Store，但各自的会话上下文应当保持逻辑隔离。

注意上图中的 `AgentRuntime` 是 <strong>English Studio 自建的应用层运行时包装</strong>，而非 yoagent 开箱提供的完整编排器。它负责加载 `AgentDefinition`、恢复会话、装配工具、调用 yoagent 执行 Run、分发事件以及持久化最终结果。

### Rust 示例：让主 Agent 委派语法子 Agent

下面的代码基于官方的 `SubAgentTool` 和 Agent Builder 编写；请注意，这里演示的是运行期的动态任务委派（Delegation），而非完整的业务 Workflow：

~~~rust
use std::sync::Arc;

use yoagent::agent::Agent;
use yoagent::provider::{AnthropicProvider, ModelConfig};
use yoagent::sub_agent::SubAgentTool;

pub fn build_tutor_coordinator(
    provider: Arc<AnthropicProvider>,
    grammar_tools: Vec<Box<dyn yoagent::tools::Tool>>,
) -> Agent {
    // 1. 定义语法专项子 Agent：拥有独立的 System Prompt、专属工具与轮次上限
    let grammar_sub_agent = SubAgentTool::new("grammar_specialist", provider.clone())
        .with_description("当用户在听力或阅读中遇到复杂长难句、虚拟语气或特定语法盲区时，委派该专家进行深度拆解与出题。")
        .with_system_prompt(
            "你是 English Studio 的语法私教。请仅针对传入的句子和错因进行语法结构剖析，并生成 1 道同类变式题。",
        )
        .with_model(ModelConfig::claude_sonnet())
        .with_tools(grammar_tools)
        .with_max_turns(5);

    // 2. 构建主教练 Agent：将子 Agent 作为 Tool 挂载，按需动态委派
    Agent::new(provider)
        .with_system_prompt(
            "你是用户的英语主教练。负责推进日常学习对话；遇到需要深度语法专项解析的场景时，请调用 grammar_specialist 工具。",
        )
        .with_model(ModelConfig::claude_sonnet())
        .with_sub_agent(grammar_sub_agent)
        .with_max_turns(10)
}
~~~

在这段代码中：

- 主 Agent 与 `grammar_specialist` 底层跑的都是同一套 <strong>yoagent Loop</strong>；
- 当主模型决定调用 `grammar_specialist` 时，`SubAgentTool` 会在内部启动一个子级 Loop（Child Loop），使用独立的上下文和 `max_turns(5)` 限制完成推理，最后只将提炼后的结论作为工具输出返回给主 Agent，从而避免子任务的中间推理过程污染主会话上下文。

---

## 四、什么时候用子 Agent，什么时候用业务 Workflow？

理解了 `SubAgentTool` 的委派机制后，工程落地中最容易踩的坑就是：把所有多步业务流程都交给主 Agent 用 `SubAgentTool` 自由调度。

在架构选型上，请用“<mark>控制权归属</mark>”来划分两者的边界：

| 对比维度 | 子 Agent 委派（Sub-Agent） | 业务工作流（Workflow） |
| --- | --- | --- |
| 调度决策者 | 大模型（LLM）在单次 Run 中自主决定是否调用、何时调用 | 应用层代码（Rust 业务逻辑）按确定性状态机或编排规则驱动 |
| 确定性要求 | 探索性、开放式、分支取决于对话实时上下文 | 强确定性、步骤固定、每步输出有严格的 Schema 与落库要求 |
| 上下文传递 | 父 Agent 生成自然语言 `prompt` 传给子 Agent，或通过 `SharedState` 按 Key 引用大对象 | 由应用层查询数据库/状态机，精确组装每一步 Run 所需的最小结构化上下文 |
| 生命周期跨度 | 短周期，共生于父 Agent 的同一次 Run 内部 | 可长可短，可跨越多次请求、多个页面交互甚至数天的学习计划 |
| 失败恢复成本 | 依赖外层重试或模型自我修正，无法精确从中间某一步断点续跑 | 具备明确的业务状态检查点（Checkpoint），可针对单步精确重试或回滚 |

### 在 English Studio 中的选型实践

<strong>1. 适合使用「子 Agent（SubAgentTool）」的场景</strong>

- <strong>伴学对话中的临时专家会诊</strong>：用户在做精听训练时突然追问“这句话里的 `were it not for` 为什么倒装？”，听力主 Agent 识别出这属于深度语法问题，在当前 Run 内通过 `SubAgentTool` 临时委派给 Grammar Agent 解答，拿回解析后再继续原来的听力训练。
- <strong>长文本/大产物局部下钻</strong>：结合 yoagent 的 `SharedState` 引用机制，让子 Agent 针对整篇外刊文章的某个段落做词根拆解，只把汇总结果返回主对话，节省主上下文 Token。

<strong>2. 适合使用「业务 Workflow（应用层编排）」的场景</strong>

- <strong>每日晨间综合训练流（听写 → 错因诊断 → 闪卡生成 → 复习排期）</strong>：这是一条必须严格按序执行的业务链路。应当由 English Studio 的业务服务来编写确定性流程：
  1. 先调用确定性代码完成音频切片与转写比对（无需 Agent）；
  2. 启动一次独立的 Listening Agent Run 生成错因诊断报告并落库；
  3. 根据诊断报告中的生词，调用确定性服务写入 Learning DB 闪卡表；
  4. 当用户点击“进入语法巩固”时，再由应用层启动一次全新的 Grammar Agent Run。

> **选型铁律**
>
> <ul>
>   <li>如果步骤顺序属于产品规则（必须先 A 后 B、中间需要落库或等待用户下一步操作），请用<strong>业务 Workflow</strong>（代码编排多次独立 Run）；</li>
>   <li>如果步骤触发属于临场判断（对话中按需请教另一个领域的专家，且上下文需要隔离），请用<strong>子 Agent（SubAgentTool）</strong>。</li>
> </ul>

---

## 总结：回顾开篇三个问题

1. <strong>Agent 是怎么循环执行的？</strong> 围绕<strong>模型推理 → 工具调用 → 结果写回</strong>循环推进；yoagent 通过无状态的 `agent_loop()` 与有状态的 Agent 封装，统一解决了流式输出、超时重试、上下文裁剪、配额控制与 Extension 切面拦截等工程难题。
2. <strong>为什么多个 Agent 可以共用 Harness？</strong> 因为执行循环与底层基建（Provider、工具执行器、日志、存储网关）是完全通用的；不同 Agent 只是在同一套 yoagent 底座之上，注入了各自独立的 System Prompt、工具子集与逻辑隔离的 Session 上下文。
3. <strong>什么时候用子 Agent，什么时候用业务 Workflow？</strong> 临时专家协同、隔离中间推理噪声用 `SubAgentTool`（让模型做<mark>动态委派</mark>）；固定教学链路、跨阶段状态流转用业务 Workflow（让应用层代码驱动多次独立 Run）。

Source: https://docs.chengchencode.com/notes/yoagent/index.mdx
