| 决策 | 结论 | 依据 |
|---|---|---|
| agent 运行时 | pi SDK in-process 嵌入 Encore 进程,无 sidecar | 三层验证通过(import / session / Encore 请求内执行);站点不提供代码执行,业务工具纯函数即可,无妥协 |
| 前后端协议 | Encore 类型化 RPC(encore gen client),不用 GraphQL | 强类型已免费拿到;核心是 SSE 流;最小攻击面 |
| 流式通道 | SSE ×2(对话流 + 轨迹流),api.raw + node:http | 同进程内通信,延迟毫秒级 |
| 会话持久化 | Postgres | 重启不丢会话;轨迹可回放 |
| 部署 | 单机 docker-compose(caddy/web/api/postgres),境内轻量服务器 | 成本最低;无 Encore Cloud 超时限制 |
| 教程内容 | 所有者经 MCP 管理服务发布(标准 markdown 入库,server 只校验不改写),pi 经只读工具访问 | 2026-08-31 裁定,替代 R5 的 vault 静态编译管道;pi 可读不可改(SELECT-only 角色) |
| 管理面 | 无状态 MCP server(2026-07-28,官方 TS SDK v2 保留向下协商;静态 bearer token) | 2026-08-31 裁定,替代 /admin 后台(画板 3a–3e 废弃);solo 维护无需 OAuth;无 cookie 即无 CSRF 面;详见 security.md §4 |
| MCP SDK 选型 | @modelcontextprotocol/server + @modelcontextprotocol/node 2.0.0(不是 @modelcontextprotocol/sdk) | R6 实测:旧包最新版(1.30.0)的 LATEST_PROTOCOL_VERSION 仍是 2025-11-25、没有 server/discover;2026-07-28 由 SDK v2 以新包名提供。createMcpHandler 默认 legacy:'stateless',同一份工具定义同时服务两个时代 |
| LLM 凭据与模型 | 全部来自 llm_config 表(多 provider,key 经 AES-256-GCM 加密入库),无引导 secret | 2026-08-31 裁定。agent/runtime.ts 在冷启动与每一轮热路径上都读一次配置并比对指纹:配置变了,会话在下一轮被重建到新配置上(走空闲回收同一条重建路径,库内历史照常注入)。这条统一规则是四轮 codex review 收敛出来的——只让新会话跟上配置,会留下「已在内存的会话拿着新 key 打旧端点」这类撤销漏洞 |
| notes 附件 | 存 Postgres,运行期由 api 供图;镜像内不烧任何 notes 内容 | 2026-08-31 裁定。对外 URL 保持 /notes/<系列>/<哈希>.webp 不变(免改写存量正文);API 侧走 /assets/notes/…,因为 Encore 路由里 /notes/:series/:file 会与 /notes/series/:slug 撞车 |
| Skills 内容 | 与 notes 同形:所有者经 MCP 整包发布(SKILL.md + scripts/ + references/ 的文本文件入库 skill_files),读面只读、文件一律当文本渲染、zip 写入时打好存库由读面吐;agent 侧本轮不可读(新表不授权任何 agent 角色) | 2026-09-03 裁定,R-SKILLS 落地(迁移 012,apps/api/skills/ + mcp 八个 skills_* 工具,判据在 shared/skill-pack.ts,zip 用 fflate);约束见 security.md §1 第 2 层 / §4 的 R-SKILLS 补记;对外 zip URL /skills/<name>.zip,API 侧 /assets/skills/…(与 notes 配图同一前缀策略) |
| agent 使用 skills(R-SKILLS-2) | 执行不进 api 进程:第五个容器 skill-runner(Python venv,network_mode: none),api 经 unix socket 调它,每次运行一次性进程 + tmpfs 目录;可执行集合在代码里(runner/skills/ → 构建期生成 api 与容器两份同源清单),库里 skills.agent_enabled 只开关、且展示副本须与代码副本 sha256 一致才注入;pi 侧 xray-guard(tool_call 否决)/ xray-skills(before_agent_start 注入)两个扩展把裁决写进既有 34 事件(派生字段 handlers),不新增事件类型 | 2026-09-03 所有者七条裁定,同日落地(spike 留证:两个 network_mode: none 容器经命名卷 unix socket,bun 1.4.0 fetch({unix}) 通;任务卡「本轮实测」);规则 9「一次性沙箱容器」措辞同日改为「独立容器可常驻 + 一次性进程」,理由与残余风险在 security.md §1 第 1 层;研究全文 rounds/round-skills/research.md。生成物落在 apps/api/shared/(不是任务卡写的 agent/):mcp 的 skills_agent_status 也要同一份清单,两个服务不互相 import |
| 读访客指定网页(R-WEBFETCH) | 不是新工具,是一个 egress 档 skill:runner/skills/web-fetch 经 skill_run 跑在 skill-runner-egress(同一 runner 镜像、第二个实例,只出公网)里;xray.json 的 network 字段决定路由,两个实例各自拒绝不属于自己档次的 skill;不维护域名黑白名单,拒的是固定内网地址段;限额与超时复用 R-SKILLS-2 的;零新工具 / 画板 / 迁移 / MCP 工具 / 前端改动 | 2026-09-03 所有者十条裁定(预研 in-process 形态同日退役),2026-09-04 落地(runner/skills/web-fetch + compose skill-runner-egress / egress 网络 / deploy/egress-filter.sh + agent/skill-runner.ts 两档路由;抽取库 trafilatura 及传递依赖全部 hash 钉进 runner/requirements.txt);任务卡 rounds/round-webfetch/round-webfetch.md;约束 security.md §0 威胁 7–9、§1 R-WEBFETCH 补记 |
| 会话区富内容(R-CARDS) | 内容级围栏 DSL,服务端不参与:模型在回复正文里写 ```xray-card + 一个 JSON 对象,前端 apps/web/lib/xray-card.ts 校验(六种 kind 闭集 kv / table / list / stat / compare / tabs、行列字数嵌套全部有上界、链接口径同 markdown)→ components/XrayCard.tsx 渲染,所有值当纯文本、交互只有 tabs / 折叠 / 排序 / 单选四种本地状态、动作按钮唯一动作 = R-CROSSLINK 的预填;任一不符整卡回落成普通代码块,流式期间围栏未闭合先画骨架。不是 pi 工具:content 原样落库,tool_end 帧 / payload / 两条 SSE / MCP(仍 51)/ 迁移零变动,api 侧只多一段系统提示;只在会话区开(Markdown 的 cards 开关,Notes / Skills / Source 的渲染器不传) | 2026-09-08 所有者裁定 2-A(工具级 2-B 要给帧与 payload 加结构字段、给 2l 折叠规则加例外,两条都是新机制),2026-09-09 落地;已认代价 = 坏 JSON 时访客看到裸 JSON 代码块、Timeline 里没有「画了一张卡」的事件;约束 security.md §0 第 11 条;任务卡 rounds/round-cards/round-cards.md |
| 会话区 UI 组件 2.0(R-CARDS-2) | 仍是内容级围栏、不是 pi 工具,两类各自一套边界:A 可回传卡 —— xray-card 加 choice(单选 / 多选)/ form(≤ 5 字段)两种 kind,出口是发送而不是预填:lib/xray-card.ts 的 composeChoiceMessage / composeFormMessage 是发出文本的唯一组成规则(只由题干 / 所选 label / 字段 label / 访客值拼成,ASCII : ; 与「、」,无隐藏模板),解析期按 UTF-16 算最坏组成长度 ≤ 1000 否则整卡回落;发送经 ComposerContext → Workbench.sendFromCard → sendText(与输入框同一个函数体,api 零改动);只由访客一次点击触发、生成中禁用、发过即锁(本地态)。B 静态 HTML 帧 —— ```xray-html → components/XrayHtml.tsx 的 <iframe sandbox="" srcdoc referrerpolicy="no-referrer">,三层各管一件事:sandbox="" 不执行 / 帧内 meta CSP(default-src 'none'; style-src 'unsafe-inline')不出网 / lib/xray-html.ts 的 DOMParser 窄清洗不出链不换页(不是 XSS 防线、不引 DOMPurify);宽 = 正文宽,高由 info string height= 夹到 [160, 480](移动 360),≤ 16 KB,主题变量注入、srcdoc 是 (围栏原文, 主题, 端) 的确定性函数。每轮最多两个组件由前端硬限:lib/remark-component-budget.ts 在 micromark 产出的 mdast 上按文档序给前两个 xray 围栏打标记(围栏是不是围栏由解析器说了算,不扫源文本 —— 所有者 2026-09-09 裁定,替代逐行正则扫描器),components/ChatFence.tsx 是恒等的围栏渲染器(内联回调会让子树重挂 → 卡的锁定态被清、帧每个 delta 重载),只把最终回答段里带标记的围栏当组件,处理过程段的围栏一律回落 | 2026-09-09 所有者裁定 A + B(C 帧内脚本 / D pi 工具级暂不考虑,记 BACKLOG)并调整回传 = 发送,同日落地;无迁移 / 无端点 / MCP 仍 51 / 无新依赖 / 无运行期开关(runtime.ts 的 HTML_COMPONENT_ENABLED 常量掐提示词,关 = 发版、只停产出);约束 security.md §0 第 11 / 12 / 13 条;任务卡 rounds/round-cards2/round-cards2.md |
pi 扩展系统 34 种事件按四模式分组,观测者扩展全量订阅、采集 {eventType, mode, timestamp, data(sanitized)} 推给前端(计数为 R1 对 @earendil-works/pi-coding-agent@0.84.3 类型面的实测,修正了旧记载 notify 18/合计 33 的笔误;逐事件清单见 apps/api/agent/events.ts):
R1 实测注意:bare createAgentSession() 不向扩展广播 session_start/resources_discover,需在创建后调用 session.bindExtensions({ mode: "print" })(run 模式层职责);R3 正式实现沿用此调用。
R4 落地轨迹流后补记两条实现约束:
前端右栏三视图 = 同一事件流的三种投影:Timeline(时序瀑布)、Chain View(单事件的链式传递)、Lifecycle Map(生命周期节点图)。
见 design/ —— 桌面 27 块(1a–1g + 2a–2t)+ 移动 26 块(4a–4z)画板静态稿 + 可交互原型(清单、计数与增删记录以 design/README.md 为准),token 与组件语汇以其为准。