把 Agent 运行时做成插件系统:agent-harness(openJiuwen Rust版) 的实践

把 Agent 运行时做成插件系统:agent-harness 的 Rust 实践

一个 Agent 系统真正难的,不是把模型调用跑通,而是让模型、工具、会话、工作流、安全策略和外部基础设施能够被组合、替换、回滚,并且在失败时留下可解释的证据。

agent-harness 正是在解决这个问题:它用 Rust 构建一个插件化、可观测、可逆的 Agent Runtime,并通过契约、事件、服务注册和 Profile 组合能力支撑不同的 Agent 应用。

先说结论:它不是一个"更大的 Agent Loop"

很多 Agent 框架的核心抽象是一个循环:

text 复制代码
用户输入 → 模型 → 工具 → 工具结果 → 模型 → 最终回答

这个抽象适合做 Demo,但当系统进入真实生产环境后,问题会迅速增加:

  • 模型 provider 如何替换,而不修改 Agent Loop?
  • 工具调用前如何统一接入权限、路径、预算和安全策略?
  • 工作流、子代理、团队运行时如何共享会话和取消语义?
  • 插件挂载失败时,已经注册的服务和监听器如何回滚?
  • 开发环境可以使用 mock,但生产环境如何保证不会误用 mock?
  • 一个模型请求到底看到了什么,系统崩溃后能否重建?
  • "测试通过"究竟证明了实现、生产路径,还是仅仅证明了某个内存 mock?

agent-harness 的选择不是继续向核心循环里添加更多分支,而是把能力拆成可组合的插件,并让内核只负责机制,不负责业务。

openJiuwen、agent-core 与 agent-harness 的关系

理解这个项目,首先要区分三个名字:

  • openJiuwen:上游开源项目与产品生态名称,包含 Agent 能力、团队协作、工作流、工具和演进能力等更大的技术背景;
  • agent-core:openJiuwen 体系中的历史 Python Agent Runtime。它提供了原始的公开行为参考,包括输入输出、状态迁移、错误、取消、超时、恢复、持久化和外部协议语义;
  • agent-harness:本仓库中的 Rust 实现。它以插件化 Runtime 的方式,重新组织并实现 openJiuwen agent-core 的公开行为目标。

可以用下面的关系理解:

text 复制代码
openJiuwen
  └── agent-core(历史 Python 行为规格)
        ↓ 公开行为、fixture、协议和状态语义
  agent-harness(Rust 插件化实现)

因此,agent-harness 不是一个与 openJiuwen 无关的通用 Agent Demo,也不是把 Python 项目简单翻译成 Rust。它的目标是:在不依赖 Python Runtime 的前提下,使用 Rust 重新实现可验证的公开行为,并用更清晰的契约、插件和生命周期边界承载这些能力。

哪些内容属于迁移目标

迁移目标覆盖 openJiuwen agent-core 公开能力中的多个领域:

  • core:application、agent loop、workflow、controller、session;
  • harness:tools、rails、security、subagents、CLI;
  • agent teams:团队任务板、消息、调度和多 Agent 协作;
  • agent evolving:轨迹、评估、经验和优化;
  • RSI:数据集生成、执行、评估、提示精化和 checkpoint;
  • extensions:Redis、向量检索、消息队列、遥测、传输和远程 sandbox 等外围能力。

这些能力在 Rust 中不是集中放进一个巨大模块,而是分别落在 ah-contractsah-hub 和多个 ah-plugins-* crate 中。这样做是为了让每一项迁移都能单独定义契约、选择 provider、验证生产路径,并记录自己的完成状态。

哪些内容不属于兼容目标

项目明确排除了三类兼容:

  1. 不提供 openjiuwen.* 的 Python import 路径;
  2. 不复制 Python 的继承、metaclass、协程对象身份等对象模型;
  3. 产品实现、默认测试和生产 Profile 不依赖 Python。

Python agent-core 在本项目中是历史行为规格参考,而不是构建依赖或运行时后端。需要执行用户提交的 Python 代码时,那属于单独声明的 code 能力边界,不意味着整个 Runtime 依赖 Python。

这一区分很重要:项目追求的是行为层面的可验证重实现,不是 Python 包级别的 API 兼容。对于可以放在同一抽象层比较的能力,仓库使用 Rust contract fixture、golden fixture 和有限的 Python/Rust differential 进行验证;不能公平比较的 Python 宿主细节,则明确记录为不适用或未完成,而不是制造一个虚假的 parity 结论。

项目结构:四层,单向依赖

仓库的核心分层很简单:

图 1:ah-app 负责组装,插件负责能力,ah-hub 负责机制,ah-contracts 负责边界。
#mermaid-svg-saBkWnIMNaM6ysNH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-saBkWnIMNaM6ysNH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-saBkWnIMNaM6ysNH .error-icon{fill:#552222;}#mermaid-svg-saBkWnIMNaM6ysNH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-saBkWnIMNaM6ysNH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-saBkWnIMNaM6ysNH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-saBkWnIMNaM6ysNH .marker.cross{stroke:#333333;}#mermaid-svg-saBkWnIMNaM6ysNH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-saBkWnIMNaM6ysNH p{margin:0;}#mermaid-svg-saBkWnIMNaM6ysNH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-saBkWnIMNaM6ysNH .cluster-label text{fill:#333;}#mermaid-svg-saBkWnIMNaM6ysNH .cluster-label span{color:#333;}#mermaid-svg-saBkWnIMNaM6ysNH .cluster-label span p{background-color:transparent;}#mermaid-svg-saBkWnIMNaM6ysNH .label text,#mermaid-svg-saBkWnIMNaM6ysNH span{fill:#333;color:#333;}#mermaid-svg-saBkWnIMNaM6ysNH .node rect,#mermaid-svg-saBkWnIMNaM6ysNH .node circle,#mermaid-svg-saBkWnIMNaM6ysNH .node ellipse,#mermaid-svg-saBkWnIMNaM6ysNH .node polygon,#mermaid-svg-saBkWnIMNaM6ysNH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-saBkWnIMNaM6ysNH .rough-node .label text,#mermaid-svg-saBkWnIMNaM6ysNH .node .label text,#mermaid-svg-saBkWnIMNaM6ysNH .image-shape .label,#mermaid-svg-saBkWnIMNaM6ysNH .icon-shape .label{text-anchor:middle;}#mermaid-svg-saBkWnIMNaM6ysNH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-saBkWnIMNaM6ysNH .rough-node .label,#mermaid-svg-saBkWnIMNaM6ysNH .node .label,#mermaid-svg-saBkWnIMNaM6ysNH .image-shape .label,#mermaid-svg-saBkWnIMNaM6ysNH .icon-shape .label{text-align:center;}#mermaid-svg-saBkWnIMNaM6ysNH .node.clickable{cursor:pointer;}#mermaid-svg-saBkWnIMNaM6ysNH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-saBkWnIMNaM6ysNH .arrowheadPath{fill:#333333;}#mermaid-svg-saBkWnIMNaM6ysNH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-saBkWnIMNaM6ysNH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-saBkWnIMNaM6ysNH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saBkWnIMNaM6ysNH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-saBkWnIMNaM6ysNH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saBkWnIMNaM6ysNH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-saBkWnIMNaM6ysNH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-saBkWnIMNaM6ysNH .cluster text{fill:#333;}#mermaid-svg-saBkWnIMNaM6ysNH .cluster span{color:#333;}#mermaid-svg-saBkWnIMNaM6ysNH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-saBkWnIMNaM6ysNH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-saBkWnIMNaM6ysNH rect.text{fill:none;stroke-width:0;}#mermaid-svg-saBkWnIMNaM6ysNH .icon-shape,#mermaid-svg-saBkWnIMNaM6ysNH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saBkWnIMNaM6ysNH .icon-shape p,#mermaid-svg-saBkWnIMNaM6ysNH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-saBkWnIMNaM6ysNH .icon-shape .label rect,#mermaid-svg-saBkWnIMNaM6ysNH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saBkWnIMNaM6ysNH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-saBkWnIMNaM6ysNH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-saBkWnIMNaM6ysNH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ah-app

组装与启动
ah-hub

注册表、事件总线、插件、Profile
ah-plugins-*

模型、工具、会话、工作流等
ah-contracts

Seam、Event、纯类型

  • ah-contracts:只定义 Seam trait、事件、ServiceKey 和纯数据类型,不放 provider 实现。
  • ah-hub:提供 ServiceRegistryEventBusPluginEffect、Profile 和依赖拓扑挂载。
  • ah-plugins-*:实现模型、工具、工作流、会话、记忆、团队、遥测、传输等能力。
  • ah-app:读取 Profile,解析插件目录,挂载插件,再通过 Seam 调用服务。

依赖方向是硬规则:

text 复制代码
ah-app → ah-hub → ah-contracts
ah-plugins-* → ah-hub + ah-contracts

除了组装层 ah-app,生产插件不能直接依赖另一个插件的具体类型。插件之间只能通过 ah-contracts 中声明的契约协作。

这条规则看起来严格,但它解决了一个长期问题:当一个 provider 被替换时,consumer 不应该因为 import 路径变化而一起修改。

Seam:把一个能力拆成三角形

项目把可替换能力称为 Seam。一个完整 Seam 不是一个 trait,而是三个部分:

部分 含义 所在位置
Service Definition 能力契约,例如 ModelProvider ah-contracts
Service Provider 契约的具体实现 ah-plugins-*
Consumer 通过契约使用能力的组件 任意插件或应用

例如,模型能力不要求 Agent Loop 知道 OpenAI、Anthropic 或 mock provider 的具体类型。Agent Loop 只查找稳定的服务键,并请求 dyn ModelProvider

rust 复制代码
let model = ctx
    .service::<dyn ModelProvider>(&LLM)
    .ok_or(AppError::MissingService(LLM))?;

let response = model.chat(request).await?;

Provider 则在插件挂载时注册自己:

rust 复制代码
impl Plugin for MyModelPlugin {
    fn name(&self) -> &'static str {
        "ah-plugins-my-model"
    }

    fn provides(&self) -> Vec<ServiceKey> {
        vec![LLM]
    }

    fn inject(&self) -> Vec<ServiceKey> {
        vec![CREDENTIALS]
    }

    fn apply(&self, ctx: &Context) -> Result<Vec<Effect>, PluginError> {
        let provider: Arc<dyn ModelProvider> =
            Arc::new(MyModelProvider::new(/* config */));

        Ok(vec![ctx.register(LLM, provider)])
    }
}

这里有三个重要约束:

  1. Provider 明确声明自己提供哪些服务。
  2. Provider 明确声明自己依赖哪些服务。
  3. apply 返回 Effect,而不是悄悄修改全局状态。

因此,插件的依赖关系可以被检查、排序和诊断,而不必依赖一长串手写的启动顺序。

无特权内核:核心只提供机制

ah-hub 不内置模型、工具、会话或 Agent Loop。它只提供组合能力:

  • 服务注册和查找;
  • 事件监听和分发;
  • 插件挂载;
  • 依赖拓扑排序;
  • 重复 provider、缺失依赖和循环依赖检测;
  • 可逆注册;
  • Profile 解析。

Context::mount_all 的语义大致是:

  1. 收集每个插件的 provides,拒绝重复 provider;
  2. 根据 injectprovides 建立依赖图;
  3. 拓扑排序,依赖先于消费者挂载;
  4. 检测循环依赖和缺失依赖;
  5. 按顺序执行 apply
  6. 任一插件失败时,释放此前已经产生的 Effect

这样做的直接收益是:失败不会留下半挂载的 Context。

图 2:插件从 Profile 进入 Context,退出时由 Effect 统一回滚。

从零跑通一次开发流程

下面是一条不需要云端模型凭据的本地路径:

sh 复制代码
cargo build --workspace
cargo test --workspace
cargo run --offline -p ah-app --bin ah-app -- profiles/dev.toml
cargo run -p ah-code-cli -- --workspace .

dev.toml 使用确定性的 mock LLM,但工具链、会话日志、工作流、记忆和安全 rail 仍然走真实插件。它适合验证挂载、解析、调用、事件广播和终端渲染,不代表生产 provider 已验证。

用一个最小插件接入新的 provider

新增 provider 时,核心工作不是修改 Agent Loop,而是实现一个 Seam provider:

rust 复制代码
impl Plugin for MyProviderPlugin {
    fn name(&self) -> &'static str { "ah-plugins-my-provider" }
    fn provides(&self) -> Vec<ServiceKey> { vec![LLM] }
    fn inject(&self) -> Vec<ServiceKey> { vec![CREDENTIALS] }

    fn apply(&self, ctx: &Context) -> Result<Vec<Effect>, PluginError> {
        let provider: Arc<dyn ModelProvider> =
            Arc::new(MyProvider::from_credentials(ctx)?);
        Ok(vec![ctx.register(LLM, provider)])
    }
}

接线步骤:在 ah-contracts 定义或复用 trait 和 ServiceKey,在独立的 ah-plugins-* crate 实现 provider,在 ah-app catalog 注册名称,在 Profile 中加入插件,最后补齐 mount、resolve、invoke、unmount 和失败路径测试。

生产模式与开发模式

真实 provider 使用 env 文件显式提供配置:

sh 复制代码
OPENAI_API_KEY=replace-me
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
REDIS_URL=redis://127.0.0.1:6379/
sh 复制代码
AH_ENV_FILE=/secure/path/agent-harness.env \
  cargo run --offline -p ah-app --bin ah-app -- profiles/prod.toml

真实 provider 请求失败时会显式报错,不会回退到 mock。不要把真实密钥提交到仓库;缺失 env 文件时启动也应失败。

观察会话事件与拦截工具调用

只观察事件时使用 emit

rust 复制代码
let session_events = ctx.on::<SessionEvent>(|event| {
    println!("session event: {event:?}");
});

run_application().await?;
drop(session_events);

工具执行前需要安全决策时使用 waterfall;允许继续必须调用 next()

rust 复制代码
let guard = ctx.on_waterfall::<ToolDecision, ToolDecision, _, _>(
    |_event, decision, next| async move {
        if decision.path_escapes_workspace() {
            ToolDecision::Deny
        } else {
            next.next(decision).await
        }
    },
);

这几个例子体现了项目的核心生命周期规则:观察、决策和资源注册都必须可追踪、可撤销。

Effect:所有注册都必须可逆

在项目中,服务注册和事件监听都返回一个 RAII guard:Effect

rust 复制代码
let service_effect = ctx.register(LLM, provider);
let listener_effect = ctx.on::<AgentStep>(|event| {
    record(event);
});

// Effect 存活期间注册有效。
// 离开作用域或显式 drop 后,注册被撤销。
drop(listener_effect);
drop(service_effect);

这使插件卸载变成普通的资源生命周期管理,而不是额外设计一套"反注册 API"。服务、监听器以及插件创建的后台资源,都应该和插件生命周期绑定。

这里有一个容易踩的 Rust 陷阱:

rust 复制代码
let _ = ctx.register(LLM, provider);

这段代码会立即丢弃 Effect,注册也会立即回滚。需要使用具名绑定持有 guard:

rust 复制代码
let _effect = ctx.register(LLM, provider);

可逆注册的价值不仅在正常关闭流程。它还保证了批量挂载的原子性:如果后续插件 apply 失败,之前的注册可以通过 Effect 统一撤销。

类型化事件:四种分发语义,而不是一个 EventBus 方法

Agent 系统中的事件并不只有一种语义。ah-hub 明确区分四种分发模式:

模式 语义 适用场景
emit 同步、按注册顺序通知 日志、观察、轻量遥测
serial 异步、按顺序逐个等待 审计链、顺序副作用
parallel 异步并发执行全部监听器 扇出通知、独立遥测
waterfall 通过 next() 形成异步委托链 rail、审批、策略决策

普通事件监听器同样返回 Effect

rust 复制代码
let effect = ctx.on::<SessionEvent>(|event| {
    println!("{event:?}");
});

最有意思的是 waterfall

rust 复制代码
let effect = ctx.on_waterfall::<ToolDecision, ToolDecision, _, _>(
    |_event, decision, next| async move {
        if should_deny(&decision) {
            return ToolDecision::Deny;
        }

        next.next(decision).await
    },
);

它不是简单的广播。监听器调用 next(),才会把结果交给下游;不调用 next() 就代表短路。于是工具执行前可以形成这样的决策链:

text 复制代码
工具请求
  → PathGuard
  → ShellGuard
  → ToolBudget
  → PermissionApproval
  → SandboxPolicy
  → 真正执行

其中任意一层都可以拒绝或改写决策,而且消费方不需要 import 每个策略插件的具体类型。

当前契约级事件包括 agent/stepsession/eventtools/pre-executetools/post-executeteams/swarmteams/task。事件目录同时记录生产者、消费者和分发模式,避免事件变成没有所有权的全局广播。

Profile:用组合配置选择环境

插件并不在代码里硬编码一套固定组合,而是通过 Profile 组织:

toml 复制代码
name = "dev"

[[bundles]]
id = "mock"
plugins = ["ah-plugins-mock"]

启动过程是:

text 复制代码
Profile
  → ah-app plugin catalog
  → 解析插件名称
  → 依赖拓扑排序
  → Context::mount_all
  → 解析 Seam
  → 调用 ApplicationRuntime

开发 Profile 可以使用确定性的 mock provider;生产 Profile 必须通过 mock gate,不能包含 ah-plugins-mock

更重要的是,Profile 只决定组合,不改变 consumer 的代码。开发环境和生产环境可以使用不同 provider,但 Agent Loop 仍然只依赖 ModelProvider

项目也明确禁止静默 fallback:真实 provider 失败时必须显式返回错误,不能悄悄切换到本地 mock,再把这次运行报告成生产验证。

日志即真相:模型看到什么,日志就记录什么

会话系统采用 append-only JSONL 事件日志。基本原则是:

任何到达模型请求的输入,都必须能够从会话事件日志重建。

这意味着用户输入、助手消息、工具调用、工具结果、Agent step 和系统事件不是散落在多个内存对象里,而是有顺序、有序列号的会话事件。

它带来几个实际能力:

  • 会话历史可以回放;
  • 崩溃后可以恢复;
  • checkpoint、fork、restore 有明确的持久化边界;
  • CLI 可以在事件落盘后实时渲染,而不必等整轮 Agent 执行结束;
  • 工具恢复可以根据稳定的 tool_call_id 判断是否允许重新执行。

这也是为什么项目不把日志只当作 observability 输出。对于 Agent Runtime,日志本身是状态的一部分。

一个可运行的例子:ah-code-cli

仓库中的 example/ah-code-cli 是 Claude Code 风格的开发工具示例,使用主项目的真实插件链:

  • Agent Loop;
  • ToolRegistry;
  • 受限文件系统和 Shell;
  • Session JSONL;
  • Todo 工具;
  • 流式终端渲染;
  • 工具权限确认;
  • Mock/OpenAI-compatible Provider。

启动:

sh 复制代码
cargo run -p ah-code-cli -- --workspace .

无交互冒烟:

sh 复制代码
cargo run -p ah-code-cli -- \
  --workspace . \
  --once "inspect this workspace"

典型输出结构类似:

text 复制代码
● LS(.)
  ⎿  Found 2 entries
● mock final answer ...
↳ completed · iterations=1 · tools=1

这里的终端输出不是直接打印原始 JSON,而是消费结构化会话事件。模型流式增量、工具调用和工具结果可以实时渲染;内部记账事件不会被渲染成噪声行。

权限也不是简单的命令白名单。工作区内的读、写、编辑和查找可以直接放行,run_shell 进入 ask 层,通过 permission-approval Seam 请求用户确认。管道、CI 和 --once 等非交互宿主无法询问用户时,会显式拒绝,而不是静默放行。

工程验证:测试通过不等于完成

这个项目最值得借鉴的地方,不是插件数量,而是它对"完成"的定义比较严格。

项目区分至少三类证据:

  1. Implementation:代码是否存在,接口是否能调用;
  2. Production verification:真实 Profile、真实协议和真实外部依赖是否跑过;
  3. Parity:与历史行为规格的差异是否经过契约或差分验证。

因此,下面这些结论不能混为一谈:

  • mock provider 能启动;
  • Rust 单元测试通过;
  • production profile 使用真实 Redis 和模型 provider 执行;
  • 与历史行为规格的差分契约通过。

CI 中对应有多层门禁:

  • cargo fmt --all --check
  • workspace Clippy,warnings 视为错误;
  • workspace 测试;
  • workspace line coverage 不低于 80%;
  • production profile mock gate;
  • 插件生产依赖隔离测试;
  • audit ledger 生成结果的新鲜度检查;
  • 独立的 LLM Rails differential job;
  • Redis 和真实协议参与的 production smoke。

项目还禁止在生产路径中使用 todo!()unimplemented!() 或静默 fallback。无法支持的能力必须显式返回错误,并在能力账本中标为未完成或不适用。

当前规模与边界

按当前工作树实测:

  • cargo metadata --no-deps:114 个 workspace package;
  • 其中 109 个主仓库 ah-plugins-* package,另有 1 个示例插件;
  • crates/example/ 中约 125,469 行 Rust 源码,包含测试;
  • 源码中有约 1,510 处 #[test]#[tokio::test] 标注;
  • git 历史自 2026-08-14 起已有 320 次提交。

这些数字只描述规模,不代表行为对等完成度。项目文档也明确区分了工作包账本和能力地图:审计账本中的工作包完成,不等于所有细粒度能力都已经具备完整的历史行为对等。

当前正式排除的范围包括:

  • openjiuwen.* Python import 路径兼容;
  • Python 对象模型兼容;
  • 产品运行时依赖 Python;
  • 运行中的热插拔和动态 ABI。

仍需要谨慎解读的部分包括:Agent Loop 的部分长尾语义、复杂 workflow 行为、部分外部 provider 的真实部署验证,以及厂商特定能力的 live smoke。当前审计记录中,DashScope 原生 embedding/rerank 的 live smoke 被明确豁免,不能写成已验证的真实厂商结果。

为什么选择这种设计

可以把 agent-harness 的设计原则压缩成三句话:

1. 核心不拥有业务,业务通过插件出现

模型、工具、会话、团队和遥测都不是内核特权。内核越小,组合和替换越容易审计。

2. 默认可逆,而不是事后补偿

服务注册、事件监听、插件挂载和资源释放都围绕 Effect 建模。失败路径不是异常情况,而是正常生命周期的一部分。

3. 默认可观察,而不是事后猜测

类型化事件、append-only 会话日志、结构化错误、版本化 checkpoint 和审计 ledger,让系统能够解释"发生了什么"。

这三点比单纯增加一个模型 provider 或一个工具更基础,但也更决定一个 Agent Runtime 能否长期演进。

快速开始

开发环境可以直接使用 mock LLM:

sh 复制代码
cargo build --workspace
cargo test --workspace
cargo run --offline -p ah-app --bin ah-app -- profiles/dev.toml

运行示例 CLI:

sh 复制代码
cargo run -p ah-code-cli -- --workspace .

生产 Profile 需要按照项目的 env 文件规则配置 OpenAI-compatible provider、Redis 以及相应外部服务。缺失配置时,启动应该失败并给出明确错误,而不是自动降级到 mock。

结语

Agent 工程的下一个阶段,不只是让模型"会调用工具",而是让整个运行时具备可替换、可恢复、可验证和可审计的结构。

agent-harness 的答案是:

  • 用 Seam 隔离能力边界;
  • 用插件实现 provider 和 consumer;
  • 用类型化事件表达扩展点;
  • Effect 保证注册可逆;
  • 用 Profile 组合环境;
  • 用 append-only 日志保存模型可见状态;
  • 用 production smoke、contract fixture 和审计账本区分"实现了"与"证明了"。

这套设计不承诺所有能力已经完成,也不把一次 mock 测试包装成生产可用。它更像一套面向长期演进的工程约束:先把边界、生命周期和证据体系建立起来,再让 Agent 能力持续增长。

相关推荐
Brilliantwxx1 小时前
【STM32】 I2C 外设内部硬件结构和状态位
开发语言·stm32·单片机·嵌入式硬件
小七在进步1 小时前
类和对象(二)
java·开发语言
CHHH_HHH1 小时前
【Linux系统篇】进程间通信揭秘:从管道到共享内存
linux·服务器·c语言·开发语言·后端·ubuntu
写后端的胖头鱼1 小时前
【高频面试题】面试题里常出现的Bitmap是什么(附源码和多种场景)
java·开发语言·面试·位图·bitmap
学习智者1 小时前
《玄》IDE v3.8.2重磅发布:数据外置+全链路优化
开发语言·c++·ide·中文语言 玄
mldong1 小时前
Node 开发者也有自己的轻量工作流引擎了:npm i 一行,5 分钟跑通一条审批流
javascript·后端·typescript
考虑考虑9 小时前
cmd局部设置java变量
运维·后端·自动化运维
2601_9669496510 小时前
为什么量化策略需要大量历史股票数据?从回测可信度理解数据规模
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash