2026-09-18,对 connectedGraph/pi(= 上游 earendil-works/pi main,e4ce7b449,v0.85.1)实读源码整理。目标场景:自建 agent 时改提示词与改 loop。

两层架构(决定你改哪里)

层包职责
通用内核packages/agent = @earendil-works/pi-agent-coreGeneral-purpose agent with transport abstraction, state management, and attachment support。对"系统提示词"完全无感知:消息进、事件出
具体 harnesspackages/coding-agentpi 本体:提示词、工具集、会话、扩展、TUI

接线处在 packages/coding-agent/src/core/agent-session.ts(约 125 KB)。

易错点:packages/agent/src/harness/ 下另有一个通用 system-prompt.ts 与 prompt-templates.ts,与 coding-agent 的 core/system-prompt.ts 是两个文件,别改错。

系统提示词

packages/coding-agent/src/core/system-prompt.ts

buildSystemPromptSections()(L121)按固定顺序组装有序、可独立替换的 section:

section来源行
preamble默认身份句,唯一不带标签L147
tools每工具一行摘要(toolSnippets)L148-151
rulesbuildRules(),随启用的工具动态变化L101-109
docspi 自身文档索引L153-160
addendum--append-system-promptL163
project_contextAGENTS.md / CLAUDE.md,<project_instructions path="..."> 包裹L164
skills技能索引L166-169
cwd工作目录(反斜杠转正斜杠)L170
自定义扩展注入的 sectionsL171-173

默认身份句(L147):

You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.

两个关键机制:

  • 除 preamble 外全部包成 <name>...</name>(L176-178)。标签不是装饰:它让模型能把后续增量更新对应回具体 section。
  • diffSystemPromptSections()(L204)只把变化的 section 发出去,配合 SystemMessage.sections 做增量更新,避免每轮重发整个提示词。

rules 是条件生成的:例如 L101 仅在「有 bash/powershell 且无 grep/find/ls」时加入 Use bash for file operations like ls, rg, find。换工具集,提示词自动变化。

forceSystemPrompt(L190)走整段替换,不生成 sections。

渲染与重建入口(agent-session.ts):L948 buildSystemPrompt()、L1079 _rebuildSystemPrompt()、L1124 调 diff 发增量、L2587 工具名变化时触发重建。

Agent Loop

packages/agent/src/agent-loop.ts

调用链:agentLoop()(L37)→ runAgentLoop()(L101)→ runLoop()(L162)。agentLoopContinue()(L70)用于重试续跑(要求 context 末条不是 assistant)。

双层循环:

  • 外层(L177):agent 本应停止但队列中仍有 follow-up → 继续
  • 内层(L181):while (hasMoreToolCalls || pendingMessages.length > 0)

单轮顺序:

  1. prepareNextTurn(L184):换模型 / thinking level / 执行 compaction
  2. 处理 prepared + steering 消息并逐条 emit(L209-214)
  3. streamAssistantResponse(L218)取得 assistant 消息
  4. stopReason 为 error / aborted → 直接 agent_end 退出(L221-225)
  5. 取 toolCall(L228)
  6. stopReason === "length" 时全部 tool call 判失败、不执行(L236-239):截断的参数可能"能解析但语义残缺",宁可让模型重发
  7. executeToolCalls → 结果并入 context(L240-246)
  8. turn_end(L249)
  9. shouldStopAfterTurn(L258)
  10. getSteeringMessages(L263)

streamAssistantResponse(L339)内部顺序:transformContext → convertToLlm → getApiKey → streamFunction → 事件循环(start / text_* / thinking_* / toolcall_* / done / error)。提示词在这里已退化为 AgentMessage[],loop 层对提示词内容零感知。

钩子面(最重要)

packages/agent/src/types.ts:156 的 AgentLoopConfig 把 loop 几乎所有决策点外置成回调:

回调行作用
convertToLlmL185AgentMessage[] → LLM Message[]
transformContextL207剪裁 / 注入上下文
getApiKeyL217每次调用动态取 key(短时 OAuth token)
shouldStopAfterTurnL230优雅停机
prepareNextTurnL237下一轮换模型 / thinking
getSteeringMessagesL252运行中插话
getFollowUpMessagesL265停止后续跑
toolExecutionL276sequential / parallel(默认 parallel)
beforeToolCallL285执行前拦截(可 {block:true})
afterToolCallL300执行后改写 content / details / isError

除 toolExecution 外全部带 must not throw 契约 —— 抛异常会打断 loop 的事件序列,所以约定返回安全兜底值。

三个改法层级(成本递增)

  1. 只改文本:动 system-prompt.ts L147,或用 customPrompt / --system-prompt / --append-system-prompt。零风险。
  2. 用扩展改(推荐起点):packages/coding-agent/src/core/extensions/runner.ts:1146 的 emitBeforeAgentStart() 把 systemPrompt(当前渲染结果)与 systemPromptOptions(已 normalize 的可变对象)交给扩展。返回 systemPrompt 会被写入 forceSystemPrompt(L1180-1182)——这是整段替换,不是追加。要追加请改 systemPromptOptions.promptGuidelines 或 sections。
  3. 真改 loop:agent-loop.ts。先自问是否配置就能做 —— 停止、续跑、steering、上下文变换、工具前后都已外置。真需要动这一层的通常是:改事件序列语义、改并行/串行调度策略、加新的 turn 边界。且它在 pi-agent-core 独立包内,抽象层低于 coding-agent,影响面更大。

相关概念


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps