feat(agent): 运行时从 pi-agent-core 0.99.2 迁到 pi-durable 1.1.0 + pi-ai 1.1.0

- PiAgentRuntime 改用 pi-durable Harness:每个 run 一个 JSONL 存储目录 sessionDir/<runId>/ + root 对话;
  系统提示 / 能力工具 / 白名单与预算钩子装在每次运行自己的扩展里;watchEvents 翻译成 AgentRunEvent
- 挂起(工具内 close)立即封口,调用保持未完成,重开后 resume 重放;replayGate 用调用级 memo
- 旧格式 <runId>.jsonl 打开时抛 LegacySessionError;skills 加载与 <skill> 块格式内置(源自 pi-agent-core,MIT)
- tool_call_end 新增可选 durationMs;用量取 pi.usage 合计(整段会话总量)
- Azure provider 改名 azure,迁移 0093 改存量;移除 pi-agent-core,锁 pi-durable/pi-ai/chord 1.1.0
- 文档:ADR-003、pi-providers、runbook、agent README、CHANGELOG

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-08 13:42:36 +08:00
1 parent dd0f6e2c09
commit 003815591c
24 files changed
+756 -363

No files matched your search

+16 -1
View File
@@ -1,6 +1,6 @@
# ADR-003:Pi 接入采用 pi-agent-core `AgentHarness`,封装在 `AgentRuntimePort` 之后
- 状态:已采纳(2026-09-27,W0-B spike 后)
- 状态:已采纳(2026-09-27,W0-B spike 后);2026-10-08 运行时迁到 `@earendil-works/pi-durable`,见文末「2026-10-08 迁到 pi-durable」,下文的 AgentHarness 细节保留为历史
- 关联:REQ §10、§11.4;plan §1、§3.1-9;验证记录 spike `w0b-pi`(已删除,见 git `ceb66f8`);代码 `spikes/pi/`(已删除,见 git `ceb66f8`)
## 决定
@@ -91,3 +91,18 @@ faux.setResponses([
- Pi 不提供权限系统(REQ §10.2)。进程、容器、网络白名单要在部署层落实。
- 模型流中途崩溃后按"合成 error + 重试"恢复,只读了规范,未实测;v0.1 的 e2e 要补上。
- 真实上游只验证了请求形状(本地抓包),没有联网调用。
## 2026-10-08 迁到 pi-durable
pi-agent-core 1.0 删掉了 harness(AgentHarness、会话存储、压缩、skills、系统提示等),只剩 `Agent` 与循环;harness 搬到 `@earendil-works/pi-durable`(实验性,版本之间 API 会变)。我们改为:
- 依赖锁精确版本:`@earendil-works/[email protected]`、`@earendil-works/[email protected]`、`@earendil-works/[email protected]`(pi-durable 的 Context / 文档状态),**移除** pi-agent-core。Pi 的类型仍只出现在 `packages/agent/src/pi/*`(另有 `prompt/skills.ts` 的格式约定),对外只有 `AgentRuntimePort`,端口与 `AgentRunEvent` 合同不变(`tool_call_end` 只新增可选 `durationMs`)。
- 一个 run = 一个 pi-durable 存储:`sessionDir/<runId>/` 下的 JSONL 存储(`openNodeJsonlStorage`),里面用 root 对话。每次 `createRun` 新建一个 `Harness` + 一个只给本次用的 registry,装一个 `umt` 扩展:系统提示是一个不加标签的 section,能力是 tools,白名单 / 工具次数预算在 `ToolTask.beforeTool`,时长 / 费用预算在 `GenerationTask.beforeRequest`。
- **单写者**:pi-durable 不做跨进程加锁,同一存储同一时刻只能由一个进程打开。runAgent 每次运行在一个持久步骤里打开一次会话、结束时关闭;同一 run 同一时刻只由持有任务引擎租约的那个 worker 驱动(租约过期才会被别的 worker 接手),挂起后由下一次 runAgent 重新打开。旧版 JSONL 会话也是同样的前提。
- 事件:用 `watchEvents` 订阅 root 对话的提交事件,翻译成我们的 `AgentRunEvent`(文本 / 思考增量、工具开始 / 结束 / 拒绝、用量、压缩)。增量按提交粒度(默认 100ms)到达。用量取 `pi.usage` 文档的合计,是整段会话的总量(换进程续跑不再从 0 计)。
- prompt = `root.submit({type:"input"})` + `wait()`;resume = `harness.resume()` 后等未完成的输入;abort = `root.abort()`;手动压缩 = `root.compact()` + 等任务;自动压缩用 `compaction: { enabled, reserveTokens, keepRecentTokens, backgroundTokens: 0 }`(不做后台提前压缩,与旧版阈值压缩一致)。
- 重放:能力 `replay=safe` → 工具 `replay:"safe"`,`never` → `"unsafe"`(崩溃后得到 interrupted 错误结果);带 `replayGate` 的审批类能力对 harness 声明 safe,用调用级 memo 区分首次与重放。调用 ID 用 `<工具任务 ID>:<toolCallId>`,重放时不变。
- 挂起(agent.await / 审批 / 选方案):工具内部调用 `handle.close()` 时立即封口 Harness(之后的提交一律被拒),该调用在存储里保持未完成;重新打开后 `pendingOperations>0`,`resume()` 重放这次调用。
- Skills:pi-durable 不再提供 skills 加载器,`prompt/skills.ts` 保留最小实现(`<name>/SKILL.md` 的发现、frontmatter 解析与校验、Pi 的 `<skill>` 调用块格式),来源 pi-agent-core 0.99.2(MIT)。
- 工具输出上限:pi-durable 默认把工具文本截到 50KB / 2000 行,我们放宽到 8MB,沿用旧版不截断的行为。
- 旧会话:0.99.2 的 `sessionDir/<runId>.jsonl` 不能续跑;只有旧文件、没有新目录时 `createRun` 抛 `LegacySessionError`,runAgent 以清楚的错误结束该 run(已结束的会话照常从 PG 事件查看)。
+1
View File
@@ -45,6 +45,7 @@
"kysely": "npm:[email protected]", "kysely/": "npm:/[email protected]/",
"pg": "npm:[email protected]", "@types/pg": "npm:@types/[email protected]", "postgres": "npm:[email protected]",
"openworkflow": "npm:[email protected]", "openworkflow/": "npm:/[email protected]/",
// 2026-10-08 起 pi-agent-core 换成 @earendil-works/[email protected] + @earendil-works/[email protected](见 ADR-003)
"@earendil-works/pi-agent-core": "npm:@earendil-works/[email protected]",
"@earendil-works/pi-agent-core/": "npm:/@earendil-works/[email protected]/",
"@earendil-works/pi-ai": "npm:@earendil-works/[email protected]", "typebox": "npm:[email protected]",