-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathagent-collab-architecture.html
More file actions
543 lines (488 loc) · 40.6 KB
/
Copy pathagent-collab-architecture.html
File metadata and controls
543 lines (488 loc) · 40.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Agent协作:工具面</title>
<style>
:root{
--bg:#16150f; --pan:#1d1c14; --bd:#2e2c20; --tx:#bdb9a6; --acc:#d4843a;
--cb:#12110b; --dim:#8a8675;
--c0:#4f8ef7; --c1:#5aad4e; --c2:#d4843a; --c3:#9d6fe0; --c4:#2db3d5;
--no:#e0445a;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--tx);
font:14px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:40px 24px 96px}
.wrap{max-width:1180px;margin:0 auto}
h1{font-size:23px;color:#f0eee6;margin:0 0 6px}
.sub{color:var(--tx);margin:0 0 20px;max-width:78ch}
h2{font-size:16px;color:var(--acc);margin:34px 0 8px;scroll-margin-top:20px}
h2 .num{color:var(--dim);font-size:12px;margin-right:8px;letter-spacing:.16em}
.lead{color:var(--dim);font-size:13px;margin:0 0 12px;max-width:80ch}
.card{background:var(--pan);border:1px solid var(--bd);border-radius:12px;padding:8px;margin-bottom:16px}
svg{display:block;width:100%;height:auto;background:var(--cb);border-radius:8px}
.legend{font-size:12.5px;color:var(--dim);padding:8px 10px;line-height:1.8}
.legend b{color:#f0eee6}
code{background:var(--cb);color:var(--acc);padding:1px 5px;border-radius:4px;font:12px Menlo,monospace}
a{color:var(--acc)}
text{font-family:-apple-system,"Segoe UI",sans-serif}
/* 顶部目录 */
.toc{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 26px}
.toc a{font-size:12px;color:var(--tx);text-decoration:none;border:1px solid var(--bd);
background:var(--pan);border-radius:99px;padding:4px 12px}
.toc a:hover{border-color:var(--acc);color:#f0eee6}
/* 四域卡片 */
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px}
.dom{background:var(--pan);border:1px solid var(--bd);border-top-width:3px;border-radius:12px;padding:13px 15px}
.dom .k{font-size:10.5px;letter-spacing:.2em;color:var(--dim);margin-bottom:3px}
.dom h3{margin:0 0 3px;font-size:17px;font-weight:700}
.dom .noun{font:12px Menlo,monospace;color:var(--dim);margin-bottom:8px}
.dom p{margin:0 0 10px;font-size:12.5px;color:#a5a190;line-height:1.65}
.dom .tools{display:flex;flex-wrap:wrap;gap:5px}
.dom .tools span{font:11px Menlo,monospace;background:var(--cb);border:1px solid var(--bd);
border-radius:5px;padding:2px 7px;color:var(--tx)}
@media(max-width:980px){.grid4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.grid4{grid-template-columns:1fr}}
/* 表格 */
.tbl{overflow-x:auto;background:var(--pan);border:1px solid var(--bd);border-radius:12px;margin-bottom:16px}
table{width:100%;border-collapse:collapse;font-size:13px;min-width:660px}
th{text-align:left;padding:10px 14px;color:var(--dim);font-weight:600;font-size:11px;
letter-spacing:.14em;border-bottom:1px solid var(--bd);white-space:nowrap}
td{padding:10px 14px;border-bottom:1px solid #26241a;vertical-align:top;line-height:1.65}
tr:last-child td{border-bottom:none}
td.y{color:var(--c1)} td.n{color:var(--no)}
td b{color:#f0eee6;font-weight:600}
/* 代码块 */
pre{background:var(--cb);border:1px solid var(--bd);border-radius:10px;padding:14px 16px;
overflow-x:auto;margin:0 0 16px;font:12.5px/1.95 Menlo,monospace;color:#a5a190}
pre b{color:var(--acc);font-weight:400}
pre i{color:#6b6a63;font-style:normal}
/* 场景步骤条 */
.sc{background:var(--pan);border:1px solid var(--bd);border-radius:12px;padding:14px 16px;margin-bottom:12px}
.sc h4{margin:0 0 2px;font-size:14px;color:#f0eee6}
.sc .why{margin:0 0 11px;font-size:12.5px;color:var(--dim)}
.steps{display:flex;gap:9px;overflow-x:auto;padding-bottom:6px}
.step{flex:1 0 auto;min-width:186px;max-width:300px;background:var(--cb);
border:1px solid var(--bd);border-radius:10px;padding:10px 12px}
.step .n{font-size:10px;letter-spacing:.18em;color:var(--dim)}
.step .t{font:11.5px/1.6 Menlo,monospace;color:var(--acc);margin:4px 0 5px;word-break:break-word}
.step .d{font-size:12px;color:#a5a190;line-height:1.6}
.note{border-left:3px solid var(--bd);padding:2px 0 2px 13px;color:var(--dim);
font-size:12.5px;margin:0 0 16px;max-width:84ch;line-height:1.8}
.note b{color:#f0eee6}
</style>
</head>
<body>
<div class="wrap">
<h1>Agent协作:工具面</h1>
<p class="sub">一个agent与别的agent交互的全部工具,按四个域摆开:计划、执行、实体、通讯。
这页说明每个工具做什么、哪个会话能操作哪些任务、两个预算怎么限制一条链的规模。
设计文档:<code>docs/reference/design/runtime/agent-collaboration.md</code>。
八个参考实现在同样八个维度上怎么做,见<a href="agent-collab-comparison.html">Agent协作:八家实现对照</a>。</p>
<div class="toc">
<a href="#vocab">01 四域词汇表</a>
<a href="#four">02 一个agent能做的四件事</a>
<a href="#power">03 消息和任务的区别</a>
<a href="#budget">04 两个预算</a>
<a href="#archive">05 归档</a>
<a href="#scenes">06 典型场景</a>
<a href="#cc">07 和Claude Code的对应</a>
</div>
<!-- ══════════ 01 四域词汇表 ══════════ -->
<h2 id="vocab"><span class="num">01</span>四域词汇表</h2>
<p class="lead">四个域各有一个名词。名词分清楚了,工具名就不用记:每个域的工具都在操作它自己那个名词。</p>
<div class="grid4">
<div class="dom" style="border-top-color:#9d6fe0">
<div class="k">计划</div>
<h3 style="color:#9d6fe0">todo</h3>
<div class="noun">写下来的计划</div>
<p>手写的计划清单。只记录打算做什么,不对应任何在跑的东西,改它不会让任何agent运行。</p>
<div class="tools"><span>todo_create</span><span>todo_update</span><span>todo_list</span></div>
</div>
<div class="dom" style="border-top-color:#d4843a">
<div class="k">执行</div>
<h3 style="color:#d4843a">task</h3>
<div class="noun">正在运行的任务</div>
<p>派出去正在跑的任务:一个task_id、一个状态、一份结果。它是任务列表里的条目,不是执行任务的那个实体。</p>
<div class="tools"><span>list_tasks</span><span>task_output</span><span>task_stop</span></div>
</div>
<div class="dom" style="border-top-color:#5aad4e">
<div class="k">实体</div>
<h3 style="color:#5aad4e">agent</h3>
<div class="noun">执行任务的实体</div>
<p>新建一个、给已有的派任务、列出所有agent、把干完的归档。一个agent就是会话DAG里的一条分支。</p>
<div class="tools"><span>agent</span><span>list_agents</span><span>archive_agent</span></div>
</div>
<div class="dom" style="border-top-color:#4f8ef7">
<div class="k">通讯</div>
<h3 style="color:#4f8ef7">message</h3>
<div class="noun">发消息,和读历史</div>
<p>发一条消息过去,不产生任务记录;或者直接读任意一条分支的全文,不触发对方运行。</p>
<div class="tools"><span>send_message</span><span>read_conversation</span></div>
</div>
</div>
<div class="note">
<b>最容易混的两组:</b>todo和task同是"任务"两个字,但一个是写下来的计划、一个是正在运行的执行记录,所以计划清单用<code>todo_*</code>前缀,
执行侧用<code>list_tasks</code>。task和agent也不是一回事:停掉一个task只是停那一轮运行,
agent本体还在agent列表里;把agent归档是<code>archive_agent</code>。
</div>
<!-- ══════════ 02 四件事 ══════════ -->
<h2 id="four"><span class="num">02</span>一个agent能做的四件事</h2>
<p class="lead">新建agent、派任务、发消息、查询。前两件产生<code>task_id</code>并记入任务列表,第三件不产生,第四件只读。</p>
<div class="card">
<svg viewBox="0 0 1160 640" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="一个agent能做的四件事">
<defs>
<marker id="a-gy" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#8a8675"/></marker>
<marker id="a-gn" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#5aad4e"/></marker>
<marker id="a-or" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#d4843a"/></marker>
<marker id="a-bl" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#4f8ef7"/></marker>
<marker id="a-cy" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#2db3d5"/></marker>
</defs>
<text x="24" y="32" fill="#d4843a" font-size="16" font-weight="700">一个agent能做的四件事</text>
<text x="24" y="56" fill="#8a8675" font-size="12.5">新建和派任务都记一条任务,发消息只是把内容送过去,查询不改动任何状态。</text>
<!-- 主体 -->
<rect x="32" y="92" width="126" height="452" rx="10" fill="#1d2738" stroke="#4f8ef7" stroke-width="1.6"/>
<text x="95" y="296" fill="#4f8ef7" font-size="24" font-weight="700" text-anchor="middle">我</text>
<text x="95" y="322" fill="#8a8675" font-size="11.5" text-anchor="middle">当前agent</text>
<text x="95" y="342" fill="#6b6a63" font-size="11" text-anchor="middle">一轮之内</text>
<!-- 新建 -->
<line x1="160" y1="130" x2="226" y2="130" stroke="#5aad4e" stroke-width="1.6" marker-end="url(#a-gn)"/>
<rect x="228" y="96" width="268" height="68" rx="8" fill="#101a10" stroke="#5aad4e" stroke-width="1.5"/>
<text x="244" y="122" fill="#5aad4e" font-size="14" font-weight="700">新建 · 创建一个新agent</text>
<text x="244" y="146" fill="#bdb9a6" font-size="11.5" font-family="Menlo,monospace">agent(prompt, description, …)</text>
<line x1="498" y1="130" x2="556" y2="130" stroke="#5aad4e" stroke-width="1.6" marker-end="url(#a-gn)"/>
<rect x="558" y="98" width="214" height="64" rx="8" fill="#101a10" stroke="#5aad4e" stroke-width="1.3"/>
<text x="576" y="124" fill="#5aad4e" font-size="13" font-weight="700">新分支 = 新agent</text>
<text x="576" y="146" fill="#8a8675" font-size="11">description成为它的名字</text>
<!-- 派任务 -->
<line x1="160" y1="246" x2="226" y2="246" stroke="#d4843a" stroke-width="1.6" marker-end="url(#a-or)"/>
<rect x="228" y="212" width="268" height="68" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.5"/>
<text x="244" y="238" fill="#d4843a" font-size="14" font-weight="700">派任务 · 给已有agent派受管任务</text>
<text x="244" y="262" fill="#bdb9a6" font-size="11.5" font-family="Menlo,monospace">agent(to="名字", prompt=…)</text>
<line x1="498" y1="246" x2="556" y2="246" stroke="#d4843a" stroke-width="1.6" marker-end="url(#a-or)"/>
<rect x="558" y="214" width="214" height="64" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.3"/>
<text x="576" y="240" fill="#d4843a" font-size="13" font-weight="700">已有agent,不新建</text>
<text x="576" y="262" fill="#8a8675" font-size="11">它忙就排队,空闲就跑</text>
<!-- 发消息 -->
<line x1="160" y1="362" x2="226" y2="362" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#a-bl)"/>
<rect x="228" y="328" width="268" height="68" rx="8" fill="#1d2738" stroke="#4f8ef7" stroke-width="1.5"/>
<text x="244" y="354" fill="#4f8ef7" font-size="14" font-weight="700">发消息 · 给已有agent发一条消息</text>
<text x="244" y="378" fill="#bdb9a6" font-size="11.5" font-family="Menlo,monospace">send_message(to="名字", message=…)</text>
<line x1="498" y1="362" x2="556" y2="362" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#a-bl)"/>
<rect x="558" y="330" width="214" height="64" rx="8" fill="#1d2738" stroke="#4f8ef7" stroke-width="1.3"/>
<text x="576" y="356" fill="#4f8ef7" font-size="13" font-weight="700">已有agent</text>
<text x="576" y="378" fill="#8a8675" font-size="11">读到之后自己决定</text>
<!-- 发消息:不记任务 -->
<line x1="774" y1="362" x2="782" y2="362" stroke="#8a8675" stroke-width="1.3" stroke-dasharray="4 4"/>
<line x1="786" y1="355" x2="800" y2="369" stroke="#e0445a" stroke-width="2"/>
<line x1="800" y1="355" x2="786" y2="369" stroke="#e0445a" stroke-width="2"/>
<text x="562" y="418" fill="#8a8675" font-size="11">不记任务:没有task_id,也就没有必回的结果</text>
<!-- 查询 -->
<line x1="160" y1="486" x2="226" y2="486" stroke="#2db3d5" stroke-width="1.6" marker-end="url(#a-cy)"/>
<rect x="228" y="440" width="268" height="92" rx="8" fill="#1e2630" stroke="#2db3d5" stroke-width="1.5"/>
<text x="244" y="466" fill="#2db3d5" font-size="14" font-weight="700">查询 · 只读,四个来源</text>
<text x="244" y="488" fill="#bdb9a6" font-size="11" font-family="Menlo,monospace">list_agents agent列表 · list_tasks 派出去的任务</text>
<text x="244" y="508" fill="#bdb9a6" font-size="11" font-family="Menlo,monospace">read_conversation 任意分支全文</text>
<text x="244" y="524" fill="#bdb9a6" font-size="11" font-family="Menlo,monospace">todo_list 自己的计划清单</text>
<text x="562" y="490" fill="#8a8675" font-size="12">返回一段文本,什么也不改</text>
<!-- 任务记录 -->
<rect x="848" y="92" width="270" height="200" rx="10" fill="#241f10" stroke="#d4843a" stroke-width="1.8"/>
<text x="866" y="120" fill="#d4843a" font-size="14" font-weight="700">任务记录</text>
<text x="866" y="144" fill="#bdb9a6" font-size="11.5">task_id · 状态 · 结果</text>
<text x="866" y="168" fill="#bdb9a6" font-size="11.5">list_tasks() 列出任务</text>
<text x="866" y="190" fill="#bdb9a6" font-size="11.5">task_output(task_id) 取结果</text>
<text x="866" y="212" fill="#bdb9a6" font-size="11.5">task_stop(task_id) 取消,级联</text>
<text x="866" y="242" fill="#8a8675" font-size="10.5">后两个带归属检查:</text>
<text x="866" y="260" fill="#8a8675" font-size="10.5">只有派它的会话及其祖先能操作</text>
<text x="866" y="278" fill="#6b6a63" font-size="10.5">别的会话拿到task_id也不行</text>
<path d="M774 130 L844 130" fill="none" stroke="#5aad4e" stroke-width="1.6" marker-end="url(#a-gn)"/>
<path d="M774 246 C 812 246, 812 210, 844 208" fill="none" stroke="#d4843a" stroke-width="1.6" marker-end="url(#a-or)"/>
<!-- 任务结果回流 -->
<path d="M848 268 L812 268 L812 600 L95 600 L95 550" fill="none" stroke="#d4843a"
stroke-width="1.5" stroke-dasharray="6 4" marker-end="url(#a-or)"/>
<text x="440" y="590" fill="#d4843a" font-size="11.5" text-anchor="middle">任务结果自动回流:目标跑完,结果接到我这条会话后面,我下一轮读到</text>
<!-- 消息回复的回流 -->
<path d="M556 392 C 460 414, 320 418, 162 406" fill="none" stroke="#4f8ef7"
stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#a-bl)"/>
<text x="352" y="432" fill="#4f8ef7" font-size="11" text-anchor="middle">目标如果回复,回复也自动送回来,回不回由它决定</text>
</svg>
</div>
<div class="legend">
<b>怎么读</b>:四条横道从左边的"我"出发。<b>新建</b>和<b>派任务</b>都走<code>agent</code>这一个工具,区别只在有没有<code>to=</code>:
没有<code>to=</code>就新建一条分支,有<code>to=</code>就把任务交给已经存在的那条。两者都记一条任务,拿回一个<code>task_id</code>。
<b>发消息</b>走<code>send_message</code>,消息送达即结束,不产生<code>task_id</code>。<b>查询</b>四个工具全是只读的。<br>
<b>颜色</b>:绿=创建实体 · 橙=受管任务和任务记录 · 蓝=消息 · 青=只读。虚线=异步回流。
</div>
<!-- ══════════ 03 消息和任务的区别 ══════════ -->
<h2 id="power"><span class="num">03</span>消息和任务的区别:消息不产生任务记录,任务可追踪可取消</h2>
<p class="lead">同样是"把内容送到另一条分支上跑一轮",消息和任务给出的承诺完全不同。差别就写在收件方第一眼看到的那行回执头里。</p>
<div class="card">
<svg viewBox="0 0 1160 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="消息与任务的承诺差异">
<defs>
<marker id="p-bl" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#4f8ef7"/></marker>
<marker id="p-or" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#d4843a"/></marker>
</defs>
<!-- 消息道 -->
<text x="32" y="48" fill="#4f8ef7" font-size="14" font-weight="700">消息 · 不产生任务记录</text>
<rect x="32" y="72" width="130" height="64" rx="8" fill="#1d2738" stroke="#4f8ef7" stroke-width="1.5"/>
<text x="97" y="100" fill="#4f8ef7" font-size="13" font-weight="700" text-anchor="middle">任意agent</text>
<text x="97" y="120" fill="#8a8675" font-size="11" text-anchor="middle">谁都能发</text>
<line x1="164" y1="104" x2="246" y2="104" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#p-bl)"/>
<text x="205" y="94" fill="#8a8675" font-size="10.5" text-anchor="middle">send_message</text>
<rect x="248" y="72" width="322" height="64" rx="8" fill="#12110b" stroke="#4f8ef7" stroke-width="1.3" stroke-dasharray="5 4"/>
<text x="264" y="96" fill="#4f8ef7" font-size="11" font-family="Menlo,monospace">[message from SID:HEAD]</text>
<text x="264" y="116" fill="#bdb9a6" font-size="11">回复是可选的,没什么要补充就别回</text>
<line x1="572" y1="104" x2="626" y2="104" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#p-bl)"/>
<rect x="628" y="72" width="140" height="64" rx="8" fill="#1d2738" stroke="#4f8ef7" stroke-width="1.5"/>
<text x="698" y="100" fill="#4f8ef7" font-size="13" font-weight="700" text-anchor="middle">目标agent</text>
<text x="698" y="120" fill="#8a8675" font-size="11" text-anchor="middle">自己决定做不做</text>
<text x="796" y="88" fill="#bdb9a6" font-size="11.5">没有task_id,没有归属检查</text>
<text x="796" y="110" fill="#bdb9a6" font-size="11.5">对方看到后自己决定回不回</text>
<text x="796" y="132" fill="#8a8675" font-size="11.5">所以多方同时发也是安全的</text>
<line x1="32" y1="180" x2="1128" y2="180" stroke="#2e2c20" stroke-width="1"/>
<!-- 任务道 -->
<text x="32" y="222" fill="#d4843a" font-size="14" font-weight="700">任务 · 可追踪可取消</text>
<rect x="32" y="246" width="130" height="64" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.5"/>
<text x="97" y="274" fill="#d4843a" font-size="13" font-weight="700" text-anchor="middle">派发方</text>
<text x="97" y="294" fill="#8a8675" font-size="11" text-anchor="middle">只能操作自己派的</text>
<line x1="164" y1="278" x2="246" y2="278" stroke="#d4843a" stroke-width="1.6" marker-end="url(#p-or)"/>
<text x="205" y="268" fill="#8a8675" font-size="10.5" text-anchor="middle">agent(to=…)</text>
<rect x="248" y="246" width="322" height="64" rx="8" fill="#12110b" stroke="#d4843a" stroke-width="1.3" stroke-dasharray="5 4"/>
<text x="264" y="270" fill="#d4843a" font-size="11" font-family="Menlo,monospace">[task from SID:HEAD]</text>
<text x="264" y="290" fill="#bdb9a6" font-size="11">这一轮就是任务,最终回复自动回给派发者</text>
<line x1="572" y1="278" x2="626" y2="278" stroke="#d4843a" stroke-width="1.6" marker-end="url(#p-or)"/>
<rect x="628" y="246" width="140" height="64" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.5"/>
<text x="698" y="274" fill="#d4843a" font-size="13" font-weight="700" text-anchor="middle">目标agent</text>
<text x="698" y="294" fill="#8a8675" font-size="11" text-anchor="middle">这一轮归任务</text>
<text x="796" y="262" fill="#bdb9a6" font-size="11.5">有task_id:结果必回、可取消</text>
<text x="796" y="284" fill="#bdb9a6" font-size="11.5">取消会级联,子任务一起停</text>
<text x="796" y="306" fill="#8a8675" font-size="11.5">所以只能操作自己派出的任务</text>
<rect x="32" y="344" width="1096" height="52" rx="8" fill="#241015" stroke="#e0445a" stroke-width="1.2"/>
<text x="50" y="368" fill="#e0445a" font-size="12.5" font-weight="700">归属检查:</text>
<text x="124" y="368" fill="#bdb9a6" font-size="12">read_conversation能读任何分支,所以任何agent都可能看到别人的task_id。</text>
<text x="50" y="386" fill="#bdb9a6" font-size="12">task_output和task_stop因此先验归属:当前会话必须是这个任务的派发者,或者任务链上的祖先,否则直接拒绝。用户和界面不受这条限制。</text>
</svg>
</div>
<div class="tbl">
<table>
<tr><th>承诺</th><th style="color:#4f8ef7">消息 send_message</th><th style="color:#d4843a">任务 agent() / agent(to=)</th></tr>
<tr><td><b>谁能发起</b></td><td class="y">任何agent,对任何未归档的分支</td><td>同样任何agent,但产生的<code>task_id</code>只有自己能操作</td></tr>
<tr><td><b>有没有task_id</b></td><td class="n">没有,只回一个delivery_id告诉你送到了</td><td class="y">有<code>task_id</code>,记入任务列表,<code>list_tasks</code>能查</td></tr>
<tr><td><b>对方必须回吗</b></td><td class="n">不必。回执头明说"没什么要补充就别回"</td><td class="y">必须。这一轮的最终回复就是任务结果</td></tr>
<tr><td><b>能不能取消</b></td><td class="n">不能。消息已经送达,无法撤回</td><td class="y"><code>task_stop</code>,排队中的直接撤回,在跑的停那一轮</td></tr>
<tr><td><b>取消会不会级联</b></td><td class="n">不涉及</td><td class="y">级联:停一个任务,它派生的任务全停</td></tr>
<tr><td><b>能操作别人的任务吗</b></td><td>不涉及,消息发出后没有后续操作</td><td class="n">不能。<code>task_output</code>/<code>task_stop</code>有归属检查</td></tr>
<tr><td><b>多方并发安不安全</b></td><td class="y">安全。不产生任务记录,最坏的结果是对方不回复</td><td>安全,且有记录:目标忙就排队,不会被打断</td></tr>
</table>
</div>
<div class="note">
归档一个agent本体是另一件事,<b>任何会话都能做</b>:归档不中断在跑的工作、也不删数据,
所以<code>archive_agent</code>不做<code>task_stop</code>那样的归属检查。见下一节。
</div>
<!-- ══════════ 04 两个预算 ══════════ -->
<h2 id="budget"><span class="num">04</span>两个预算:两个计数器,各管一件事</h2>
<p class="lead">一条链是一次用户轮次产生的全部调用。消息数每跳加一,回复回流也算一跳;代数只有创建新agent时加一。读回结果花的是消息,不花代数,所以派一批活、看结果、再派一批走得通。</p>
<div class="card">
<svg viewBox="0 0 1160 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="两个预算">
<defs>
<marker id="b-gy" markerWidth="8" markerHeight="8" refX="6" refY="2.6" orient="auto"><path d="M0,0 L6,2.6 L0,5.2 Z" fill="#8a8675"/></marker>
</defs>
<text x="24" y="32" fill="#d4843a" font-size="16" font-weight="700">一条链的两个计数器</text>
<text x="24" y="54" fill="#8a8675" font-size="12.5">下面这条轴是消息数:spawn、send_message、agent(to=)、回流每跳都+1。代数是另一个计数器,只有创建新agent才+1。</text>
<!-- 上限标注 -->
<text x="228" y="98" fill="#e0445a" font-size="12" font-weight="700">agent.max_spawn_depth = 1(代数)</text>
<text x="228" y="118" fill="#bdb9a6" font-size="11">只有左边这一跳涨代数,后面的消息跳都不涨;worker不能再创建新agent</text>
<text x="1052" y="98" fill="#e0445a" font-size="12" font-weight="700" text-anchor="end">agent.max_messages = 8</text>
<text x="1052" y="118" fill="#bdb9a6" font-size="11" text-anchor="end">整条链传满8跳就不再投递,A和B来回也停</text>
<line x1="220" y1="128" x2="220" y2="194" stroke="#e0445a" stroke-width="1.6" stroke-dasharray="5 4"/>
<line x1="1060" y1="128" x2="1060" y2="194" stroke="#e0445a" stroke-width="1.6" stroke-dasharray="5 4"/>
<!-- 跳跃弧 -->
<path d="M100 200 Q 160 156 220 200" fill="none" stroke="#5aad4e" stroke-width="1.6" marker-end="url(#b-gy)"/>
<text x="160" y="150" fill="#5aad4e" font-size="11" text-anchor="middle">新建</text>
<path d="M220 200 Q 280 156 340 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<text x="280" y="150" fill="#4f8ef7" font-size="11" text-anchor="middle">发消息</text>
<path d="M340 200 Q 400 156 460 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<text x="400" y="150" fill="#4f8ef7" font-size="11" text-anchor="middle">回复</text>
<path d="M460 200 Q 520 156 580 200" fill="none" stroke="#d4843a" stroke-width="1.6" marker-end="url(#b-gy)"/>
<text x="520" y="150" fill="#d4843a" font-size="11" text-anchor="middle">派任务</text>
<path d="M580 200 Q 640 156 700 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<path d="M700 200 Q 760 156 820 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<path d="M820 200 Q 880 156 940 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<path d="M940 200 Q 1000 156 1060 200" fill="none" stroke="#4f8ef7" stroke-width="1.6" marker-end="url(#b-gy)"/>
<text x="880" y="150" fill="#6b6a63" font-size="11" text-anchor="middle">…</text>
<!-- 计数轴 -->
<line x1="100" y1="200" x2="1060" y2="200" stroke="#2e2c20" stroke-width="1.4"/>
<g fill="#8a8675" font-size="11.5" text-anchor="middle">
<circle cx="100" cy="200" r="4" fill="#4f8ef7"/><text x="100" y="224">0</text>
<circle cx="220" cy="200" r="4" fill="#5aad4e"/><text x="220" y="224">1</text>
<circle cx="340" cy="200" r="3.5" fill="#3d3b2e"/><text x="340" y="224">2</text>
<circle cx="460" cy="200" r="3.5" fill="#3d3b2e"/><text x="460" y="224">3</text>
<circle cx="580" cy="200" r="3.5" fill="#3d3b2e"/><text x="580" y="224">4</text>
<circle cx="700" cy="200" r="3.5" fill="#3d3b2e"/><text x="700" y="224">5</text>
<circle cx="820" cy="200" r="3.5" fill="#3d3b2e"/><text x="820" y="224">6</text>
<circle cx="940" cy="200" r="3.5" fill="#3d3b2e"/><text x="940" y="224">7</text>
<circle cx="1060" cy="200" r="4" fill="#e0445a"/><text x="1060" y="224">8</text>
</g>
<text x="100" y="246" fill="#4f8ef7" font-size="11" text-anchor="middle">主agent</text>
<text x="220" y="246" fill="#5aad4e" font-size="11" text-anchor="middle">worker</text>
<!-- 底部两条说明 -->
<rect x="32" y="272" width="536" height="60" rx="8" fill="#12110b" stroke="#2e2c20" stroke-width="1"/>
<text x="50" y="294" fill="#d4843a" font-size="12" font-weight="700">代数用完时</text>
<text x="50" y="314" fill="#bdb9a6" font-size="11.5">越界的那次创建被拒,并给出理由("自己完成"),别的工具全留着。</text>
<text x="50" y="330" fill="#bdb9a6" font-size="11.5">worker还能用agent(to=)和send_message,工具也不摘。</text>
<rect x="592" y="272" width="536" height="60" rx="8" fill="#12110b" stroke="#2e2c20" stroke-width="1"/>
<text x="610" y="294" fill="#d4843a" font-size="12" font-weight="700">消息数用完时</text>
<text x="610" y="314" fill="#bdb9a6" font-size="11.5">agent、task_output、task_stop直接从工具列表里消失:每种派发都要</text>
<text x="610" y="330" fill="#8a8675" font-size="11.5">交出一条消息,留在列表里模型只会调用一次再被拒,白费一轮。</text>
</svg>
</div>
<p class="lead">两个都支持设0,0就是这条线不存在,什么也不拦。</p>
<pre><i># 命令行改,即时生效</i>
openprogram config set <b>agent.max_spawn_depth</b> 1 <i># 默认:主agent创建worker,worker自己完成工作</i>
openprogram config set <b>agent.max_spawn_depth</b> 2 <i># worker可以再创建一层,第三层被拒</i>
openprogram config set <b>agent.max_spawn_depth</b> 0 <i># 不限层数</i>
openprogram config set <b>agent.max_messages</b> 8 <i># 默认:一条链最多传8跳</i>
openprogram config set <b>agent.max_messages</b> 0 <i># 不限条数,agent之间可以一直互发</i>
openprogram config set <b>agent.max_spawn_fanout</b> 8 <i># 默认:一轮最多创建8个agent</i>
openprogram config set <b>agent.max_spawn_fanout</b> 0 <i># 一轮想创建多少个都行</i>
</pre>
<div class="note">
设置页的<b>Agent</b>分组里是同样的数字框:Max spawn depth、Max messages per chain、Max spawn fan-out,
改完即时生效,落盘在<code>~/.openprogram/config.json</code>的<code>agent</code>段。
全设0之后,防失控就只剩并发上限(<code>OPENPROGRAM_JOB_WORKERS</code>,默认4)、
每轮50次工具调用的上限和用户自己按停。<br>
<b>扇出预算不在上面那两个链上计数器里。</b>派生把计数交给孩子、自己那份不动,
所以链上计数器数不到兄弟。扇出按(会话,轮次)单独计,只拒绝不摘工具:
工具清单在轮次开始时就冻结了,这个数在轮次里才花掉。<br>
<b>回流那一跳两个计数走不同方向。</b>消息数从孩子那边接着走,A↔B来回才会停;
代数退回派发方的计数,读结果不算创建,协调者读完第一波结果还能派第二波。<br>
有一条规则不受预算影响:<b>自己发给自己一律拒</b>。<code>to=</code>指向发起方当前这条分支是直接的环,立刻拒掉。
</div>
<!-- ══════════ 05 归档 ══════════ -->
<h2 id="archive"><span class="num">05</span>归档:停止接收投递,历史保留</h2>
<p class="lead">分支在会话DAG里永远留着,fork、回放、读全文都要用它。所以归档不是删除,只是在分支上写一个<code>archived</code>标记。</p>
<div class="card">
<svg viewBox="0 0 1160 330" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="归档后的行为">
<defs>
<marker id="r-gn" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#5aad4e"/></marker>
<marker id="r-or" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#d4843a"/></marker>
</defs>
<text x="24" y="28" fill="#d4843a" font-size="15" font-weight="700">两种归档方式,一个标记,五种后果</text>
<!-- 两种归档入口 -->
<rect x="296" y="44" width="256" height="50" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.3"/>
<text x="312" y="66" fill="#d4843a" font-size="11.5" font-family="Menlo,monospace">agent(archive_when_done=true)</text>
<text x="312" y="84" fill="#8a8675" font-size="11">派任务时就声明:做完自动归档</text>
<rect x="608" y="44" width="256" height="50" rx="8" fill="#241f10" stroke="#d4843a" stroke-width="1.3"/>
<text x="624" y="66" fill="#d4843a" font-size="11.5" font-family="Menlo,monospace">archive_agent(to="名字")</text>
<text x="624" y="84" fill="#8a8675" font-size="11">事后指定:干完的逐个归档</text>
<path d="M424 96 L500 122" fill="none" stroke="#d4843a" stroke-width="1.4" marker-end="url(#r-or)"/>
<path d="M736 96 L660 122" fill="none" stroke="#d4843a" stroke-width="1.4" marker-end="url(#r-or)"/>
<rect x="460" y="128" width="240" height="92" rx="10" fill="#241f10" stroke="#d4843a" stroke-width="1.8"/>
<text x="580" y="164" fill="#d4843a" font-size="15" font-weight="700" text-anchor="middle">已归档的分支</text>
<text x="580" y="188" fill="#bdb9a6" font-size="11.5" text-anchor="middle" font-family="Menlo,monospace">archived: true</text>
<text x="580" y="208" fill="#8a8675" font-size="11" text-anchor="middle">写在它自己的分支meta上</text>
<!-- 拒收 -->
<text x="372" y="122" fill="#e0445a" font-size="11.5" font-weight="700" text-anchor="end">拒收</text>
<g font-size="12" fill="#bdb9a6" text-anchor="end">
<text x="372" y="152">list_agents 默认视图里不列</text>
<text x="372" y="180">send_message(to=…) 拒收</text>
<text x="372" y="208">agent(to=…) 拒收</text>
</g>
<g stroke="#3a382c" stroke-width="1.3">
<line x1="382" y1="148" x2="456" y2="152"/>
<line x1="382" y1="176" x2="456" y2="174"/>
<line x1="382" y1="204" x2="456" y2="196"/>
</g>
<g stroke="#e0445a" stroke-width="2">
<line x1="412" y1="143" x2="424" y2="155"/><line x1="424" y1="143" x2="412" y2="155"/>
<line x1="412" y1="169" x2="424" y2="181"/><line x1="424" y1="169" x2="412" y2="181"/>
<line x1="412" y1="194" x2="424" y2="206"/><line x1="424" y1="194" x2="412" y2="206"/>
</g>
<!-- 照常 -->
<text x="790" y="122" fill="#5aad4e" font-size="11.5" font-weight="700">照常</text>
<g font-size="12" fill="#bdb9a6">
<text x="790" y="160">read_conversation(…) 照读全文</text>
<text x="790" y="196">agent(start_from="SID:MSG_ID") 照fork</text>
</g>
<path d="M704 158 L784 156" fill="none" stroke="#5aad4e" stroke-width="1.4" marker-end="url(#r-gn)"/>
<path d="M704 190 L784 192" fill="none" stroke="#5aad4e" stroke-width="1.4" marker-end="url(#r-gn)"/>
<text x="790" y="216" fill="#8a8675" font-size="11">另外 list_agents(scope="archived") 单独列已归档的</text>
<text x="32" y="262" fill="#bdb9a6" font-size="12">拒收只写在一个地方:两条投递路径共用的地址解析器。地址一旦落到分支当前的tip上就查这个标记,所以没有哪条调用能绕开。</text>
<text x="32" y="286" fill="#bdb9a6" font-size="12">任何会话都能归档任何agent:归档不中断在跑的工作、也不删数据,所以不做归属检查。</text>
<text x="32" y="310" fill="#8a8675" font-size="12">没有反归档。归档的意思是"这段对话结束了";还想接着用,就用 agent(start_from="SID:MSG_ID") fork一条新的出来,新名字新生命周期。</text>
</svg>
</div>
<!-- ══════════ 06 场景 ══════════ -->
<h2 id="scenes"><span class="num">06</span>典型场景走一遍</h2>
<p class="lead">三条最常走的路。同一批工具,组合方式不同。</p>
<div class="sc">
<h4>场景一 · 派一批任务出去</h4>
<p class="why">手上有五件互不依赖的事。先写成计划,再一件件派出去,最后回来更新计划。</p>
<div class="steps">
<div class="step"><div class="n">01 计划</div><div class="t">todo_create(subject=…)</div>
<div class="d">五件事写成五条。<code>blocked_by</code>标出谁等谁。</div></div>
<div class="step"><div class="n">02 派任务</div><div class="t">agent(prompt, description, run_in_background=true)</div>
<div class="d">逐条派。后台形式立刻返回<code>task_id</code>,不占我这一轮。</div></div>
<div class="step"><div class="n">03 盯</div><div class="t">list_tasks()</div>
<div class="d">五个task_id五个状态。要结果就<code>task_output</code>,要取消就<code>task_stop</code>。</div></div>
<div class="step"><div class="n">04 收</div><div class="t">结果自动回流</div>
<div class="d">不用轮询也行。哪个跑完,它的结果就接到我这条会话后面,我下一轮读到。</div></div>
<div class="step"><div class="n">05 更新计划</div><div class="t">todo_update(todo_id, status="completed")</div>
<div class="d">计划状态和实际执行一致。</div></div>
</div>
</div>
<div class="sc">
<h4>场景二 · 找一个已经存在的agent</h4>
<p class="why">这件事已经有agent做过一半,不需要再创建一个从零开始的。先查agent列表,再决定是发消息还是正式派任务。</p>
<div class="steps">
<div class="step"><div class="n">01 查agent列表</div><div class="t">list_agents(scope="all")</div>
<div class="d">按会话分组。每条分支给名字、一个能直接用的<code>to="SID:HEAD"</code>、轮数和大致字数、还有末尾预览。</div></div>
<div class="step"><div class="n">02a 发消息</div><div class="t">send_message(to="名字", message=…)</div>
<div class="d">问一句、通知一声、把结论同步过去。不产生<code>task_id</code>,对方可以不回复。</div></div>
<div class="step"><div class="n">02b 派任务</div><div class="t">agent(to="名字", prompt=…)</div>
<div class="d">这件事确实由它做,而且我要结果。产生<code>task_id</code>,可取消,结果必回。</div></div>
<div class="step"><div class="n">03 忙就排队</div><div class="t">目标在跑就进收件箱</div>
<div class="d">不打断也不丢弃。它这一轮结束后就接着跑我这条。</div></div>
</div>
</div>
<div class="sc">
<h4>场景三 · 回看一段旧会话</h4>
<p class="why">想知道上次那件事到底怎么做成的,但不想触发任何agent运行,也不想把整段历史全部读进上下文。</p>
<div class="steps">
<div class="step"><div class="n">01 拿地址</div><div class="t">list_agents(scope="all")</div>
<div class="d">同一份agent列表。它标出的轮数和字数可以先判断体量。</div></div>
<div class="step"><div class="n">02 只看结尾</div><div class="t">read_conversation(session_id="SID:HEAD", start_turn=-10)</div>
<div class="d">负数从末尾数,<code>-10</code>就是最后十轮。<code>list_agents</code>给的<code>SID:HEAD</code>整串粘进去也认。</div></div>
<div class="step"><div class="n">03 要不要工具细节</div><div class="t">include_function_calls=false</div>
<div class="d">默认连每轮调了什么工具、参数、结果都给。只想看对话就关掉。</div></div>
<div class="step"><div class="n">04 控体量</div><div class="t">max_chars=60000</div>
<div class="d">超了就丢后面的轮次。归档的分支也照读,读取不触发任何agent运行。</div></div>
</div>
</div>
<!-- ══════════ 07 CC 对应 ══════════ -->
<h2 id="cc"><span class="num">07</span>和Claude Code的对应</h2>
<p class="lead">同名的地方是有意对齐的,差别只有一处命名冲突和三个我们额外提供的工具。</p>
<div class="tbl">
<table>
<tr><th>这边</th><th>Claude Code</th><th>关系</th></tr>
<tr><td><code>agent</code></td><td>同名</td><td class="y">同义。创建新agent的唯一入口;<code>to=</code>派任务是我们这边的扩展</td></tr>
<tr><td><code>list_agents</code></td><td>同名</td><td class="y">同义。看得见对方是通信的前提</td></tr>
<tr><td><code>send_message</code></td><td>同名</td><td class="y">同义。跨分支发消息</td></tr>
<tr><td><code>task_output</code></td><td>TaskOutput</td><td class="y">参数形状照抄:<code>block</code>默认true,<code>timeout</code>毫秒,默认30000,上限600000</td></tr>
<tr><td><code>task_stop</code></td><td>同名</td><td class="y">同义。我们额外加了归属检查和级联取消</td></tr>
<tr><td><code>todo_create</code> / <code>todo_update</code> / <code>todo_list</code></td><td>TaskList</td>
<td>同物不同名。Claude Code的TaskList是<b>计划清单</b>,和我们的<code>list_tasks</code>撞名不撞义,
所以我们的计划清单一律走<code>todo_*</code>前缀,把<code>list_tasks</code>这个名字留给真正在运行的任务</td></tr>
<tr><td><code>list_tasks</code></td><td>没有</td><td>我们多出来的:让模型自己查后台正在跑的任务,不必一直保存派生时返回的<code>task_id</code></td></tr>
<tr><td><code>archive_agent</code></td><td>没有</td><td>我们多出来的:agent不归档,agent列表就会一直堆积早就做完的worker</td></tr>
<tr><td><code>read_conversation</code></td><td>没有</td><td>我们多出来的:一条分支渲染成可读的文字稿,带轮次范围和体量上限,不用直接读原始记录文件</td></tr>
</table>
</div>
<div class="legend">
<b>一句话收尾</b>:四个名词分四个域,<code>agent</code>一个工具管新建和派任务,消息不产生任务记录所以任何agent都能发,
任务可追踪可取消所以只能操作自己派出的,两个预算限制一条链的规模,归档只停止后续投递。
</div>
</div>
</body>
</html>