Repository navigation
Expand file tree
/
Copy path08-learning-path.html
More file actions
775 lines (751 loc) · 47.6 KB
/
Copy path08-learning-path.html
File metadata and controls
775 lines (751 loc) · 47.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
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>08 学习路线 · Pi Agent 设计文档</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<button class="menu-btn" id="menuBtn" aria-label="切换目录">☰</button>
<div class="layout">
<aside class="toc" id="toc">
<h1>Pi Agent</h1>
<div class="sub">设计文档 & 学习指南</div>
<ol>
<li><a href="index.html">目录与导览</a></li>
<li><a href="01-overview.html">01 全景与分层</a></li>
<li><a href="02-pi-ai.html">02 pi-ai 深入</a></li>
<li><a href="03-pi-agent-core.html">03 pi-agent-core 深入</a></li>
<li><a href="04-pi-tui.html">04 pi-tui 深入</a></li>
<li><a href="05-pi-coding-agent.html">05 pi-coding-agent 深入</a></li>
<li><a href="06-end-to-end.html">06 端到端时序</a></li>
<li><a href="07-extensions.html">07 扩展点手册</a></li>
<li><a href="08-learning-path.html" class="active">08 学习路线</a></li>
<li><a href="demos.html">Demo 实践</a></li>
</ol>
<div class="nav-bottom">
<a href="https://pi.dev" target="_blank">pi.dev 官网</a>
<a href="https://github.com/earendil-works/pi-mono" target="_blank">GitHub: pi-mono</a>
<a href="https://rfc.earendil.com/keyword/pi/" target="_blank">RFCs</a>
</div>
</aside>
<main>
<h1>08 学习路线</h1>
<p class="page-meta">
本章目标:提供 3 条由浅入深的学习路径——5 天速通、2 周深读、1 月贡献者。每条路径配每日任务清单、配套 demo 和必读源码位置,
让你能根据时间预算和目标(理解 / 修 bug / 提 PR)选择合适的入口。
</p>
<h2>8.0 三条路径总览</h2>
<p>
三条路径面向不同读者:<strong>速通</strong>给"想用 pi 解决日常工作"的人;
<strong>深读</strong>给"想成为贡献者、能修小 bug"的人;
<strong>贡献者</strong>给"想对接核心模块、独立交付特性"的人。
下图横向对比它们的输入门槛、时间预算、产出物和最终能力。
</p>
<script type="text/mermaid">
flowchart LR
subgraph FAST["路径 A · 5 天速通"]
F1[目标: 能向别人讲清 pi]:::fast
F2[投入: 约 8–10 小时]:::fast
F3[产出: 架构图 + 一个 tool/skill]:::fast
end
subgraph DEEP["路径 B · 2 周深读"]
D1[目标: 能修小 bug、提小 PR]:::deep
D2[投入: 约 25–30 小时]:::deep
D3[产出: 1 个 PR + 关键算法讲义]:::deep
end
subgraph CONTRIB["路径 C · 1 月贡献者"]
C1[目标: 独立交付中等特性]:::contrib
C2[投入: 约 80–100 小时]:::contrib
C3[产出: 2 个合并 PR + 模块 owner]:::contrib
end
FAST ==>|"3–4 天加深"| DEEP
DEEP ==>|"2 周深化"| CONTRIB
FAST -.->|"跳读 B 中段"| CONTRIB
classDef fast fill:#1c2a22,stroke:#8fcf80,color:#e4e7ee
classDef deep fill:#1c2230,stroke:#7aa2f7,color:#e4e7ee
classDef contrib fill:#2a1c30,stroke:#b48bff,color:#e4e7ee
</script>
<p>
横向关系:路径 A 是路径 B 的前 5 天子集,路径 B 是路径 C 的前 14 天子集。如果你时间充裕,
建议按 A → B → C 顺序推进;时间紧的读者可直接走 A,熟练后切 B。
</p>
<h2 id="fast">8.1 路径 A:5 天速通(理解设计,能向别人讲清)</h2>
<p>
目标:理解 pi 的 4 包分层、关键概念、能用 pi 解决日常工作。每天 2–3 章节 + 1–2 个 demo,
周末能向同事讲清"pi 由什么组成、怎么协作"。
</p>
<table>
<thead>
<tr>
<th>天</th>
<th>章节阅读</th>
<th>每日任务</th>
<th>配套 demo</th>
<th>必读源码</th>
</tr>
</thead>
<tbody>
<tr>
<td>Day 1<br/>全景 + pi-ai 基础</td>
<td><a href="01-overview.html">01</a> §1.1–§1.6<br/><a href="02-pi-ai.html">02</a> §2.1–§2.5</td>
<td>画一张 4 包依赖图;<code>pi --list-models</code> 跑通;运行 <code>examples/pi-hello</code></td>
<td><code>examples/pi-hello</code></td>
<td><span class="path-ref">packages/ai/src/models.ts:75</span> <code>Provider</code> 接口<br/><span class="path-ref">packages/ai/src/index.ts:38</span> pi-ai 入口</td>
</tr>
<tr>
<td>Day 2<br/>pi-ai 流式 + Tool</td>
<td><a href="02-pi-ai.html">02</a> §2.6–§2.10</td>
<td>用 <code>models.streamSimple()</code> 写 30 行 demo 打印所有 event;跑 <code>pi -p "say ok"</code></td>
<td><code>examples/pi-hello</code> 改造</td>
<td><span class="path-ref">packages/ai/src/models.ts:512</span> <code>streamSimple()</code><br/><span class="path-ref">packages/ai/src/api/anthropic-messages.ts:487</span> Anthropic <code>stream()</code></td>
</tr>
<tr>
<td>Day 3<br/>agent runtime 状态机</td>
<td><a href="03-pi-agent-core.html">03</a> §3.1–§3.6</td>
<td>用 <code>new Agent(...)</code> 写 50 行 demo 订阅所有 <code>AgentEvent</code>;画状态机</td>
<td><code>examples/pi-hello</code></td>
<td><span class="path-ref">packages/agent/src/agent-loop.ts:31</span> <code>agentLoop()</code><br/><span class="path-ref">packages/agent/src/agent.ts:171</span> <code>Agent</code> class</td>
</tr>
<tr>
<td>Day 4<br/>Compaction + Harness</td>
<td><a href="03-pi-agent-core.html">03</a> §3.9–§3.11</td>
<td>写 80 行:手动构造长 session,触发 <code>shouldCompact</code> 看截断位置;用 <code>pi -c</code> 续接</td>
<td><code>examples/pi-hello</code> 长会话版</td>
<td><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:263</span> <code>shouldCompact()</code><br/><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:396</span> <code>findCutPoint()</code></td>
</tr>
<tr>
<td>Day 5<br/>coding-agent + 扩展</td>
<td><a href="05-pi-coding-agent.html">05</a> §5.1–§5.8<br/><a href="07-extensions.html">07</a> §7.1 + §7.3</td>
<td>跑通 <code>examples/pi-hello-tool</code>、<code>pi-hello-skill</code>;自己写一个最小 tool</td>
<td><code>examples/pi-hello-tool</code><br/><code>examples/pi-hello-skill</code></td>
<td><span class="path-ref">packages/coding-agent/src/main.ts:1</span> CLI 入口<br/><span class="path-ref">packages/coding-agent/src/core/extensions/types.ts:1177</span> <code>ExtensionAPI</code></td>
</tr>
</tbody>
</table>
<p>
<strong>产出物</strong>:① 一张 4 包依赖图(手绘或 mermaid 均可)② 50 行能跑通的 <code>Agent</code> demo ③ 一个最小 tool extension。
<strong>验收</strong>:能用 5 分钟讲清"agent loop 在做什么、compaction 为什么必要、tool 怎么注册"。
</p>
<h2 id="deep">8.2 路径 B:2 周深读(核心贡献者入门)</h2>
<p>
目标:能修小 bug、提小 PR、参与 review。每天 1 章节精读 + 1 个 demo 改造 + 关键源码 fork 标注。
2 周内对 pi 所有关键算法有 working 理解。
</p>
<table>
<thead>
<tr>
<th>天</th>
<th>章节</th>
<th>主题</th>
<th>demo 改造</th>
<th>必读源码 + 标注</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="01-overview.html">01</a> + <a href="02-pi-ai.html">02</a> §2.1–§2.3</td>
<td>4 包分层 + 三层抽象 Model/Provider/Api</td>
<td>读 <code>examples/pi-hello</code> 跑通</td>
<td><span class="path-ref">packages/ai/src/models.ts:75</span> ★</td>
</tr>
<tr>
<td>2</td>
<td><a href="02-pi-ai.html">02</a> §2.4–§2.6</td>
<td>Models 集合 + <code>streamSimple</code></td>
<td>改 hello 调 <code>streamSimple</code></td>
<td><span class="path-ref">packages/ai/src/models.ts:512</span> ★<br/><span class="path-ref">packages/ai/src/index.ts:38</span></td>
</tr>
<tr>
<td>3</td>
<td><a href="02-pi-ai.html">02</a> §2.7–§2.10</td>
<td>OpenAI / Anthropic / Google Api 实现</td>
<td>对比 3 个 Api 写法</td>
<td><span class="path-ref">packages/ai/src/api/anthropic-messages.ts:487</span> ★</td>
</tr>
<tr>
<td>4</td>
<td><a href="03-pi-agent-core.html">03</a> §3.1–§3.3</td>
<td><code>agentLoop</code> 状态机 + 事件协议</td>
<td>订阅所有 event 打印</td>
<td><span class="path-ref">packages/agent/src/agent-loop.ts:31</span> ★<br/><span class="path-ref">packages/agent/src/agent-loop.ts:95</span> <code>runAgentLoop</code></td>
</tr>
<tr>
<td>5</td>
<td><a href="03-pi-agent-core.html">03</a> §3.4–§3.6</td>
<td><code>Agent</code> class + barrier 语义</td>
<td>看 <code>examples/pi-hello-agent</code></td>
<td><span class="path-ref">packages/agent/src/agent.ts:171</span> ★<br/><span class="path-ref">packages/agent/src/types.ts:327</span> <code>AgentState</code></td>
</tr>
<tr>
<td>6</td>
<td><a href="03-pi-agent-core.html">03</a> §3.7</td>
<td>Tool 执行模型(sequential / parallel)</td>
<td>写一个 50 行自定义 tool</td>
<td><span class="path-ref">packages/agent/src/agent-loop.ts:281</span> <code>streamAssistantResponse</code></td>
</tr>
<tr>
<td>7</td>
<td><a href="03-pi-agent-core.html">03</a> §3.9–§3.11</td>
<td>Compaction 算法</td>
<td>构造 100 轮 session 触发 compaction</td>
<td><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:263</span> ★<br/><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:396</span></td>
</tr>
<tr>
<td>8</td>
<td><a href="03-pi-agent-core.html">03</a> §3.12 + <a href="05-pi-coding-agent.html">05</a> §5.1</td>
<td>Harness 整体 + <code>AgentHarness.executeTurn</code></td>
<td>改 harness 加日志</td>
<td><span class="path-ref">packages/agent/src/harness/agent-harness.ts:171</span> <code>AgentHarness</code><br/><span class="path-ref">packages/agent/src/harness/agent-harness.ts:581</span> <code>executeTurn()</code> ★</td>
</tr>
<tr>
<td>9</td>
<td><a href="03-pi-agent-core.html">03</a> §3.13</td>
<td>Session JSONL 持久化</td>
<td>用 <code>pi -c</code> 续接 session</td>
<td><span class="path-ref">packages/agent/src/harness/session/jsonl-storage.ts:187</span> ★</td>
</tr>
<tr>
<td>10</td>
<td><a href="04-pi-tui.html">04</a> §4.1–§4.4</td>
<td>pi-tui 渲染 + editor</td>
<td>改 keybinding 试 <code>Ctrl+K</code></td>
<td><span class="path-ref">packages/tui/src/components/editor.ts:270</span> <code>Editor</code> class<br/><span class="path-ref">packages/tui/src/components/editor.ts:1273</span> <code>onSubmit</code> ★</td>
</tr>
<tr>
<td>11</td>
<td><a href="05-pi-coding-agent.html">05</a> §5.1–§5.4</td>
<td>coding-agent 入口 + AgentSession</td>
<td>看 <code>examples/pi-hello-tool</code> 加载流程</td>
<td><span class="path-ref">packages/coding-agent/src/main.ts:1</span> ★<br/><span class="path-ref">packages/coding-agent/src/core/agent-session.ts:303</span> <code>AgentSession</code></td>
</tr>
<tr>
<td>12</td>
<td><a href="07-extensions.html">07</a> §7.1–§7.3</td>
<td>Extension API + 加载机制</td>
<td>改 hello-tool 注册第二个 tool</td>
<td><span class="path-ref">packages/coding-agent/src/core/extensions/types.ts:1177</span> ★</td>
</tr>
<tr>
<td>13</td>
<td><a href="05-pi-coding-agent.html">05</a> §5.6 + <a href="06-end-to-end.html">06</a> §6.4</td>
<td>RPC 协议 + 模式分发</td>
<td>用 <code>examples/pi-hello-rpc-client</code> 调 RPC</td>
<td><span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-types.ts:20</span> ★<br/><span class="path-ref">packages/coding-agent/src/rpc-entry.ts:1</span> RPC 入口</td>
</tr>
<tr>
<td>14</td>
<td>—</td>
<td>收口:选 1 个 good first issue 提 PR</td>
<td>改完跑 <code>./test.sh</code> + <code>npm run check</code></td>
<td>对照 <a href="https://github.com/earendil-works/pi-mono/labels/good%20first%20issue">good first issue label</a></td>
</tr>
</tbody>
</table>
<p>
<strong>标注规则</strong>:表中带 ★ 的行表示需要在源码处加 <code>// TODO(me):</code> 注释,写下"我理解的这一段做了什么"。14 天后你会有 9+ 个 ★ 标注——这就是你的私有"源码阅读笔记"。
</p>
<p><strong>产出物</strong>:① 9+ 个 ★ 标注 ② 1 个最小可运行 PR(哪怕只改一行) ③ 能在 30 分钟内讲清 pi 全部关键算法。</p>
<h2 id="contributor">8.3 路径 C:1 月贡献者(核心模块 owner)</h2>
<p>
目标:能独立设计并交付中等特性,对接 1–2 个核心模块。在路径 B 基础上增加 issue 阅读、PR 模拟提交、扩展点实践、benchmark 复现。
每天分上午(精读 / RFC)/ 下午(实战 / 提 PR)两个时段。
</p>
<table>
<thead>
<tr>
<th>天</th>
<th>主题</th>
<th>上午(阅读 / 设计)</th>
<th>下午(实战 / 输出)</th>
<th>必读 / 产出</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>路径 B 收口</td>
<td>重读 01 + 02,整理速通笔记</td>
<td>跑通 4 个 demo(hello / hello-tool / hello-skill / hello-rpc-client)</td>
<td><span class="path-ref">packages/ai/src/index.ts:38</span></td>
</tr>
<tr>
<td>2</td>
<td>Provider / Api 矩阵</td>
<td>读 RFC #3(Provider 抽象)</td>
<td>给 pi-ai 加一个 mock Provider</td>
<td><span class="path-ref">packages/ai/src/models.ts:75</span></td>
</tr>
<tr>
<td>3</td>
<td>api 实现模式</td>
<td>对比 Anthropic / OpenAI / Google 三个 Api</td>
<td>在 <code>examples/pi-hello-provider</code> 加新模型</td>
<td><span class="path-ref">packages/ai/src/api/anthropic-messages.ts:487</span></td>
</tr>
<tr>
<td>4</td>
<td>认证解析链</td>
<td>读 <code>packages/ai/src/auth/</code></td>
<td>写 <code>CredentialStore</code> mock</td>
<td><span class="path-ref">packages/ai/src/auth/types.ts:60</span></td>
</tr>
<tr>
<td>5</td>
<td><code>agentLoop</code> 状态机</td>
<td>读 <code>agent-loop.ts</code> 全文</td>
<td>加日志看 <code>turn_start/end</code></td>
<td><span class="path-ref">packages/agent/src/agent-loop.ts:31</span></td>
</tr>
<tr>
<td>6</td>
<td><code>Agent</code> 修饰层</td>
<td>读 <code>agent.ts</code> + barrier 语义</td>
<td>写一个订阅所有 event 的 demo</td>
<td><span class="path-ref">packages/agent/src/agent.ts:171</span></td>
</tr>
<tr>
<td>7</td>
<td>Tool 执行 + 错误处理</td>
<td>读 <code>executeToolCalls</code> 路径</td>
<td>写一个会失败的 tool 看 retry 行为</td>
<td><span class="path-ref">packages/agent/src/agent-loop.ts:281</span></td>
</tr>
<tr>
<td>8</td>
<td>Compaction 算法</td>
<td>读 <code>compaction.ts</code> 全文</td>
<td>用 <code>examples/pi-hello</code> 构造 200 轮</td>
<td><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:164</span></td>
</tr>
<tr>
<td>9</td>
<td>Compaction 摘要生成</td>
<td>读 <code>generateSummary</code></td>
<td>构造 boundary 看 cut point</td>
<td><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:263</span></td>
</tr>
<tr>
<td>10</td>
<td>Harness 整体</td>
<td>读 <code>agent-harness.ts</code> + system-prompt 拼接</td>
<td>改 system-prompt 看 effect</td>
<td><span class="path-ref">packages/agent/src/harness/agent-harness.ts:171</span></td>
</tr>
<tr>
<td>11</td>
<td>Session JSONL</td>
<td>读 <code>jsonl-storage.ts</code> + <code>jsonl-repo.ts</code></td>
<td>手写一个分支、读 <code>jsonl</code></td>
<td><span class="path-ref">packages/agent/src/harness/session/jsonl-storage.ts:187</span></td>
</tr>
<tr>
<td>12</td>
<td>pi-tui 渲染</td>
<td>读 <code>render.ts</code> + 组件树</td>
<td>改 <code>theme.ts</code> 改主色</td>
<td><span class="path-ref">packages/tui/src/components/editor.ts:270</span></td>
</tr>
<tr>
<td>13</td>
<td>Editor + keybinding</td>
<td>读 <code>DEFAULT_EDITOR_KEYBINDINGS</code></td>
<td>加一个自定义 keybinding</td>
<td><span class="path-ref">packages/tui/src/components/editor.ts:1273</span></td>
</tr>
<tr>
<td>14</td>
<td>第一周收口</td>
<td>重读所有 ★ 标注</td>
<td>在 <code>examples/</code> 写一个"全部 demo 大杂烩"</td>
<td>14 ★ 标注</td>
</tr>
<tr>
<td>15</td>
<td>RFC 阅读周开始</td>
<td>读 RFC 列表(<a href="https://rfc.earendil.com/keyword/pi/">RFCs</a>)挑 3 篇深读</td>
<td>在 Discord 提一个 RFC 解读问题</td>
<td>RFC 阅读笔记</td>
</tr>
<tr>
<td>16</td>
<td>open issue 分类</td>
<td>扫 30 个 open issue,按模块打 tag</td>
<td>挑 3 个"可复现"的 issue 跑一遍</td>
<td>issue 分类表</td>
</tr>
<tr>
<td>17</td>
<td>扩展点实战</td>
<td>读 07 §7.4–§7.8 全部扩展点</td>
<td>改 hello-skill 加一个新 hook</td>
<td><span class="path-ref">packages/coding-agent/src/core/extensions/types.ts:1177</span></td>
</tr>
<tr>
<td>18</td>
<td>扩展点注册冲突</td>
<td>读 EventBus + 注册顺序</td>
<td>写一个故意冲突的 extension 看 behavior</td>
<td><span class="path-ref">packages/coding-agent/src/core/event-bus.ts:3</span></td>
</tr>
<tr>
<td>19</td>
<td>RPC 协议</td>
<td>读 <code>rpc-types.ts</code> 全部</td>
<td>用 <code>examples/pi-hello-rpc-client</code> 加一条命令</td>
<td><span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-types.ts:20</span></td>
</tr>
<tr>
<td>20</td>
<td>RPC 鉴权</td>
<td>读 <code>rpc-entry.ts</code> 入口</td>
<td>本地起 RPC 服、用 token 验证</td>
<td><span class="path-ref">packages/coding-agent/src/rpc-entry.ts:1</span></td>
</tr>
<tr>
<td>21</td>
<td>第二周收口</td>
<td>读 06 端到端时序,对照 RFC</td>
<td>用 mermaid 画一张自己理解的时序图</td>
<td>个人版时序图</td>
</tr>
<tr>
<td>22</td>
<td>特性设计:选定一个 issue</td>
<td>写设计草稿(参考 03 章模式)</td>
<td>起 draft PR、提早求 review</td>
<td>设计 doc + draft PR 链接</td>
</tr>
<tr>
<td>23</td>
<td>实现:核心逻辑</td>
<td>实现主要分支</td>
<td>写单元测试(<code>packages/*/test</code>)</td>
<td>测试覆盖率 ≥ 80%</td>
</tr>
<tr>
<td>24</td>
<td>实现:边界 + 错误</td>
<td>补 error path、boundary</td>
<td>跑 <code>./test.sh</code> 全绿</td>
<td><code>npm run check</code> 0 errors</td>
</tr>
<tr>
<td>25</td>
<td>benchmark 复现</td>
<td>找 1 个 benchmark(参考 issues)</td>
<td>本地复现 + 记录 baseline</td>
<td>benchmark 数字</td>
</tr>
<tr>
<td>26</td>
<td>文档 + CHANGELOG</td>
<td>更新 <code>CHANGELOG.md</code> Unreleased 段</td>
<td>更新对应章节(本设计文档的相应段落)</td>
<td><code>CHANGELOG.md</code> + 文档 PR</td>
</tr>
<tr>
<td>27</td>
<td>PR review 应对</td>
<td>读 reviewer 意见、拆解</td>
<td>改代码、push commit</td>
<td>commits</td>
</tr>
<tr>
<td>28</td>
<td>PR 合并</td>
<td>最后 review 反馈</td>
<td>合并 + 写 release notes 草稿</td>
<td>merged PR</td>
</tr>
<tr>
<td>29</td>
<td>第二 PR 巩固</td>
<td>挑一个 <code>good first issue</code> 之外的 issue</td>
<td>实现 + 提 PR</td>
<td>draft PR #2</td>
</tr>
<tr>
<td>30</td>
<td>收口 + 接力</td>
<td>整理自己的 30 天笔记</td>
<td>在 Discord 发"我接下来想对接 X 模块"</td>
<td>30 天总结</td>
</tr>
</tbody>
</table>
<p>
<strong>产出物</strong>:① 1 个 merged PR(中等特性) ② 1 个 draft PR(巩固) ③ 1 张个人版时序图 ④ 1 份 CHANGELOG 贡献 ⑤ 30 天总结笔记。
<strong>验收</strong>:能独立设计并交付一个 200–500 行代码的 PR,能在 review 中清晰解释设计取舍。
</p>
<h2>8.4 源码阅读顺序(mermaid graph LR)</h2>
<p>
无论走哪条路径,<strong>统一的源码阅读顺序</strong>是"自底向上":从 <code>packages/ai</code> 入口出发,
一路走到 <code>packages/coding-agent</code> 出口。下图标注每一步的 <code>file:line</code>,方便你跳转阅读。
</p>
<script type="text/mermaid">
graph LR
S0["packages/ai/src/index.ts:38<br/>pi-ai 入口 · 导出 Provider/Models/Context"]:::base
S1["packages/ai/src/models.ts:75<br/>Provider 接口定义"]:::base
S2["packages/ai/src/models.ts:127<br/>Models 接口 · streamSimple/stream/complete"]:::base
S3["packages/ai/src/models.ts:512<br/>Models.streamSimple() 实现"]:::base
S4["packages/ai/src/api/anthropic-messages.ts:487<br/>Anthropic stream() SSE 解析"]:::base
S5["packages/agent/src/index.ts:3<br/>pi-agent-core 入口 · 导出 Agent"]:::core
S6["packages/agent/src/types.ts:327<br/>AgentState 公共状态接口"]:::core
S7["packages/agent/src/agent-loop.ts:31<br/>agentLoop() 状态机入口"]:::core
S8["packages/agent/src/agent-loop.ts:281<br/>streamAssistantResponse() 转换边界"]:::core
S9["packages/agent/src/agent.ts:171<br/>Agent class · barrier + subscribe 修饰"]:::core
S10["packages/agent/src/harness/agent-harness.ts:171<br/>AgentHarness · system-prompt + skills"]:::core
S11["packages/agent/src/harness/compaction/compaction.ts:263<br/>shouldCompact() 触发判定"]:::core
S12["packages/agent/src/harness/session/jsonl-storage.ts:187<br/>JsonlSessionStorage 持久化"]:::core
S13["packages/tui/src/components/editor.ts:270<br/>Editor 组件 · 用户输入"]:::ui
S14["packages/tui/src/components/editor.ts:1273<br/>onSubmit() 提交到 agent"]:::ui
S15["packages/coding-agent/src/main.ts:1<br/>pi CLI 入口 · 模式分发"]:::app
S16["packages/coding-agent/src/core/agent-session.ts:303<br/>AgentSession · product 层会话"]:::app
S17["packages/coding-agent/src/core/extensions/types.ts:1177<br/>ExtensionAPI 扩展点接口"]:::app
S18["packages/coding-agent/src/modes/rpc/rpc-types.ts:20<br/>RPC 协议类型"]:::app
S19["packages/coding-agent/src/index.ts:178<br/>pi-coding-agent 主入口导出"]:::app
S0 --> S1 --> S2 --> S3 --> S4
S4 --> S5
S5 --> S6 --> S7 --> S8 --> S9
S9 --> S10 --> S11 --> S12
S12 --> S13 --> S14
S14 --> S15 --> S16 --> S17 --> S18 --> S19
classDef base fill:#1c2230,stroke:#7aa2f7,color:#e4e7ee
classDef core fill:#1c2a22,stroke:#8fcf80,color:#e4e7ee
classDef ui fill:#2a1c30,stroke:#b48bff,color:#e4e7ee
classDef app fill:#2a221c,stroke:#f0a35e,color:#e4e7ee
</script>
<p>
节点编号即阅读顺序:先看 4 个 pi-ai 节点(<span class="path-ref">packages/ai/src/index.ts:38</span> 起)建立 LLM 抽象,
再看 8 个 pi-agent-core 节点理解状态机,然后切到 2 个 pi-tui 节点,最后看 5 个 pi-coding-agent 节点到达产品出口。
每步建议读 30–60 分钟;全部走完约 8 小时。
</p>
<div class="readlist">
<h4>源码阅读顺序 · 20 步</h4>
<ol>
<li><span class="path-ref">packages/ai/src/index.ts:38</span> — pi-ai 入口导出表(<code>export * from "./types.ts"</code> / <code>"./models.ts"</code>)</li>
<li><span class="path-ref">packages/ai/src/models.ts:75</span> — <code>Provider</code> 接口定义(<code>api</code> / <code>auth</code> / <code>models</code>)</li>
<li><span class="path-ref">packages/ai/src/models.ts:127</span> — <code>Models</code> 接口,<code>stream()</code> / <code>complete()</code> / <code>streamSimple()</code></li>
<li><span class="path-ref">packages/ai/src/models.ts:512</span> — <code>ModelsImpl.streamSimple()</code> 实现,<code>provider.streamSimple()</code> 委派</li>
<li><span class="path-ref">packages/ai/src/api/anthropic-messages.ts:487</span> — Anthropic <code>stream()</code>:SSE 解码、tool_use 提取、reasoning 处理</li>
<li><span class="path-ref">packages/agent/src/index.ts:3</span> — pi-agent-core 入口,<code>export * from "./agent.ts"</code> / <code>"./agent-loop.ts"</code></li>
<li><span class="path-ref">packages/agent/src/types.ts:327</span> — <code>AgentState</code> 接口,宿主读取的公共状态</li>
<li><span class="path-ref">packages/agent/src/agent-loop.ts:31</span> — <code>agentLoop()</code> 函数签名,<code>prompt(s, options?)</code></li>
<li><span class="path-ref">packages/agent/src/agent-loop.ts:281</span> — <code>streamAssistantResponse()</code>,<code>AgentMessage[] → Message[]</code> 转换 + LLM 调用</li>
<li><span class="path-ref">packages/agent/src/agent.ts:171</span> — <code>Agent</code> class,外层加 barrier + subscribe 修饰</li>
<li><span class="path-ref">packages/agent/src/harness/agent-harness.ts:171</span> — <code>AgentHarness</code> class,组合 system-prompt / skills / tools / compaction</li>
<li><span class="path-ref">packages/agent/src/harness/agent-harness.ts:581</span> — <code>executeTurn()</code>,单轮逻辑:拼上下文 → 调 <code>runAgentLoop</code></li>
<li><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:263</span> — <code>shouldCompact()</code>,<code>contextTokens > window − reserveTokens</code></li>
<li><span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:396</span> — <code>findCutPoint()</code>,找切点保留 <code>keepRecentTokens</code></li>
<li><span class="path-ref">packages/agent/src/harness/session/jsonl-storage.ts:187</span> — <code>JsonlSessionStorage</code>,append-only 持久化</li>
<li><span class="path-ref">packages/tui/src/components/editor.ts:270</span> — <code>Editor</code> class,多行编辑 + 提交钩子</li>
<li><span class="path-ref">packages/tui/src/components/editor.ts:1273</span> — <code>onSubmit</code> 回调,用户按 Enter 触发</li>
<li><span class="path-ref">packages/coding-agent/src/main.ts:1</span> — CLI 入口,解析 argv 派发到 interactive / print / json / rpc</li>
<li><span class="path-ref">packages/coding-agent/src/core/agent-session.ts:303</span> — <code>AgentSession</code> class,slash 命令 + 模板展开 + streaming 队列</li>
<li><span class="path-ref">packages/coding-agent/src/core/extensions/types.ts:1177</span> — <code>ExtensionAPI</code>,扩展点契约</li>
</ol>
</div>
<h2>8.5 关键类型:CompactionSettings</h2>
<p>
<code>CompactionSettings</code> 是理解 compaction 行为的"控制面板"。它定义在
<span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:164</span>,
仅 3 个字段,但每个都决定一个关键取舍:<em>启不启用</em>、<em>给摘要留多少预算</em>、<em>截断后保留多少近期 token</em>。
</p>
<pre><code>// packages/agent/src/harness/compaction/compaction.ts:164 — CompactionSettings
export interface CompactionSettings {
/** 是否启用自动 compaction 判断。设为 false 时即使超阈值也不压缩 */
enabled: boolean;
/** 给"摘要 prompt + 摘要输出"预留的 token 数。压缩本身也要花 token */
reserveTokens: number;
/** 压缩后保留的"近期上下文"近似 token 数。超出此值的旧消息会被摘要替代 */
keepRecentTokens: number;
}
// 默认值(同一文件 :174)
export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
enabled: true, // 默认开启
reserveTokens: 16384, // 给摘要留 16k 预算
keepRecentTokens: 20000 // 保留最近约 20k token
};
/**
* 是否触发 compaction 的判定(同一文件 :263)。
* 简化逻辑:已用 token > 上下文窗口 − reserveTokens 时触发。
*/
export function shouldCompact(
contextTokens: number,
contextWindow: number,
settings: CompactionSettings,
): boolean {
if (!settings.enabled) return false;
return contextTokens > contextWindow - settings.reserveTokens;
}</code></pre>
<p>
<strong>三个字段的取舍</strong>:<code>enabled</code> 关掉可避免大上下文下"摘要导致原始细节丢失";
<code>reserveTokens</code> 调大能容纳更长摘要但会降低触发频率;
<code>keepRecentTokens</code> 调大保留更多近期但延迟触发。修改这三个值后必须跑回归:
<code>./test.sh</code> 包含 <code>packages/agent/test/compaction</code> 的全部分支覆盖。
</p>
<h2>8.6 FAQ</h2>
<p>路径 B / C 阶段最常被问到的 6 个问题。读完源码后答案应该都在 03/06 章里有出处。</p>
<h3>Q1. compaction 何时触发?哪些消息会被丢弃?</h3>
<p>
<strong>触发条件</strong>:<code>shouldCompact()</code>(<span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:263</span>)返回 true,
即 <code>contextTokens > contextWindow − settings.reserveTokens</code>。<code>settings.enabled = false</code> 时永不触发。
</p>
<p>
<strong>被丢的</strong>:老消息(cut point 之前的 <code>message</code> 类型的 SessionTreeEntry)会被替换成一条 <code>compaction</code> 类型的 summary entry;
切点由 <code>findCutPoint()</code>(<span class="path-ref">packages/agent/src/harness/compaction/compaction.ts:396</span>)从后向前累计 <code>keepRecentTokens</code> 后定位。
</p>
<p>
<strong>不丢的</strong>:JSONL 文件本身是 append-only 的——<code>JsonlSessionStorage.appendEntry()</code>
(<span class="path-ref">packages/agent/src/harness/session/jsonl-storage.ts:278</span>)只追加不覆盖。
所以"被丢"严格说是<em>从送 LLM 的上下文里被替换成 summary</em>,原 entry 仍在 JSONL 文件里。
可直接读 <code>~/.pi/agents/<agent>/sessions/<session-id>.jsonl</code>(每行一个 JSON)回看被摘要的原始消息。
</p>
<h3>Q2. rpc 模式如何鉴权?是否支持多客户端?</h3>
<p>
<strong>鉴权</strong>:<strong>没有</strong>鉴权层。RPC 模式(<code>pi --mode rpc</code> 启动的 headless 子进程)通过
<strong>stdin / stdout JSONL 长连接</strong>通信,进程边界 = 安全边界,假设只有可信本机进程会连进来。
<code>runRpcMode()</code>(<span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-mode.ts:51</span>)注释里明确写:
"Listens for JSON commands on stdin, outputs events and responses on stdout"。
命令协议见 <code>RpcCommand</code> 联合类型(<span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-types.ts:20</span>),
包含 <code>prompt</code> / <code>steer</code> / <code>follow_up</code> / <code>abort</code> / <code>get_state</code> / <code>compact</code> 等。
</p>
<p>
<strong>多客户端</strong>:不支持<em>多客户端连同一进程</em>——一个 <code>runRpcMode()</code> 实例独占 <code>process.stdin</code>,
只有 1 个 reader。多客户端场景的常见做法是<strong>每个客户端 spawn 独立子进程</strong>,参看 <code>RpcClient</code>
(<span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-client.ts:1</span>)用 <code>child_process.spawn()</code> 拉起 <code>pi --mode rpc</code>,
通过 stdin/stdout 与该子进程通信;子进程之间互不干扰,每个持有独立的 <code>AgentSession</code>。
</p>
<h3>Q3. 多个扩展点的优先级冲突如何解决?</h3>
<p>
pi-coding-agent <strong>没有 <code>priority</code> 字段</strong>(代码库 <code>grep -rn "priority"</code> 在 <code>packages/coding-agent/src/core/extensions/</code> 下零结果)。
加载顺序由两个层次决定:
</p>
<ul>
<li>
<strong>扩展发现顺序</strong>:<code>discoverExtensionsInDir()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:636</span>)
用 <code>fs.readdirSync()</code> 遍历目录,<em>OS-dependent</em>(典型是字母序但不要依赖),
同一目录里谁先被发现谁先加载。
</li>
<li>
<strong>单扩展入口解析</strong>:<code>resolveExtensionEntries()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:594</span>)按以下顺序确定一个目录的入口文件:
① <code>package.json</code> 的 <code>pi.extensions</code> 字段声明的路径 ② 同目录的 <code>index.ts</code> ③ 同目录的 <code>index.js</code>。
<code>PiManifest</code> 接口(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:561</span>)描述 <code>pi</code> 字段结构,<code>readPiManifest()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:568</span>)负责读取。
</li>
</ul>
<p>
<strong>注册行为</strong>:
</p>
<ul>
<li>
<code>registerTool()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:245</span>):用 <code>extension.tools.set(name, ...)</code>——
<em>每个扩展维护自己的 tools Map</em>;跨扩展同名 tool 由后续 <code>runtime.refreshTools()</code> 聚合,最终展平到全局,
同名时<em>后加载者覆盖先加载者</em>。
</li>
<li>
<code>registerCommand()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:254</span>):写 <code>extension.commands.set(name, ...)</code>——
命令也是<em>每个扩展私有</em>,跨扩展同名互不覆盖,由 <code>getCommands()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:354</span>)聚合返回。
</li>
<li>
<code>event</code> 订阅:<code>EventBus</code>(<span class="path-ref">packages/coding-agent/src/core/event-bus.ts:3</span>)是无短路的多播总线,
<em>所有订阅 handler 都会被调用</em>,顺序按订阅先后。
</li>
</ul>
<p>
调试加载顺序用 <code>pi -e ./my-ext --log-level debug</code>,loader 在每个扩展加载成功/失败时都会打印路径和错误。
</p>
<h3>Q4. pi-package 与普通 npm package 的区别?</h3>
<p>
<strong>pi-package</strong> 是声明了 <code>pi</code> 字段的 npm 包,由 <code>PiManifest</code> 接口
(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:561</span>)描述:
</p>
<pre><code>// packages/coding-agent/src/core/extensions/loader.ts:561
interface PiManifest {
extensions?: string[];
themes?: string[];
skills?: string[];
prompts?: string[];
}</code></pre>
<p>
加载逻辑:<code>readPiManifest()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:568</span>)读 <code>package.json</code> 的 <code>pi</code> 字段;
<code>resolveExtensionEntries()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:594</span>)发现扩展时优先看
<code>manifest.extensions</code> 里声明的路径列表——这才决定扩展"是谁"。典型例子:
</p>
<pre><code>{
"name": "@my-org/pi-hello-tool",
"pi": {
"extensions": ["./dist/index.js"]
}
}</code></pre>
<p>
<strong>和普通 npm package 的区别</strong>:① 必须有 <code>pi.extensions</code> 字段才能被 loader 发现为扩展
② 模块必须导出 <code>default export (pi: ExtensionAPI) => Extension</code> 工厂函数(参看 <code>ExtensionAPI</code> 接口
<span class="path-ref">packages/coding-agent/src/core/extensions/types.ts:1177</span>)。
普通 npm package 即使被 <code>npm install</code> 进项目也不会被自动加载——
必须显式 <code>pi -e ./path</code> 或放在 <code>~/.pi/extensions/</code> 下、由 <code>discoverExtensionsInDir()</code>(<span class="path-ref">packages/coding-agent/src/core/extensions/loader.ts:636</span>)扫到。
</p>
<h3>Q5. <code>agentLoop</code> 与 <code>agentLoopContinue</code> 的差异?</h3>
<p>
两者都是 <code>packages/agent/src/agent-loop.ts</code> 导出的低层工厂,签名形如 <code>(ctx, config) => Stream</code>。
</p>
<ul>
<li>
<strong><code>agentLoop</code>(同文件 :31)</strong>:<em>全功能</em>流。包含"首轮 prompt 注入"+"turn 状态机"+"tool 执行"+"compaction 触发判定"+"steering/follow-up 队列检查"。
适合从用户输入到 agent 结束的全过程。
</li>
<li>
<strong><code>agentLoopContinue</code>(同文件 :64)</strong>:<em>续接</em>流。<em>不</em>注入首轮 prompt,直接以当前 <code>ctx.messages</code> 作为输入继续下一轮;
适合"恢复一个中断的 session"或"重放已有消息"的场景。底层共享 <code>runAgentLoopContinue()</code>(<span class="path-ref">packages/agent/src/agent-loop.ts:120</span>)。
</li>
</ul>
<p>
实际选择:99% 的情况用 <code>agentLoop</code>;<code>-c <session></code> 续接、RIPGREP 调试重放时才用 <code>agentLoopContinue</code>。
上层 <code>Agent.prompt()</code> 总是走 <code>agentLoop</code>,<code>Agent.continue()</code> 走 <code>agentLoopContinue</code>。
</p>
<h3>Q6.(可选)steering 与 abort 的区别?</h3>
<p>
<strong>Steering</strong>(<span class="path-ref">packages/coding-agent/src/modes/rpc/rpc-types.ts:22</span> 附近):agent 正在 streaming 时插入的新消息,
会在当前 LLM 响应<em>结束后</em>立刻进入下一轮。语义是"在 agent 自然停下来的间隙,悄悄塞一条消息让它继续"。不中断当前 token 流。
</p>
<p>
<strong>Abort</strong>(<code>Agent.abort()</code> / <code>signal.abort()</code>):立即取消当前 LLM 请求和正在执行的 tool,
把 <code>stopReason</code> 设为 <code>"aborted"</code>,随后不再启动新 turn。语义是"立刻停下,不许继续"。
</p>
<p>
<strong>关键区别</strong>:steering 是<em>追加</em>(不影响当前 turn),abort 是<em>中断</em>(终结当前 turn)。
使用场景:用户想加一句约束用 steering;用户想"算了停一下"用 abort。Steering 消息本身可以再次被 steering(队列可累积),
而 abort 后只能重新 <code>prompt()</code> 才能再启动 agent。
</p>
<h2>8.7 配套资源</h2>
<ul>
<li><a href="01-overview.html">01 全景与分层</a> — 路径 A Day 1 必读</li>
<li><a href="03-pi-agent-core.html">03 pi-agent-core 深入</a> — 路径 B/C 第 1 周核心</li>
<li><a href="06-end-to-end.html">06 端到端时序</a> — 路径 C Day 21 时序图绘制参考</li>
<li><a href="07-extensions.html">07 扩展点手册</a> — 路径 C Day 17–18 扩展点实战</li>
<li><a href="https://pi.dev" target="_blank">pi.dev</a> — 官方文档</li>
<li><a href="https://rfc.earendil.com/keyword/pi/" target="_blank">RFCs</a> — 设计意图,路径 C 必读</li>
<li><a href="https://github.com/earendil-works/pi-mono" target="_blank">GitHub: pi-mono</a> — 源码、issue、PR</li>
<li><a href="https://github.com/earendil-works/pi-mono/labels/good%20first%20issue" target="_blank">good first issues</a> — 路径 B Day 14 / 路径 C 收口</li>
</ul>
<h2>8.8 学习建议</h2>
<ol>
<li><strong>不要从头读 README</strong>:先看 <a href="01-overview.html">01</a> + <a href="06-end-to-end.html">06</a> 建立全局感</li>
<li><strong>边读边跑</strong>:每个概念找一段对应代码改一下、看效果</li>
<li><strong>从 issue 倒推</strong>:看历史 PR 的 discussion 是最快理解设计取舍的方式</li>
<li><strong>动手写</strong>:路径 A 至少跑通 3 个 demo;路径 C 至少提 1 个 PR</li>
<li><strong>提 issue / 问 Discord</strong>:作者和社区很活跃,问具体问题比独自 debug 效率高</li>
</ol>
<div class="prev-next">
<a href="07-extensions.html">← 07 扩展点手册</a>
<a href="index.html">↑ 回到目录</a>
</div>
<footer class="footer">由 OpenTeams 工作流自动生成</footer>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script src="assets/app.js"></script>
</body>
</html>