OpenHuman 工作流设计深潜:tinyflows 节点 I/O 信封契约与可选 Agent Kind(agent_ref)
2026/9/10 5:30:00 网站建设 项目流程

OpenHuman 工作流设计深潜:tinyflows 节点 I/O 信封契约与可选 Agent Kind(agent_ref)

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

本文以 OpenHuman 仓库中的规划文档 tinyflows — Node I/O Alignment & Selectable Agent Kinds 为核心,完整拆解该计划的两条主线:Part A 为 tinyflows 引擎的能力节点(agent/tool_call/http_request/code)引入稳定的输出信封(envelope),消除节点间"碰运气式"的数据形状对齐;Part B 让工作流中的agent节点可以通过agent_ref选择一种已注册的 Agent Kind(coding agent、researcher、crypto agent……),使其携带各自的工具集、模型提示、沙箱与迭代策略参与执行。读完本文,你可以掌握该设计的问题根因、信封契约的字段语义、per-item 执行与端口感知路由的原理,以及AgentRunner能力在 OpenHuman 宿主侧的实际实现路径(含超时、并发、HITL 审批等关键细节)。

该文档状态为proposed(2026-07-05),是 tinyflows 集成计划 的扩展。需要说明的是:截至本文撰写时的仓库快照,Part B 的大部分设计已在源码中落地(agent_ref路由、AgentRunner宿主实现、builder 工具与 UI 选择器均存在),下文会在对应位置用源码证据对照说明"计划设计"与"当前实现"的关系。

一、问题背景:同一个根因引出的两类缺陷

审计发现了两个问题,它们共享同一个根因——节点没有声明式的 I/O 契约:上游节点吐什么、下游节点读什么,只是"恰好对齐"。

  1. Shape drift(形状漂移)。每个能力节点都把宿主能力的原始返回值原样包进Item.json(计划文档引用的审计位置:agent.rs:115tool_call.rs:31http_request.rs:24)。agent节点还会随子端口(sub-port)不同而吐出三种不同形状,且其普通输出在运行时会在"解析后的模型 JSON"与{text}之间来回翻转(caps.rs:359,369)。下游的=item.<field>表达式只能靠猜。
  2. No agent identity(没有 Agent 身份)agent节点只是一次裸的provider.chat调用,附带一个自由格式的 inlinetools列表(caps.rsOpenHumanLlm::complete,注释明确写着"no agent loop is driven here")。没有办法说"把这一步作为codingagent 跑"或"作为researcher跑"——尽管 OpenHuman 已经内置了一个大规模 agent 注册表:每个 agent(researchercode_executorcrypto_agent……)都在自己的agent.toml中声明了专属工具集、模型提示、沙箱模式与迭代策略。仓库中当前存在 29 个内置 agent 目录(计划文档称"35-agent registry",口径差异可能来自 harness 定义的计数方式),例如 code_executor/agent.toml 与 researcher/agent.toml。

Part A 修复契约,Part B 在既有注册表之上构建可选 Agent Kind。两者的排序经过刻意设计:A1(agent 信封)先落地,B(agent kinds 的输出)随后写入同一个信封。

二、Part A:节点 I/O 对齐

A0. 设计原则:归一化的 item 信封

为能力节点采纳一个小型、稳定的输出信封,使每个下游表达式都能拥有可保证的访问器,与 provider 或配置无关:

// agent / tool_call / http_request / code 发出的 item 形状: { "json": <结构化 payload | null>, // 存在时的解析/结构化结果 "text": <string | null>, // 存在时的人类可读文本 "raw": <provider 原生值>, // 逃生舱:未动过的能力原始返回 "error": <null | { message, ... }> // 仅出现在 continue/route 错误项上 }

规则明确:=item.text永远可解析(或显式为null);=item.json.<field>是结构化路径;=item.raw为需要 provider 原始 blob 的人保留现有行为。这是纯增量的——raw正是节点今天输出的内容——因此迁移是机械的。

计划还强调保持 cratehost-agnostic(宿主无关):信封定义在vendor/tinyflowssrc/data.rs/ 节点执行器)中;宿主适配器(caps.rs)本就产出{text}/ 解析后 JSON,可直接映射到json/text

A1. 归一化agent节点输出——杠杆最高,最先做

  • Crate 侧vendor/tinyflows/src/nodes/integration/agent.rs:115):用信封包裹 completion,而不是Item::new(value)。若output_parser跑过,强制转换后的值放进json;completion 文本(如存在)放进text;未动过的响应放进raw;模型自行选中的工具结果保留在json.tool_result镜像到稳定的tool_result访问器(见 A2)。
  • 宿主侧src/openhuman/flows/tinyflows/caps.rsOpenHumanLlm::complete):返回{ json: <解析后或 null>, text: <response.text>, raw: <完整响应> },而不是"要么裸解析对象、要么{text}兜底"。这消除了运行时形状翻转(审计 M1)。
  • 测试:更新agent.rs单元测试 +caps.rsseam 测试;新增 e2e,断言=item.text在 JSON 输出模型与纯文本输出模型上都能解析(mock 两者)。

对照当前仓库实现,这条线已经落地:宿主侧 agent.rs 的build_agent_result函数(L330–L349)会把 harness turn 的最终文本整形为节点输出值——当节点请求结构化输出且文本可解析为 JSON 时返回解析后的对象,否则返回{ text, agent_ref };其文档注释明确写道:"vendoragent节点随后将其折叠进稳定的{ json, text, raw }信封,output_parser子端口仍然生效"。这正是 A1 所定义的折叠行为。

A2. 统一 inline 工具与tool_call节点的结果形状(审计 M2)

让工具结果无论是在agent节点内联执行、还是作为独立tool_call节点执行,都可在同一路径到达。标准统一为信封:tool_call节点发出{ json: <工具输出>, raw: <composio 信封> };agent 的内联工具结果落在item.json.tool_result,节点信封的raw保留完整 completion。并文档化唯一的规范访问器

A3. 集成节点的 per-item 执行(审计 M3——静默丢数据陷阱)

现状是agent/tool_call/http_request永远只发一个 item,且配置只针对input.first()解析,因此split_out (N) → tool_call只触发一次,丢掉 N−1 个 item。计划方案:

  • 新增节点配置旗标execution: "once" | "per_item"tool_call/http_request默认per_itemagent默认once,因为 agent turn 通常是批次级别的——但允许per_item)。
  • per_item模式下:对ctx.input映射执行器,逐 item 重新解析配置(使=item.x指向当前item),每个输入项发出一个输出项,并携带paired_item血缘(vendor/tinyflows/src/data.rs)。
  • 涉及agent.rstool_call.rshttp_request.rs以及nodes/mod.rs的 per-item 解析路径。
  • 测试split_out → tool_call跑 N 次;paired_item血缘保留;once模式行为不变。

当前仓库源码印证了 per-item 模式的实际代价与防护:agent.rs 的模块头注释(L54–L61)指出,per-item 执行下引擎对每个输入项调用一次run_agent,全量 harness turn(含记忆注入)会按 item 扇出——引擎获得有界 per-item 并发后,这些调用还会同时到达,因此宿主侧设置了HARNESS_AGENT_SLOTS信号量作为进程级天花板。

A4. 端口感知的collect_input(审计 M4——未走分支泄漏 + BUG-3/BUG-4)

collect_inputvendor/tinyflows/src/engine.rs:171)会拼接所有前驱槽的 item,完全无视Edge.to_port(存了但从不读)以及前驱实际从哪个端口发出。结果是condition之后的节点会读到未走分支的 item。方案:

  • 读取Edge.to_port/ 前驱记录的port,只收集前驱实际从连接端口发出的 item。
  • 支持命名 merge 输入(input A 对 B),并消除泄漏。
  • 顺带收编审计中的 merge-barrier 缺口(BUG-4)与混合端口扇出丢失(BUG-3)——同属路由/lowering 面(engine.rs:422789-826)。
  • 测试:接在 true 端口上的节点看不到 condition-false 槽位;由分支前驱喂入的 merge 正确 barrier;main→a, main→b, error→h的形状能同时跑ab

A5.merge模式(审计 M5)

新增merge.modeappend(今天的 concat)、combine_by_key(按键字段 join item)、combine_by_position(zip)。仅在vendor/tinyflows/src/nodes/control_flow/merge.rs做配置层面的变更,barrier 语义不变。

A6. 创作期对齐 lint(供 Part C 工具化)

一个校验路径:给定生产者节点已知/声明的输出信封,把下游引用了生产者不可能发出=item.<field>标记出来——以结构化、带节点地址的诊断信息呈现。它依赖于收紧validate.rs(审计 BUG-10),并接入面向 agent 的validate/revise_workflow工具,让 builder agent 直接拿到反馈。

A-bugs:随上述一起打包(审计 §3)

编号问题修复位置
BUG-1(安全,立即热修)jqenv内建函数泄漏宿主环境变量关闭jaq-std默认特性 / 在vendor/tinyflows/src/expr.rs:302过滤env/input*
BUG-2switch/transform拿不到nodes作用域使用expr_scopeswitch.rs:26transform.rs:27
BUG-5/6子工作流 HITL 被丢弃;失败时on_run_finish从不触发对照FlowRunObserver验证
BUG-9code/output_parser/sub_workflow跳过=解析统一表达式绑定

三、Part B:可选 Agent Kind

B0. 核心想法

agent节点通过引用 OpenHuman 注册表 agent 来声明运行哪个 agent

{ "kind": "agent", "config": { "agent_ref": "code_executor", // 或 "researcher"、"crypto_agent"…… "prompt": "=item.text", "connection_ref": "…", // 仍由宿主解析,绝不来自模型输出 // 可选的逐节点覆盖: "model": "…", "max_iterations": 6, "tools_allow": ["grep", "edit"], }, }

agent_ref存在时,宿主运行那个注册 agent——携带它自己的精选工具集、模型提示、沙箱模式与迭代策略——作为一个完整的多轮 agent loop,而不是当前的单次provider.chat调用。编码步骤拿到编码工具;研究步骤拿到web_search/web_fetch;加密步骤拿到行情工具。这正是注册表现有的契约(src/openhuman/agent/registry/agents/*/agent.toml)。

B1. Crate 缝——新增AgentRunner能力(宿主无关)

crate 不能知道 OpenHuman 注册表的存在;且"把一个具名 agent 跑到完成(多轮、用工具)"是不同于LlmProvider.complete(单发)的另一种能力。计划在vendor/tinyflows/src/caps/mod.rs新增 trait:

#[async_trait] pub trait AgentRunner: Send + Sync { /// Run a host-registered agent identified by `agent_ref` to completion. /// `request` carries prompt/input/overrides; `conn` is the opaque credential. async fn run_agent(&self, agent_ref: &str, request: Value, conn: Option<&str>) -> Result<Value>; }
  • Capabilities添加agent: Option<Arc<dyn AgentRunner>>(可选,使没有注册表的宿主继续工作)。
  • agent.rs分发config.agent_ref存在caps.agent已接线 →run_agent(...);否则回落到今天的LlmProvider.complete路径。两条路径都发 A1 信封,因此下游表达式完全一致。
  • caps/mock.rs提供 mock 实现(echoagent_ref+ request),使 crate 测试覆盖两条路径。
  • agent_ref只来自受信配置,绝不取自模型输出(与tool_call.connection_ref同一条规则,agent.rs:71-79)。

这一设计判断被仓库源码验证为正确方向:agent.rs 的OpenHumanAgentRunner正是该 trait 的宿主实现(use tinyflows::caps::*;导入AgentRunner,L351–L352 处impl AgentRunner for OpenHumanAgentRunner),其文档注释开宗明义:"agent_ref从受信节点配置解析(绝不来自模型输出),因此提示注入的 completion 无法挑选任意 agent kind"(L50–L52)。

B2. 宿主适配器——基于注册表 + delegate 运行时实现AgentRunner

计划要求(对应src/openhuman/flows/tinyflows/caps.rs):

  1. agent_registry::ops::get_agent(agent_ref)解析条目(tools、模型提示、sandbox、max_iterationsiteration_policy)。仓库中该操作现位于 registry/ops.rs 的pub async fn get_agent(id: &str) -> Result<Option<AgentRegistryEntry>, String>(L28),同文件还有list_agents(include_disabled)(L20)。
  2. 应用可选逐节点覆盖(modelmax_iterations,以及只做收窄tools_allow——节点可以子集化agent 的工具,永远不能添加)。
  3. 用 flow run 的TurnOrigin驱动既有的 delegate 运行时(src/openhuman/agent/tools/delegate.rssrc/openhuman/agent/turn_origin.rs已有 flow origin 变体)。
  4. 返回{ json, text, raw }(A1 信封):最终结构化输出进json,最终消息文本进text

自治/安全:agent kind 的工具仍受SecurityPolicy/ 自治层级门控;sandboxedagent(如code_executor)沙箱运行;flow 自身的审批门仍拦截外发动作。不引入新特权路径。深度/成本护栏:flow 内跑完整 agent loop 的 agent 节点会扇出成本——需要限界(复用MAX_SUB_WORKFLOW_DEPTH风格的计数器或 per-run agent 调用上限),并把取消 token 穿进 delegate 运行(今天的子工作流路径丢弃 token——BUG-5——在此一并修复)。

当前仓库实现比计划更进一步,agent.rs 揭示了完整的路由与防护细节:

  • 三级路由route_for_agent_ref,L129–L139):agent_ref若命中 harness 的AgentDefinitionRegistryHarness 路径Agent::from_config_for_agent+run_single跑完整工具循环,定义中的ToolScope/sandbox_mode/max_iterations直接生效);否则查自定义注册表(find_custom_in_config),已知且启用的 custom entry 由工厂合成一个真实的AgentDefinition再走 harness(保留其tool_allowlist/model);两者皆不中才落到RegistryFallback——仅人设整形的单次 completion(把条目的system_prompt前置为 system 消息、采纳其model,然后走OpenHumanLlm::complete)。未知或 disabled 的agent_ref会返回明确错误而非静默跳过。
  • 模型优先级resolve_node_model,L244–L257):节点config.model(管理 tier 如reasoning-v1hint:*别名)> 注册表entry.model> 无覆盖(harness 定义/角色默认)。harness_model_default_override(L273–L278)把 tier 归一化为hint:<role>以便会话构建器路由;raw/BYOK 模型 id(如claude-opus-4)则原样转发,避免被折叠到chat-v1tier。
  • 超时治理(L144–L199):clamp_run_timeout_secs把节点请求的timeout_secs钳制到10..=600,缺省240秒;scale_timeout_for_iteration_cap对迭代上限超过全局旧默认 10 的 agent(如extended策略下可达 50 次迭代的code_executor/tools_agent)按cap × 12s(上限 600s)抬升默认超时;resolve_run_timeout_secs保证显式设置的timeout_secs是作者有意选定的 fast-fail/SLA 边界,绝不被抬升。
  • 并发天花板(L73–L114):进程级信号量HARNESS_AGENT_SLOTS,默认 8 个并发 harness agent turn,可用环境变量OPENHUMAN_FLOWS_MAX_PARALLEL_AGENTS覆盖(非法或 0 值回落到默认,避免零 permit 死锁)。引擎 per-nodeconcurrency只约束单节点扇出;这个宿主级天花板是"承重"的那一个——一个图可以对 200 项数组开concurrency: "all",多个节点/多次 run 还能同时扇出。等待 permit不算错误:过宽的扇出被节流到该宽度,工作流仍会完成,只是更慢。
  • 嵌套 HITL 升级(L582–L620):引擎 future 运行在 flow 的Workfloworigin 下,但 flow 作者只预声明了agent_ref,没有预声明 harness LLM 会从定义ToolScope中挑哪些具体工具。若让内层 turn 继承Workflow { require_approval: false },审批门会视其为信任根并对external_effect工具自动放行(如 Slack/邮件/桌面控制)。因此run_single周围强制require_approval: true,使外效工具像 flow acting 节点一样停靠等待真实的人工决策;只读工具不受影响,不产生审批噪声。取消则通过 run_registry token 中止引擎 future,内层 turn 随之丢弃。
  • 同步边界(L481–L492):flowagent节点无绑定聊天线程,若其中运行的 agent 是 delegating agent 并直接调用spawn_async_subagent,该工具会因无parent_thread_id而拒绝,而非静默丢弃后台结果——需要并行时应建模为并行的 flow 节点(引擎已支持扇出)。

B3. 创作面——为 builder agent 落地选择

  • Builder 工具list_agent_profilessrc/openhuman/flows/builder_tools.rs),由agent_registry::ops::list_agents(false)支撑,返回{ id, display_name, when_to_use, tools, sandbox_mode }。镜像既有search_tool_catalog模式,使workflow_builder子 agent 挑选真实存在agent_ref,而不是幻觉一个出来。仓库中该工具已就位:workflow_builder 的 tool_wording_tests.rs 甚至专门守护工具描述文案,断言其必须把agent_ref描述为"跑一个 specialist 的完整工具循环",且不得携带过时的措辞。
  • 校验vendor/tinyflows/src/validate.rs+ 宿主):带无法解析agent_refagent节点应产生结构化校验错误(需要宿主把已知 agent 集合传入校验,或在flows::ops::validate中宿主侧校验)。
  • workflow_builder 提示词src/openhuman/agent/registry/agents/workflow_builder/prompt.md):文档化agent_ref及何时应优先选专用 agent 而非裸 completion。

B4. UI——Agent Kind 选择器

(即将到来的)节点配置面板(集成计划中的U2)为agent节点提供Agent kind下拉框,数据来自agent_registrylist_agentsRPC(已存在),展示display_name+when_to_use,并提供可选的工具子集多选;只读画布则把所选 agent kind 显示为节点徽标。当前仓库中该 UI 已落地为 AgentNodeInspector.tsx:组件头注释写明agent_ref是"哪个已注册agent 运行此节点",且"agent 节点中命名了 harnessAgentDefinitionagent_ref走 Phase A 路由";选择器提供 "inherit" 默认项 + 全部注册 agent(L54–L62)。

四、Part C:阶段排序与归属

计划将工作切分为 8 个阶段,每个阶段随附测试(crate 单测 + 宿主 seam + JSON-RPC E2E),遵循仓库"先测后层"(tests before the next layer)规则,并把设计决策记录到vendor/tinyflows/local/docs/11-decisions.md

阶段工作依赖面(Surface)
C0BUG-1 env 泄漏热修;BUG-2 switch/transform 作用域vendor/tinyflows/src/expr.rscontrol_flow/*
C1A0 信封 +A1 agent 输出归一化C0agent.rscaps.rs
C2A2 工具形状统一;A3 per-item 执行C1tool_call.rshttp_request.rsnodes/mod.rs
C3A4 端口感知collect_input(+ BUG-3/4);A5 merge 模式—(与 C1/C2 并行)engine.rsmerge.rs
C4B1AgentRunner能力+ mockC1(共享信封)caps/mod.rscaps/mock.rsagent.rs
C5B2 宿主OpenHumanAgentRunner(基于 registry+delegate,+ BUG-5 取消)C4tinyflows/caps.rsagent/tools/delegate.rs
C6B3list_agent_profiles+agent_ref校验;A6 对齐 lintC5、BUG-10flows/builder_tools.rsvalidate.rs
C7B4 UI agent-kind 选择器C5、U2 节点面板app/src/components/flows/*

这个排序的关键约束是 C1 先于 C4/C5:agent kinds 的输出必须写入 Part A 定义的同一信封,下游表达式才不会因"哪种 agent"而出现分支。对照当前仓库可确认 C4–C7 已落地(AgentRunner实现、list_agent_profiles、UI 选择器均存在)。

五、开放设计问题

计划文档保留了四个待决问题,阅读当前实现时仍值得注意:

  1. crate 中的agent_ref:专用AgentRunner能力(B1,推荐——保持LlmProvider.complete为干净单发契约)vs. 让complete见到agent_ref时就循环。推荐:前者(仓库实现采纳了此方案)。
  2. 逐节点 model/tool 覆盖:只允许收窄(tools_allow是 agent 工具集的子集)——永不放宽,以保留注册表的安全包络。
  3. flow 内(及子工作流内)agent-kind 节点的 cost/loop 边界:复用深度计数器,还是引入 per-run agent 调用预算?(当前实现以HARNESS_AGENT_SLOTS并发信号量 + 超时钳制给出了第一版答案。)
  4. 自定义 agentupsert_custom_agent已存在——用户自定义 agent kind 可免费地通过agent_ref寻址。确认它们是否应在 flows 中可选(当前实现已允许:已知且启用的 custom entry 会走完整 harness 路径)。

六、延伸阅读路径

围绕本计划,可继续深入仓库的以下位置(均为仓库根相对路径):

  • 计划本体与集成背景:tinyflows-node-io-and-agent-kinds.md、tinyflows-integration/README.md
  • 宿主能力实现:src/openhuman/flows/tinyflows/caps/agent.rs(OpenHumanAgentRunner的三级路由、超时与并发治理)、src/openhuman/flows/tinyflows/caps/ 目录(llm.rshttp.rscode.rs等其余能力适配器)
  • Agent 注册表:src/openhuman/agent/registry/ops.rs(list_agents/get_agent)、src/openhuman/agent/registry/agents/(各agent.toml声明的工具集、模型提示与沙箱策略)
  • 运行时缝:src/openhuman/agent/tools/delegate.rs、src/openhuman/agent/turn_origin.rs
  • 创作面与 UI:src/openhuman/flows/builder_tools.rs、app/src/components/flows/canvas/AgentNodeInspector.tsx

需要提醒的适用前提:计划文档引用的vendor/tinyflows/src/...路径属于 vendored 引擎(仓库vendor/下的子模块),在部分 checkout 中未检出内容,文中相关行号引用以计划文档原文为准;而src/openhuman/app/src/下的路径则可直接在当前仓库中打开核对。

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询