AI Agent 框架探秘:拆解 OpenHands(14)--- Microagents

0 阅读33分钟

AI Agent 框架探秘:拆解 OpenHands(14)--- Microagents

0x00 概要

很多Agent系统会采用多智能体(multi-agent)架构,划分不同子模块/子Agent各司其职,由中央调度Agent统筹管理整个生命周期。这种模块化架构能将复杂任务拆解给最擅长该子任务的模块,发挥各模型所长,避免单一模型在某些任务上的弱点。

在OpenHands 中,Microagents 本质上是一组量身定制的指令模块,核心作用是给 OpenHands 工具注入更聚焦的能力 —— 不管是某个细分领域的专业知识,还是特定任务的标准化流程,都能通过它们来落地。对开发者来说,这些小模块就像身边的专项助手:遇到 Git 操作、代码审查这类具体场景时,不用再手动梳理步骤,微型代理会提供现成的专业指引;重复任务能直接交给它们自动化处理,还能保证不同项目里的操作逻辑保持一致,省不少事。

Microagents 允许我们为 Agent “外挂”领域知识,而无需修改 Agent 的核心代码或 Prompt。Memory 组件会在任务开始时或在对话中检测到特定关键词时,自动加载相应的 Microagent 文件内容,并将其作为上下文信息提供给 LLM。这使得 Agent 能够:

  • 快速适应特定项目: 通过加载项目专属的 repo.md,Agent 可以了解项目的架构、编码规范和测试方法。
  • 利用领域知识: 比如,当用户提到 “Python” 时,可以自动注入一份关于 Python 最佳实践的 knowledge.md

图片

图片

0x01 需求

1.1 当前问题

既然单智能体能搞定不少复杂事,为啥还要费劲搞多智能体协作?核心原因很简单:任务一超出一定复杂度,单个智能体就容易 “力不从心”。

就像一个人要同时处理项目规划、数据调研、数学计算、文案撰写一堆事,不仅容易顾此失彼,还会被海量信息淹没 —— 智能体也一样,面对太多工具要选、太多上下文要记,它的 “思考空间” 根本不够用,决策效率和准确性都会明显下滑。更别说有些任务横跨好几个专业领域,指望一个智能体精通所有,就像让一个医生同时当好工程师,显然不现实。

单一的、庞大的“万能”Agent在处理多任务时,其上下文窗口和工具集会变得异常臃肿,导致“上下文污染)”(Context Pollution),最终影响系统的可靠性和效率。

LangChain的benchmark研究显示,当distractor domains(干扰域)从0增加到8个时,单agent架构的性能从0.67暴跌至0.34,下降50%。

1.2 多智能体系统

而多智能体系统恰好能解决这些痛点。它的核心逻辑是 “分工协作”:遇到复杂任务时,先把大任务拆成一个个小模块,比如把 “完成一份市场分析报告” 拆成 “数据收集”“统计计算”“报告撰写”“合规审核” 几个子任务,再给每个子任务分配专门的 “专家” 智能体 —— 有的专攻数据爬取,有的擅长数学建模,有的精通文案润色。

这些智能体不用包揽所有事,只需要把自己领域的工作做精。它们之间还能随时沟通:数据爬取的智能体拿到素材后,会同步给统计计算的智能体;计算结果出来后,再传给文案智能体,过程中如果遇到问题,还能互相协调调整。这种模式不仅让每个智能体都能发挥专长,还可能产生意外的 “协作红利”—— 就像一个高效的团队,整体能完成的事,远超出单个成员的能力总和。

从实际开发来看,多智能体也更实用:每个智能体都是独立模块,开发时不用考虑全盘,测试和维护起来更简单;如果某个领域需要升级,直接替换对应的智能体就行,不用动整个系统;而且智能体之间怎么沟通、传递信息,都能提前设定好规则,比单个智能体混乱调用工具要可控得多。

1.3 Multi-Agent

多智能体(Multi-Agent)系统与单一智能体工作流的核心区别,在于突破了 “顺序接力” 的任务执行模式,实现了智能体间的高效并行协作。也有研究人员认为,Sub-Agents 是 Multi-Agent 体系下的一种架构模式,而不是一个与 Multi-Agent 对立的总体范式。

Multi-Agent(多智能体系统) ,本质上是一群相对独立的智能体在协作。它们可能有各自的目标、各自的状态、各自的上下文,通过通信协议来协调。就像一个真正的工程团队,产品经理、架构师、工程师、测试,大家有明确的分工,各自负责自己的部分,通过会议、文档、工具来协同。

而Sub-Agent(子代理) ,本质上是集中式架构下的分工。一个主智能体掌控全局,把任务委派给几个专用的子代理。这些子代理更像工具,无状态,只处理被分配的子任务。就像一个项目经理手下有几个专精的执行者。项目经理知道全局目标和上下文,根据需要把任务分发给不同的人,拿到结果后再整合。

关键区别在于:控制权和上下文

Multi-Agent模式下,控制权是分布式的,每个Agent都有一定的自主权,上下文是隔离的。而Sub-Agent模式下,控制权是集中式的,主智能体说了算,上下文是共享的。

1.4 连接机制

**多智能体(Multi-Agent)**的核心思想是:让专业的人干专业的事。我们创建多个拥有独立 Prompt 和独立工具的 Agent,然后通过某种机制把它们连起来。最基础的连接机制有两种:Handoffs(交接/路由)Orchestrator-Workers(指挥/分发)

handoffs 指的是一个智能体将其执行上下文和执行权交接给另一个智能体。handoffs需要包含两个最基本的要素:

  • 目的地:下一个智能体
  • State:传递给下一个智能体的信息

其实,工具调用也是一种连接机制,比如一个智能体(如主管)将另一个智能体作为工具进行调用。移交更适用于自主协作的场景,而工具调用则提供了更明确的层级控制和接口约束。

1.5 Sub-agent

Sub-agent 这一架构的核心设计是引入 “协调者智能体(Orchestrator Agent)” 作为全局管控核心,其核心职责是先深度理解整体任务目标,再通过合理的任务拆解策略,将复杂任务分解为多个可独立执行的子任务,进而委派给多个并行工作的 “子智能体(Sub-agent)”。

该系统的实现逻辑为:

  • 在协调者智能体的视角中,调用子智能体与调用普通工具(Tool)的交互模式完全一致。协调者通过工具调用(tool calling)机制,以提示词(prompt)形式向子智能体下达明确指令;
  • 子智能体接收指令后,在独立的执行环境中自主完成分配的任务,无需与其他子智能体交互,最终仅将完成结果反馈给协调者。

子智能体架构能够高效落地的本质,是上下文工程(Context Engineering)的成功实践。其核心思想在于精准把控 “信息供给的时机与内容”—— 为每个子智能体创建专注且隔离的执行环境,确保其在处理对应子任务时,能获得最匹配的信息与工具支持。这种设计不仅能大幅提升整个系统的任务处理性能,还能通过职责拆分与环境隔离,降低复杂目标的实现成本,成为应对大规模、多维度复杂任务的高效架构方案。

1.6 microagents

Google Cloud的一篇文章给出了**Agent as Tool(工具式子代理)**的说法。。

Agent as Tool就像一个专家顾问,主智能体调用它时,给出明确的输入,拿到明确的输出,就像调用一个API。这个专家顾问有自己的逻辑,但主智能体不需要知道细节。

而Sub-Agent(委派式子代理)更像一个项目经理的分身,它在主智能体的全局上下文中工作,处理复杂的多步骤流程,可以访问主智能体的对话历史和状态。

结合 Google Cloud 对两种代理模式的核心界定,以及 OpenHands 的 microagents(微代理)的功能特性来看,microagents 本质上属于 Agent as Tool(工具式子代理) 。下面结合两者的核心差异与 microagents 的具体表现展开分析:

  1. 从核心定义与控制逻辑来看

    • Google Cloud 明确,Agent as Tool 是封装好的特定任务专家,主代理调用它时只需传递清晰输入并获取直接输出,类似事务性 API,无需关注其内部逻辑;而 Sub - Agent 是接受委派的角色,需自主处理复杂多步骤流程,和主代理是层级协作关系,拥有一定自主决策与流程管理权。
    • OpenHands 的 microagents 不管是知识代理、任务代理还是仓库代理,均是响应特定触发条件来执行固定功能。比如知识代理靠 “docker”“container” 等关键词触发,提供对应领域的标准化支持;任务代理按预设交互式模板,接收参数后完成 PR 描述生成等操作。它们不会自主规划复杂任务流程,完全由系统或主流程触发调用,契合 Agent as Tool 的 “被动响应、执行特定功能” 的控制逻辑。
  2. 从上下文与状态特性来看

    • Agent as Tool 具有上下文隔离、无状态的特点,运行在自身独立会话中,无法获取调用方的对话历史和状态,且每次交互的信息都靠单次请求传递。而 Sub - Agent 能共享主代理的上下文,处于同一会话,适合需要多步骤推进的有状态流程。
    • microagents 是独立封装的模块,不同代理间相互隔离。例如仓库代理仅加载所在项目的专属规范,知识代理只聚焦单一领域的知识输出。它们的运行不依赖其他代理的历史状态,每次触发都是基于当前的输入信息执行任务并返回结果,不存在与主流程或其他代理共享上下文来推进多步骤任务的情况,符合 Agent as Tool 的无状态、上下文隔离特征。
  3. 从复用性与耦合度来看

    • Agent as Tool 的一大优势是复用性强,可在不同代理或系统中被重复调用,与调用方的耦合度低;而 Sub - Agent 和主代理耦合紧密,是特定流程的一部分,复用性较弱,多适配所属的层级协作体系。
    • microagents 的设计着重于高复用性。公共微代理库中的代理可跨不同项目使用,私有仓库代理虽为团队专属,但也是在项目内部的固定场景中重复发挥作用。并且它们可通过简单配置接入系统,无需和主流程进行深度的层级绑定开发,这种高复用、低耦合的特性,和 Sub - Agent 的强耦合特征不符,反而匹配 Agent as Tool 的核心特点。

1.7 原理

1.7.1 协作

多智能体不仅仅是把任务拆分,它引入了一个全新的优化维度,协作(Collaboration)

有研究者问了一个深刻的问题:**为什么两个Agent配合往往比一个超级Agent更好?**答案在于一个新的概率项--- 协作概率:

在多智能体系统中,Agent A(比如产品经理)执行动作后,不仅仅是产生一个结果,它通过动作产生了一个上下文(Context),并把这个传递给Agent B(比如程序员)。

这听起来很抽象,但请这样理解: 协作和协商(Negotiation),本质上是在搜索最优的通信上下文

  • 单体Agent:只能自己闷头干,必须在给定的S下硬解a。
  • 多智能体:Agent A的任务变成了“寻找一种最好的说法()”,使得Agent B成功的概率最大化。

研究者指出,这种“通过对话来动态调整上下文”的能力,实际上是在运行时(Runtime)动态微调系统的参数,而不需要重新训练模型。这就是多智能体系统强大的数学根源,它增加了一个巨大的、可优化的参数空间。

1.7.2 token

Anthropic 在其博客中指出,多智能体系统之所以有效,主要是因为它们投入足够的token来解决问题。在分析中,三个因素解释了BrowseComp评估中95%的性能差异(该评估测试浏览智能体定位难以找到信息的能力)。研究发现,仅token使用量就解释了80%的差异,工具调用次数模型选择是另外两个解释因素。

1.7.3 成本

虽然多智能体协作听起来很美,但研究者非常冷静地泼了一盆冷水:协作是有成本的(Collaboration Costs)。

用户增加的每一个Agent,每一次交互,都会带来:

  1. 延迟(Latency) :网络请求和生成的耗时。
  2. 算力消耗(Tokens) :真金白银的成本。
  3. 复杂性(Complexity) :系统越复杂,越容易出错。

0x02 基本梳理

2.1 基类定义

Microagents 是 Openhands 中一种模块化的知识注入机制。它们通常是一些 Markdown 文件,包含了针对特定领域、特定仓库或特定任务的知识、指南或代码片段。

从系统架构的角度看,微型代理(Microagents)本质是轻量化的 “专项执行者”—— 它们不负责整体任务的统筹规划,只聚焦某一类特定工作,比如专门处理代码格式化、数据校验这类单一职责。和主智能体(主 Agent)的 “总指挥” 角色不同,它们更像随时待命的 “专业小分队”,平时不占用过多系统资源,一旦主智能体需要,要么被直接召唤上场,要么接手主智能体拆分出来的细分任务,灵活又高效。

这些专项执行者并不是孤立的 “散兵”,系统早就设计好了一套统一的集成逻辑,核心就是get_microagents_from_selected_repo这个核心方法。具体用起来很简单:用户或者团队可以在自己的代码仓库里,单独建一个文件夹专门存放微型代理,不管是自己开发的,还是适配好的专项工具,都可以放在这里统一管理。等系统将这个仓库设为当前的工作仓库时,会自动扫描这个专属文件夹,把里面所有的微型代理一次性加载进来,相当于为系统搭建了一个 “专项工具储备库”。之后主智能体在处理复杂任务时,比如遇到需要专门做日志分析或者接口调试的环节,就能直接从这个储备库里调取对应的微型代理,一起协同完成工作。

class BaseMicroagent(BaseModel):
    """Base class for all microagents."""

    name: str
    content: str
    metadata: MicroagentMetadata
    source: str  # path to the file
    type: MicroagentType

    PATH_TO_THIRD_PARTY_MICROAGENT_NAME: ClassVar[dict[strstr]] = {
        '.cursorrules''cursorrules',
        'agents.md''agents',
        'agent.md''agents',
    }

2.2 微型代理的类型

大多数微型代理使用带有YAML前导的Markdown文件。对于仓库代理(repo.md),前导是可选的 - 如果未提供,文件将使用默认设置作为仓库代理加载。

KnowledgeMicroagent 和 RepoMicroagent 都是 BaseMicroagent 的子类,但它们有不同的用途和激活机制。这两种微代理类型共同构成了 OpenHands 系统中灵活而强大的知识管理机制,允许同时拥有按需访问的专业知识和持续可用的仓库特定知识。

2.2.1. KnowledgeMicroagent

知识代理提供由对话中的关键词触发的专业技能。它们帮助:

  • 语言最佳实践
  • 框架指南
  • 常见模式
  • 工具使用

基本特征

  • 类型:MicroagentType.KNOWLEDGE 或 MicroagentType.TASK
  • 激活方式:关键词触发,当消息中包含特定触发词时激活

主要功能

  • 提供专业领域知识和特定技能指导
  • 用于语言最佳实践、框架指南、常见模式和工具使用
  • 通过 match_trigger 方法匹配消息中的触发词

激活机制

  • 需要在 frontmatter 中定义 triggers 数组
  • 仅在用户输入包含触发词时才会被激活

适用场景

  • 特定技术栈的使用指南
  • 框架或工具的最佳实践
  • 特定领域的专业知识
  • 需要用户输入的任务型微代理(TaskMicroagent)
class KnowledgeMicroagent(BaseMicroagent):
    """Knowledge micro-agents provide specialized expertise that's triggered by keywords in conversations.

    They help with:
    - Language best practices
    - Framework guidelines
    - Common patterns
    - Tool usage
    """

    def __init__(self, **data):
        super().__init__(**data)
        if self.type not in [MicroagentType.KNOWLEDGE, MicroagentType.TASK]:
            raise ValueError('KnowledgeMicroagent must have type KNOWLEDGE or TASK')

    def match_trigger(self, message: str) -> str | None:
        """Match a trigger in the message.

        It returns the first trigger that matches the message.
        """
        message = message.lower()
        for trigger in self.triggers:
            if trigger.lower() in message:
                return trigger

        return None

    @property
    def triggers(self) -> list[str]:
        return self.metadata.triggers

可以在OpenHands的GitHub微型代理中看到一个基于知识的代理示例。

2.2.2. RepoMicroagent

仓库代理提供仓库特定的知识和指导方针。它们是:

  • .openhands/microagents/repo.md加载
  • 特定于个别仓库
  • 为其仓库自动激活
  • 非常适合团队实践和项目惯例

基本特征

  • 类型为 MicroagentType.REPO_KNOWLEDGE
  • 激活方式:始终激活,与特定仓库关联

主要功能

  • 提供仓库特定的知识和指南
  • 包含私有的、仓库特定的指令
  • 自动加载并与当前仓库关联

激活机制

  • 自动激活,不需要触发词
  • 在处理相关仓库时始终可用
  • 通常来自 .openhands/microagents/repo.md 文件

适用场景

  • 仓库特定的开发规范
  • 团队实践和约定
  • 项目特定的工作流程
  • 自定义文档引流
  • 通用仓库指南
class RepoMicroagent(BaseMicroagent):
    """Microagent specialized for repository-specific knowledge and guidelines.

    RepoMicroagents are loaded from `.openhands/microagents/repo.md` files within repositories
    and contain private, repository-specific instructions that are automatically loaded when
    working with that repository. They are ideal for:
        - Repository-specific guidelines
        - Team practices and conventions
        - Project-specific workflows
        - Custom documentation references
    """

    def __init__(self, **data):
        super().__init__(**data)
        if self.type != MicroagentType.REPO_KNOWLEDGE:
            raise ValueError(
                f'RepoMicroagent initialized with incorrect type: {self.type}'
            )

可以在OpenHands仓库本身的代理中看到一个仓库代理的示例。

2.2.3 对比

以下是两类微代理的全维度对比,涵盖激活、功能、适用场景等核心差异,便于快速区分与选型:

对比维度KnowledgeMicroagent(知识型微代理)RepoMicroagent(仓库型微代理)
核心类型通用知识 / 技能载体特定仓库专属知识载体
激活方式关键词触发(需匹配预设触发词)自动激活(与仓库关联后始终生效)
作用范围跨仓库通用(所有场景均可调用)绑定特定仓库(仅当前仓库可用)
触发条件需用户输入包含触发词的内容无需额外操作,关联仓库即生效
使用频率按需使用(用户主动触发)持续可用(处理仓库任务时自动调用)
配置要求必须定义 triggers(触发词列表)无需配置 triggers
内容来源通用技术文档 / 公共知识库仓库内 .openhands/microagents/repo.md 文件
典型内容技术栈指南、框架使用方法、领域专业知识仓库开发规范、团队协作约定、项目工作流程
适用场景解答通用技术问题、工具 / 框架使用指导仓库内开发约束、团队实践对齐、项目流程指引
2.2.4 TaskMicroagent

TaskMicroagent 是 KnowledgeMicroagent 的子类,具有特殊的任务导向特性,需要用户输入才能执行。

核心特点

需要用户输入

  • 变量提取:通过 extract_variables 方法从内容中提取变量(格式为 ${variable_name}
  • 输入检测:requires_user_input 方法检查内容中是否包含变量
  • 输入定义:通过 inputs 属性获取预定义的输入元数据

特殊触发方式

  • 命令式触发:通过格式 /agent_name 触发,例如 /test_task
  • 自动触发词添加:如果 frontmatter 中没有定义触发词,系统会自动添加 / 前缀的触发词
适用场景

TaskMicroagent 适用于以下场景:

  • 需要参数的任务:需要用户提供特定参数才能执行的任务
  • 交互式操作:需要与用户进行交互以获取必要信息的操作
  • 模板化任务:可以通过填充变量来执行的标准化任务

2.3 微型代理的来源

OpenHands从两个来源加载微型代理:

2.3.1. 可共享微型代理(公共)

此目录(OpenHands/microagents)包含所有OpenHands用户都可以使用的可共享微型代理:

  • 由OpenHands仓库维护
  • 非常适合重用知识和常见工作流程

目录结构:

OpenHands/microagents/
├── # 关键词触发的专业技能
│   ├── git.md         # Git 操作
│   ├── testing.md     # 测试实践
│   └── docker.md      # Docker 指南
├── # 这些微型代理总是加载
    ├── pr_review.md   # PR 审查流程
    ├── bug_fix.md     # Bug 修复工作流程
    └── feature.md     # 功能实现
2.3.2. 仓库指令(私有)

每个仓库可以在.openhands/microagents/repo.md中拥有自己的指令。这些指令是:

  • 私有于该仓库
  • 在使用该仓库时自动加载
  • 非常适合仓库特定的指导方针和团队实践

示例仓库结构:

your-repository/
├── .openhands/
    ├── microagents/
        ├── repo.md    # 仓库特定的指令
        ├── ...        # 仅在此仓库内可用的私有微型代理

当OpenHands与仓库协作时,它将:

  1. 如果存在,从.openhands/microagents/repo.md加载仓库特定的指令
  2. 根据对话中的关键词加载相关的知识代理

2.4 升级为 Skill

注意:在OpenHands最新代码中,对MicroAgent升级为Skills,我们会在其他系列中进行Skill的相关学习和分析。

github.com/OpenHands/e…

docs.openhands.dev/overview/sk…

docs.openhands.dev/sdk/arch/sk…

KnowledgeMicroagent
KnowledgeMicroagent is the legacy name for what is now called a Knowledge Skill (keyword-triggered skill).
​
Knowledge Skills are keyword-triggered skills that activate when specific keywords are detected in user messages. They use a KeywordTrigger with regex patterns to match against user input, and when matched, inject domain-specific knowledge into the agent's context.
RepoMicroagent
RepoMicroagent is the legacy term for what is now called a Repository Skill (or "General Skill" / "Permanent Context"). These are always-active, repository-specific guidelines that are automatically loaded into the agent's context at conversation start.
​
The recommended approach is to create an AGENTS.md file at your repository root. This file contains project purpose, setup instructions, repo structure, and development guidelines. It has no trigger — it's always injected into the system prompt.
​
You can also use model-specific variants like GEMINI.md or CLAUDE.md. Legacy paths (.openhands/microagents/) are still supported but deprecated in favor of .agents/skills/

0x03 实现

3.1 处理流程

  • CodeActAgent 通过 response_to_actions 将工具调用转换为 AgentDelegateAction
  • AgentController 接收到 AgentDelegateAction 并调用 start_delegate 方法创建新的代理控制器处理委托任务。

3.2 触发条件

AgentDelegateAction 会在以下条件下生成:

  • LLM 决定将任务委托给另一个专门的代理,比如LLM 调用名为 delegate_to_browsing_agent 的工具函数

该工具函数需要以下参数:

  • agent:要委托给的代理名称
  • task:委托的具体任务描述
  • 可选的 inputs:传递给委托代理的额外输入参数
            # ================================================
            # AgentDelegateAction (Delegation to another agent)
            # ================================================
            elif tool_call.function.name == 'delegate_to_browsing_agent':
                action = AgentDelegateAction(
                    agent='BrowsingAgent',
                    inputs=arguments,
                )

3.3 AgentDelegateAction

AgentDelegateAction 是由 LLM 决定委托任务时通过调用相应工具函数生成的,然后通过 response_to_actions 方法处理并添加到待处理动作队列中,最终在 step 方法中返回执行。

@dataclass
class AgentDelegateAction(Action):
    agent: str
    inputs: dict
    thought: str = ''
    action: str = ActionType.DELEGATE

    @property
    def message(self) -> str:
        return f"I'm asking {self.agent} for help with this task."

3.4 特色

3.4.1 委托代理(Delegate)机制

AgentController 通过 “委托代理(Delegate)机制” 实现对 microAgent(子智能体)的全生命周期控制,核心流程如下:

  1. 触发启动:主 Agent 生成 AgentDelegateAction 动作(含子智能体名称、任务参数),主控制器通过 start_delegate() 方法初始化子智能体控制器(AgentController 实例,标记 is_delegate=True)。
  2. 资源与状态隔离:子智能体控制器继承主控制器的事件流、文件存储等资源,但拥有独立的状态(State),包括独立的迭代次数、预算限制、任务上下文,避免与主 Agent 相互干扰。
  3. 事件转发与独立执行:子智能体运行期间,主控制器将所有事件(如用户消息、工具反馈)转发给子控制器处理,子智能体独立执行任务(无需主 Agent 干预)。
  4. 状态监控与终止:主控制器实时检查子智能体状态,当子智能体完成(FINISHED)、拒绝(REJECTED)或出错(ERROR)时,通过 end_delegate() 方法终止子智能体,回收资源并接收其执行结果。
  5. 结果整合:子智能体终止后,主控制器将其输出结果封装为 AgentDelegateObservation 事件,回传给主 Agent,主 Agent 基于该结果继续执行后续任务。
3.4.2 功能概述

AgentController 是 OpenHands 框架中智能体(Agent)的核心控制组件,负责管理 Agent 的生命周期(启动、运行、终止)、事件处理(动作 / 观察结果)、状态维护、资源调度,以及子智能体(microAgent)的委托与协同,是多智能体系统中实现 “主 - 子 Agent 协作” 与 “任务拆分执行” 的核心枢纽。

  1. 主 - 子 Agent 交互机制

    • 主 Agent 以 AgentDelegateAction 为 “调用接口”,将子任务委托给 microAgent,类比工具调用(Tool Calling)的简洁模式。
    • 子智能体拥有独立的控制器实例,运行期间事件全转发、状态全隔离,执行完成后通过 AgentDelegateObservation 回传结果,实现 “委托 - 执行 - 回调” 的闭环。
  2. 事件处理机制

    • 事件分流:根据是否存在活跃子智能体,决定事件是转发给子控制器还是主控制器自行处理。
    • 类型适配:区分 Action(动作)和 Observation(观察结果)事件,分别调用对应处理方法,支持 AgentDelegateAction、用户消息、工具反馈等多类事件。
    • 步骤触发:通过 should_step() 方法判断是否触发 Agent 下一步执行(如用户消息、子智能体结果回调时自动触发)。
  3. 全生命周期状态管理

    • 支持 RUNNINGAWAITING_USER_INPUTAWAITING_USER_CONFIRMATIONFINISHED 等多状态切换,状态变更时自动同步到事件流并持久化。
    • 子智能体状态实时监控,异常时自动终止并回收资源,确保系统稳定性。
  4. 鲁棒性设计

    • 防卡死机制:内置 StuckDetector 检测 Agent 循环卡死,触发异常处理。
    • 预算与迭代限制:通过 State 中的 iteration_flagbudget_flag 控制最大迭代次数与任务预算,避免资源耗尽。
    • 异常容错:对 LLM 错误(如上下文窗口溢出、API 超时)、子智能体执行错误等场景,提供降级策略(如历史截断、重试)。
  5. 多智能体协同支持

    • 子智能体继承主 Agent 的资源(事件流、文件存储、安全分析器),但状态独立,支持多层级委托(子智能体可再委托其他 microAgent)。
    • 主控制器统一汇总所有智能体的执行 metrics(成本、Token 消耗),便于全局监控。
3.4.3 层级型的合作模式

Agent和Microagent之间是层级型的合作模式。:

层级结构:

  • Agent是主要的决策者,负责整体任务的执行
  • Microagent是小型的、专门化的代理,用于处理特定子任务或提供特定功能
  • Agent可以调用Microagent,但Microagent不能直接调用Agent

具体实现方式:

  • Microagent被加载到Memory中,作为工具提供给Agent使用
  • 在openhands/server/session/agent_session.py中,可以看到microagents通过get_microagents_from_selected_repo方法加载,并通过memory.load_user_workspace_microagents添加到内存中
  • Agent可以在需要时调用这些microagents来执行特定任务

两种类型的Microagents:

  • Repo Agents:针对特定仓库的代理,处理与该仓库相关的任务
  • KnowledgeAgents:提供特定领域知识的代理

工作流程:

  • Agent在执行任务时,可以决定是否需要调用某个Microagent
  • Microagent执行完任务后,将结果返回给Agent
  • Agent根据Microagent的输出继续执行后续操作

加载机制:

  • Microagents可以从多个位置加载: 从选定的仓库中的.openhands/microagents目录
  • 从组织/用户级别的仓库(例如github.com/acme-co/.openhands/microagents)

因此,这是一种层级型的合作模式,其中Agent作为主控制器,Microagent作为专门的助手提供特定功能,两者之间不是对等的网络型关系,也不是监督者与被监督者的关系。

3.4.4 流程图
主 - 子 Agent 交互与控制流程图

14-1

14-1

AgentController 核心事件处理流程图

14-2

14-2

3.4.5 容错和隔离机制
独立的Controller实例

当父Agent创建委托时,会为子Agent创建一个AgentController实例,这确保了子Agent有自己独立的状态管理和事件流程。

        # Create the delegate with is_delegate=True so it does NOT subscribe directly
        self.delegate = AgentController(
            sid=self.id + '-delegate',
            file_store=self.file_store,
            user_id=self.user_id,
            agent=delegate_agent,
            event_stream=self.event_stream,
            conversation_stats=self.conversation_stats,
            iteration_delta=self._initial_max_iterations,
            budget_per_task_delta=self._initial_max_budget_per_task,
            agent_to_llm_config=self.agent_to_llm_config,
            agent_configs=self.agent_configs,
            initial_state=state,
            is_delegate=True,
            headless_mode=self.headless_mode,
            security_analyzer=self.security_analyzer,
        )
独立的状态管理

每个AgentController都有自己的State实例,子Agent的状态变化不会影响父Agent。

事件流隔离

虽然父子Agent共享一个Event Stream,但是通过不同的ID和事件源来区分。

异常处理

系统通过_step_with_exception_handling方法处理异常,子Agent的异常不会中断父Agent的运行。

错误状态传播

当子Agent出错时,会通过AgentDelegateObservation将错误信息发给父Agent。

状态恢复机制

_react_to_exception函数让系统可以从错误状态中恢复。

3.5 代码

具体工作流程如下:

3.5.1 调用大模型
class CodeActAgent(Agent):
    def step(self, state: State) -> 'Action':
        """Performs one step using the CodeAct Agent."""
        initial_user_message = self._get_initial_user_message(state.history)
        messages = self._get_messages(condensed_history, initial_user_message)
        params: dict = {
            'messages': messages,
        }
        params['tools'] = check_tools(self.tools, self.llm.config)
        params['extra_body'] = {
            'metadata': state.to_llm_metadata(
                model_name=self.llm.config.model, agent_name=self.name
            )
        }
        response = self.llm.completion(**params)
        actions = self.response_to_actions(response) # 在这里处理返回值
        for action in actions:
            self.pending_actions.append(action)
        return self.pending_actions.popleft()
3.5.2 解析

如果发现需要调用 delegate_to_browsing_agent,则生成一个 AgentDelegateAction。

def response_to_actions(
    response: ModelResponse, mcp_tool_names: list[str] | None = None
) -> list[Action]:
            # ================================================
            # AgentDelegateAction (Delegation to another agent)
            # ================================================
            elif tool_call.function.name == 'delegate_to_browsing_agent':
                action = AgentDelegateAction(
                    agent='BrowsingAgent',
                    inputs=arguments,
                )
3.5.3 执行

AgentController 中会处理AgentDelegateAction,执行microAgent。

    async def start_delegate(self, action: AgentDelegateAction) -> None:
        """启动委托智能体以处理子任务。

        OpenHands 是多智能体系统:
        - 「任务(task)」:系统与用户之间的完整对话,始于用户初始输入(通常是任务描述),
          终于智能体发起的完成动作、用户停止操作或错误触发。
        - 「子任务(subtask)」:智能体与用户或其他智能体之间的对话。
          若单个智能体即可完成任务,则任务与子任务合一;否则任务由多个子任务组成,每个子任务由独立智能体处理。

        参数:
            action (AgentDelegateAction):包含待启动委托智能体信息的动作对象
        """
        # 根据动作中指定的智能体名称,获取对应的智能体类
        agent_cls: Type[Agent] = Agent.get_cls(action.agent)
        # 获取智能体配置:优先使用动作指定的配置,未指定则复用当前智能体的配置
        agent_config = self.agent_configs.get(action.agent, self.agent.config)
        # 创建委托智能体实例(确保父子智能体共享LLM注册信息)
        # 注:父子智能体共享指标,实现全局指标累积
        delegate_agent = agent_cls(
            config=agent_config, llm_registry=self.agent.llm_registry
        )

        # 启动委托智能体前,创建初始状态(继承父智能体关键配置)
        state = State(
            session_id=self.id.removesuffix('-delegate'),  # 会话ID:移除父智能体的委托后缀
            user_id=self.user_id,  # 继承用户ID,保持用户关联
            inputs=action.inputs or {},  # 子任务输入参数(默认为空字典)
            iteration_flag=self.state.iteration_flag,  # 继承迭代控制标志(限制迭代次数)
            budget_flag=self.state.budget_flag,  # 继承预算控制标志(限制资源使用)
            delegate_level=self.state.delegate_level + 1,  # 委托层级+1(标识子智能体层级)
            metrics=self.state.metrics,  # 共享全局指标(父子智能体指标统一累积)
            start_id=self.event_stream.get_latest_event_id() + 1,  # 事件起始ID:从最新事件后开始记录
            parent_metrics_snapshot=self.state_tracker.get_metrics_snapshot(),  # 父智能体指标快照(用于后续对比)
            parent_iteration=self.state.iteration_flag.current_value,  # 父智能体当前迭代次数
        )
        # 输出调试日志:记录委托智能体启动信息
        self.log(
            'debug',
            f'start delegate, creating agent {delegate_agent.name}',
        )

        # 创建委托智能体的控制器(核心:标记is_delegate=True,避免直接订阅事件流)
        self.delegate = AgentController(
            sid=self.id + '-delegate',  # 会话ID:在父ID后添加委托后缀,唯一标识
            file_store=self.file_store,  # 继承文件存储对象(用于状态持久化)
            user_id=self.user_id,  # 继承用户ID
            agent=delegate_agent,  # 待管理的委托智能体实例
            event_stream=self.event_stream,  # 共享事件流(父子智能体事件互通)
            conversation_stats=self.conversation_stats,  # 继承对话统计信息
            iteration_delta=self._initial_max_iterations,  # 迭代次数增量(子任务的最大迭代限制)
            budget_per_task_delta=self._initial_max_budget_per_task,  # 单任务预算增量(子任务的资源限制)
            agent_to_llm_config=self.agent_to_llm_config,  # 继承LLM配置映射
            agent_configs=self.agent_configs,  # 继承智能体配置字典
            initial_state=state,  # 初始状态(继承父智能体配置后的状态)
            is_delegate=True,  # 标记为委托智能体(关键:避免重复订阅事件流)
            headless_mode=self.headless_mode,  # 继承无头模式(无交互界面)配置
            security_analyzer=self.security_analyzer,  # 继承安全分析器(用于安全校验)
        )
3.5.4 微代理记忆提示模板

openhands/microagent/prompts/generate_remember_prompt.j2 是一个 Jinja2 模板文件,其主要作用是生成用于更新特殊参考文件的提示语。这个特殊文件存储着重要的信息和学习成果,用于执行特定任务,并且可以在时间推移过程中扩展以纳入新知识和经验。

核心功能
  • 事件分析:分析提供给它的新事件子集
  • 更新决策:确定是否需要对特殊参考文件进行更新
  • 提示生成:生成指导另一个 AI 正确高效地进行这些更新的提示语
处理流程
  • 接收事件数据:通过 {{ events }} 变量接收待分析的事件
  • 内容分析:分析这些事件以确定需要更新文件的哪些部分
  • 生成更新指令:创建一个结构化的提示,包含具体的更新说明
指导原则

根据模板内容,生成的提示必须遵循以下准则:

内容要求

  • 清晰指定:明确指出文件的哪些部分需要更新或添加新章节
  • 提供上下文:解释基于新事件为什么需要这些更新
  • 具体信息:精确说明应添加或修改的信息内容

格式要求

  • 保持结构:维持文件现有的结构和格式
  • 保持一致性:确保更新与现有内容一致,不产生矛盾
技术实现

模板结构

jinja2 <update_prompt></update_prompt>

数据流

  • 输入:通过 events 变量传入的新事件数据
  • 处理:模板逻辑分析事件并生成更新提示
  • 输出:包含在 <update_prompt> 标签内的生成提示
应用场景

这个模板主要用于:

记忆维护

  • 在 AI 执行任务过程中,持续更新重要知识库
  • 确保系统能从新经验中学习并改进

自动化更新

  • 使 AI 能够自动维护自己的知识基础
  • 减少手动更新参考文件的需求
与其他组件的关系

与微代理系统集成

  • 作为 Microagent 系统的一部分,支持知识的动态更新
  • 与 KnowledgeMicroagent 和 RepoMicroagent 配合工作

与事件系统连接

  • 利用事件系统(EventStream)收集需要学习的事件
  • 与 AgentController 协同工作以维护状态和历史记录

这个模板是 OpenHands 系统中实现持续学习和知识管理的关键组件,允许 AI 系统从交互中学习并将这些知识持久化到参考文件中。

3.5.5 get_prompt 函数

get_prompt 函数是一个 FastAPI 路由处理器,位于 /openhands/server/routes/manage_conversations.py 文件中,路径为 /conversations/{conversation_id}/remember-prompt。其主要作用是基于特定事件生成一个提示模板,用于更新特殊参考文件。这个函数是 OpenHands 系统中实现持续学习和知识管理的关键组件之一,它使得 AI 系统能够从对话历史中的特定事件生成更新提示,从而维护和扩展其知识库。

参数处理
  • conversation_id:通过依赖注入验证的对话 ID
  • event_id:查询参数,指定要从中获取上下文文件的事件 ID
  • 其他依赖:用户设置存储、对话元数据等
核心处理步骤

获取事件存储

event_store = EventStore(
    sid=conversation_id, file_store=file_store, user_id=metadata.user_id
)

创建一个事件存储实例来访问特定对话的事件历史。

提取上下文事件

调用 _get_contextual_events(event_store, event_id) 方法:

  • 获取目标事件前后各 4 个事件(总共约 9 个事件)
  • 过滤掉无意义的事件类型(如 NullAction、NullObservation 等)
  • 返回格式化的事件字符串

生成提示模板

  • 从用户设置中加载 LLM 配置
  • 使用 generate_prompt_template 函数基于事件内容生成提示模板
  • 模板使用 generate_remember_prompt.j2 Jinja2 模板

生成最终提示

  • 通过 generate_prompt 函数调用 LLM 生成最终提示
  • 从 LLM 响应中提取 `` 标签之间的内容
与系统其他组件的关系

模板系统

  • 使用 generate_remember_prompt.j2 模板(位于 /openhands/microagent/prompts/ 目录)
  • 该模板专门用于生成更新特殊参考文件的提示

事件系统

  • 与 EventStore 和事件过滤系统集成 利用 EventFilter 来筛选相关事件
  • 与 conversation_manager 集成以请求 LLM 完成

应用场景

这个函数主要用于:

  • 记忆维护:生成用于更新 AI 记忆文件的提示
  • 知识积累:基于特定事件序列构建知识更新
  • 自动化学习:允许 AI 从交互中学习并更新其参考知识

返回值

返回一个 JSON 响应,包含:

  • status:请求状态(成功/失败)
  • prompt:生成的提示内容,可用于更新特殊参考文件

0xFF 参考

大模型总 “健忘”?Dify 记忆工程架构实践:让 AI 真正记住该记的事

Multi-Agent全面爆发!一文详解多智能体核心架构及LangGraph框架

年终总结:2025年 AI 产品及架构演进深度研究报告

AI智能体,第16章 协作模式:交接与指挥

深度拆解 Claude 的 Agent 架构:MCP + PTC、Skills 与 Subagents 的三维协同

性能,成本,可控性:从Sub-Agents到Multi-Agent的工程详细指南

本文使用 markdown.com.cn 排版