docs: add architecture and classroom guide
This commit is contained in:
@@ -1,15 +1,158 @@
|
|||||||
# Agent Studio Mini
|
# Agent Studio Mini
|
||||||
|
|
||||||
Training-grade Agent Harness built with Pi Coding Agent SDK, Fastify, SSE, React, and assistant-ui.
|
一个面向课堂演示的可观察 Agent Harness。它把 Pi Coding Agent SDK 放在 Fastify 服务端,通过 HTTP + SSE 将 Session、流式回复、Skills、工具调用、审批与图片附件映射到 assistant-ui 三栏工作台。
|
||||||
|
|
||||||
The project is under active construction. Verified setup, security guidance, and demo instructions are added with the completed implementation.
|
> 截图占位说明:仓库不提交与本机配置绑定的截图。启动后打开 `http://127.0.0.1:5173`,即可看到左侧 Skills、中央 Streaming Chat、右侧 Event Timeline 的完整界面。
|
||||||
|
|
||||||
## Verified dependency baseline
|
## 架构
|
||||||
|
|
||||||
- Node.js 22.22.3 (Pi 0.85.1 requires Node.js 22.19 or newer)
|
```mermaid
|
||||||
- pnpm 11.7.0
|
flowchart TD
|
||||||
- TypeScript 5.9.3 (the newest release supported by typescript-eslint 8.69)
|
UI[assistant-ui Composer / Thread / Tool UI]
|
||||||
- Pi Coding Agent SDK 0.85.1, using `typebox` 1.3.7
|
API[Fastify API]
|
||||||
- assistant-ui React 0.15.18
|
REG[SessionRegistry]
|
||||||
|
PI[Pi AgentSession]
|
||||||
|
EVT[EventStore / ApprovalBroker]
|
||||||
|
RES[ResourceLoader / Tools]
|
||||||
|
VIEW[Timeline / Approval]
|
||||||
|
CORE[Skills / Workspace / Model]
|
||||||
|
|
||||||
The original brief allowed Node.js 20+ and referred generically to the current TypeBox package. The installed Pi release has a higher Node.js floor and directly uses the `typebox` package rather than `@sinclair/typebox`; this project follows the installed public types.
|
UI -->|HTTP + SSE| API
|
||||||
|
API --> REG
|
||||||
|
REG --> PI
|
||||||
|
API --> EVT
|
||||||
|
PI --> RES
|
||||||
|
EVT --> VIEW
|
||||||
|
RES --> CORE
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as 用户
|
||||||
|
participant W as assistant-ui
|
||||||
|
participant H as Harness
|
||||||
|
participant P as Pi AgentSession
|
||||||
|
U->>W: 发送文字和可选图片
|
||||||
|
W->>H: POST message
|
||||||
|
H-->>W: 202 + runId
|
||||||
|
H->>P: prompt(text, images)
|
||||||
|
P-->>H: 文本、Skill read、Tool 事件
|
||||||
|
H-->>W: SSE HarnessEvent
|
||||||
|
P->>H: save_report_draft
|
||||||
|
H-->>W: approval.required
|
||||||
|
U->>H: Approve 或 Deny
|
||||||
|
H-->>P: 审批结果
|
||||||
|
P-->>H: tool/result + message end
|
||||||
|
H-->>W: run.completed
|
||||||
|
```
|
||||||
|
|
||||||
|
职责边界:assistant-ui 只负责交互和渲染;Harness 是 Session、事件、附件、模型与审批的权威;Pi 负责 Agent loop、模型调用、ResourceLoader 与工具执行。浏览器不直接持有 Pi Session,也不能自行把写工具标为已批准。
|
||||||
|
|
||||||
|
更详细的组件和数据流见 [docs/architecture.md](docs/architecture.md),事件对应关系见 [docs/event-mapping.md](docs/event-mapping.md)。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/server Fastify API、SSE、启动装配
|
||||||
|
apps/web React + assistant-ui 三栏工作台
|
||||||
|
packages/shared 浏览器与服务端共享的 Zod 协议
|
||||||
|
packages/harness Pi adapter、Session、事件、审批、附件和安全边界
|
||||||
|
.agents/skills 示例 ads-analysis Skill
|
||||||
|
fixtures 确定性的模拟广告账户数据
|
||||||
|
docs 架构、安全、事件、课堂演示和生产路线
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境与安装
|
||||||
|
|
||||||
|
- Node.js 22.19 或更高版本。Pi 0.85.1 的实际要求高于原始需求中的 Node.js 20。
|
||||||
|
- pnpm 11.7.0。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
cp .env.example .env
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Web 默认地址是 `http://127.0.0.1:5173`,Fastify 默认监听 `http://127.0.0.1:3001`。根命令会同时启动两者,Vite 将 `/api` 和 `/health` 代理到 Fastify。
|
||||||
|
|
||||||
|
## 配置模型
|
||||||
|
|
||||||
|
模型解析优先级为:当前 Session 的 Settings 配置、服务器环境变量、明确的 `MODEL_NOT_CONFIGURED` 错误。没有“第一个可用模型”的隐式回退。
|
||||||
|
|
||||||
|
环境变量示例:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
AGENT_PROVIDER=openai
|
||||||
|
AGENT_MODEL=gpt-5-mini
|
||||||
|
LLM_API=openai-responses
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
LLM_BASE_URL=
|
||||||
|
AGENT_MODEL_SUPPORTS_IMAGES=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Anthropic 使用 `AGENT_PROVIDER=anthropic`、`LLM_API=anthropic-messages` 和 `ANTHROPIC_API_KEY`。自定义兼容服务可设置 `LLM_BASE_URL`;默认仅接受安全的 HTTPS 地址,开发环境可接受 localhost,内网地址仅在明确设置 `ALLOW_PRIVATE_LLM_BASE_URLS=true` 后允许。
|
||||||
|
|
||||||
|
也可以在页面右上角打开 Settings,填写 Provider、API 类型、Base URL、API Key、Model 和图片能力。保存后配置只属于当前 Session。连接测试在当前实现中安全地返回“首次运行验证”,不会编造一次真实调用成功。
|
||||||
|
|
||||||
|
API Key 从同源请求提交后只驻留服务器进程内存,并在 Pi `InMemoryCredentialStore` 中注入;不会写入 localStorage、sessionStorage、IndexedDB、SSE、事件、响应或用户目录。GET 只返回脱敏的 `keyHint`。刷新页面会新建 Session;删除 Session、清除凭证或重启进程都会丢失该内存配置。
|
||||||
|
|
||||||
|
## 图片链路
|
||||||
|
|
||||||
|
Composer 支持选择、拖拽和粘贴 JPEG、PNG、WebP,默认单图最大 5 MiB、每条消息最多 4 张。上传采用 multipart 流,服务端同时校验文件名扩展、MIME、magic bytes、尺寸与像素数,再用 Sharp 自动旋转和重新编码。规范化文件仅保存在当前 Session 临时目录。
|
||||||
|
|
||||||
|
浏览器状态和 SSE 只保存 `attachmentId` 与安全元数据。真正调用 Pi 时,服务端才读取规范化图片并构造 `ImageContent[]`。如果显式模型是 text-only,发送图片会返回 `MODEL_DOES_NOT_SUPPORT_IMAGES`,不会静默丢图或切换模型。
|
||||||
|
|
||||||
|
## 课堂 Demo
|
||||||
|
|
||||||
|
1. 打开页面并配置模型。
|
||||||
|
2. 点击“填入演示任务”,得到:`请分析 demo-account 最近 7 天广告数据,并生成一份简短报告草稿。`
|
||||||
|
3. 可选:附加一张广告素材截图,并补充“请结合图片解释创意表现,但不要从图片猜测账户指标”。
|
||||||
|
4. 点击发送。POST 会立即返回 202;中央区域流式显示回复,左右两栏显示 Skill 和事件。
|
||||||
|
5. 观察 `mock_ads_metrics` 只读工具卡。数据来自 `fixtures/ads-account.json`,不是实时广告平台。
|
||||||
|
6. `save_report_draft` 出现时选择 Approve 或 Deny。Approve 只会在当前 Session 工作区原子写入固定的 `report.md`;Deny 或超时不会创建文件。
|
||||||
|
7. 删除或重置 Session 后,附件、报告和内存凭证一起清理。
|
||||||
|
|
||||||
|
完整 10–15 分钟讲解脚本见 [docs/classroom-demo.md](docs/classroom-demo.md)。
|
||||||
|
|
||||||
|
## Skill 渐进披露
|
||||||
|
|
||||||
|
启动时 `SkillCatalog` 使用 Pi `DefaultResourceLoader` 发现 Skills,并向页面只展示名称、描述、来源、安全显示路径和 diagnostics。Skill 正文不会塞入初始前端状态。
|
||||||
|
|
||||||
|
当 Pi 的 `read` 工具成功读取一个已索引的规范化 `SKILL.md` 路径时,Harness 推导 `skill.requested` 和 `skill.loaded`。它们不是 Pi 原生事件:requested 表示已识别目标 Skill,loaded 只在 read 成功后发出。示例 Skill 要求在分析任何账户指标前调用 `mock_ads_metrics`。
|
||||||
|
|
||||||
|
## Approval 与安全边界
|
||||||
|
|
||||||
|
- 工具策略:`read`、`grep`、`find`、`ls`、`mock_ads_metrics` 允许;`save_report_draft` 必须审批;其余工具拒绝。
|
||||||
|
- 审批状态和超时由服务器 `ApprovalBroker` 决定。重复或冲突决策被拒绝。
|
||||||
|
- Session 工作区经过 realpath、路径包含关系、符号链接和保护路径检查;报告只能写固定文件。
|
||||||
|
- Base URL 做协议、userinfo、fragment、端口、DNS、私网、loopback、link-local 和云 metadata 检查。
|
||||||
|
- 上传图片不作为静态资源公开,也不把 base64 放入事件。
|
||||||
|
|
||||||
|
Pi 没有内置 Sandbox,Project Trust 也不是 Sandbox。Pi 和 Extension 继承当前 Node 进程的操作系统权限。本项目的路径校验、工具 allowlist 和审批是应用层约束,不能替代容器、VM、系统用户或网络隔离。完整说明见 [docs/security-boundaries.md](docs/security-boundaries.md)。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm test
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
最近一次完整测试包含 117 条测试:shared 13、harness 68、server 16、web 20。默认测试使用可编程 Fake AgentSession,不需要真实 API Key、真实模型或外网。最终交付前还会重新运行以上四个命令并以实际结果为准。
|
||||||
|
|
||||||
|
## 当前依赖与 API 核对
|
||||||
|
|
||||||
|
- TypeScript 5.9.3,typescript-eslint 8.69。
|
||||||
|
- Pi Coding Agent SDK / Pi AI 0.85.1,当前公开 API 使用 `ModelRuntime.create`、`registerProvider`、`InMemoryCredentialStore`、`setRuntimeApiKey`、`createAgentSession`、`DefaultResourceLoader` 与 `defineTool`。
|
||||||
|
- Pi 0.85.1 的 schema 包是 `typebox` 1.3.7,不是 `@sinclair/typebox`。
|
||||||
|
- assistant-ui React 0.15.18,外部 Harness 状态通过 `useExternalStoreRuntime` 映射,附件通过自定义 `AttachmentAdapter` 上传。
|
||||||
|
- React 19.2.8、Fastify 5.12.3、Vite 8.2.2、Zod 4.5.4、Sharp 0.35.4。
|
||||||
|
|
||||||
|
实现只使用安装包导出的公开类型;没有修改用户目录下的 Pi 配置,也没有依赖已废弃的 Runtime 示例。
|
||||||
|
|
||||||
|
## 已知限制与生产化
|
||||||
|
|
||||||
|
课堂版是单进程、单实例、内存 EventStore / ApprovalBroker / ModelConnectionStore;刷新会创建新 Session,进程退出不会恢复运行,报告和附件只是本机临时文件。它没有用户身份、跨设备同步、分布式事件总线或操作系统级隔离。
|
||||||
|
|
||||||
|
生产化需要 Redis/Postgres 持久化、对象存储、KMS/Secret Manager、身份鉴权、授权与租户隔离、CSRF 和速率限制、容器或 microVM Sandbox、出站网络策略、不可篡改审计日志、分布式锁与可重放 SSE。路线见 [docs/production-roadmap.md](docs/production-roadmap.md)。
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# 架构与数据流
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
Agent Studio Mini 把课堂上最容易被隐藏的 Agent 行为变成可观察事件:模型流式输出、Skill 渐进加载、工具生命周期、写操作审批和 Session 清理。浏览器是投影,服务端 Harness 才是状态权威。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph Browser
|
||||||
|
Composer
|
||||||
|
Thread[assistant-ui Thread]
|
||||||
|
ToolUI[Tool UI / Approval Dialog]
|
||||||
|
Timeline
|
||||||
|
Store[HarnessClientStore]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Fastify
|
||||||
|
Routes[HTTP Routes]
|
||||||
|
SSE[SSE Connection]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Harness
|
||||||
|
Registry[SessionRegistry]
|
||||||
|
Session[HarnessSession]
|
||||||
|
Events[EventStore]
|
||||||
|
Broker[ApprovalBroker]
|
||||||
|
Attachments[AttachmentStore]
|
||||||
|
Models[ModelConnectionStore]
|
||||||
|
Skills[SkillCatalog / Inference]
|
||||||
|
Workspace[SafeWorkspace]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Pi
|
||||||
|
Agent[AgentSession]
|
||||||
|
Loader[DefaultResourceLoader]
|
||||||
|
Runtime[ModelRuntime]
|
||||||
|
Tools[Pi Tools + Extension]
|
||||||
|
end
|
||||||
|
|
||||||
|
Composer -->|multipart / JSON| Routes
|
||||||
|
Routes --> Registry --> Session --> Agent
|
||||||
|
Store -->|EventSource| SSE --> Events
|
||||||
|
Events --> Store --> Thread
|
||||||
|
Store --> Timeline
|
||||||
|
Store --> ToolUI
|
||||||
|
Session --> Attachments
|
||||||
|
Session --> Models --> Runtime
|
||||||
|
Session --> Skills
|
||||||
|
Agent --> Loader
|
||||||
|
Agent --> Tools --> Workspace
|
||||||
|
Tools --> Broker --> ToolUI
|
||||||
|
```
|
||||||
|
|
||||||
|
## 一次消息的生命周期
|
||||||
|
|
||||||
|
1. Web 创建 Session,读取 Skill Catalog,并建立 SSE。
|
||||||
|
2. 图片由 `AttachmentAdapter` 先上传,Composer 只保留 `attachmentId`。
|
||||||
|
3. Web 提交文本和 attachment IDs;Server 校验后立即返回 `202 + runId`。
|
||||||
|
4. `HarnessSession` 显式解析当前模型,转换图片,创建或复用对应模型指纹的 Pi Session。
|
||||||
|
5. Pi 事件由 `PiEventAdapter` 串行映射。异步 Skill 路径识别完成之前不会越过后续 read-completed 或 `run.completed`。
|
||||||
|
6. `InMemoryEventStore` 分配单调 sequence;SSE 支持 cursor replay、heartbeat 和断线清理。
|
||||||
|
7. Web reducer 去重并拒绝 sequence gap,映射为 assistant-ui 外部 Runtime、工具卡、审批框和时间线。
|
||||||
|
8. run 完成、取消或失败后释放订阅;删除 Session 时取消审批、终止 run、删除附件和工作区。
|
||||||
|
|
||||||
|
## 核心所有权
|
||||||
|
|
||||||
|
| 状态 | 权威组件 | Web 的角色 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Session / active run | `SessionRegistry` / `HarnessSession` | 发起命令、展示结果 |
|
||||||
|
| Event sequence | `EventStore` | 去重、检测缺口、重连 |
|
||||||
|
| Approval | `ApprovalBroker` | 提交一次决策、等待事件确认 |
|
||||||
|
| API Key | `ModelConnectionStore` + Pi 内存凭证 | 仅在 password input 中临时持有 |
|
||||||
|
| Attachment bytes | `AttachmentStore` | 上传阶段持有 File/object URL |
|
||||||
|
| Tool write target | `SafeWorkspace` | 不参与路径选择 |
|
||||||
|
|
||||||
|
## 进程与存储模型
|
||||||
|
|
||||||
|
课堂版只有一个 Node 进程。EventStore、ApprovalBroker 和模型配置均在内存;Session 工作区位于 `.agent-studio-sessions/<sessionId>`。这种结构便于展示边界和清理行为,但不提供横向扩展、崩溃恢复或多租户隔离。
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 课堂演示脚本(10–15 分钟)
|
||||||
|
|
||||||
|
## 演示前准备(1 分钟)
|
||||||
|
|
||||||
|
运行 `pnpm dev`,打开 `http://127.0.0.1:5173`。准备一个可用模型配置;若演示图片,确认 `supportsImages` 已开启并准备一张 JPEG、PNG 或 WebP。
|
||||||
|
|
||||||
|
先指出三栏:左侧是被发现的 Skills,中间是 assistant-ui 对话与工具卡,右侧是服务器事件时间线。强调页面不是 Agent 本体,而是 Harness 状态的投影。
|
||||||
|
|
||||||
|
## 1. 未配置模型(1 分钟)
|
||||||
|
|
||||||
|
展示 Composer 的明确禁用提示。解释系统不会偷偷选择任意可用模型,也不会把图片悄悄丢弃。打开 Settings,填写 Provider、API、Model、可选 Base URL 和 API Key。
|
||||||
|
|
||||||
|
指出 API Key 字段刷新后不会恢复,GET 响应也只能看到末四位提示。
|
||||||
|
|
||||||
|
## 2. Skill 渐进披露(2 分钟)
|
||||||
|
|
||||||
|
点击“填入演示任务”,不要立刻发送:
|
||||||
|
|
||||||
|
> 请分析 demo-account 最近 7 天广告数据,并生成一份简短报告草稿。
|
||||||
|
|
||||||
|
先看左侧只有 `ads-analysis` 的名称、描述和 diagnostics,没有把正文放进浏览器。发送后观察 `read`、`skill.requested` 和 `skill.loaded`。说明两个 Skill 事件由 Harness 根据规范路径和 read 成功状态推导,不是模型自报。
|
||||||
|
|
||||||
|
## 3. 只读工具与流式回复(2–3 分钟)
|
||||||
|
|
||||||
|
观察中央文字逐段增加,右侧 sequence 单调增长。`mock_ads_metrics` 工具卡展示 account、days、spend、revenue 和 ROAS。
|
||||||
|
|
||||||
|
明确数据来自仓库 fixture,不连接真实广告平台。Skill 约束模型先取工具结果再做数字结论,这体现“指令披露 + 结构化工具”的组合。
|
||||||
|
|
||||||
|
## 4. 服务器审批(2–3 分钟)
|
||||||
|
|
||||||
|
当 `save_report_draft` 请求出现时,Dialog 显示风险、固定目标、approval ID 和剩余时间。先讲清浏览器按钮不是权限边界,Broker 的状态才是权威。
|
||||||
|
|
||||||
|
第一轮点击 Deny:工具显示未执行,Session 目录不产生 `report.md`。
|
||||||
|
|
||||||
|
第二轮重发任务并点击 Approve:只有这次会在当前 Session 临时目录写入固定 `report.md`。重复点击被 UI 锁定,冲突决策被 Server 拒绝。也可等待超时展示 expired 不写文件。
|
||||||
|
|
||||||
|
## 5. 图片多模态(2 分钟)
|
||||||
|
|
||||||
|
附加一张广告截图,输入:
|
||||||
|
|
||||||
|
> 请结合这张素材解释可能的创意问题,并用 demo-account 最近 7 天数据验证建议。不要从图片猜测账户指标。
|
||||||
|
|
||||||
|
展示上传、缩略图、预览与移除。说明浏览器/SSE 只保留 attachmentId,Server 规范化文件后才转换为 Pi `ImageContent[]`。若将模型设置为 text-only,系统会明确阻止发送图片。
|
||||||
|
|
||||||
|
## 6. 重连与清理(1–2 分钟)
|
||||||
|
|
||||||
|
说明 SSE 使用 sequence 和 Last-Event-ID 重放,重复事件会去重,缺口会触发全量重建。点击重置或删除 Session,展示 run、审批、附件、报告与内存凭证一并清理。
|
||||||
|
|
||||||
|
最后强调:当前的 SafeWorkspace、工具策略和审批是教学用应用边界;Pi 没有内置 Sandbox,生产环境仍要用容器或 microVM 隔离。
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# 事件映射
|
||||||
|
|
||||||
|
## 三层映射
|
||||||
|
|
||||||
|
Pi 发出的事件首先经过 `PiEventAdapter` 变成经过裁剪和脱敏的 `HarnessEvent`。Web 的 reducer 再把有序事件投影为 assistant-ui 消息、工具和运行状态。Pi 原始对象不会直接进入浏览器。
|
||||||
|
|
||||||
|
| Pi / Harness 来源 | HarnessEvent | Web / assistant-ui 状态 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Session 创建 | `session.started` | 建立初始 timeline |
|
||||||
|
| HTTP message input | `user.message` | user message,附件仅为 IDs |
|
||||||
|
| Harness 开始 run | `run.started` | `isRunning=true`,显示停止按钮 |
|
||||||
|
| `message_update.text_delta` | `assistant.delta` | 按稳定 messageId 追加文本 |
|
||||||
|
| assistant `message_end` | `assistant.completed` | assistant message complete |
|
||||||
|
| `tool_execution_start` | `tool.requested` | 创建工具调用 part |
|
||||||
|
| `tool_execution_end` 成功 | `tool.completed` | 工具卡显示 result |
|
||||||
|
| `tool_execution_end` 失败 | `tool.failed` | 工具卡显示安全错误 |
|
||||||
|
| Broker 请求 | `approval.required` | 打开服务器审批 Dialog |
|
||||||
|
| Broker 决策 / 超时 / 取消 | `approval.resolved` | 关闭 pending 状态 |
|
||||||
|
| AttachmentStore 成功 | `attachment.uploaded` | 保存安全附件元数据 |
|
||||||
|
| AttachmentStore 拒绝 | `attachment.rejected` | timeline / 错误状态 |
|
||||||
|
| ModelStore 配置 | `model.configured` | 更新安全模型摘要 |
|
||||||
|
| Harness 正常结束 | `run.completed` | run 和消息标为 complete |
|
||||||
|
| Harness 取消 | `run.cancelled` | 消息 incomplete/cancelled |
|
||||||
|
| Harness 失败 | `run.failed` | 消息 incomplete/error |
|
||||||
|
| Session 删除 | `session.ended` | 连接回到 idle |
|
||||||
|
|
||||||
|
## Skill 推导事件
|
||||||
|
|
||||||
|
`skill.requested` 和 `skill.loaded` 不是 Pi SDK 的原生事件。Harness 按下面条件推导:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Pi read start] --> B[realpath]
|
||||||
|
B --> C{是否匹配 SkillIndex}
|
||||||
|
C -->|否| D[只保留普通 tool 事件]
|
||||||
|
C -->|是| E[skill.requested]
|
||||||
|
E --> F{同一 toolCallId read 成功}
|
||||||
|
F -->|是| G[skill.loaded]
|
||||||
|
F -->|否| H[不发 loaded]
|
||||||
|
```
|
||||||
|
|
||||||
|
真实路径匹配避免仅凭用户提供的字符串伪造 Skill 活动。事件由 Session 队列串行处理,因此异步 `realpath` 不会导致 read end 越过 read start。
|
||||||
|
|
||||||
|
## 顺序、重放与去重
|
||||||
|
|
||||||
|
- 每个 Session 的 sequence 从 1 单调递增。
|
||||||
|
- SSE 初次连接可用 `afterSequence`,重连可用 `Last-Event-ID`。
|
||||||
|
- Server 先重放 cursor 之后的事件,再订阅新事件,并发送 heartbeat。
|
||||||
|
- Web 忽略 sequence 小于等于当前值的重复事件。
|
||||||
|
- sequence 出现缺口时,Web 不应用该事件,而是重置投影并从 0 重连。
|
||||||
|
- timeline 最多保留 500 条客户端状态,界面显示最近 100 条筛选结果。
|
||||||
|
|
||||||
|
## 数据最小化
|
||||||
|
|
||||||
|
事件负载经过 `redactAndLimit`。API Key、authorization、token 等敏感字段被脱敏;工具参数和结果有字节上限。图片事件只含 ID、MIME、字节数和尺寸,不含 base64。错误映射只保留明确 code 和有限长度的安全消息。
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# 从课堂 MVP 到生产 Harness
|
||||||
|
|
||||||
|
## 目标架构
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Web --> Gateway[Gateway / Auth / CSRF / Rate Limit]
|
||||||
|
Gateway --> API[Stateless API]
|
||||||
|
API --> DB[(Postgres)]
|
||||||
|
API --> Redis[(Redis Streams / Locks)]
|
||||||
|
API --> KMS[KMS / Secret Manager]
|
||||||
|
API --> Blob[Object Storage]
|
||||||
|
Redis --> Workers[Agent Workers]
|
||||||
|
Workers --> Sandbox[Per-run Container / microVM]
|
||||||
|
Sandbox --> Egress[Egress Proxy / Allowlist]
|
||||||
|
Workers --> Audit[Immutable Audit Log]
|
||||||
|
Redis --> SSE[Distributed SSE Gateway]
|
||||||
|
SSE --> Web
|
||||||
|
```
|
||||||
|
|
||||||
|
## 阶段 1:身份与持久化
|
||||||
|
|
||||||
|
- 引入用户、组织、Session 所有权与 RBAC。
|
||||||
|
- Postgres 保存 Session、Run、Message、Event、ToolCall 和 Approval 的不可变记录。
|
||||||
|
- 以 `(session_id, sequence)` 唯一约束保证事件顺序;命令使用幂等键。
|
||||||
|
- Redis 保存短期锁、队列和 fan-out,避免同 Session 多 Worker 并发。
|
||||||
|
- 为迁移和清理任务定义明确的数据保留周期。
|
||||||
|
|
||||||
|
## 阶段 2:凭证与 Web 安全
|
||||||
|
|
||||||
|
- API Key 进入 KMS / Secret Manager,应用只持有短期解密结果。
|
||||||
|
- OAuth 或短期 provider token 优先于长期静态密钥。
|
||||||
|
- Secure、HttpOnly、SameSite cookie;CSRF token;严格 CSP 与 frame-ancestors。
|
||||||
|
- 对 Session、模型测试、消息、上传和审批分别限速并设置租户配额。
|
||||||
|
- 所有错误响应使用稳定 code,不回显 provider body 或敏感 headers。
|
||||||
|
|
||||||
|
## 阶段 3:文件与执行隔离
|
||||||
|
|
||||||
|
- 原图和规范化图进入私有对象存储,使用租户前缀、服务端加密和生命周期规则。
|
||||||
|
- 图片扫描、解码超时、内存限制和异步规范化。
|
||||||
|
- 每个 run 在低权限容器或 microVM 内执行;只挂载该 Session 工作区。
|
||||||
|
- 使用只读根文件系统、CPU/内存/文件数/时长配额、seccomp 和无特权用户。
|
||||||
|
- 出站流量经过代理,只允许已批准的模型域名;禁止 metadata 和横向内网访问。
|
||||||
|
|
||||||
|
## 阶段 4:可靠 Agent 运行
|
||||||
|
|
||||||
|
- Durable queue 驱动 run 状态机,Worker 崩溃后可重试或终止。
|
||||||
|
- Approval request 持久化并使用 compare-and-set 解决重复、冲突和超时竞态。
|
||||||
|
- 工具 side effect 使用幂等键或事务 outbox;审批不等于可以重复执行。
|
||||||
|
- 版本化 Prompt、Skill、模型配置和工具 schema,以便重放与审计。
|
||||||
|
- 对 token、费用、延迟、tool error、approval time 和 cancellation 建立指标。
|
||||||
|
|
||||||
|
## 阶段 5:分布式事件与审计
|
||||||
|
|
||||||
|
- Redis Streams、Kafka 或数据库 outbox 发布事件。
|
||||||
|
- SSE Gateway 按租户鉴权、从持久 cursor 重放,并支持跨实例 fan-out。
|
||||||
|
- 审计日志记录操作者、决策、策略版本、输入摘要和 side effect,不记录秘密或原始敏感内容。
|
||||||
|
- 定期验证删除任务确实清除了数据库、对象存储、缓存、凭证与 Sandbox volume。
|
||||||
|
|
||||||
|
## 上线门槛
|
||||||
|
|
||||||
|
生产发布前需要威胁建模、依赖与镜像扫描、SSRF/路径/上传模糊测试、故障注入、备份恢复演练、租户逃逸测试和第三方安全评审。课堂测试套件应保留,并新增真实 provider 的隔离式 smoke test;后者不能进入默认 CI,也不能使用个人密钥。
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# 安全边界
|
||||||
|
|
||||||
|
## 信任模型
|
||||||
|
|
||||||
|
浏览器输入、模型输出、工具参数、上传文件名、MIME、Base URL、Skill 额外路径和 SSE cursor 都是不可信输入。Zod / TypeBox 负责结构校验,Harness 负责语义与权限校验,操作系统仍是最终权限边界。
|
||||||
|
|
||||||
|
## 已实现的课堂版保护
|
||||||
|
|
||||||
|
### 凭证
|
||||||
|
|
||||||
|
- Session 配置覆盖服务器环境变量;没有配置时明确报错。
|
||||||
|
- API Key 只保存在 Server 内存和 Pi `InMemoryCredentialStore`。
|
||||||
|
- 响应只返回 `keyHint`,不会返回原值或长度。
|
||||||
|
- 不写浏览器 Storage、日志、事件、URL、Pi 用户目录或仓库文件。
|
||||||
|
- 运行中禁止修改模型;清除凭证幂等。
|
||||||
|
|
||||||
|
### Base URL / SSRF
|
||||||
|
|
||||||
|
- 使用 WHATWG URL,默认仅 HTTPS。
|
||||||
|
- 拒绝 userinfo、fragment、非 HTTP(S)、异常端口和含凭证 query。
|
||||||
|
- DNS 结果若命中 loopback、private、link-local、unspecified、multicast 或 metadata 范围则拒绝。
|
||||||
|
- 开发模式只对显式 localhost HTTP 开例外;企业内网需要服务器管理员设置环境变量。
|
||||||
|
- 当前请求策略要求同源重定向,不能依赖首次 URL 校验后任意跳转。
|
||||||
|
|
||||||
|
### 路径与写入
|
||||||
|
|
||||||
|
- 每个 Session 使用独立随机目录。
|
||||||
|
- realpath + relative containment 防止 `..` 和绝对路径逃逸。
|
||||||
|
- 逐段拒绝符号链接,并保护 `.env`、`.git`、`.ssh` 和常见私钥名。
|
||||||
|
- write tool 只能调用 `SafeWorkspace.writeReportDraft`,目标固定为 `report.md`,临时文件以独占创建并原子 rename。
|
||||||
|
- 删除 Session 会取消 run 和审批,并清除附件和整个 Session 目录。
|
||||||
|
|
||||||
|
### 图片
|
||||||
|
|
||||||
|
- 只收 JPEG、PNG、WebP;同时检查扩展名、MIME 和 magic bytes。
|
||||||
|
- 流式字节上限、宽高上限与总像素上限阻止常见资源耗尽输入。
|
||||||
|
- Sharp 自动旋转并重新编码,去掉不需要的元数据。
|
||||||
|
- 随机服务端文件名,权限 0600;不提供任意路径下载或静态目录。
|
||||||
|
- base64 只在 Server 构造 Pi prompt 的最后阶段短暂存在。
|
||||||
|
|
||||||
|
### 工具与审批
|
||||||
|
|
||||||
|
- allowlist 只开放课堂所需 read-only 工具。
|
||||||
|
- 未知工具默认拒绝。
|
||||||
|
- `save_report_draft` 在 Pi tool preflight 阶段等待服务器 Broker。
|
||||||
|
- 审批绑定 approvalId、runId 和 toolCallId;支持 approve、deny、expired、cancelled。
|
||||||
|
- Browser 点击不是最终事实,只有 `approval.resolved` 事件能改变投影状态。
|
||||||
|
|
||||||
|
## 不提供的隔离
|
||||||
|
|
||||||
|
Pi 没有内置 Sandbox,Project Trust 也不是 Sandbox。Pi Agent、Extension、Sharp 和所有 Node 依赖继承启动进程的用户权限与网络权限。应用层路径校验和审批不能防御依赖漏洞、Node 进程逃逸、同用户数据读取或不受控的出站模型请求。
|
||||||
|
|
||||||
|
因此不要用该课堂版处理生产密钥、不可信租户代码或敏感业务文件。真正部署至少需要:低权限系统用户、只读基础镜像、每 Session 容器或 microVM、挂载白名单、seccomp、出站网络 allowlist、资源配额和销毁验证。
|
||||||
|
|
||||||
|
## 部署前仍需补齐
|
||||||
|
|
||||||
|
- 登录、Session 所有权、租户授权和管理员权限。
|
||||||
|
- SameSite Secure cookie、CSRF、防点击劫持和严格 CSP。
|
||||||
|
- API、上传、run 和审批的速率限制与配额。
|
||||||
|
- KMS / Secret Manager,不在应用堆中长期保存明文凭证。
|
||||||
|
- 持久化且防篡改的审计日志。
|
||||||
|
- 恶意图片扫描、对象存储隔离和生命周期策略。
|
||||||
|
- 分布式锁、幂等键、Broker 持久化和崩溃恢复。
|
||||||
Reference in New Issue
Block a user