@linnlabs/linnkit 0.8.0

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 (123) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/LICENSE +21 -0
  3. package/README.md +178 -0
  4. package/README.zh-CN.md +182 -0
  5. package/dist/agent-invocation-BHcNfrBV.d.cts +30 -0
  6. package/dist/agent-invocation-BznDaXDs.d.ts +30 -0
  7. package/dist/agentEvents-DEB7Fy_J.d.cts +81 -0
  8. package/dist/agentEvents-DEB7Fy_J.d.ts +81 -0
  9. package/dist/agentSpec-EkmviZjy.d.cts +2621 -0
  10. package/dist/agentSpec-EkmviZjy.d.ts +2621 -0
  11. package/dist/ai-engine.types-BpeU_XQG.d.cts +158 -0
  12. package/dist/ai-engine.types-vZRnQcJa.d.ts +158 -0
  13. package/dist/audit-BaRUGaqv.d.cts +307 -0
  14. package/dist/audit-BaRUGaqv.d.ts +307 -0
  15. package/dist/audit-CtcfART1.d.ts +33 -0
  16. package/dist/audit-LeOrm2hX.d.cts +33 -0
  17. package/dist/checkpointMarker-DAI3wUQu.d.cts +8 -0
  18. package/dist/checkpointMarker-DAI3wUQu.d.ts +8 -0
  19. package/dist/cli.cjs +8028 -0
  20. package/dist/cli.cjs.map +1 -0
  21. package/dist/cli.d.cts +4 -0
  22. package/dist/cli.d.ts +4 -0
  23. package/dist/cli.js +8025 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/context-manager.cjs +8704 -0
  26. package/dist/context-manager.cjs.map +1 -0
  27. package/dist/context-manager.d.cts +2190 -0
  28. package/dist/context-manager.d.ts +2190 -0
  29. package/dist/context-manager.js +8650 -0
  30. package/dist/context-manager.js.map +1 -0
  31. package/dist/context-trace-DRi5M4lX.d.ts +239 -0
  32. package/dist/context-trace-HE2qY5Q-.d.cts +239 -0
  33. package/dist/contracts.cjs +1333 -0
  34. package/dist/contracts.cjs.map +1 -0
  35. package/dist/contracts.d.cts +8 -0
  36. package/dist/contracts.d.ts +8 -0
  37. package/dist/contracts.js +1214 -0
  38. package/dist/contracts.js.map +1 -0
  39. package/dist/defaultGraphExecutor-BBswR8wn.d.ts +624 -0
  40. package/dist/defaultGraphExecutor-BIjJj7WF.d.cts +624 -0
  41. package/dist/execution-CAIypb41.d.cts +129 -0
  42. package/dist/execution-CAIypb41.d.ts +129 -0
  43. package/dist/index-CHqwkvGp.d.ts +149 -0
  44. package/dist/index-CJeWHopy.d.ts +584 -0
  45. package/dist/index-Cm-JbzTH.d.cts +1450 -0
  46. package/dist/index-Cvr23YCl.d.cts +23 -0
  47. package/dist/index-DDzuSb0n.d.ts +23 -0
  48. package/dist/index-DO4dQgf2.d.cts +584 -0
  49. package/dist/index-DRBWi1fy.d.ts +1450 -0
  50. package/dist/index-Dl5PLgAv.d.cts +149 -0
  51. package/dist/index.cjs +9577 -0
  52. package/dist/index.cjs.map +1 -0
  53. package/dist/index.d.cts +89 -0
  54. package/dist/index.d.ts +89 -0
  55. package/dist/index.js +9563 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/messages-XthmnHZ3.d.cts +8007 -0
  58. package/dist/messages-XthmnHZ3.d.ts +8007 -0
  59. package/dist/ports-DaatKJXp.d.cts +90 -0
  60. package/dist/ports-DnLuKfpE.d.ts +90 -0
  61. package/dist/ports.cjs +4 -0
  62. package/dist/ports.cjs.map +1 -0
  63. package/dist/ports.d.cts +7 -0
  64. package/dist/ports.d.ts +7 -0
  65. package/dist/ports.js +3 -0
  66. package/dist/ports.js.map +1 -0
  67. package/dist/quickstart.cjs +7697 -0
  68. package/dist/quickstart.cjs.map +1 -0
  69. package/dist/quickstart.d.cts +24 -0
  70. package/dist/quickstart.d.ts +24 -0
  71. package/dist/quickstart.js +7691 -0
  72. package/dist/quickstart.js.map +1 -0
  73. package/dist/runAgent-CPj_9e58.d.ts +88 -0
  74. package/dist/runAgent-HYKlXbVr.d.cts +88 -0
  75. package/dist/runHandle-CyXvzgzk.d.ts +239 -0
  76. package/dist/runHandle-D3gPsD7B.d.cts +239 -0
  77. package/dist/runtime-kernel/events.cjs +1485 -0
  78. package/dist/runtime-kernel/events.cjs.map +1 -0
  79. package/dist/runtime-kernel/events.d.cts +8 -0
  80. package/dist/runtime-kernel/events.d.ts +8 -0
  81. package/dist/runtime-kernel/events.js +1475 -0
  82. package/dist/runtime-kernel/events.js.map +1 -0
  83. package/dist/runtime-kernel.cjs +8656 -0
  84. package/dist/runtime-kernel.cjs.map +1 -0
  85. package/dist/runtime-kernel.d.cts +19 -0
  86. package/dist/runtime-kernel.d.ts +19 -0
  87. package/dist/runtime-kernel.js +8568 -0
  88. package/dist/runtime-kernel.js.map +1 -0
  89. package/dist/sse-vPyrOPa0.d.cts +1687 -0
  90. package/dist/sse-vPyrOPa0.d.ts +1687 -0
  91. package/dist/testkit.cjs +10613 -0
  92. package/dist/testkit.cjs.map +1 -0
  93. package/dist/testkit.d.cts +284 -0
  94. package/dist/testkit.d.ts +284 -0
  95. package/dist/testkit.js +10593 -0
  96. package/dist/testkit.js.map +1 -0
  97. package/dist/todo-B1PmDlp3.d.cts +2253 -0
  98. package/dist/todo-B1PmDlp3.d.ts +2253 -0
  99. package/dist/tokenizer-DFL4I7-I.d.ts +28 -0
  100. package/dist/tokenizer-DH_JXv-H.d.cts +28 -0
  101. package/dist/toolContracts-Blll0241.d.ts +463 -0
  102. package/dist/toolContracts-CLkQmhTG.d.cts +463 -0
  103. package/docs/README.md +76 -0
  104. package/docs/integration/01-installation.md +94 -0
  105. package/docs/integration/02-quickstart.md +104 -0
  106. package/docs/integration/README.md +223 -0
  107. package/docs/integration/agent-registration-guide.md +330 -0
  108. package/docs/integration/audit.md +64 -0
  109. package/docs/integration/child-runs.md +87 -0
  110. package/docs/integration/constraints-and-pitfalls.md +87 -0
  111. package/docs/integration/context-engineering.md +650 -0
  112. package/docs/integration/context-fences.md +289 -0
  113. package/docs/integration/glossary.md +69 -0
  114. package/docs/integration/llm-provider.md +76 -0
  115. package/docs/integration/persistence.md +44 -0
  116. package/docs/integration/realtime.md +76 -0
  117. package/docs/integration/run-supervisor.md +69 -0
  118. package/docs/integration/telemetry.md +48 -0
  119. package/docs/integration/testing.md +95 -0
  120. package/docs/integration/tool-development-guide.md +362 -0
  121. package/docs/integration/tool-history.md +202 -0
  122. package/docs/integration/tools.md +188 -0
  123. package/package.json +115 -0
@@ -0,0 +1,330 @@
1
+ # Agent Registration Guide · Agent 注册与装配规范
2
+
3
+ > **What** · `AgentSpec` 静态蓝图 + `defineAgent` quickstart helper + `contextPolicy` 12 大分组 + 多 agent 协作。
4
+ > **When to read** · 第一次注册 agent;精细化控制上下文;多 agent 串接;做 agent 注册表。
5
+ > **Prerequisites** · [`02-quickstart.md`](./02-quickstart.md);建议先读 [`tool-development-guide.md`](./tool-development-guide.md) ⭐。
6
+ > **Key exports** · `AgentSpec` / `ToolBindingSpec` / `defineContextPolicy` from `@linnlabs/linnkit/contracts` · `defineAgent` / `runAgent` from `@linnlabs/linnkit/quickstart`。
7
+ > **Related** · [`context-engineering.md`](./context-engineering.md) ⭐ · [`context-fences.md`](./context-fences.md) ⭐ · [`child-runs.md`](./child-runs.md) · [`llm-provider.md`](./llm-provider.md)
8
+
9
+ linnkit 把 Agent 视为**一等对象**——一个 Agent 是**可序列化的静态蓝图**(`AgentSpec`),不是一段散落的配置 + prompt。这让它能进入 audit / replay / testkit 不变量校验路径。
10
+
11
+ ---
12
+
13
+ ## 0. 一句话分层
14
+
15
+ | 层 | 是什么 | 谁持有 |
16
+ |----|--------|--------|
17
+ | **`AgentSpec`** | 蓝图:id / version / 工具集 / contextPolicy / model hints | host 装配期注册到 registry |
18
+ | **`AgentInvocationRequest`** | 一次调用:query / history / fences / modelId | runtime 每次调用构造 |
19
+
20
+ 修改 `AgentSpec` = 版本升级(需 audit);修改 `AgentInvocationRequest` = 运行期决策(不需 audit)。
21
+
22
+ ---
23
+
24
+ ## 1. 在哪里定义 Agent · 示例
25
+
26
+ > **TL;DR**:一个 Agent 一个文件 / 子目录。**Quickstart** → `agents/<id>.ts`;**生产 host** → `agent-registry/<id>/spec.ts`。linnkit 不规定路径——下面是推荐形态。
27
+
28
+ ### 1.1 Quickstart 形态(demo / 试用 / 单测)
29
+
30
+ ```text
31
+ my-agent-demo/
32
+ ├── tools/searchDocs.ts # SearchDocsTool extends BaseTool
33
+ ├── agents/
34
+ │ ├── pptAssistant.ts # defineAgent({...})
35
+ │ └── emailWriter.ts
36
+ └── main.ts # runAgent(agent, { input, llm })
37
+ ```
38
+
39
+ ### 1.2 生产 host 形态(产品里长期维护)
40
+
41
+ ```text
42
+ app-hosts/<your-app>/
43
+ ├── agent-registry/
44
+ │ ├── index.ts # 集中导出 + 注册到 host registry
45
+ │ ├── pptAssistant/
46
+ │ │ ├── spec.ts # AgentSpec.parse({...})
47
+ │ │ ├── prompt.ts # systemPrompt(host 自管,不进 AgentSpec)
48
+ │ │ └── tools.ts # toolId 字符串数组
49
+ │ └── emailWriter/spec.ts
50
+ ├── adapters/{tools,llm}/
51
+ └── runtime-assembly/ # GraphExecutor 装配
52
+ ```
53
+
54
+ ### 1.3 新加一个 Agent · 三步走
55
+
56
+ | 步 | Quickstart | 生产 host |
57
+ |----|------------|----------|
58
+ | 1. 入口 API | `defineAgent()` from `@linnlabs/linnkit/quickstart` | `AgentSpec.parse()` from `@linnlabs/linnkit/contracts` |
59
+ | 2. 建文件 | `agents/<id>.ts` 一个文件 | `agent-registry/<id>/spec.ts` 一个子目录 |
60
+ | 3. 注册 | `import` 给 `runAgent()` | `agent-registry/index.ts` 集中 `AgentRegistry.register(spec)` |
61
+
62
+ > `AgentRegistry` 是 host 自己实现的(最简就是 `Map<string, AgentSpec>`)——linnkit 协议层不规定形状。详见 §6.1。
63
+
64
+ ---
65
+
66
+ ## 2. `AgentSpec` 字段速查
67
+
68
+ ```ts
69
+ import { AgentSpec, defineContextPolicy } from '@linnlabs/linnkit/contracts';
70
+
71
+ const spec = AgentSpec.parse({
72
+ id: 'pptAssistant',
73
+ version: '1.2.3',
74
+ capabilities: ['agent', 'createPpt'],
75
+ tools: [{ toolId: 'search_docs', argsSchema: searchDocsTool.parameters }],
76
+ contextPolicy: defineContextPolicy({
77
+ profileId: 'agent',
78
+ budget: { maxTokens: 128_000 },
79
+ }),
80
+ role: 'PPT 制作助手', // ⚪
81
+ modelHints: { preferredProviders: ['anthropic'] }, // ⚪
82
+ audit: { redactionLevel: 'standard', pii: false }, // ⚪
83
+ metadata: {}, // ⚪ host 业务字段,不要升格为协议字段
84
+ });
85
+ ```
86
+
87
+ | 字段 | 必填 | 说明 |
88
+ |------|------|------|
89
+ | `id` | ✅ | host registry 唯一标识;audit / replay / telemetry 全部用它 |
90
+ | `version` | ✅ | semver;任何协议级改动 bump 这里(见 §7) |
91
+ | `capabilities` | ✅ | 能力声明字符串数组;host 路由 / 权限决策可用 |
92
+ | `tools` | ✅ | `ToolBindingSpec[]` —— 见 §3 |
93
+ | `contextPolicy` | ✅ | **必须**用 `defineContextPolicy()` helper —— 见 §4 |
94
+ | `role` / `description` / `modelHints` / `audit` / `metadata` | ⚪ | 见上方示例 |
95
+
96
+ ---
97
+
98
+ ## 3. `tools` 字段:`ToolBindingSpec[]`
99
+
100
+ ```ts
101
+ tools: [
102
+ { toolId: 'search_docs', argsSchema: searchDocsTool.parameters },
103
+ { toolId: 'search_docs', argsSchema: ..., bindingId: 'search_v2', config: { topK: 10 } },
104
+ ]
105
+ ```
106
+
107
+ **核心约束**:
108
+
109
+ 1. **`argsSchema` 是可序列化 JSON Schema 副本**——直接塞 runtime `zod` 对象会被协议层拒绝(让 `AgentSpec` 不可 audit / replay)。
110
+ 2. **`toolId` ≠ `BaseTool` 实例**——`AgentSpec` 只持名字字符串,实际工具实例由 host 的 `ToolRuntimePort` 管理。
111
+ 3. **同一 `toolId` 可绑定多次**——用 `bindingId` 区分(如同一搜索工具配两套 `topK`)。
112
+ 4. **`config` / `metadata` 是 host 自由字段**——framework 不解读。
113
+
114
+ ---
115
+
116
+ ## 4. `contextPolicy` 与 `defineContextPolicy()`
117
+
118
+ ```ts
119
+ import { defineContextPolicy } from '@linnlabs/linnkit/contracts';
120
+
121
+ const contextPolicy = defineContextPolicy({
122
+ profileId: 'agent',
123
+ budget: { maxTokens: 128_000, reservedForResponse: 8_000 },
124
+ toolHistory: { strategy: 'per-run', overflowStrategy: 'keep-latest' },
125
+ summarization: { enabled: true, agentId: 'summarizer', triggerThreshold: 0.8 },
126
+ });
127
+ ```
128
+
129
+ 12 大分组的详细说明见 [`context-engineering.md`](./context-engineering.md) ⭐。
130
+
131
+ > ⚠️ **不要手写 `AgentSpecContextPolicy` 对象**——`defineContextPolicy()` 不只补默认值,还**校验组合约束**(如 `summarization.enabled = true` 必须有 `agentId`)。手写绕过 helper 会在运行时崩溃。
132
+
133
+ ### 4.1 `maxTokens` 谁来算?
134
+
135
+ linnkit 内置默认 tokenizer(`tiktoken` + 字节比兜底),用于 `contextPolicy.budget` 决策。三种调整方式:
136
+
137
+ | 你的场景 | 推荐做法 |
138
+ |---------|---------|
139
+ | 试用 / 默认行为够用 | 不动 |
140
+ | GPT-4o 系 | `tokenEstimation: { encoding: 'o200k_base' }` |
141
+ | 中文为主 | `tokenEstimation: { avgCharsPerToken: 1.7 }` |
142
+ | 严格按真实 Claude/Gemini 计费做预算 | 注入自定义 `TokenizerPort`(见 [`context-engineering.md §9.4`](./context-engineering.md)) |
143
+
144
+ linnkit **不**做:跨 provider 统一计费 token 数协议(不同模型口径不一样)。计费 token 走 provider `usage`。
145
+
146
+ ### 4.2 摘要 agent:`summarization.agentId`
147
+
148
+ 被动摘要(`contextPolicy.summarization`)会把一批旧消息交给 host 注册表里的**无工具**摘要 agent/chat;framework 不写摘要 prompt、不直接裸调 LLM。接入顺序:**先在 host 侧注册摘要项** → **再让业务 `AgentSpec` 的 `summarization.agentId` 指向该注册 id**。
149
+
150
+ ```ts
151
+ // ① host:注册一个无工具的摘要 agent/chat(表单项形状随 host 而定;核心是 id 可被解析、tools 为空)
152
+ // 例如注册 id: 'history_compression',并自行装配 prompt / 模型。
153
+
154
+ // ② 业务 AgentSpec.contextPolicy(片段)
155
+ defineContextPolicy({
156
+ profileId: 'agent',
157
+ summarization: {
158
+ agentId: 'history_compression',
159
+ triggerThreshold: 0.72,
160
+ failureBehavior: 'continue-if-within-budget',
161
+ },
162
+ }),
163
+ ```
164
+
165
+ - `agentId` 必须在 host 注册表里存在;未知 id 应在装配期失败。
166
+ - 摘要 agent **不要带工具**,否则摘要路径可能二次进工具循环。
167
+ - `failureBehavior: 'continue-if-within-budget'`:仅当当前上下文仍不超预算时可继续用原文;**已超预算仍会 fail-fast**。字段语义与阈值细则见 [`context-engineering.md`](./context-engineering.md) §5.4。
168
+
169
+ ---
170
+
171
+ ## 5. 两种注册 API
172
+
173
+ ### 5.1 生产:`AgentSpec.parse()`
174
+
175
+ ```ts
176
+ // agent-registry/pptAssistant/spec.ts
177
+ import { AgentSpec, defineContextPolicy } from '@linnlabs/linnkit/contracts';
178
+
179
+ export const pptAssistantSpec = AgentSpec.parse({
180
+ id: 'pptAssistant',
181
+ version: '1.2.3',
182
+ capabilities: ['agent', 'createPpt'],
183
+ tools: [{ toolId: 'search_docs', argsSchema: searchDocsToolSchema }],
184
+ contextPolicy: defineContextPolicy({ profileId: 'agent', budget: { maxTokens: 128_000 } }),
185
+ modelHints: { preferredProviders: ['anthropic'] },
186
+ });
187
+ ```
188
+
189
+ ### 5.2 Quickstart:`defineAgent()`
190
+
191
+ ```ts
192
+ import { defineAgent } from '@linnlabs/linnkit/quickstart';
193
+
194
+ export const pptAssistant = defineAgent({
195
+ id: 'pptAssistant',
196
+ version: '1.2.3',
197
+ role: 'PPT 制作助手',
198
+ systemPrompt: '你是 ...',
199
+ modelId: 'claude-sonnet-4',
200
+ capabilities: ['agent', 'createPpt'],
201
+ tools: [new SearchDocsTool()],
202
+ contextPolicy: { budget: { maxTokens: 128_000 } },
203
+ });
204
+ ```
205
+
206
+ | 维度 | `defineAgent()` | `AgentSpec.parse()` |
207
+ |------|----------------|---------------------|
208
+ | 工具引用 | 持有 `BaseTool` 实例 | 只持 `toolId` 字符串 |
209
+ | systemPrompt | 必填,由 helper 持有 | 不存在(host 自管 prompt 装配)|
210
+ | modelId | helper 字段 | 走 `modelHints` |
211
+ | 用途 | demo / 测试 / 5 分钟入门 | 生产 host 接入 |
212
+
213
+ 完整 quickstart demo 见 [`02-quickstart.md`](./02-quickstart.md)。
214
+
215
+ ---
216
+
217
+ ## 6. 生产 host 装配 · 三件事
218
+
219
+ ### 6.1 实现 `AgentRegistry`
220
+
221
+ linnkit 不规定形状,host 自己实现。最小职责:`register(spec)` + `get(agentId)`。
222
+
223
+ | Pattern | 适用场景 |
224
+ |---------|----------|
225
+ | **A · 内存 Map** | 80% 场景;agent 集合在编译期确定(典型单进程宿主)。`new Map<string, AgentSpec>()` 即可 |
226
+ | **B · 启动时从 JSON 加载** | 让运维 / PM 改配置就能调 agent。boot 时 `AgentSpec.parse(json)` 校验 |
227
+ | **C · 数据库 + 动态更新** | 多租户 SaaS;UI 编辑 spec;要 audit trail |
228
+
229
+ > ⚠️ 无论用哪种 pattern,**`AgentSpec.parse()` 是必经入口**——保证进入 runtime 的 spec 100% 满足协议约束。绕过 = 让坏数据流到生产。
230
+
231
+ ### 6.2 装配 GraphExecutor(进程级共享单例)
232
+
233
+ ```ts
234
+ import { createDefaultGraphExecutor, LlmNode, LlmCaller } from '@linnlabs/linnkit/runtime-kernel';
235
+
236
+ const executor = createDefaultGraphExecutor({
237
+ llmNode: new LlmNode({ llmCaller: new LlmCaller({ aiEngine }) }),
238
+ toolRuntime, // host 实现,见 tools.md
239
+ observationPreview, // host 实现,见 tools.md
240
+ });
241
+ ```
242
+
243
+ 所有 agent 共用一个 executor,每次调用通过 `agentSpec` 切换蓝图。
244
+
245
+ ### 6.3 运行期入口 · `AgentInvocationRequest`
246
+
247
+ ```ts
248
+ // HTTP / IPC / CLI handler
249
+ const spec = AgentRegistry.get(req.agentId);
250
+ if (!spec) throw new Error(`Unknown agent: ${req.agentId}`);
251
+
252
+ await executor.run({
253
+ agentSpec: spec,
254
+ runId: generateRunId(),
255
+ query: req.userMessage,
256
+ history: await loadHistory(req.conversationId),
257
+ fences: await buildFences(req), // 见 context-fences.md
258
+ modelId: spec.modelHints?.preferredModels?.[0] ?? 'claude-sonnet-4',
259
+ });
260
+ ```
261
+
262
+ 详细装配见 [`02-quickstart.md`](./02-quickstart.md) 与 [`run-supervisor.md`](./run-supervisor.md)。
263
+
264
+ ---
265
+
266
+ ## 7. AgentSpec 版本管理
267
+
268
+ `AgentSpec` 是协议层蓝图——任何修改都要 bump version:
269
+
270
+ | 修改 | 版本号 | 配套动作 |
271
+ |------|--------|----------|
272
+ | 加工具 / 加 capability | minor | audit log 记"能力扩展" |
273
+ | 改 `contextPolicy.budget` / `toolHistory.strategy` | minor | audit log;考虑 replay 验证 |
274
+ | 删工具 / 删 capability | **major** | audit log;既有 run 进 deprecated 路径 |
275
+ | 改 `metadata` / `role` / `description` 文案 | patch | 一般不需要 audit |
276
+
277
+ **对 replay 的影响**:未来 Replay SDK 按 `AgentSpec.version` 精确匹配——同一 runId 必须用**当时的 spec 版本**重演。这是 `version` 必填、spec 必须可序列化的原因。
278
+
279
+ ---
280
+
281
+ ## 8. 多 Agent 协作
282
+
283
+ linnkit 协议层只承认两种多 agent 形态——**不做** AgentMessageBus / role / backstory 这类"自由 chat"协议。所有多 agent 行为必须能映射到下面其一,才能 100% 可审计 / 可回放。
284
+
285
+ ```ts
286
+ import { runSupervisor } from '@linnlabs/linnkit/runtime-kernel';
287
+
288
+ // 同步嵌入:子 run 的 cost 聚合到父 run
289
+ const result = await runSupervisor.invokeChildRun({
290
+ parentRunId: context.runId,
291
+ agentSpec: emailWriterSpec,
292
+ input: { query: '写感谢信...' },
293
+ });
294
+
295
+ // 异步后台:立刻返回 handle,可 observe / cancel / waitForTerminal
296
+ const handle = runSupervisor.spawnDetached({
297
+ agentSpec: backgroundAgentSpec,
298
+ input: { task: '生成每日报告' },
299
+ });
300
+ ```
301
+
302
+ 详细对比见 [`child-runs.md`](./child-runs.md)。
303
+
304
+ ---
305
+
306
+ ## 9. 自查清单
307
+
308
+ - [ ] agent 文件位置正确(Quickstart `agents/<id>.ts` / 生产 `agent-registry/<id>/spec.ts`)
309
+ - [ ] 一个文件 / 子目录只放一个 Agent
310
+ - [ ] 已在入口处注册(`runAgent()` 引用 或 `AgentRegistry.register(spec)`)
311
+ - [ ] `AgentSpec.parse()` 是 registry 入口必经路径(生产 host)
312
+ - [ ] `id` 在 registry 中唯一;`version` 是 semver;`capabilities` 明确
313
+ - [ ] `argsSchema` 是可序列化 JSON Schema 副本(**不是** runtime `zod` 对象)
314
+ - [ ] `contextPolicy` 用 `defineContextPolicy()` 而**不是**手写
315
+ - [ ] 多 agent 协作走 `invokeChildRun` 或 `spawnDetached`,不自由 chat
316
+ - [ ] 有交互工具 → 配套 `WaitUserNode` 路径(见 [`tool-development-guide.md §7`](./tool-development-guide.md))
317
+ - [ ] 配套写了 testkit 不变量测试(见 [`testing.md`](./testing.md))
318
+
319
+ ---
320
+
321
+ ## 10. 与其他文档的关系
322
+
323
+ - [`tool-development-guide.md`](./tool-development-guide.md) — 工具内部设计规范
324
+ - [`tools.md`](./tools.md) — `ToolRuntimePort` / `ObservationPreviewPort` 接入面
325
+ - [`context-engineering.md`](./context-engineering.md) — 12 大分组 `contextPolicy` + `TokenizerPort`
326
+ - [`context-fences.md`](./context-fences.md) — fence 注册与注入(与 `mustKeep` 配合)
327
+ - [`run-supervisor.md`](./run-supervisor.md) — `RunHandle.cost()` / `observe` / `cancel`
328
+ - [`child-runs.md`](./child-runs.md) — `invokeChildRun` vs `spawnDetached`
329
+ - [`audit.md`](./audit.md) — Agent 调用 / spec 升级的审计 envelope
330
+ - [`testing.md`](./testing.md) — testkit 26 条 strict invariants
@@ -0,0 +1,64 @@
1
+ # AuditPort · 决策账本
2
+
3
+ > **What** · `AuditPort` 接入 —— 把 5 类非确定性决策(policy / tool retry / overflow / fence drop / tokenizer fallback)写入审计账本。Telemetry 记录事实,Audit 记录**为什么**。
4
+ > **When to read** · 要追溯"agent 为什么这么做";做合规 / 多租户审计;调试上下文裁剪 / overflow 决策;企业接入强烈建议。
5
+ > **Prerequisites** · [`02-quickstart.md`](./02-quickstart.md)。
6
+ > **Key exports** · `AuditPort` / `AuditEnvelope` from `@linnlabs/linnkit/ports` · `createFileAudit` / `createCompositeAudit` from `@linnlabs/linnkit/runtime-kernel`。
7
+ > **Related** · [`telemetry.md`](./telemetry.md) · [`persistence.md`](./persistence.md) · [`testing.md`](./testing.md)(`createCollectingAuditPort` for tests)
8
+
9
+ > 可选,但企业接入强烈建议。
10
+
11
+ AuditPort 是"决策账本"。Telemetry 记录事实(比如一次 LLM 调用了多久、用了多少 token),Audit 记录**为什么做了这个决定**(比如为什么取消、为什么拒绝工具、为什么 fallback 到另一个模型)。
12
+
13
+ 这套接入方式是 host-neutral 的:桌面应用、在线秘书 agent、知识库 agent 或任意自研 agent host 都只需要实现自己的 EventStore / file / SIEM sink,不需要把产品语义写回 linnkit。
14
+
15
+ ## 1. 最小接入骨架
16
+
17
+ 默认进 EventStore,再按需分发到文件 / SIEM:
18
+
19
+ ```ts
20
+ import { runtimeKernel } from '@linnlabs/linnkit';
21
+
22
+ const eventStoreAudit = runtimeKernel.audit.createEventStoreAudit({ eventStore });
23
+ const fileAudit = runtimeKernel.audit.createFileAudit({ filePath: '/var/log/linnkit/audit.jsonl' });
24
+ const auditPort = runtimeKernel.audit.createCompositeAudit({
25
+ ports: [eventStoreAudit, fileAudit],
26
+ });
27
+
28
+ const supervisor = new runtimeKernel.runSupervisor.DefaultRunSupervisor({
29
+ registryStore,
30
+ auditPort,
31
+ });
32
+ ```
33
+
34
+ ## 2. 当前已自动发出的 envelope
35
+
36
+ - `RunHandle.cancel({ reason })` 会发 `action: 'run.cancel'`,并把 `reason`、`forceCleanup`、`runId`、`conversationId`、`agentSpecId` 写进 envelope。
37
+ - `GraphAgentExecutor` 会发 `model.select`;发生模型切换时发 `model.fallback`。
38
+ - `ToolNode` 会发 `tool.allow` / `tool.deny`。
39
+ - `WaitUserNode` 会发 `wait_user.request`。
40
+ - `runtimeKernel.audit.emitSandboxDecisionAudit()` 是 sandbox 决策标准入口;当前 linnkit 还没有内置 SandboxPort,所以不会伪造 sandbox 执行链。
41
+
42
+ ## 3. 你现在可以选的 sink
43
+
44
+ | sink | 用途 |
45
+ |---|---|
46
+ | `runtimeKernel.audit.noopAudit` | 测试或本地开发占位 |
47
+ | `runtimeKernel.audit.consoleAudit` / `createConsoleAudit()` | 开发期看结构 |
48
+ | `runtimeKernel.audit.createFileAudit({ filePath })` | 追加写 JSONL,适合最小生产审计或回归测试 |
49
+ | `runtimeKernel.audit.createEventStoreAudit({ eventStore })` | 默认推荐落点,写入 `type: 'audit_envelope'` 的隐藏 RuntimeEvent |
50
+ | `runtimeKernel.audit.createCompositeAudit({ ports })` | 组合多个 sink |
51
+
52
+ ## 4. 接入规则
53
+
54
+ - Audit envelope 是追加只读记录;不要在 sink 里回写或修改 run 状态。
55
+ - `AuditPort.emit()` 可以是同步或异步;如果你的 sink 有缓冲,暴露 `flush()`,在进程退出或测试结束时显式调用。
56
+ - 不要把 AuditPort 当普通日志散用。只有"决策"进 audit,普通耗时 / token / 节点状态继续走 telemetry。
57
+ - `audit_envelope` 会持久化,但不会进 UI、不会进 agent context、不会走 SSE。
58
+ - `createEventStoreAudit()` 要求 envelope 带 `scope.conversationId`,这是为了避免跨会话审计混流。
59
+
60
+ ## 5. 最小验证
61
+
62
+ - 单测:注入一个数组 sink,调用 `handle.cancel({ reason: 'user_request' })` 后能收到 `action === 'run.cancel'` 的 envelope。
63
+ - 单测:`createFileAudit()` 连续 emit 两条 envelope 后,JSONL 文件应有两行且可逐行 `JSON.parse`。
64
+ - 单测:`createEventStoreAudit()` emit 后,EventStore 中应出现 `type === 'audit_envelope'` 且 `shouldEnterAgentContext(event) === false`。
@@ -0,0 +1,87 @@
1
+ # Child Runs · 同步嵌入 vs 异步后台子 agent
2
+
3
+ > **What** · 两种子 agent 调用形态 —— `invokeChildRun`(同步嵌入,父 agent 等结果)vs `spawnDetached`(异步后台,立刻返回 `RunHandle`)。
4
+ > **When to read** · 多 agent 协作;要在一个 agent 里调另一个 agent;想做后台调度 / 长任务 / 通知触发。
5
+ > **Prerequisites** · [`run-supervisor.md`](./run-supervisor.md)。
6
+ > **Key exports** · `invokeChildRun` / `spawnDetached` from `@linnlabs/linnkit/runtime-kernel`。
7
+ > **Related** · [`run-supervisor.md`](./run-supervisor.md) · [`agent-registration-guide.md` §6](./agent-registration-guide.md) ⭐
8
+
9
+ linnkit 提供**两条 API**承载"子 agent"概念。它们不是配置开关,是两种本质不同的调用形态——按需选用。
10
+
11
+ ## 1. 概念对照
12
+
13
+ | API | 场景 | 语义 |
14
+ |---|---|---|
15
+ | `toolContext.invokeChildRun(...)` | 父 agent 工具内**同步调用**子 agent | 父等待子完成;用于 deep search / task subagent 这类嵌入式执行 |
16
+ | `runSupervisor.spawnDetached(...)` | 顶层后台任务、定时任务、wake hook、在线秘书 | 立刻返回 handle;调用方后续 `peek / waitForTerminal / cancel / drain` |
17
+
18
+ ## 2. 何时用同步 `invokeChildRun`
19
+
20
+ 适合场景:
21
+
22
+ - 子 agent 是**父 agent 工具的实现细节**("调用搜索 agent → 取结果作为本工具的 observation")
23
+ - 调用方需要立刻拿到子 agent 的结构化输出来决定下一步
24
+ - 子 agent 的成本/取消语义跟父 agent 绑死(取消父则子必停)
25
+
26
+ 调用形态:在父 agent 的某个工具实现里调 `toolContext.invokeChildRun(spec)`。返回值是 child run 的最终输出,且 child run 内的 LLM cost / tool cost 会自动归到父 run 的 `childrenTotal`。
27
+
28
+ ## 3. 何时用异步 `spawnDetached`
29
+
30
+ 适合场景:
31
+
32
+ - 任务由**外部触发器**(HTTP / cron / wake hook / 用户在前端点了"启动一个后台代办")触发
33
+ - 调用方**不阻塞**等待结果——立刻拿 handle 用于 cancel / observe / cost
34
+ - 子 agent 与父 agent 的生命周期解耦(子 agent 失败不直接导致父 agent 失败)
35
+
36
+ 骨架:
37
+
38
+ ```ts
39
+ const supervisor = new runtimeKernel.runSupervisor.DefaultRunSupervisor({
40
+ registryStore,
41
+ executor: {
42
+ async execute(ctx) {
43
+ // 这里接你自己的 GraphExecutor / daemon runner。
44
+ // ctx.signal、ctx.eventBus、ctx.eventStore、ctx.costCollector 都来自 register spec。
45
+ await runYourAgent(ctx);
46
+ return {
47
+ runId: ctx.runId,
48
+ status: 'completed',
49
+ completedAt: Date.now(),
50
+ };
51
+ },
52
+ },
53
+ });
54
+
55
+ const handle = await supervisor.spawnDetached({
56
+ conversationId,
57
+ agentSpec,
58
+ request,
59
+ eventBus,
60
+ eventStore,
61
+ costCollector,
62
+ wakeSource: 'cron',
63
+ iterationBudget: { max: 20, refundable: true },
64
+ });
65
+
66
+ const outcome = await supervisor.waitForTerminal(handle.runId);
67
+ ```
68
+
69
+ `spawnDetached` 与 `registerRun + 自己跑` 等价,但显式告诉 supervisor"这不是同步等结果的 run"——`RunRegistrationSpec.wakeSource` / `iterationBudget` / `ephemeral` 等字段都在这里生效。
70
+
71
+ ## 4. 命名注意
72
+
73
+ - 公开 namespace:`runtimeKernel.childRunTrace`(含 `subrun_trace` 观测协议 publisher 与合同)。
74
+ - 事件 type 仍叫 `subrun_trace`(前端可继续按这个名字处理)。
75
+ - 内部目录是 `child-run-trace/`;外部消费者一律走公开 namespace。
76
+
77
+ ## 5. 关键边界
78
+
79
+ - **不要**把 `invokeChildRun` 当成"小一号的 run"——它本质上是父 run 内部的一个嵌入式调用,与父 run 共享 abort signal、cost 聚合、enrichment registry。
80
+ - **不要**把 `spawnDetached` 用于工具调用流(HTTP 端到端响应里不应该等 spawnDetached 完成)——那是 invokeChildRun 的场景。
81
+ - 父子 run 的 cost 通过 `scope.parentRunId` 关联;如果你的 telemetry adapter 没把 `parentRunId` 透传到 sink,那 `childrenTotal` 字段就是 0。
82
+
83
+ ## 6. 最小验证
84
+
85
+ - 单测:父 agent 工具内 `invokeChildRun` → 父 run 的 `cost().childrenTotal.llmCost > 0`
86
+ - 单测:`spawnDetached` 立刻返回;`waitForTerminal` 在执行结束后 resolve 出最终 status
87
+ - 单测:`spawnDetached` 中的 run 被 `cancel()` 后,executor 收到的 `ctx.signal.aborted === true`
@@ -0,0 +1,87 @@
1
+ # Constraints & Pitfalls · 硬约束 + 不建议做的事 + FAQ
2
+
3
+ > **What** · linnkit 接入侧的硬约束 + 不建议做的事 + 常见踩坑 FAQ(含 AST 级 guard 规则、deep import 风险、浏览器 bundle 边界)。
4
+ > **When to read** · 上线前 review;遇到诡异 import 错误;review 别人的接入实现;接入 PR 自检。
5
+ > **Prerequisites** · 起码读完你用到的 §7.2 单点接入篇。
6
+ > **Key exports** · 无(本文是约束清单)。
7
+ > **Related** · [`glossary.md`](./glossary.md) · [`README §5`](./README.md)(browser rules) · [`realtime.md`](./realtime.md)
8
+
9
+ ## 1. 硬约束(接入方必读)
10
+
11
+ linnkit 仓库内部的 package-boundary 由 **AST 级 guard**(基于 TypeScript Compiler API)强制 10 条规则。其中**直接影响外部消费者**的:
12
+
13
+ 1. **只能从公开子入口 import**。`exports` 字段没声明的路径会被 Node 16+ ESM 解析直接拒绝。
14
+ 2. **不要 deep import**。`@linnlabs/linnkit/runtime-kernel/some-internal-folder/foo` 不算公开 API;下个 minor 随时可能挪。
15
+ 3. **不要依赖 internal-only 模块**:`shared/logger` / `shared/errorClassifier` / `shared/TokenCalculator` 等都是包内私有。
16
+ 4. **不要把你自己的 provider/tool/adapter 反向塞回 linnkit**——linnkit 是你装的 npm 包,物理上塞不进去;逻辑上也不要试图通过 monkey patch 修改 linnkit 内部。
17
+ 5. **`promptKey` 在 ports 层是 opaque string**——linnkit 不认识你的产品菜单,也不会替你解析。
18
+ 6. **前端代码禁止 import `@linnlabs/linnkit/runtime-kernel`**(namespace 全展开入口,含 `node:async_hooks` / `crypto` 等 Node-only 子树)。前端只能从 `@linnlabs/linnkit/runtime-kernel/events` slim seam 取 events governance 纯函数。
19
+ 7. **生产代码禁止 import `@linnlabs/linnkit/testkit`**。`testkit` 顶层 `import { vi, expect } from 'vitest'`,会把 vitest runtime 拖进生产 bundle。如果你确实在 monorepo 里有 mixed 代码,请用打包阶段的 lint 规则守门。
20
+
21
+ > 第 6 / 7 条来自早期打包事故:`@linnlabs/linnkit/testkit` 一旦从根入口被静态导入,esbuild/tsup 会把整棵 testkit 子树带进 backend production bundle,导致生产启动时加载测试运行时。公开版本用独立子入口和 package smoke 测试守住这条边界。
22
+
23
+ ## 2. 当前不建议你做的事
24
+
25
+ 不要这样接:
26
+
27
+ 1. **直接把 linnkit 真源仓内的 host adapters 整个抄过来当模板**——那是产品决策内嵌的实现(默认 provider / 默认 task / 默认 schema),里面糊了具体产品的语义。可以参考它们的**形状**和**装配顺序**,但不要直接拷贝再硬改。
28
+ 2. **把别人的 agent registry / context / flow 当作公开 API 引用**——它们没在 `package.json#exports` 里。
29
+ 3. **试图通过自定义 build 钩子修改 `@linnlabs/linnkit` 内部行为**——所有定制点必须通过依赖注入。
30
+ 4. **为了省事继续从外部 schemas 包拿本该属于 agent 的 A 类协议**——0.1.1 已经把 schemas 收回包内(`@linnlabs/linnkit/contracts`),不要再走老路径。
31
+
32
+ 正确做法:
33
+
34
+ - 复用 `@linnlabs/linnkit` 的 7 个公开子入口
35
+ - 在你自己的 host layer 决定 provider、tool、persistence、flow 的真实实现
36
+ - 通过 fence registry 把产品上下文挂进框架,而不是改框架
37
+
38
+ ## 3. FAQ
39
+
40
+ **Q:我装的是 `@linnlabs/linnkit`,但 npm/yarn 报 401 / 404?**
41
+
42
+ 优先检查项目或用户级 `.npmrc` 是否还保留旧的 GitHub Packages scope override:
43
+
44
+ ```bash
45
+ npm config get @linnlabs:registry
46
+ ```
47
+
48
+ 如果输出是 `https://npm.pkg.github.com/`,删掉这条配置,让 npm 使用默认的 `https://registry.npmjs.org/`。`@linnlabs/linnkit` 当前是 npmjs.com 公开包,不需要 GitHub token。
49
+
50
+ **Q:我能不能 fork linnkit、改它内部然后用我自己的 fork?**
51
+
52
+ 技术上可以,但你要自己负担"和上游同步 + boundary guard 自维护"的成本。99% 你想做的事都能通过依赖注入在 host 层完成;如果你发现某个改动只能 fork 才能做,那大概率说明你应该来跟 linnkit 维护方提 issue / PR。
53
+
54
+ **Q:我的产品上下文是不是只能用 fence 表达?**
55
+
56
+ A 类(system / user 注入)走 fence 是最干净的路。B 类(per-tool 工具调用上下文)按 tool 自己的 schema/context/patch 表达,跟 fence 无关。C 类(运行时副作用、telemetry)走 telemetry port。三类互不替代。
57
+
58
+ **Q:legacy `document_fragment` / `context_before` 字段我该不该用?**
59
+
60
+ **不该**。它们在 0.2.x 仍保留是为了存量 host 渐进迁移;新接入方一律走 fence 通道。
61
+
62
+ **Q:我能跳过 host-bound testkit,只用 linnkit 自带的 testkit 写测试吗?**
63
+
64
+ 能跑通 contract 测试是可以的。但一旦你的 host 装配里有任何"默认 LlmNode 行为 / 默认 tool registry 默认值"等产品决策,第一层 testkit 不会替你验证。所以建议第二层 testkit 至少薄薄一层包一下。
65
+
66
+ **Q:父子 agent 的 cost 为什么 `childrenTotal` 是 0?**
67
+
68
+ 99% 是你的 telemetry adapter 没把 `scope.parentRunId` 透传到 sink。检查:
69
+
70
+ - `withLLMTelemetryContext` 内是否传了 `parentRunId`
71
+ - LLM caller / tool runtime 是否包在 `withLLMTelemetryContext` 内
72
+ - 你的 `RunCostCollector` 是否监听了 `scope.parentRunId` 而不是只看 `scope.runId`
73
+
74
+ **Q:`@linnlabs/linnkit/runtime-kernel` 导入报 "Missing tiktoken_bg.wasm"?**
75
+
76
+ 升级到 ≥ `0.1.3`。0.1.0~0.1.2 三个版本里 tiktoken wasm 被错误 inline 进 dist,已在 0.1.3 修复。
77
+
78
+ **Q:升级到 0.5.0 后我之前的 import 报错了?**
79
+
80
+ 0.4.x → 0.5.0 主要变化:
81
+
82
+ - `linnkitCompat` 命名空间已删除(0.3.0 起)
83
+ - `AgentProfileRequest` 的 host 产品字段(`document_fragment` / `context_before` 等)已移除(0.4.0 起)
84
+ - `MessageFormatter` 不再特殊处理 `document_fragment` / `<additional_context>` / `[任务完成]` 包装
85
+ - `linnkit/context-manager` 主入口冻结 chat namespace(`chatContext` / `chatTasks` 等已不再暴露)
86
+
87
+ 具体兼容性看仓根 `CHANGELOG.md` 的 0.5.0 entry。