@migor/agentia 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +969 -64
  2. package/LICENSE +1 -1
  3. package/README.en.md +93 -38
  4. package/README.md +105 -218
  5. package/dist/AGENTS.md +234 -41
  6. package/dist/core/limits.d.ts +33 -1
  7. package/dist/core/limits.js +37 -1
  8. package/dist/core/limits.js.map +1 -1
  9. package/dist/core/run.d.ts +2 -2
  10. package/dist/core/timeout.d.ts +5 -5
  11. package/dist/core/timeout.js +5 -5
  12. package/dist/core/tool.d.ts +2 -2
  13. package/dist/core/trace-sink.d.ts +8 -0
  14. package/dist/core/trace-sink.js +24 -0
  15. package/dist/core/trace-sink.js.map +1 -0
  16. package/dist/core/trace.d.ts +12 -2
  17. package/dist/core/trace.js +10 -1
  18. package/dist/core/trace.js.map +1 -1
  19. package/dist/engine/concurrency.d.ts +1 -1
  20. package/dist/engine/concurrency.js +1 -1
  21. package/dist/engine/errors.d.ts +2 -2
  22. package/dist/engine/errors.js +2 -2
  23. package/dist/engine/loop-result.d.ts +3 -3
  24. package/dist/engine/loop.js +99 -56
  25. package/dist/engine/loop.js.map +1 -1
  26. package/dist/engine/mcp-server-http.js +1 -1
  27. package/dist/engine/mcp-server-http.js.map +1 -1
  28. package/dist/engine/mcp-server-stdio.js +7 -3
  29. package/dist/engine/mcp-server-stdio.js.map +1 -1
  30. package/dist/engine/mcp-server.d.ts +17 -2
  31. package/dist/engine/mcp-server.js +42 -17
  32. package/dist/engine/mcp-server.js.map +1 -1
  33. package/dist/engine/resume-input.d.ts +1 -1
  34. package/dist/engine/resume-input.js +1 -1
  35. package/dist/engine/retry.d.ts +15 -0
  36. package/dist/engine/retry.js +51 -2
  37. package/dist/engine/retry.js.map +1 -1
  38. package/dist/engine/span-scope.d.ts +15 -0
  39. package/dist/engine/span-scope.js +8 -1
  40. package/dist/engine/span-scope.js.map +1 -1
  41. package/dist/engine/spec.d.ts +5 -1
  42. package/dist/engine/spec.js.map +1 -1
  43. package/dist/engine/tracer.d.ts +8 -4
  44. package/dist/engine/tracer.js +24 -17
  45. package/dist/engine/tracer.js.map +1 -1
  46. package/dist/engine/turn.d.ts +2 -2
  47. package/dist/engine/turn.js +19 -4
  48. package/dist/engine/turn.js.map +1 -1
  49. package/dist/engine/types.d.ts +13 -5
  50. package/dist/engine/types.js +1 -0
  51. package/dist/engine/types.js.map +1 -1
  52. package/dist/eval/export.js +2 -2
  53. package/dist/eval/export.js.map +1 -1
  54. package/dist/eval/harvest.d.ts +4 -1
  55. package/dist/eval/harvest.js +32 -6
  56. package/dist/eval/harvest.js.map +1 -1
  57. package/dist/index.d.ts +3 -1
  58. package/dist/index.js +5 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/integrations/anthropic.d.ts +1 -1
  61. package/dist/integrations/anthropic.js +1 -1
  62. package/dist/integrations/anthropic.js.map +1 -1
  63. package/dist/integrations/file-sink.d.ts +0 -16
  64. package/dist/integrations/file-sink.js +24 -6
  65. package/dist/integrations/file-sink.js.map +1 -1
  66. package/dist/integrations/mcp-http.js +4 -3
  67. package/dist/integrations/mcp-http.js.map +1 -1
  68. package/dist/integrations/mcp-protocol.d.ts +35 -10
  69. package/dist/integrations/mcp-protocol.js +19 -1
  70. package/dist/integrations/mcp-protocol.js.map +1 -1
  71. package/dist/integrations/mcp-stdio.js +91 -50
  72. package/dist/integrations/mcp-stdio.js.map +1 -1
  73. package/dist/integrations/mcp.d.ts +1 -1
  74. package/dist/integrations/mcp.js +19 -3
  75. package/dist/integrations/mcp.js.map +1 -1
  76. package/dist/integrations/metrics-otlp.d.ts +1 -1
  77. package/dist/integrations/metrics-otlp.js +8 -2
  78. package/dist/integrations/metrics-otlp.js.map +1 -1
  79. package/dist/integrations/metrics-render.js +17 -6
  80. package/dist/integrations/metrics-render.js.map +1 -1
  81. package/dist/integrations/metrics-state.d.ts +2 -2
  82. package/dist/integrations/metrics-state.js +4 -4
  83. package/dist/integrations/metrics-state.js.map +1 -1
  84. package/dist/integrations/metrics.d.ts +2 -2
  85. package/dist/integrations/otlp.d.ts +29 -6
  86. package/dist/integrations/otlp.js +128 -7
  87. package/dist/integrations/otlp.js.map +1 -1
  88. package/dist/integrations/report.d.ts +14 -2
  89. package/dist/integrations/report.js +39 -6
  90. package/dist/integrations/report.js.map +1 -1
  91. package/dist/runtime/run.d.ts +1 -1
  92. package/dist/runtime/run.js +19 -17
  93. package/dist/runtime/run.js.map +1 -1
  94. package/dist/store/fsStore.js +9 -5
  95. package/dist/store/fsStore.js.map +1 -1
  96. package/dist/store/record.d.ts +2 -2
  97. package/dist/store/record.js +1 -1
  98. package/dist/store/sqliteStore.d.ts +2 -2
  99. package/dist/store/sqliteStore.js +3 -3
  100. package/dist/store/sqliteStore.js.map +1 -1
  101. package/dist/store/store.d.ts +27 -7
  102. package/dist/store/store.js +43 -5
  103. package/dist/store/store.js.map +1 -1
  104. package/dist/toolkit/collect.d.ts +11 -5
  105. package/dist/toolkit/collect.js +10 -4
  106. package/dist/toolkit/collect.js.map +1 -1
  107. package/dist/toolkit/discover.js +27 -1
  108. package/dist/toolkit/discover.js.map +1 -1
  109. package/dist/toolkit/module.js +183 -50
  110. package/dist/toolkit/module.js.map +1 -1
  111. package/dist/toolkit/prompt.d.ts +1 -1
  112. package/dist/toolkit/prompt.js +2 -2
  113. package/dist/toolkit/prompt.js.map +1 -1
  114. package/dist/toolkit/subagent.d.ts +1 -1
  115. package/dist/toolkit/subagent.js +17 -3
  116. package/dist/toolkit/subagent.js.map +1 -1
  117. package/dist/transport/approval-policy.js +1 -1
  118. package/dist/transport/approval-policy.js.map +1 -1
  119. package/dist/transport/approval-supervisor.d.ts +8 -0
  120. package/dist/transport/approval-supervisor.js +23 -3
  121. package/dist/transport/approval-supervisor.js.map +1 -1
  122. package/dist/transport/async.d.ts +3 -0
  123. package/dist/transport/async.js +128 -25
  124. package/dist/transport/async.js.map +1 -1
  125. package/dist/transport/http-endpoints.js +1 -1
  126. package/dist/transport/http-route.js +2 -2
  127. package/dist/transport/http-route.js.map +1 -1
  128. package/dist/transport/http-shapes.d.ts +1 -1
  129. package/dist/transport/http-shapes.js +1 -1
  130. package/dist/transport/resume-scanner.d.ts +7 -0
  131. package/dist/transport/resume-scanner.js +40 -9
  132. package/dist/transport/resume-scanner.js.map +1 -1
  133. package/dist/transport/run-call-options.js +7 -3
  134. package/dist/transport/run-call-options.js.map +1 -1
  135. package/dist/transport/scheduler.js +5 -0
  136. package/dist/transport/scheduler.js.map +1 -1
  137. package/dist/transport/serve.d.ts +92 -0
  138. package/dist/transport/serve.js +155 -0
  139. package/dist/transport/serve.js.map +1 -0
  140. package/dist/transport/signal-supervisor.d.ts +10 -2
  141. package/dist/transport/signal-supervisor.js +15 -2
  142. package/dist/transport/signal-supervisor.js.map +1 -1
  143. package/dist/transport/wake-policy.d.ts +1 -1
  144. package/package.json +23 -10
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  [中文](./README.md) · [English](./README.en.md)
4
4
 
5
- [![CI](https://github.com/retrychx/agentia/actions/workflows/ci.yml/badge.svg)](https://github.com/retrychx/agentia/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/@migor/agentia)](https://www.npmjs.com/package/@migor/agentia) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ [![CI](https://github.com/migoric/agentia/actions/workflows/ci.yml/badge.svg)](https://github.com/migoric/agentia/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/@migor/agentia)](https://www.npmjs.com/package/@migor/agentia) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ **A declarative framework for building production agent services in TypeScript.** Declare four kinds of
8
+ capability with decorators + DI; a main agent orchestrates them; every run yields structured output and an
9
+ observable call tree (trace, cost, metrics).
6
10
 
7
11
  面向应用开发的声明式 agent 服务开发框架:装饰器 + DI 声明四类能力,主 agent 编排执行;每次 run 产出结构化结果与可观测调用树(trace、成本、指标),交付可直接上线的服务。
8
12
 
9
- **1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫** —— 这一组数是选型第一个问题(安装面有多大)的答案:
10
- `npm i @migor/agentia` 只往 `node_modules` 放**一个**包,不装任何厂商 SDK。
11
- 数字由 `tests/docs/scoreboard.test.ts` 每次现场复算,**手写的读数会腐烂**(本仓的老病)。
13
+ **1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫** —— 由 `tests/docs/scoreboard.test.ts` 每次现场复算。
12
14
 
13
15
  - [安装与配置](#安装与配置)
14
16
  - [快速开始](#快速开始)
@@ -25,62 +27,55 @@
25
27
  ## 安装与配置
26
28
 
27
29
  ```bash
28
- npx @migor/cli create my-app # 脚手架(含 .env / .env.example)
29
- cd my-app && npm install # 框架与 CLI 都装进工程
30
- $EDITOR .env # 填 ANTHROPIC_API_KEY(也可直接 export,真实环境变量优先)
31
- npm run dev # = agentia dev:本地 inspector 面板(输入 prompt / 选能力 / 选工作目录)
32
- # 可选:ANTHROPIC_BASE_URL(兼容端点)、AGENTIA_MODEL(缺省 claude-opus-5)
33
-
34
- # 工程内直接用:npx agentia g tool fetch-weather / npx agentia doctor / npx agentia --version
35
- # (脚手架把 @migor/cli 装进 devDependencies ⇒ 走本地 bin,离线可用、版本与工程一同 pin)
36
- # 首次创建必须带 scope —— npm 上另有一个别人的 `agentia` 包,短名会装错东西。
37
- # 想全局装(到处都能敲 agentia):npm i -g @migor/cli
30
+ npx @migor/cli create my-app --minimal # 最小模板:单文件 + 裸工具(含 .env / .env.example)
31
+ cd my-app && npm install
32
+ $EDITOR .env # 填 ANTHROPIC_API_KEY(也可直接 export,真实环境变量优先)
33
+ npm run dev -- "你的问题" # = tsx src/main.ts,跑一次并退出
38
34
  ```
39
35
 
40
- > 框架**不自动**读 `.env`:脚手架 `src/app.ts` 的 `loadEnvFile()` 负责把它读进 `process.env`。
41
- > 放在**装配模块**(app.ts)而不是任一个入口(`main.ts` / `batch.ts`),是因为 `agentia dev`
42
- > 只 import app.ts、从不执行入口 —— 写错一侧会让 `npm run dev` 静默读不到 `.env` 而 `npm start` 读得到。
43
- > 真实环境变量优先(CI / docker / 命令行永远赢过文件),`loadEnvFile({ override: true })` 才反过来。
44
-
45
- > **版本**:`0.10.2`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
46
- > `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
47
- > [`examples/README.md`](examples/README.md) 的「依赖说明」。
48
-
49
- > **运行时**:Node ≥ 18(`engines` 唯一要求)。CI 覆盖 **18 / 20 / 24** —— `import-floor` 矩阵跑
50
- > `engines` 声明的**下限**,其余 job 跟**当前 LTS** 走。按 Node 设计并测试,
51
- > **未对 Deno / edge 做验证**。唯一碰内置模块的 store 是 `SqliteTaskStore`(需 Node ≥ 22.5 的
52
- > `node:sqlite`)—— 未提供时**构造期给可读报错**,不影响包本身被导入。
36
+ - **首次创建必须带 scope** —— npm 上另有一个别人的 `agentia` 包,短名会装错东西。
37
+ - 可选:`ANTHROPIC_BASE_URL`(兼容端点)、`AGENTIA_MODEL`(缺省 `claude-opus-5`)。
38
+ - 框架**不自动**读 `.env`:模板入口的 `loadEnvFile()` 负责把它读进 `process.env`。
39
+ - **运行时**:Node ≥ 18(`engines` 唯一要求),CI 覆盖 18 / 20 / 24。按 Node 设计并测试;
40
+ **Bun / Deno 已实测可跑**(四步判定 + 各宿主最小代码 + 实测版本见 [`docs/frameworks.md`](docs/frameworks.md),
41
+ 门面上不写版本号 —— 会腐烂);**edge 仍未验证**。
42
+ `SqliteTaskStore` 需 Node ≥ 22.5 的 `node:sqlite`,未提供时构造期给可读报错。
43
+ - 工程内命令:`npx agentia g tool fetch-weather` / `npx agentia doctor` / `npx agentia --version`;
44
+ 想全局装:`npm i -g @migor/cli`。
53
45
 
46
+ > **版本**:`0.10.4`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。
54
47
 
55
48
  ## 快速开始
56
49
 
57
- 两条路线,按需选一即可(也可混用)。
58
-
59
50
  ### 路线 A:CLI 脚手架
60
51
 
52
+ minimal 先跑通模型与裸工具,不要求先理解装饰器、DI 或 HTTP 宿主。需要 inspector、四类能力目录和
53
+ 生产服务时,使用**不带 `--minimal` 的默认完整模板**(既有行为不变):
54
+
61
55
  ```bash
62
- agentia create my-app # 脚手架新项目(依赖 @migor/agentia)
63
- cd my-app && npm install
56
+ agentia create my-service
57
+ cd my-service && npm install
64
58
  agentia g tool weather # 生成 src/tools/weather/index.ts 并登记 src/registry.ts
65
59
  agentia g subagent doc-reviewer # 生成 src/subagents/doc-reviewer/{index.ts,system.md}
66
60
  agentia g skill note-writer # 生成 src/skills/note-writer/index.ts
67
61
  agentia g prompt style-guide # 生成 src/prompts/style-guide/{index.ts,asset.md}
68
- agentia dev # 本地 inspector 面板:输入 prompt 驱动一次 run + 看 trace
62
+ agentia dev # 本地 inspector:输入 prompt 驱动一次 run + 看 trace
69
63
  ```
70
64
 
65
+ 完整模板的 `src/main.ts` 是 HTTP 服务入口,`src/batch.ts` 是 `npm run start:batch` 的一次性入口。
66
+
71
67
  **目录约定**:四分类目录,一能力一文件夹 —— `src/tools/` · `src/skills/` · `src/prompts/` · `src/subagents/`。
72
- 目录名就是类型,不用记别名。每个文件夹的 `index.ts` default export 一个 provider 类,DI token 缺省 = 文件夹名;
73
- 长文本放文件夹内 `.md`,代码里用 `asset(import.meta.url, './system.md')` 读取。
68
+ 目录名就是类型;每个文件夹的 `index.ts` default export 一个 provider 类,DI token 缺省 = 文件夹名;
69
+ 长文本放文件夹内 `.md`,用 `asset(import.meta.url, './system.md')` 读取。
74
70
 
75
71
  ### 路线 B:手写装配
76
72
 
77
- 不依赖 CLI,直接声明能力并装配成应用:
73
+ 不依赖 CLI,直接声明能力并装配(与路线 A 可混用):
78
74
 
79
75
  ```ts
80
76
  import { Tool, createApp, SystemPrompt, RunContext } from '@migor/agentia';
81
77
 
82
78
  class WeatherTools {
83
- // 工具名缺省取方法名(建议 snake_case);入参 = 模型按 schema 解析的结构化 input
84
79
  @Tool({
85
80
  description: '查询城市天气',
86
81
  schema: {
@@ -92,9 +87,7 @@ class WeatherTools {
92
87
  strict: true,
93
88
  })
94
89
  get_weather(input: { city: string }): string {
95
- // 方法体内随时可读本次 run 的上下文(blackboard)。
96
- // 想让键有补全/校验:在一次 declare module 里合并 Blackboard(见 docs/usage-guide.md §5.1);
97
- // 未声明时键为 string、值为 unknown,这里按需断言。
90
+ // 方法体内可读本次 run 的上下文(blackboard);键的类型补全见 usage-guide §5.1
98
91
  const token = RunContext.current()?.get('authToken') as string | undefined;
99
92
  return `city=${input.city};token=${token ?? 'none'}`;
100
93
  }
@@ -108,16 +101,15 @@ const app = createApp({
108
101
 
109
102
  const { run, result } = await app.run(
110
103
  [{ role: 'user', content: '上海天气如何?' }],
111
- { blackboard: { authToken: 'sk-...' } }, // 预置本次 run 的上下文
104
+ { blackboard: { authToken: 'sk-...' } },
112
105
  );
113
106
  console.log(result.finalText); // 最终文本
114
107
  console.log(result.trace); // 调用树 + 每步 token/成本(traceId == runId)
115
108
  ```
116
109
 
117
- 装配还有**目录扫描**这条路(与显式注册表可混用):
110
+ 也可以按目录扫描(与显式注册表混用):
118
111
 
119
112
  ```ts
120
- // 按给定顺序扫各目录(返回 Promise);也可用 CLI 维护的 src/registry.ts 显式注册
121
113
  const app = await createApp({
122
114
  name: 'my-app',
123
115
  discover: ['src/tools', 'src/skills', 'src/prompts', 'src/subagents'],
@@ -125,6 +117,17 @@ const app = await createApp({
125
117
  });
126
118
  ```
127
119
 
120
+ ### 路线 C:已有服务
121
+
122
+ 不新建项目,也不重写现有服务。把已有函数包成 `@Tool`,在 `createServer` 里加**一条分支** ——
123
+ 业务路由优先(行为不变),其余交给 `createHttpHandler`;鉴权复用同一个函数,`drain()` 接进已有的 SIGTERM。
124
+
125
+ 六步、约 30 行胶水。可跑示例:[`examples/agent-endpoint/`](examples/agent-endpoint/) ——
126
+ 一个已在跑的订单服务(`src/orders.ts` 一行未改),旁边加了 `/run` + `/tasks`。
127
+
128
+ > 三个刻意的决定:**一条鉴权路径**(框架给缝,不给策略);**RAG 是你的工具**(用 `@Tool` 包自己的
129
+ > 检索,框架不内置);**不设预算旋钮 = 没有保险丝**。
130
+
128
131
  ## 四类能力
129
132
 
130
133
  主 agent 按 `description` 从同一张「能力菜单」自选;装饰器只决定**谁控制流程**:
@@ -136,62 +139,11 @@ const app = await createApp({
136
139
  | 子 agent | `@SubAgent` | **模型自己**(独立循环 + 裁剪上下文) | 自主多步、且中间过程不该污染主上下文 |
137
140
  | 提示资产 | `@Prompt` | 模型拉取(按需注入的文本) | 长文规范/模板,平时不进上下文 |
138
141
 
139
- `@Tool` 的写法见上方路线 B;其余三类:
140
-
141
- ```ts
142
- import { Skill, SubAgent, Prompt, asset, type SkillContext } from '@migor/agentia';
143
-
144
- // @Skill:代码控制的流程,模型调用只发生在显式 ctx.llm()
145
- class Notes {
146
- @Skill({ description: '按主题整理要点' })
147
- async note_writer(input: { topic: string }, ctx: SkillContext): Promise<string> {
148
- const r = await ctx.llm({ prompt: `就「${input.topic}」给出三个要点` });
149
- return r.text; // 返回值即产物,以 tool_result 交回主 agent
150
- }
151
- }
152
-
153
- // @SubAgent:独立循环 + 裁剪上下文,只有最终报告回流主上下文(方法体不执行)
154
- class Pipeline {
155
- @SubAgent({
156
- name: 'reviewer',
157
- description: '审查给定文档并输出书面评审意见',
158
- schema: { type: 'object', properties: { doc: { type: 'string' } }, required: ['doc'], additionalProperties: false },
159
- system: '你是评审 agent。结论必须以“审查通过/不通过”开头。',
160
- tools: ['weather'], // 子 agent 自己的工具菜单(provider token)
161
- })
162
- reviewer(_input: { doc: string }): void {}
163
- }
164
-
165
- // @Prompt:纯文本资产,模型判定需要时拉取进上下文
166
- class Assets {
167
- @Prompt({ description: '品牌基调文案规范' })
168
- style_guide(): string {
169
- return asset(import.meta.url, './asset.md'); // 从能力文件夹读 .md
170
- }
171
- }
172
- ```
173
-
174
- 四类一起装配(`providers` 里逐个登记;token 即子 agent / 技能引用工具的菜单名):
175
-
176
- ```ts
177
- import { createApp, SystemPrompt } from '@migor/agentia';
178
-
179
- const app = createApp({
180
- name: 'weather-app',
181
- providers: [
182
- { provide: 'weather', useClass: WeatherTools },
183
- { provide: 'notes', useClass: Notes },
184
- { provide: 'pipeline', useClass: Pipeline },
185
- { provide: 'assets', useClass: Assets },
186
- ],
187
- system: new SystemPrompt().add('role', '你是天气助手。', true),
188
- });
189
- ```
142
+ `@Tool` 见上方路线 B;其余三类的完整写法与 spec 字段见
143
+ [`docs/usage-guide.md` §3](docs/usage-guide.md)(四类可同时装配,token 即子 agent / 技能引用工具的菜单名)。
190
144
 
191
145
  ## 运行时特性
192
146
 
193
- ### 触发方式
194
-
195
147
  同一份应用,三种触发任选:
196
148
 
197
149
  ```ts
@@ -210,95 +162,53 @@ const scheduler = new Scheduler(runner);
210
162
  scheduler.every(60_000, '巡检一次', { idempotencyPrefix: 'patrol' });
211
163
  ```
212
164
 
213
- ### 长上下文预算
214
-
215
- ```ts
216
- import { createBudgetPolicy } from '@migor/agentia';
217
-
218
- const app = createApp({
219
- // ...
220
- contextPolicy: createBudgetPolicy({
221
- budgetTokens: 60_000, // 超预算先丢旧工具对,仍超且有 summarize 才压缩
222
- summarize: (text) => mySummarizer(text), // 可选,框架不替你调模型
223
- }),
224
- });
225
- ```
226
-
227
- ### 结构化结果
228
-
229
- ```ts
230
- const { result } = await app.run(messages, {
231
- resultSchema: {
232
- type: 'object',
233
- properties: { pass: { type: 'boolean' }, reason: { type: 'string' } },
234
- required: ['pass', 'reason'],
235
- additionalProperties: false,
236
- },
237
- });
238
- console.log(result.typed); // 校验过的结构化结果,不再从文本里猜 JSON
239
- // schema 也可来自 zod(peer 可选):fromZod(z.toJSONSchema(S), S)
240
- ```
241
-
242
- ### 中间件
165
+ 另外三处,按需查 [`docs/usage-guide.md`](docs/usage-guide.md):
243
166
 
244
- 挂在每一次能力调用前后的洋葱链——鉴权、限流、缓存、审计都走这里:
245
-
246
- ```ts
247
- const app = createApp({
248
- // ...
249
- middleware: [
250
- async (call, next) => {
251
- if (call.capability.name === 'danger_op' && !RunContext.current()?.get('isAdmin')) {
252
- return 'forbidden'; // 短路,不调 next 即拦截
253
- }
254
- return next(); // next(newInput) 还可改写入参
255
- },
256
- ],
257
- });
258
- ```
167
+ - **长上下文预算** `createBudgetPolicy({ budgetTokens, summarize })` —— 超预算先丢旧工具对,仍超且有 `summarize` 才压缩(§6.3)
168
+ - **结构化结果** `run(..., { resultSchema })` → `result.typed`,schema 也可来自 zod:`fromZod(z.toJSONSchema(S), S)`(§5.3)
169
+ - **中间件** `middleware: [(call, next) => ...]` —— 能力调用前后的洋葱链,鉴权/限流/缓存/审计都走这里(§6.6)
259
170
 
260
171
  ## 可观测性与成本
261
172
 
262
- 一次 run == 一条 trace(`traceId === runId`),**Turn 0 起内建** —— 不是外挂的第三方追踪 SDK 集成:
173
+ 一次 run == 一条 trace(`traceId === runId`),**Turn 0 起内建**,不是外挂的第三方追踪 SDK:
263
174
 
264
175
  ```ts
265
- const { run, result } = await app.run(messages);
176
+ const { result } = await app.run(messages);
266
177
  result.trace.spans; // 调用树:llm.turn / 能力 span / 事件
267
- result.trace.totalUsage; // token 汇总(只累加 llm.turn;能力 span 是子孙聚合,不参与求和)
178
+ result.trace.totalUsage; // token 汇总(只累加 llm.turn)
268
179
  ```
269
180
 
270
181
  - **每步记账**:span 属性带 model、input/output/cache tokens、成本估计、状态、错误类型
271
- - **出口是一条缝**:`TraceSink { export(trace) }` —— run 收尾(成功/失败两条路径)都投递,sink 抛错不影响 run。落库 / 采样 / 脱敏都在缝外用 sink 组合(实码见 `examples/observability/`,说明见 `docs/observability.md`)
182
+ - **一条缝**:`TraceSink { export(trace) }` —— run 收尾(成功/失败)都投递,sink 抛错不影响 run;落库/采样/脱敏在缝外用 sink 组合(`docs/observability.md`)
272
183
  - **指标**:`metricsSink` 满足 `TraceSink` 即可接入(Prometheus 文本 / OTLP metrics),零依赖
273
- - **成本**:`priceOverrides` 注入价目表;未定价模型显式发 `usage.unpriced` 事件;`createBudgetGuard` 做**硬管控**(超限 run 以 `budget_exceeded` 收尾、算失败)
274
- - **调优闭环**:`buildRunReport` 出「能力 / 模型的耗时·token·成本·错误率排行」,CLI `agentia report <trace.jsonl>` 直接渲染
275
- - **回放**:`traceToMessages` 把已完成的 trace 线性化喂回模型(调试基底)
276
- - **本地开发**:`agentia dev` 的 inspector 面板与 `@migor/trace-view` 共用同一份渲染器(避免两处漂移)
277
- - **导出 OTLP**:
278
-
279
- ```ts
280
- import { createOtlpExporter } from '@migor/agentia';
281
-
282
- await createOtlpExporter({ endpoint: 'http://localhost:4318' }).export(result.trace);
283
- ```
284
-
285
- > 权威口径见 `docs/usage-guide.md` 的「观测」与「成本硬管控」两节。
184
+ - **成本**:`priceOverrides` 注入价目表;未定价模型发 `usage.unpriced` 事件;`createBudgetGuard` 做硬管控(超限以 `budget_exceeded` 收尾、算失败)
185
+ - **调优闭环**:`buildRunReport` 出「能力 / 模型的耗时·token·成本·错误率排行」,`agentia report <trace.jsonl>` 直接渲染
186
+ - **回放**:`traceToMessages` 把已完成的 trace 线性化喂回模型
286
187
 
287
188
  ## 集成
288
189
 
289
- ### HTTP 宿主
190
+ **HTTP 宿主**(`POST /run` 同步 / `POST /tasks` 异步 / `GET /tasks/:id` 轮询):
290
191
 
291
192
  ```ts
292
193
  import { createServer } from 'node:http';
293
194
  import { createHttpHandler } from '@migor/agentia';
294
195
 
295
- // POST /run(同步) POST /tasks(异步) GET /tasks/:id(轮询)
296
196
  createServer(createHttpHandler(app, { runner })).listen(8080);
297
197
  ```
298
198
 
299
- ### MCP(连接器出厂自带)
199
+ **Web 框架宿主** —— `createHttpHandler` 返回的就是 `(req, res) => void`,任何能挂原始 node handler 的地方都能挂,不需要适配层:
300
200
 
301
- MCP server 的工具映射成菜单项 —— 连接器(stdio / StreamableHTTP)**随框架发布**,只用标准库(`node:child_process` + 全局 `fetch`),不新增第三方依赖:
201
+ | 宿主 | 最小接入 | 必须配的 |
202
+ |---|---|---|
203
+ | `node:http` | `createServer(createHttpHandler(app, { runner }))` | 无 |
204
+ | NestJS 12(CJS / ESM) | `nestApp.use('/agent', handler)` 三行 | CJS 模式需 Node ≥ 22.12(或 `await import()`) |
205
+ | Egg 4 | 中间件 + `ctx.respond = false` | CSRF / bodyParser 两条 ignore(字符串写法会让 worker 启动即崩) |
206
+ | Bun / Deno | 与 `node:http` 相同,零改动 | Deno 的 `--allow-net/read/write/run` |
207
+
208
+ cluster / 多实例下 task store **必须共享** —— 每 worker 一份内存 store,同一个 taskId 轮询会 `404 200` 交替。
209
+ 结论表、最小代码与 Egg 的三个坑见 [`docs/frameworks.md`](docs/frameworks.md)。
210
+
211
+ **MCP** —— 连接器(stdio / StreamableHTTP)随框架发布,只用标准库,不新增第三方依赖:
302
212
 
303
213
  ```ts
304
214
  import { createApp, createStdioMcpConnector, createStreamableHttpMcpConnector, mcpTools } from '@migor/agentia';
@@ -316,9 +226,9 @@ const remote = createStreamableHttpMcpConnector('https://mcp.example/mcp', {
316
226
  createApp({ system, providers: [...], tools });
317
227
  ```
318
228
 
319
- `McpClientLike` 这条缝仍然在:接官方 SDK / 远程 server / 自研传输时实现 `listTools()` + `callTool()` 两个方法即可(框架**不 import** MCP SDK)。
229
+ `McpClientLike` 这条缝仍然在:接官方 SDK / 自研传输时实现 `listTools()` + `callTool()` 两个方法即可(框架**不 import** MCP SDK)。
320
230
 
321
- ### 多模型(OpenAI 兼容端点)
231
+ **多模型(OpenAI 兼容端点)**:
322
232
 
323
233
  ```ts
324
234
  import { createOpenAIClient } from '@migor/agentia';
@@ -329,20 +239,7 @@ const { result } = await app.run(messages, {
329
239
  });
330
240
  ```
331
241
 
332
- > **本地模型(Ollama 等)**:Ollama 暴露 OpenAI 兼容端点,用同一个适配器即可 ——
333
- > `createOpenAIClient({ baseURL: 'http://localhost:11434/v1' })` + `model: 'qwen3'`,
334
- > **不需要任何新代码或新依赖**。
335
-
336
-
337
- ### 跨 run 记忆
338
-
339
- ```ts
340
- import { InMemoryMemoryStore } from '@migor/agentia';
341
-
342
- const { result } = await app.run(messages, {
343
- memory: { store: new InMemoryMemoryStore(), keys: ['profile'] },
344
- });
345
- ```
242
+ Ollama 等本地模型同样走这个适配器(`baseURL: 'http://localhost:11434/v1'`),不需要新代码或新依赖。
346
243
 
347
244
  ## API 速查
348
245
 
@@ -358,67 +255,58 @@ const { result } = await app.run(messages, {
358
255
  | `AsyncRunner` / `Scheduler` / `runSync` / `createHttpHandler` | 异步 / 定时 / 同步 / HTTP 触发 |
359
256
  | `FileTaskStore` / `SqliteTaskStore` / `RedisTaskStore` / `InMemoryTaskStore` | 任务记录存储 |
360
257
  | `createBudgetPolicy` | 长上下文预算护栏 |
361
- | `createAnthropicClient` | 默认 ModelClient(Anthropic):自定义只传 `apiKey` / `baseURL`,不必直接依赖厂商 SDK |
258
+ | `createAnthropicClient` | 默认 ModelClient(Anthropic):自定义只传 `apiKey` / `baseURL` |
362
259
  | `createOpenAIClient` | OpenAI 兼容端点适配(多模型) |
363
260
  | `InMemoryMemoryStore`(`memory` 选项) | 跨 run 记忆水合/回写 |
364
261
  | `createOtlpExporter` | trace 导出 OTLP |
365
- | `traceToMessages` | trace 重放基底:把完成的 run 还原成消息喂回模型调试 |
262
+ | `traceToMessages` | trace 重放基底 |
366
263
  | `fromZod` | zod schema 接入(peer 可选) |
367
264
  | `runAgent` / `executeRun` | 裸引擎入口(不走装配) |
368
265
 
369
266
  ## 稳定性与版本策略
370
267
 
371
268
  **`0.x` 的含义**:版本号按 SemVer,但 `0.x` 期间 **minor 版本可以包含破坏性变更**。
372
- 规矩只有一条 —— **破坏性变更必须留痕**:`CHANGELOG.md` 对应的那个版本里要有一段专门写它
269
+ 规矩只有一条 —— **破坏性变更必须留痕**:`CHANGELOG.md` 对应版本里有一段专门写它
373
270
  (小节标题含「迁移」或「破坏性变更」),写清「**你要改什么**」。
374
271
 
375
272
  ```bash
376
273
  grep -nE '^#{3,4} .*(迁移|破坏性)' CHANGELOG.md # 列出所有「要你做动作」的小节
377
274
  ```
378
275
 
379
- > ⚠️ **如实标注**:这条纪律是**逐步收紧**的 —— 早期的 `0.7.1`(类型面变更)把动作写在版本条目
380
- > 开头的引用块里、没有独立小节,`0.8.1`~`0.8.3` 用一行「**迁移**:无」表态。⇒ 上面那条命令是
381
- > **下限**而非全集,要读全就通读该版本的条目。
382
- > **「N 个版本 / M 次迁移」这类读数本 README 刻意不写死** —— 写死的读数会腐烂(本仓为此吃过教训),
383
- > 所以给命令、不给数字。
276
+ > 这条命令是**下限而非全集**:早期版本没按这个格式写,要读全就通读该版本条目。
277
+ > 版本数、迁移次数这类读数刻意不写死 —— 写死的读数会腐烂,所以给命令、不给数字。
384
278
 
385
279
  **承诺的范围**:
386
280
 
387
281
  | 面 | 承诺 | **不**承诺 |
388
282
  |---|---|---|
389
- | 公共 API | `@migor/agentia` 的**具名导出**(真源 `src/index.ts`;官网 API 页有反向全覆盖守卫)+ `@migor/cli` 的命令行面 | 深路径导入(`dist/**`)、`src/**` 内部模块路径、`packages/*`、`examples/*` —— 这条边界**由 `package.json` 的 `exports` 字段封着**(`tests/architecture/package-exports.test.ts` 守;加 `exports` 之前 `dist/**` 真的导得进来) |
283
+ | 公共 API | `@migor/agentia` 的**具名导出**(真源 `src/index.ts`)+ `@migor/cli` 的命令行面 | 深路径导入(`dist/**`)、`src/**` 内部模块、`packages/*`、`examples/*` —— 由 `package.json` 的 `exports` 封着 |
390
284
  | 行为 | 该版本条目里写明的**迁移动作** | 没写进迁移说明的任何行为变化 |
391
- | 运行环境 | `engines.node` 声明的区间;CI 在**该区间内每个版本**上跑完整条链 | Deno / Bun / edge runtime(**未验证**);已 EOL 的 Node(会随 minor 去掉) |
392
- | 依赖 | **运行时零第三方依赖**(`tests/architecture/no-runtime-deps.test.ts` 守着,非口头承诺) | `devDependencies` 与 `examples/*` 的依赖面 |
393
-
394
- **1.0 的门槛**(三条都是可核验的,不是口号):
285
+ | 运行环境 | `engines.node` 声明的区间;CI 在区间内每个版本跑完整条链 | edge(**未验证**);已 EOL 的 Node。Bun / Deno 已实测但**不在 `engines` 承诺内**([`docs/frameworks.md`](docs/frameworks.md)) |
286
+ | 依赖 | **运行时零第三方依赖**(`no-runtime-deps.test.ts` 守着,非口头承诺) | `devDependencies` 与 `examples/*` 的依赖面 |
395
287
 
396
- 1. `docs/spec.md` §11「开放项」里每一条要么落地、要么被**明确划掉**(写清为什么不阻塞);
397
- 2. `docs/guards.md` §2「待守」为空 —— 已声明的边界都由机器守着,而不是靠人记得;
398
- 3. 公共导出面**连续 3 个 minor 无破坏性变更**。
288
+ **1.0 的门槛**(三条都可核验):
399
289
 
400
- 在此之前,请把每个 minor 当成「**这次修的可能是某个承诺没兑现的地方**」——这正是 `0.x` 的含义,
401
- 也是 `CHANGELOG.md` 每条都写「为什么」而不是「做了什么」的原因。
290
+ 1. `docs/spec.md` §11「开放项」每一条要么落地、要么被明确划掉(写清为什么不阻塞);
291
+ 2. `docs/guards.md` §2「待守」为空 —— 已声明的边界由机器守着,不靠人记得;
292
+ 3. 公共导出面连续 3 个 minor 无破坏性变更。
402
293
 
403
294
  ## 性能量级
404
295
 
405
- 框架自带五个基准(`scripts/bench-*.ts`)。它们要回答的是**形状**问题(哪笔钱在什么条件下付、
406
- 换实现能省掉的是什么),不是给你一个 SLA:
296
+ 框架自带五个基准(`scripts/bench-*.ts`)。它们回答的是**形状**问题(哪笔钱在什么条件下付),不是 SLA:
407
297
 
408
298
  | 你想问的 | 本机读数(macOS / Node 24,**量级**) | 形状结论(**不会随机器变**的那部分) | 复跑 |
409
299
  | --- | --- | --- | --- |
410
- | 装配一次 app 多贵 | 纯装配 `0.4 ms`;发现+装配 `8.1 ms`;整进程启动+装配 `4040 ms`(其中「冷装配」≈2963 ms,整进程启动本身就 1077 ms) | 「换一次能力选择」在**常驻 runner** 里只付 `S2`(≈8 ms),**不需要**付 `S6b`(≈4 s)⇒ 差 **~500×**。那 4 s 里 ~1.1 s 是 `tsx` 解释器启动,与框架无关 | `npm run bench:assembly` |
411
- | MCP 连接器什么时候付钱 | 冷(spawn + initialize + list)`279.5 ms`;热(复用连接器,仅一次 `tools/list`)`0.3 ms` | 只有**重建连接器**才付冷的那一笔 ⇒ 差 **~900×**。所以「重建 app 含 MCP 握手」成不成立,**取决于连接器跟谁同寿** | `npm run bench:assembly` |
412
- | trace 记不记内容,差多少 | 大出参(≈37 KB/次)5 次调用:缺省 `17.2 KB` / 不截断 `233.9 KB`(**13.6×**)/ 截断 200 `6.2 KB`;20 次:`65 KB → 932 KB`(14.3×) | 缺省**已经截断**(≈14× 的量级差来自关掉截断)。`traceContent:'full'` 是**按出参字节付费**的开关 —— 小出参档实测只差 1.0× ⇒ 代价与你的出参多大成正比 | `PAYLOAD_ROWS=1000 CALLS='[5,20]' npm run bench:trace` |
413
- | 指标快照 / OTLP flush 的成本 | `snapshot()` `6.56 ms`(30 个组合 × 2 次分位);`buildOtlpPayload()` `0.073 ms` | flush 的成本**几乎全在 `snapshot()`** ⇒ 省掉它 ≈ 每次 flush 省 6.5 ms,**与 flush 频率成正比**,与 OTLP 是否被消费无关 | `npm run bench:otlp` |
414
- | 挂起任务的到期扫描 | sqlite N=10000:全表 `list()` `61.9 ms` / 走到期索引 `listDue` `0.1 ms`;file store N=10000:`load` `69.5 ms`、`list` `0.0 ms` | **有没有索引**是结构性的(差 **~600×**),不是调优问题。file store 的记录在内存里 ⇒ 它贵的那笔是**构造期的全表 `load`** | `npx tsx scripts/bench-resume-scan.ts 10000` |
415
- | redis store 的 `list()` 形状 | N=10000:往返 **10001** 次、解析 **42.1 MB**、纯 CPU `104.1 ms`;有辅助 ZSET(`score = wakeAt`)⇒ 往返 **1001** | 往返次数是**结构量**(换实现能省掉的就是它);本机无 redis 实例,延迟只做外推(RTT 0.2ms ⇒ 2 s) | `npx tsx scripts/bench-redis-due.ts 10000` |
416
-
417
- > ⚠️ **毫秒数不是承诺**:它们随机器 / Node 版本 / 负载而变,抄走当 SLA 会错。
418
- > 真正恒定的是**形状**(差多少倍、往返几次、哪一笔跟什么成正比)—— 那才是这五个基准要说的东西。
300
+ | 装配一次 app 多贵 | 纯装配 `0.4 ms`;发现+装配 `8.1 ms`;整进程启动+装配 `4040 ms` | 常驻 runner 里换一次能力选择只付 `S2`(≈8 ms),不需要付整进程启动(≈4 s)⇒ 差 **~500×** | `npm run bench:assembly` |
301
+ | MCP 连接器什么时候付钱 | 冷(spawn + initialize + list)`279.5 ms`;热(复用,仅 `tools/list`)`0.3 ms` | 只有**重建连接器**才付冷的那一笔 ⇒ 差 **~900×**;是否值得重建取决于连接器跟谁同寿 | `npm run bench:assembly` |
302
+ | trace 记不记内容,差多少 | 大出参(≈37 KB/次)5 次:缺省 `17.2 KB` / 不截断 `233.9 KB`(**13.6×**)/ 截断 200 `6.2 KB` | 缺省**已经截断**;`traceContent:'full'` 是按出参字节付费的开关,代价与出参多大成正比 | `PAYLOAD_ROWS=1000 CALLS='[5,20]' npm run bench:trace` |
303
+ | 指标快照 / OTLP flush | `snapshot()` `6.56 ms`;`buildOtlpPayload()` `0.073 ms` | flush 成本几乎全在 `snapshot()` ⇒ 与 flush 频率成正比,与是否被消费无关 | `npm run bench:otlp` |
304
+ | 挂起任务的到期扫描 | sqlite N=10000:全表 `61.9 ms` / 到期索引 `0.1 ms`;file store:`load` `69.5 ms` | **有没有索引**是结构性的(差 **~600×**),不是调优问题 | `npx tsx scripts/bench-resume-scan.ts 10000` |
305
+ | redis store 的 `list()` 形状 | N=10000:往返 **10001** 次、解析 **42.1 MB**;有辅助 ZSET ⇒ 往返 **1001** | 往返次数是结构量;本机无 redis 实例,延迟只做外推 | `npx tsx scripts/bench-redis-due.ts 10000` |
306
+
307
+ > ⚠️ **毫秒数不是承诺**:随机器 / Node 版本 / 负载而变,抄走当 SLA 会错。恒定的是**形状**。
419
308
  >
420
- > 五个基准**刻意不进 CI**(计时类基准在 CI 机器上只会制造抖动;`docs/plans/2026-09-22-dev-debug-loop.md`
421
- > 定下的处置是「要看时跑」)⇒ 上表的数**没有守卫帮你盯着**,要看就按「复跑」那一列自己跑一遍。
309
+ > 五个基准**刻意不进 CI**(计时类基准在 CI 上只会制造抖动)⇒ 上表的数没有守卫盯着,要看就按「复跑」那列自己跑。
422
310
 
423
311
  ## 开发本仓库
424
312
 
@@ -428,20 +316,19 @@ npm run build # 编译框架(dist/)
428
316
  npm run typecheck # 类型检查
429
317
  npm run lint # lint + 格式检查(Biome)
430
318
  npm test # 单元测试(node:test)
431
- npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete 与 examples/deploy(含崩溃续跑)
319
+ npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete 与 examples/deploy
432
320
  ```
433
321
 
434
- > 注:本仓库的 `npm run dev`(tsx)前需先 `npm approve-scripts` 批准 esbuild/tsx 的 postinstall;
435
- > 脚手架项目里的 `agentia dev` 不受此限。
322
+ > 本仓库的 `npm run dev`(tsx)前需先 `npm approve-scripts` 批准 esbuild/tsx 的 postinstall;脚手架项目不受此限。
436
323
 
437
324
  ## 延伸阅读
438
325
 
439
- - **使用者向完整说明**(API 速查 / 类型链路 / 已知边界 / 反例):[`docs/usage-guide.md`](docs/usage-guide.md)
326
+ - **使用者向完整说明**(API / 类型链路 / 已知边界 / 反例):[`docs/usage-guide.md`](docs/usage-guide.md)
440
327
  - 完整导出清单:[`src/index.ts`](src/index.ts)
441
328
  - 设计规格:[`docs/spec.md`](docs/spec.md)
442
- - 生产可观测配方(落库检索 / 日志关联 / 采样 / 脱敏):[`docs/observability.md`](docs/observability.md)
443
- - 上线清单(store 选型 / 鉴权边界 / 优雅停机 / 预算 / 回滚 / 上线自检):[`docs/deployment.md`](docs/deployment.md)
444
- - 完整示例(四类能力 + 三种触发 + 全观测栈):[`examples/complete/`](examples/complete/)
445
- - 最小部署示例:[`examples/deploy/`](examples/deploy/)
446
- - 官网:[agentia-web.pages.dev](https://agentia-web.pages.dev)([在线 Playground](https://agentia-web.pages.dev/playground) · [文档](https://agentia-web.pages.dev/docs) · [取舍对照:不做什么 / 什么场景该选别人](https://agentia-web.pages.dev/tradeoffs))
447
- · **英文版** [agentia-web.pages.dev/en](https://agentia-web.pages.dev/en)(导航右侧可切换;首次访问会按浏览器语言自动落到中文或英文)
329
+ - 生产可观测配方:[`docs/observability.md`](docs/observability.md)
330
+ - 上线清单(store 选型 / 鉴权边界 / 优雅停机 / 预算 / 回滚):[`docs/deployment.md`](docs/deployment.md)
331
+ - 接数据库(业务数据构造注入 / 自建 `TaskStore` 的 MySQL 骨架 / 别把业务事务和 run 混一个事务):[`docs/database.md`](docs/database.md)
332
+ - 框架兼容性实测(NestJS / Egg / Bun / Deno 结论表 + 最小代码 + Egg 三坑):[`docs/frameworks.md`](docs/frameworks.md)
333
+ - 完整示例:[`examples/complete/`](examples/complete/) · 最小部署:[`examples/deploy/`](examples/deploy/) · 存量改造:[`examples/agent-endpoint/`](examples/agent-endpoint/)
334
+ - 官网:[agentia-web.pages.dev](https://agentia-web.pages.dev)([Playground](https://agentia-web.pages.dev/playground) · [文档](https://agentia-web.pages.dev/docs) · [取舍对照](https://agentia-web.pages.dev/tradeoffs))