agent-kit

module
v0.0.0-...-fd6a81b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: Apache-2.0

README

agent-kit — 基于 eino 的 agent 快速搭建框架

声明式定义整个应用:能力供给、提示词、skill、agent、HTTP/A2A 服务、 飞书接入。配置按所有权切分为三层文件(app / agents / namespaces), 也支持单文件形态。设计立场:大脑即循环,流程即兜底,结构进能力

┌──────────────────────────────────────────────────────────────┐
│ 接入层   serving(HTTP/SSE + A2A 供给面) channel(飞书/IM)      │
├──────────────────────────────────────────────────────────────┤
│ 门面     agent(会话织入/结构化输出;Agent 本身也是能力)          │
├──────────────────────────────────────────────────────────────┤
│ 循环     engine: react 唯一主循环 │ loop: L1-L4 提示词拼装、压缩、 │
│          plan-execute 是引擎模板  │ 预算、审批、重试、超时(Ring 0) │
├──────────────────────────────────────────────────────────────┤
│ 结构     namespace: tools(ns 内共享)→ components(执行单元声明)  │
│          → skills(对外产品:参数 + DAG 编排,唯一进目录的单元)    │
├──────────────────────────────────────────────────────────────┤
│ 目录     Source → Catalog → Agent(多源聚合/冲突/准入/选品)      │
│          CapRef: cap://kind/domain/name@version                │
├──────────────────────────────────────────────────────────────┤
│ 供给     mcp │ http │ rpc │ local │ a2a │ prompt │ secrets      │
├──────────────────────────────────────────────────────────────┤
│ 底座     eino(compose.Graph / react / 组件 / callbacks / 流式)  │
└──────────────────────────────────────────────────────────────┘

核心抽象:一切节点皆能力,一个能力两种形态

capability.Capability 是唯一的中心接口:

type Capability interface {
    Meta() Meta                             // Ref、描述、参数 schema、Risk
    AsTool(ctx) (tool.BaseTool, error)      // 工具形态:大脑决定何时调用
    AsLambda(ctx) (*compose.Lambda, error)  // 节点形态:流程决定何时执行
}

工具、模型、记忆、RAG、skill、workflow、完整 Agent 全部实现它。同一个 能力既能进 ReAct 循环(动态编排),也能被钉进图里(静态编排)——这个 选择是部署时的配置,不是架构时的承诺

CapRef 协议与三层供给

能力以 4 段 cap://<kind>/<domain>/<name>@<version> 标识 (core/capability/ref.go)。kind 是对象类别,domain 是该 kind 下 name 的归属域(callable→源名/ns;store/retriever→存储用途; prompt→prompt 源)。去掉了旧的 provider 段——供给方类型(mcp/http…) 只在配置期选工厂,运行时只有统一的 Capability,写进 ref 是泄漏执行期 不存在的区分。两条不变式:kind 段永远精确(是 kind 优先解析的前提, *-kind 无法选解析域;domain/name 可通配),Key 不含 version(版本 共存靠 registry)。模型可见短名撞车时目录自动升级为 domain_name。 Risk 分级(readonly/mutating/dangerous)是审批拦截与目录准入的依据。

kind 二分:可调用能力(进 include:tool/skill/component/agent)与 可命名对象(装配注入槽:prompt/store/retriever)。model 走内联 ModelConfig,不入 cap 体系(从不被引用)。

多源供给走 Source → Catalog → Agent 三层(契约与目录治理在 protocol/source/,各类供给实现在 impl/source/): source 供货(可选源断连自动降级)、catalog 治理(冲突报错、优先级遮蔽、 风险准入)、agent 用 include/exclude 通配选品。供给类型:mcp(stdio/ sse/http)、http(纯配置声明接口)、rpc(泛化调用契约)、local (Go 函数泛型推断 schema)、a2a(远端 agent,与本框架 serving 协议互通)、 vector(向量知识库检索,即 RAG——是 tools 层的一种工具而非独立组件; 内置词法保底后端,真实向量库经 vector.RegisterBackend 接入;离线摄入 不归框架管)。

提示词即资源

所有提示词位置(system prompt、skill 任务书、planner/replanner)一律 写标量:cap://prompt/<source>/<name>@<label> 前缀识别为引用,其余为 字面量(旧 {ref: ...} 映射写法已移除,装配期报错指路) (protocol/prompt/),provider 有 inline/file/http(平台适配,带缓存 降级)。版本随轨迹打点,可回溯"坏回答对应哪个提示词版本"。

提示词架构:一次模型调用的完整结构

每次模型调用,发出去的消息列表由分层拼装(runtime/loop/prompt.goPromptLayers.Modifier),把 loop 纪律、agent persona、会话视图、记忆、 计划整合成一个结构。头部稳定(供应商 prompt cache 命中),变动部分注入尾部。

主循环(agent)的完整提示词
┌─ 系统头部(稳定,前缀缓存)───────────────────────────┐
│ L1  loop 运行纪律      prompt.loop / 内置 DefaultLoopPrompt │  框架级,普适
│ L2  agent persona      prompt.system                       │  业务身份
│ L3  环境信息           日期 / 会话 ID / 用户 id(代码生成)  │  禁业务塞指令
├─ 会话视图(session,loadTurn 组装)──────────────────┤
│     [滚动摘要]          loop.compaction(恒保留,老对话梗概) │
│     [首条用户消息锚定]  最初任务不随归并漂移                │
│     [近期原文 × window] session.window(user/执行记录/终稿) │  三件套
│     [当前输入 $input]                                       │
├─ 系统尾部(每轮变动,不污染缓存前缀)──────────────┤
│ L4  相关记忆(背景参考,非指令)                            │
│       · 长期记忆命中    memory.recall(kv 检索)             │
│       · 窗外会话召回    session.recall(bigram,近因/指代)  │
│     计划注入           todo.PlanSection(harness 强制每轮可见)│
│     本轮问题重述       Focus(占最尾近因位,锚定当前目标)   │
└──────────────────────────────────────────────────────┘

注意力排序(尾部近因位):当前问题 > 计划 > 记忆——最该聚焦的排最后。

大内容旁路(不进上面的直接结构):超长工具结果经 digest 消化为"要点 + read_result 指针"再入会话视图,原文旁置(见"上下文卫生")。

各层的来源、配置、作用域、缓存
来源 配置键 作用域 缓存
L1 loop 纪律 内置/覆盖 prompt.loop 每个循环都有(子循环用无 todo 裁剪版) 稳定
L2 persona agent prompt.system 仅主循环,不下沉子循环 稳定
L3 环境 代码生成(日期/会话 ID/用户 id) —(Env 生成器) 主循环 稳定(天粒度)
滚动摘要 压缩 loop.compaction.prompt 定侧重 agent 级 两次压缩间稳定
近期原文 会话 session.window agent 级 随轮变
L4 长期记忆 长期记忆 memory.recall.top_k agent 级 尾部,随轮变
L4 会话召回 会话 session.recall(bigram) agent 级 尾部,随轮变
计划 todo 内置(todo 启用) 仅主循环 尾部,随轮变
本轮重述 Focus 内置(主循环开) 仅主循环 尾部,随轮变
调用角色与层次:每次模型调用拼什么,由角色决定

框架内每一次模型调用先归入四种角色之一,角色决定系统消息的层次—— 不是代码路径各自决定(那样必然漂移不一致):

角色 定义 系统消息 用户消息 归入者
① 循环调用 有工具面、会迭代 L1 + P + E3(+计划 if todo) input 顶层 agent、react/direct 组件、plan-execute/reflection 的 executor
② 阶段调用 引擎编排内的单发、无工具 P + 阶段提示词 阶段材料 rewoo planner/solver、router、plan-execute planner/replanner、reflection reviewer
③ 独立单发 use: model 步骤 P(prompt 即全部) input model 步骤
④ 框架事务 框架自用 仅事务提示词 材料 digest 消化、压缩摘要

(L1=循环纪律;P=组件 prompt 渲染后的身份指令,即 persona;E3=环境信息。)

三条不变式:

  1. P 是组件身份,该组件的每一次模型调用都带(①②③)。传递统一走 runctx.WithPersona:循环调用经 PromptLayers.Modifier 织入 L2,阶段调用经 engine 的 stageSystem 前置——两个装配点,同一来源,新引擎照角色接入 不会漏;
  2. L1 只跟工具面走:能调工具、会迭代才有 L1(①)。②③无工具,循环纪律 是噪音,明文不带;executor 有工具面,必须有 L1(经 stageLoopModifier 组合而非顶替);
  3. input 空 → P 降级为用户消息(全角色同一条降级规则,零退化兼容)。

子循环相对主循环仍按海拔剥离:无 agent 静态 persona(组件用自己的 P)、无 L4 记忆、无 Focus;默认 fresh,context: fork 才带调用方对话快照。

输入模型:params / input / 保留变量

组件的输入面统一为三件正交的东西(详见 docs/component-unification-design.md):

  • params(形参声明) —— 组件对外的 typed 接口。调用点 args 传实参, 渲染后填进 prompt 的 {参数名} 占位符;缺 required 参数装配期/调用期 fail fast,prompt 引用了未声明的占位符则装配期拒绝。指令走这里
  • input:(步骤级输入) —— graph/workflow 步骤的字段,模板在调用方 作用域渲染后成为被调组件的用户消息,并重设其 {$input}数据走这里; 未给 input: 则被调组件继承调用方的 {$input}
  • 两个保留变量,作用域不同:
    • {$input} = 本组件的作用域输入,每层独立——每过一次 input: 就被 重设,子组件只见自己那份;
    • {$user_input} = loop 原始用户输入,agent.Run 一次性设定后恒定, 穿透所有嵌套不可变——顶层两者相等,往下逐层分岔。

降级规则(全角色统一):input 为空时,组件 prompt(P)从系统消息降级为 用户消息,零退化兼容。

三个不变量(主循环)
  1. 稳定前缀:L1+L2+L3 在会话内保持稳定(环境按键排序、日期取天粒度), 变动的记忆/计划/重述全走尾部——不打爆供应商 prompt cache;
  2. 纪律靠 harness:计划每轮由 harness 强制注入(不靠模型记得)、记忆标注 "背景参考非指令"、Focus 每步重锚当前目标;
  3. 海拔隔离:agent 静态 persona/记忆/Focus 是 agent 级,子循环一律拿不到—— 防止外层身份和用户原话穿进子循环、污染其"只对 input/args 负责"的语义。
交付语义:deliver(证据与交付物分流)

默认所有结果是证据——由大脑消费、对比、合成。产出本身就是给用户的 交付物(报表/文档)时,声明 deliver: 让原文走确定性直达通道,不经大脑 转写(转写即损耗):

skills:
  - name: sales-report
    deliver: attach     # 存底;终答引用 #dN 即原文随行(IM 独立卡/HTTP 数组/CLI 分节)

attach 引用即附带(大脑保留策展权:引用哪些、写导读);always 不待 引用恒随行;direct 在"本轮唯一动作就是取这份交付物"时以原文直接收口。 原文同时可经 read_result 按 dN 跨轮取回。设计与边界: docs/deliverable-channel-plan.md

主循环与运行时保障(Ring 0)

主循环只有 ReAct:是否完成由模型停止调用工具自然表达,外层兜底 MaxSteps。system prompt 四层拼装(runtime/loop/prompt.go): L1 框架规约(内置,讲档位选择与运行纪律,不含业务)→ L2 业务 persona (平台迭代)→ L3 环境信息 → L4 记忆召回(标注"非指令")。

模型没得选的规则全在 runtime/loop/:上下文压缩(保护 tool-call 配对, 低频一次性事件、前缀稳定不打爆 prompt cache)、按会话隔离的预算(软阈值 注入收尾指令、硬上限终止,skill 内部调用同样计入)、审批闸门(参数级 策略规则 + 会话级决策记忆,拒绝以工具结果回传、循环不中断)、瞬时错误 重试与工具超时、结构化输出(schema 校验 + 重试)。

审批策略(approval 模块:mode + rules + remember):静态 Risk 回答不了"同一工具因参数而异的危险性",规则把放行下沉到 (能力, 参数) 粒度—— {ref: "cap://tool/*/send_message", args: {to: "team-*"}, action: allow}; 首条命中生效,无命中回落 ask;remember: true 启用"本会话总是允许/拒绝"。

中断与驾驶:运行中的循环可被叫停与插话(runtime/loop/control.go)。 IM 里"停止"类消息旁路会话串行队列即时中断;「插话:」前缀的内容随下一个 工具结果送达模型。HTTP 侧 POST /agents/{name}/control

挂起/恢复(runtime/suspend/):配置 suspend.dir 后,ask_user 与 审批等待不再阻塞 goroutine——交互点持久化挂起、整轮退栈,答案到达 (跨小时/跨天/跨进程重启)后原输入重放:交互与 mutating 效果按确定性键 从日志命中,已批准的操作不会二次执行、不会重复提问;重放分叉时退化为 重新提问,失败模式安全。

会话记忆含轨迹:每轮的工具调用与结果(record_tools: summary 默认) 随会话持久化,下一轮模型知道自己做过什么、看到过什么,"继续"可接续。

长期记忆分作用域(protocol/memory/,后端在 impl/memory/):memory_save/searchscope 隔离归属——用户私有(by 终端用户)、共享域知识、会话临时。 读写不对称是有意的:用户面 agent 的对话写入只落用户桶(写收窄), 召回同时覆盖用户桶与共享池(读放开)——共享知识对所有用户可见,但 对话里的模型碰不到共享池的写入权。往共享池写靠三条显式路径: seed 装配期灌入、宿主代码 Put、或某个特权 agent 显式声明 write_scope: shared——不是模型运行时自选。终端用户身份由 serving 的 user 字段 / 飞书发送者 open_id 提供,缺身份时用户记忆写入 fail fast, 不静默落进共享池。会话召回策略(protocol/session/ 的 Retriever,实现在 impl/session/)可注册替换,词法 bigram 是缺省保底。

分布式存储(可换后端):四类内部状态——session(会话历史)、memory (长期记忆)、todo(计划清单)、result(digest 大结果暂存)——都能外置到 共享后端。多副本 serving / 进程重启下,包级内存会让「A 副本写、B 副本读 不到」破坏一致性;外置后键按 agent/会话隔离、跨副本一致。KV 家族 (todo/result,见 protocol/store/impl/store/)的写是原子读改写原语(inmemory 用 mutex,redis 用 WATCH/MULTI 乐观锁 + 抖动退避),多副本并发不丢更新。 用法:agent 层 stores: 只声明具名实例 {name, kind, type, config, ttl}; 四大模块各自独立配置(session 会话短期记忆 / memory 长期记忆 / todo 计划 / digest 大结果暂存),各自的 store: 槽用 cap://store/<kind>/<name> 引用实例。换后端=改 type、或指向另一实例; 跨 agent 共享=各自指同一实例。裸 store: file 作缺省简写,存量零迁移。 redis 后端空导入 impl/store/redis 即为四类 (session / memory / todo / result)一并开启分布式存储;memory 的 redis 后端是关键词匹配(向量检索另属一族,由 qdrant 等提供)。

stores:                       # 仅定义
  - {name: sess,  kind: session, type: redis, config: {addr: 127.0.0.1:6379}}
  - {name: ltm,   kind: memory,  type: redis, config: {addr: 127.0.0.1:6379}}  # 或 qdrant 走向量
  - {name: plans, kind: todo,    type: redis, config: {addr: 127.0.0.1:6379}}
  - {name: cache, kind: result,  type: redis, config: {addr: 127.0.0.1:6379}}

loop:                         # 执行画像:压缩归 loop(主循环与 component 共用)
  max_rounds: 30
  compaction: {max_messages: 30, keep_recent: 10}
session:                      # 会话短期记忆
  store: cap://store/session/sess
  window: 40
  recall: {top_k: 3}
memory:                       # 长期记忆(独立模块)
  store: cap://store/memory/ltm
  expose_tools: true
  recall: {top_k: 3}
todo:                         # 计划清单
  store: cap://store/todo/plans
digest:                       # 大结果消化/暂存
  over: 4000
  store: cap://store/result/cache

命名空间:声明与使用分离的三层结构

配置的主路径是 namespaces:(config/namespace.go), 每个命名空间三层,逐层回答一个问题:

  • tools — 有什么原子能力(mcp/http/rpc 声明),ns 内共享,对外不可见;
  • components — 执行单元是什么("能力声明"),ns 内复用,不进全局目录。 两族形态,engine 必填(执行形态决定成本模型与行为保证,不做隐式 默认)。循环族(prompt + tools):direct(单发:一次调用+一轮工具+ 收尾)| react(自主循环)| plan-execute(规划循环)| reflection (产稿→评审→修正,轮次代码钉死)| router(一次分诊调用路由到 工具面上的能力)| rewoo(一次规划生成带 {eN} 引用的工具计划, 按依赖并行执行,一次求解,全程两次模型调用);编排族(steps,无脑 钉死):workflow(纯顺序,禁 needs)| graph(DAG,可并行);
  • skills — 对外长什么样、怎么串(描述 + 参数,即"能力使用"),唯一 进全局目录、被 agent 发现的单元。执行可内联 steps,也可 use: components/<名> 整体委托给一个编排族 component——skill 与私有图的 区别只剩可见性。

边界规则在装配期落实:工具不出命名空间;skill 是命名空间的默认公开 接口(跨 ns 直接以 cap://skill/<ns>/<name> 引用)。component 也可 跨 ns 复用,但必须双向显式:目标 component 声明 export: true, 引用方 ns 声明 imports: [<目标 ns>],引用形式 cap://component/<ns>/<name>(被依赖的 ns 须先于引用方装配/挂载)。

skill 的执行语义是 DAG(runtime/engine/graph.go):steps 声明为带 needs 的列表,缺省依赖上一步(退化为串行链),显式 needs 表达并行与汇合;步骤支持 timeout/retry。装配期校验依赖存在、无环、 模板引用 ⊆ needs 传递闭包(数据流与控制流一致,并行下无竞态读)。 state = 参数 ∪ {步骤名: 输出},每次调用一份、单写者无冲突。运行期没有 大脑做路由,执行路径是强保证的。

Skill 三边界与治理下沉

skill/component 装配时固定三条边界:接口(大脑只见 description+params)、 上下文(独立会话,过程不回流)、权限(工具面锁定为声明子集);风险取 绑定能力的最大值;依赖解析失败即拒绝装配。

Ring 0 的运行时保障覆盖每一层大脑,不止主循环:审批闸门与预算门闸 由 agent 在每次运行装入 ctx(loop.WithApprovalMode/loop.WithBudget), component 内部的 mutating 工具调用同样过闸、内部模型调用同样计入调用方 会话预算——同一 skill 被不同策略的 agent 复用时各自独立生效。

plan-execute 不是 agent 的配置项,是 component 引用的引擎模板——"从架构 时的模式选择,变成运行时大脑面前的一个选项"。平铺的 skills: 段保留为兼容路径。

可靠性(Ring 0)

reliability: 段(执行画像的一部分,{retry, tool_timeout},零值即默认, 可逐层降级)声明可靠性策略(runtime/loop/retry.goruntime/loop/timeout.go):模型调用对限流/瞬时服务端/网络错误 指数退避重试(默认 3 次尝试,确定性错误不重试);工具单次调用超时 (默认 5 分钟,超时以结果回传模型换路径推进,循环不中断;审批等待 不计入超时)。编排步骤的 timeout 超时则视为步骤失败,确定性中断整图。

上下文卫生:digest 与 fork

两个正交开关防止上下文污染与背景丢失(runtime/loop/digest.goruntime/loop/fork.go):

  • digest(结果消化):agent 级 digest.over 阈值之上的工具结果先落 暂存(后端 digest.store 可外置),由模型带着当前任务提取要点后入 上下文,附取回指针——搜索、捞日志等大数据量工具不再挤爆窗口;摘要 不够时模型可用内置 read_result(id, offset) 分页翻原文。消化是有损 优化,失败退回原样,截断闸兜底;result:raw 标签可豁免。digest(over/ truncate/store)是执行画像的一部分,主循环与 component 逐层就近降级。
  • fork(上下文继承):带内部循环的能力默认从零起步(fresh,背景靠 args 转述);步骤声明 context: fork 后,内部循环以"调用方对话快照 + 任务书"起步——背景无损继承,隔离方向不变(过程不回流,只返回结果)。 fork 复制一份调用方历史 token 且吃不到其 prompt cache,默认 fresh。

编排步骤之间的数据流走 state 变量、不进模型上下文,大数据管道天然 免疫——重数据流程优先下沉到 steps。

todo(计划外化)只属于主循环:agent 默认挂载(harness 强制纪律: 写入校验、每轮注入、卡住提醒),子 agent 按执行域隔离;component 默认没有(能结构化的计划用 steps/引擎表达)。react component 可 todo: true调用级临时清单——键为本次执行域、调用结束即弃, 宿主计划不受影响,组件保持无状态可重入;这是给确实拆不动的研究型 长循环的例外通道,不是常规选项。

信息传递的通道谱系(成本与信息量递增,均为使用点显式声明):

params(主脑转写,有损) → {$input}/{$user_input}(保留变量,框架直取)
→ fork(全量对话快照) │ 大内容旁路:digest 指针 + read_result 点对点拉取

两个保留输入变量作用域不同:{$input}本组件的作用域输入——步骤 input: 每传一次就重设一层,子组件各见自己那份;{$user_input}用户的原始输入,agent.Run 一次性设定后恒定、穿透所有嵌套。二者均可 在步骤 args/input 模板里引用,装配期校验($ 前缀仅限保留变量, params/步骤名禁用 $ 开头),调用方传入同名键不能顶掉框架注入值。

session 与 loop.compaction:职责分野(按作用域划)

会话记忆的配置分在两个块,边界是作用域,不是随意拆:

  • session(agent 级:对话档案)——持久身份与检索。store(历史存哪)、 record_tools(工具轨迹存多详细)、window(最近几条织进 prompt)、 recall(窗外具体片段捞回)。归 agent 所有:这段对话是 agent 的。
  • loop.compaction(loop 级:上下文预算)——把工作上下文塞进模型窗口。 max_messages/max_tokens(触发阈值)、keep_recent(压缩后保留最近 条数)、prompt(摘要侧重)、tool_clear_over/keep(旧工具结果清理)。 归执行单元所有:主循环和每个 component/skill 子循环各有一份预算, 而子循环没有持久 session——所以 compaction 挂在 LoopProfile 上、不进 session。放一起反而错(子循环会背上它没有的 session 概念)。

window 与 keep_recent(职责解耦 + 一条装配期硬校验): 视图 = [滚动摘要] + [首条用户消息锚定] + 近期原文。窗口裁剪始终保留 合成头部(摘要 + 锚定),只裁近期原文(见 agent.windowKeepingHead)。 两个旋钮职责不同:window = 织入 prompt 的近期原文条数,keep_recent = 压缩时保留不摘要的近期条数。

硬约束:启用压缩且 session.window > 0 时,装配期强制 session.window >= loop.compaction.keep_recent + 2,不满足直接报错 (config/agent.go:窗口必须容得下摘要视图,+2 = 摘要 + 锚定两条合成 消息,否则滚动摘要会被窗口裁剪静默切掉、跨轮记忆凭空消失)。

摘要边界在 len−keep_recent(之前的压成梗概),窗口边界在 len−window。 window ≥ keep_recent+2 之下,窗口外的一切都被摘要覆盖,prompt = 摘要 + 窗口严丝合缝、无"既不在摘要也不在窗口"的夹层;窗外具体片段仍可由 session.recall 按词法/近因捞回。

超长消息的处理(按类型不同,不是都走 summary):

  • 超长工具结果 → digest(消化 + read_result 指针),调用当时就压,原文 可取回;
  • 滚出窗口的老消息 → compaction 滚动摘要(单向梗概);
  • 最近的长 user/assistant 文本 → 当前无专门策略,原样留在窗口直到滚出 keep_recent 才被摘要(已知缺口,见 docs/brain-loop-upgrades.md)。

接入:HTTP / A2A / 飞书

serving.addr 一开即是 Gateway(serving/): POST /agents/{name}/messages(JSON/SSE)、A2A 供给面(GET /a2a/agents

  • POST /a2a/agents/{name}/tasks,与 provider/a2a 消费端同协议,部署 之间互通)、IM webhook。配置 suspend:/messages 支持持久化挂起: ask_user/审批不占请求等待,响应 {status: "waiting", question},同会话 的下一个请求即答案(跨进程重启/多副本可恢复,与 IM 通道共用同一后端)。

飞书(impl/channel/feishu):事件解密验签、卡片伪流式、 tenant_access_token 缓存。Dispatcher(serving/dispatcher.go) 负责会话映射(chat/chat_user)、同会话串行、事件幂等,并把 IM 对话桥接为 HITL 通道——ask_user 的答案和审批的批复,就是会话里用户的下一条消息

配置的三层文件形态

配置按所有权切分(config/app.go),每层文件回答一个问题:

app.yaml                 装配成什么进程(部署拥有):secrets/serving/channels/
                         observability + 执行画像基线(model/loop/reliability/
                         digest/step_defaults)+ agents 接线,业务含量为零
agents/<name>.yaml       对外是什么产品(产品面拥有):执行画像 + 记忆/预算/审批
                         + namespaces 关联(挂载即获得其全部导出 skill)
namespaces/<name>.yaml   有什么能力(域团队拥有):tools/components/skills
                         (+ 可声明执行画像,但不含 model——能力不可自指)

约定:文件名即名字;相对路径相对引用它的文件解析。namespace 是库, agent 挂载时按解析出的执行画像实例化(源连接按文件缓存共享);跨 ns 的 cap://skill 引用在同一 agent 的挂载集合内按关联顺序解析。

统一执行画像 + 五级降级(config/profile.go):一套 Profile(model/loop/reliability/digest/step_defaults)四层共用,声明在哪层 就在哪层生效,缺失则逐字段就近降级(nil=继承)。component 生效值从高到低:

agent给该ns指定(per-mount) → component → namespace → agent自己 → app

主循环取 app.merge(agent自己)model 特例:能力不能自指模型(部署/成本 决策由集成方定),ns/component 不参与,链退化为 per-mount → agent自己 → app; 典型场景 agent 挂 catalog(便宜模型)+ research(强模型),靠给各 namespace per-mount 指定 model 实现。会话状态(session/memory/todo)与治理边界 (approval/budget/structured_output)不进画像链:app→agent 整块降级、不下沉 component——那是 agent/部署持有的安全边界,库不能给自己放权。

per-mount 覆盖写法(mount 条目从裸路径升为路径 + 覆盖画像):

namespaces:
  - ../namespaces/catalog.yaml            # 仅路径
  - path: ../namespaces/research.yaml     # 给这个域单独指定强模型 + 步数
    model: {provider: openai, config: {...}}
    loop:  {max_rounds: 5}

单文件形态(config.Load+Build)保留为兼容路径。

资源加载:ResourceLoader

状态:设计已定稿(见 docs/resource-loading-design.md),实施分 5 批推进中。本节是加载逻辑的权威说明。

所有只读资源——配置(app/agent/namespace,component 内联于 ns)、 提示词文件、skill 包及其文件读取能力(pack_read,即 fs cap)——经同一个 ResourceLoader 抽象加载。核心是 Go 标准库 io/fs.FS:一个 Resolve(ref) 把资源 ref 解析为「根 FS + 入口路径」,之后所有相对引用都在这个根 FS 内 用 / 分隔的路径解析(拒绝 .. 逃逸)。唯一锚点 = 根 FS,不再依赖 进程 CWD。

为什么这么设计:早期 agent/namespace 文件相对 app.yaml 目录解析(可移植), 但 prompt 目录和可写目录(旧 work_dir,现拆义为 state_dir)相对进程 CWD 解析(不可移植)——同一份配置两套 基准,换目录/进容器就断。统一到根 FS 后,配置无论来自本地盘、内嵌二进制、 还是远程,加载语义完全一致。

加载逻辑(一张决策图)
资源 ref(AGENTKIT_CONFIG 或启动参数)
  │
  ├─ 无 scheme:./app.yaml、/etc/app/app.yaml   → file loader:os.DirFS(目录)
  ├─ embed:main/config/app.yaml                → 宿主注册的 embed.FS
  └─ https://…(后置)                          → http loader:拉取为内存 FS
        │
        ▼
   resource.Resolve(ref) → (根 FS, 入口路径)
        │
        ├─ config.LoadAppFS(根FS, 入口)
        │     ├─ agents/<name>.yaml     ┐ 全部在根 FS 内按 path 解析
        │     └─ namespaces/<name>.yaml ┘ (component 内联于 ns 文件)
        │
        ├─ prompt / secrets:fs.Sub(根FS, 子目录) —— 与配置天然同源
        │
        └─ skill 包:
              ├─ bundled(随配置分发)→ fs.Sub(根FS, 包目录),不落盘
              └─ remote(from: github…)→ 装配期 fetch 到 state_dir(可写)
                    │
                    ▼
              pack_read(fs cap)持有包的 fs.FS —— 内嵌/本地/远程一套代码,
              fs.FS 语义顺带把"读到包外"的路径穿越堵死
只读资源 vs 可写状态(必须分开)
类别 内容 承载
只读资源 配置、提示词、secrets、skill 包清单与包内文件 根 FS(本地盘 / 内嵌 / 远程,均可)
可写状态 skill 远程安装、file 后端(session/todo/digest)、轨迹落盘 state_dir(必须真实可写目录;装配期校验可写,不可写 fail fast)

state_dir 默认链:state_dir 配置 → 环境 AGENTKIT_STATE_DIR → OS 约定 ($XDG_STATE_HOME/agentkit)。容器里显式挂 volume 指过来。

部署形态
// 本地盘(开发 / 传统部署):
spec, _ := config.LoadApp("/etc/agentkit/app.yaml")   // 或裸名走搜索路径

// 单二进制内嵌(容器 / 免依赖交付):配置与提示词全部编进二进制
//go:embed config
var cfgFS embed.FS
spec, _ := config.LoadAppFS(cfgFS, "config/app.yaml")  // 零磁盘依赖

入口裸名走搜索路径:显式 AGENTKIT_CONFIG → 进程 CWD → 可执行文件目录 → /etc/agentkit/,命中即用并在启动日志明示"从哪加载"。

扩展:新增资源来源(OCI artifact、S3、配置中心)= 实现一个 scheme 解析器返回 fs.FSresource.Register——与全框架"代码注册、配置按名 启用"同构,无需改核心。

运行

cd examples
MINIMAX_API_KEY=... go run .          # CLI REPL;serving.addr 配置后即 Gateway
go test ./...                          # 全套测试(脚本化假模型,无需真实 API)

完整配置示例见 examples/app.yamlexamples/agents/examples/namespaces/。 代码侧能力(local.Func、rpctool、子 agent)经 config.BuildOptions.ExtraCapabilities 注入,与声明式能力同目录。

三环边界(什么必须写死)

判据:"模型不遵守就会出事"的东西和"解释协议本身"的东西写死; 工具面上的一切只是给大脑的选项,选项不能承载保证。

  • Ring 0(内核):主循环、Capability 契约、历史织入/MaxSteps/预算/ 审批闸门/压缩、目录治理规则、观测切面;
  • Ring 1(代码扩展点,registry 注册):引擎模板、source/prompt/ channel/model 的类型适配器、存储后端;
  • Ring 2(纯配置):能力实例、skill、提示词、agent、策略值、版本通道。

日常迭代应 95% 落在 Ring 2;若业务需求经常要动内核,说明协议漏了东西, 应回来改协议而不是打补丁。

Roadmap

  • 评测框架:基于轨迹 JSONL 的回放与断言(轨迹格式已落地);
  • skill-registry source(平台下发 skill 声明,含依赖解析与风险传播);
  • 更多通道(钉钉/企微/Slack)与模型厂商(ark/claude,参照 impl/model/openai/openai.go 各约 20 行);
  • 工具结果缓存、RAG 写入侧 pipeline;编排步骤的条件分支(when)。

Directories

Path Synopsis
Package agent 提供 Agent 门面:主循环 Runner + 会话记忆 + 运行时 保障(预算、结构化输出)的组合体。
Package agent 提供 Agent 门面:主循环 Runner + 会话记忆 + 运行时 保障(预算、结构化输出)的组合体。
Package askuser 提供内置的 ask_user 能力:大脑主动向用户求澄清。
Package askuser 提供内置的 ask_user 能力:大脑主动向用户求澄清。
agent.go:AgentConfig → agent.Agent 的装配(单文件 Build 与多文件 BuildApp 共用),含模型/存储/召回的解析辅助。
agent.go:AgentConfig → agent.Agent 的装配(单文件 Build 与多文件 BuildApp 共用),含模型/存储/召回的解析辅助。
core
capability
Package capability 定义框架的最小统一抽象:一切节点皆能力。
Package capability 定义框架的最小统一抽象:一切节点皆能力。
runctx
deliver.go:交付物收集器(交付物直达通道的 ctx 原语,设计见 docs/deliverable-channel-plan.md)。
deliver.go:交付物收集器(交付物直达通道的 ctx 原语,设计见 docs/deliverable-channel-plan.md)。
examples/main.go:从一份 YAML 拉起整个应用。
examples/main.go:从一份 YAML 拉起整个应用。
embedded command
examples/embedded demonstrates single-binary deployment: the entire config tree (app.yaml, agents, namespaces, prompt files) is compiled into the binary with //go:embed and loaded via config.LoadAppFS — with zero dependence on the filesystem or the process working directory.
examples/embedded demonstrates single-binary deployment: the entire config tree (app.yaml, agents, namespaces, prompt files) is compiled into the binary with //go:embed and loaded via config.LoadAppFS — with zero dependence on the filesystem or the process working directory.
interactive command
backend.go:内置 mock 业务后端(商品/库存/销售/订单/客户)。
backend.go:内置 mock 业务后端(商品/库存/销售/订单/客户)。
pipeline command
examples/pipeline —— 无脑硬编排样例:把多个 agent 串成固定流程。
examples/pipeline —— 无脑硬编排样例:把多个 agent 串成固定流程。
superpowers command
examples/superpowers:把 obra/superpowers 技能集拉起为**可交互的研发教练** (smoke.yaml 保持纯测试用途,本 runner 独立)。
examples/superpowers:把 obra/superpowers 技能集拉起为**可交互的研发教练** (smoke.yaml 保持纯测试用途,本 runner 独立)。
impl
channel/feishu
Package feishu 是飞书(Lark)的 Channel 适配器:
Package feishu 是飞书(Lark)的 Channel 适配器:
exec/docker
Package docker 是 agent-kit 官方的 docker 执行沙箱:在一次性、加固的 容器里跑脚本(无网络、只读根、tmpfs、限内存/CPU/PID、丢权限、禁提权)。
Package docker 是 agent-kit 官方的 docker 执行沙箱:在一次性、加固的 容器里跑脚本(无网络、只读根、tmpfs、限内存/CPU/PID、丢权限、禁提权)。
interactor/cli
Package cli 提供 runctx.Interactor 的终端内置实现。
Package cli 提供 runctx.Interactor 的终端内置实现。
memory/inmemory
Package inmemory 是 memory 的进程内关键词匹配后端(memory type: inmemory), 按 scope 分桶。
Package inmemory 是 memory 的进程内关键词匹配后端(memory type: inmemory), 按 scope 分桶。
memory/redis
Package redis 提供长期记忆的 redis 后端:每个 scope 落一个 redis hash (<prefix>mem:<scope>,field=key、value=value),跨副本共享。
Package redis 提供长期记忆的 redis 后端:每个 scope 落一个 redis hash (<prefix>mem:<scope>,field=key、value=value),跨副本共享。
model/minimax
thinkstrip.go:推理模型 <think> 块的适配层剥离。
thinkstrip.go:推理模型 <think> 块的适配层剥离。
model/openai
Package openai 注册 openai 兼容模型工厂。
Package openai 注册 openai 兼容模型工厂。
model/zhipu
Package zhipu 走智谱 GLM 的 OpenAI 兼容接口(bigmodel.cn)。
Package zhipu 走智谱 GLM 的 OpenAI 兼容接口(bigmodel.cn)。
secrets/file
Package file 提供凭证协议的文件实现:从一个不入库的 YAML 文件 (name: value 平铺)取凭证。
Package file 提供凭证协议的文件实现:从一个不入库的 YAML 文件 (name: value 平铺)取凭证。
session/bigram
Package bigram 是 session 的内置词法召回器(retriever: bigram):字符 bigram 重叠打分,对中文友好、无 embedding 依赖的保底实现。
Package bigram 是 session 的内置词法召回器(retriever: bigram):字符 bigram 重叠打分,对中文友好、无 embedding 依赖的保底实现。
session/file
Package file 是 session 的文件后端(session type: file):dir 下每个会话 一个 <id>.jsonl,进程重启后会话可恢复。
Package file 是 session 的文件后端(session type: file):dir 下每个会话 一个 <id>.jsonl,进程重启后会话可恢复。
session/inmemory
Package inmemory 是 session 的进程内滑动窗口后端(session type: inmemory), 适合开发与无状态短会话。
Package inmemory 是 session 的进程内滑动窗口后端(session type: inmemory), 适合开发与无状态短会话。
session/redis
Package redis 提供 session 会话历史的 redis 后端(RPUSH/LRANGE 追加日志, 实现 FullLoader 支持滚动摘要与窗外召回)。
Package redis 提供 session 会话历史的 redis 后端(RPUSH/LRANGE 追加日志, 实现 FullLoader 支持滚动摘要与窗外召回)。
skill/einoskill
Package einoskill 把 agent-kit 的技能物化目录(EnsurePack 产物, <work_dir>/agent-kit/.skills/<ns>/<name>@<version>)适配为 eino ADK Skill middleware 的 Backend 接口——用 eino ADK 的团队可以直接消费我们分发、 锁定(skills.lock)、校验过的技能目录,SKILL.md frontmatter 两边同一份 (name/description/context/agent/model 全兼容)。
Package einoskill 把 agent-kit 的技能物化目录(EnsurePack 产物, <work_dir>/agent-kit/.skills/<ns>/<name>@<version>)适配为 eino ADK Skill middleware 的 Backend 接口——用 eino ADK 的团队可以直接消费我们分发、 锁定(skills.lock)、校验过的技能目录,SKILL.md frontmatter 两边同一份 (name/description/context/agent/model 全兼容)。
source/a2a
Package a2a 把远端 agent 服务接入为 Source:远端跑的是 skill 还是 完整 agent 对本地不可见,统一以 cap://agent.a2a/<source名>/<agent名> 的黑盒能力挂载。
Package a2a 把远端 agent 服务接入为 Source:远端跑的是 skill 还是 完整 agent 对本地不可见,统一以 cap://agent.a2a/<source名>/<agent名> 的黑盒能力挂载。
source/exectool
Package exectool 把"执行一段脚本"声明为 Source:一段配置列几个工具、 各绑一种运行时(python/node/bash/sh),每个成为一个 cap://tool/<源名>/<工具名> 能力,入参只有 script + args。
Package exectool 把"执行一段脚本"声明为 Source:一段配置列几个工具、 各绑一种运行时(python/node/bash/sh),每个成为一个 cap://tool/<源名>/<工具名> 能力,入参只有 script + args。
source/httptool
Package httptool 把 HTTP 接口声明为 Source:一段配置声明一批接口, 每个接口成为一个 cap://tool/<source名>/<接口名> 能力,无需写代码。
Package httptool 把 HTTP 接口声明为 Source:一段配置声明一批接口, 每个接口成为一个 cap://tool/<source名>/<接口名> 能力,无需写代码。
source/local
Package local 把本地 Go 函数包装成能力,零配置成本。
Package local 把本地 Go 函数包装成能力,零配置成本。
source/mcptool
Package mcptool 把 MCP server 接入为 Source:server 的每个工具 成为一个 cap://tool/<source名>/<工具名> 能力。
Package mcptool 把 MCP server 接入为 Source:server 的每个工具 成为一个 cap://tool/<source名>/<工具名> 能力。
source/rpctool
Package rpctool 把 RPC 接口暴露为能力。
Package rpctool 把 RPC 接口暴露为能力。
source/vector
Package vector 把向量知识库检索接成一种工具源(source type: vector), 与 http/mcp/rpc 并列——RAG 在本框架里就是 tools 层的一种工具,不是 独立组件。
Package vector 把向量知识库检索接成一种工具源(source type: vector), 与 http/mcp/rpc 并列——RAG 在本框架里就是 tools 层的一种工具,不是 独立组件。
store/file
Package file 提供 store.KV 的文件后端:一键一文件,写入 tmp+rename 原子落盘,进程重启后可读回。
Package file 提供 store.KV 的文件后端:一键一文件,写入 tmp+rename 原子落盘,进程重启后可读回。
store/redis
Package redis 提供 store.KV 的 redis 后端。
Package redis 提供 store.KV 的 redis 后端。
utils/decode
Package decode 是协议实现方的配置解码工具:把 map 形式的配置解码到 各实现自己的强类型配置结构上(JSON round-trip,足够覆盖 YAML 基础类型)。
Package decode 是协议实现方的配置解码工具:把 map 形式的配置解码到 各实现自己的强类型配置结构上(JSON round-trip,足够覆盖 YAML 基础类型)。
utils/redisconn
Package redisconn 定义 agent-kit 对 redis 的全部诉求(Client 能力面 接口),并提供基于官方 go-redis 的实现。
Package redisconn 定义 agent-kit 对 redis 的全部诉求(Client 能力面 接口),并提供基于官方 go-redis 的实现。
utils/redisconn/redisconntest
Package redisconntest 提供 redisconn.Client 的内存实现:测试里替代 真 redis,同时是第三方实现 Client 接口的最小参考——每个方法的语义 契约(Update 的原子性、Get 的 ok 语义、LRange 的负下标)在此逐一体现。
Package redisconntest 提供 redisconn.Client 的内存实现:测试里替代 真 redis,同时是第三方实现 Client 接口的最小参考——每个方法的语义 契约(Update 的原子性、Get 的 ok 语义、LRange 的负下标)在此逐一体现。
internal
testmodel
Package testmodel 提供测试用的脚本化 ChatModel:按预设序列返回消息, 无需真实模型即可端到端验证循环、skill、workflow 等机制。
Package testmodel 提供测试用的脚本化 ChatModel:按预设序列返回消息, 无需真实模型即可端到端验证循环、skill、workflow 等机制。
protocol
channel
Package channel 定义 IM 接入层(Ring 1):飞书、钉钉、Slack 等各写 一个适配器,agent 侧零改动。
Package channel 定义 IM 接入层(Ring 1):飞书、钉钉、Slack 等各写 一个适配器,agent 侧零改动。
exec
Package exec 定义脚本执行沙箱的协议(可扩展接缝):脚本在哪个隔离环境 执行,由使用方实现并注册(docker/远程/WASM/…),核心不含沙箱实现(官方 docker 实现见 impl/exec/docker)。
Package exec 定义脚本执行沙箱的协议(可扩展接缝):脚本在哪个隔离环境 执行,由使用方实现并注册(docker/远程/WASM/…),核心不含沙箱实现(官方 docker 实现见 impl/exec/docker)。
memory
Package memory 提供长期记忆:以 memory_save / memory_search 能力 暴露给模型,由大脑决定何时记、何时查——这是"模型自主"的部分。
Package memory 提供长期记忆:以 memory_save / memory_search 能力 暴露给模型,由大脑决定何时记、何时查——这是"模型自主"的部分。
model
Package model 是模型协议:按 provider 注册工厂、按配置构造 ChatModel。
Package model 是模型协议:按 provider 注册工厂、按配置构造 ChatModel。
prompt
Package prompt 把提示词抽象为 provider 供给的资源:按名字+版本通道 拉取,渲染变量,版本随轨迹打点可回溯。
Package prompt 把提示词抽象为 provider 供给的资源:按名字+版本通道 拉取,渲染变量,版本随轨迹打点可回溯。
resource
Package resource is the single read-only resource abstraction for the framework: configuration (app/agent/namespace), prompt files, secrets files, and skill-pack contents all load through an io/fs.FS resolved from a resource ref.
Package resource is the single read-only resource abstraction for the framework: configuration (app/agent/namespace), prompt files, secrets files, and skill-pack contents all load through an io/fs.FS resolved from a resource ref.
secrets
Package secrets 是凭证协议:配置文本中的 ${ENV_VAR} 与 ${secret:NAME} 占位符在加载时展开,凭证不落配置文件。
Package secrets 是凭证协议:配置文本中的 ${ENV_VAR} 与 ${secret:NAME} 占位符在加载时展开,凭证不落配置文件。
session
Package session 定义会话历史存储的契约与工厂,不含具体后端实现。
Package session 定义会话历史存储的契约与工厂,不含具体后端实现。
source
Package source 定义能力供给的三层中第一层:Source(供给源)。
Package source 定义能力供给的三层中第一层:Source(供给源)。
store
Package store 提供 KV 家族存储原语。
Package store 提供 KV 家族存储原语。
vectorstore
Package vectorstore 定义向量库后端的协议(可扩展接缝):真实向量库 (qdrant/milvus/自定义)实现 eino Retriever 并在这里注册,由 vector 检索 工具源(impl/source/vector)按名字选用。
Package vectorstore 定义向量库后端的协议(可扩展接缝):真实向量库 (qdrant/milvus/自定义)实现 eino Retriever 并在这里注册,由 vector 检索 工具源(impl/source/vector)按名字选用。
runtime
engine
Package engine 提供执行引擎模板(Ring 1)。
Package engine 提供执行引擎模板(Ring 1)。
loop
dedup.go:重复调用断路器(Ring 0)。
dedup.go:重复调用断路器(Ring 0)。
observe
events.go:结构化进度事件的发射点(设计见 docs/channel-card-design.md §2.3)。
events.go:结构化进度事件的发射点(设计见 docs/channel-card-design.md §2.3)。
suspend
Package suspend 实现挂起/恢复的持久化:ask_user 与审批等待不再靠 进程内 goroutine 阻塞——飞书审批常常跨小时甚至隔天,占着 goroutine 等、进程一重启全丢,撑不住真实使用。
Package suspend 实现挂起/恢复的持久化:ask_user 与审批等待不再靠 进程内 goroutine 阻塞——飞书审批常常跨小时甚至隔天,占着 goroutine 等、进程一重启全丢,撑不住真实使用。
decorate.go:出站装饰与进度订阅的第三方扩展面 (设计见 docs/channel-card-design.md §2.4/§3)。
decorate.go:出站装饰与进度订阅的第三方扩展面 (设计见 docs/channel-card-design.md §2.4/§3)。
agent.go 装配声明式 sub-agent:与主循环**同构**的隔离子循环—— 同一套 harness(L1 纪律 + Ring 0 闸门 + 评审循环 + 压缩),只是 persona/工具面/画像不同。
agent.go 装配声明式 sub-agent:与主循环**同构**的隔离子循环—— 同一套 harness(L1 纪律 + Ring 0 闸门 + 评审循环 + 压缩),只是 persona/工具面/画像不同。
Package std 空导入即拉起 agent-kit 的默认存储后端(session/memory 的 inmemory·file·bigram + store.KV 的 file),恢复开箱即用的 zero-config。
Package std 空导入即拉起 agent-kit 的默认存储后端(session/memory 的 inmemory·file·bigram + store.KV 的 file),恢复开箱即用的 zero-config。
Package todo 提供内置的计划外化能力(todo_write/todo_read)。
Package todo 提供内置的计划外化能力(todo_write/todo_read)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL