docs: add architecture and classroom guide

This commit is contained in:
Wenxin Duan
2026-09-08 10:47:42 +08:00
parent da7558ab49
commit 8fa006344d
6 changed files with 459 additions and 9 deletions
+78
View File
@@ -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>`。这种结构便于展示边界和清理行为,但不提供横向扩展、崩溃恢复或多租户隔离。
+49
View File
@@ -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 隔离。
+55
View File
@@ -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 和有限长度的安全消息。
+62
View File
@@ -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,也不能使用个人密钥。
+63
View File
@@ -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 持久化和崩溃恢复。