Files
mnote/design/old/07-ai/reference/7-17-acp-session-convex-sharing-contract-v1.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

482 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# [recycle] 7-17 [reference] ACP Session 与控制面账号作用域 / 分享合同 v1
> 创建时间:2026-05-18
>
> 当前状态:`reference / future`
>
> 归档说明(2026-05-21):本文冻结 ACP session 与控制面分享/账号作用域远期合同;当前只作为 future reference,不作为 MVP 后阶段 active implementation checklist。
>
> 2026-05-22 口径补充:本文标题中的 Convex 是历史控制面语境。当前默认控制面已由 Rust SQLite `control-plane` 承接;ACP/Hermes runtime session、auth、share grants、AI policy 默认不再依赖 Convex。下文涉及 Convex store 的内容只作为历史 / compat / sync replica 对照。
>
> 本稿目的:
> 1. 明确 ACP session 必须绑定当前账号权限与控制面作用域,不能以内存态绕过权限。
> 2. 冻结后续“复制会话 / 只读分享 / 多人共同会话”的产品语义,避免当前实现误扩。
> 3. 区分 MNote 产品层 AI session 与 ACP Hermes / ACP Reasonix 执行层 session。
> 4. 为后续项目基本完成后扩展共享能力预留 schema、API 和验证边界。
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-25-acp-session-runtime-enhancement-plan-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md`
> - `/mnt/Data1T/mnote/recycle/design/07-ai/retired-http-hermes/`
---
## 0. 当前结论
ACP 默认主链已经替代旧 Hermes HTTP 主链,但这不意味着 ACP runtime session 可以成为产品层会话真相。
当前必须成立的规则:
- **Rust SQLite control-plane 持有产品层 AI session 的账号作用域真相;Convex 只作为历史 / compat / sync replica 对照,不应被写死成唯一真相。**
- **ACP runtime session 只是执行层会话**,可以被控制面 session 绑定或索引,但不能直接作为跨用户共享对象。
- **ACP session 记录必须带 `userId / workspaceId / documentId / sessionId / runId / actorId / acpRuntime / profile`**。
- **写入控制面 session store 失败时,不允许静默降级为可继续写入的内存 session**。这会破坏账号隔离、审计和后续分享语义。
- 当前只实现单用户页面 AI session 基础路径;复制、分享、多人共同会话全部是后续功能,不在当前阶段实施。
---
## 1. 术语边界
### 1.1 MNote AI Session
MNote AI Session 是产品层会话对象,长期应该由控制面持有;当前默认控制面是 Rust SQLite control-plane,目标不是把消息全文和权限真相永久绑死在 Convex。
它回答:
- 谁能看到这段 AI 会话?
- 这段会话属于哪个 workspace / document
- 它是否可被复制、分享、共同编辑?
- 每条 run 是哪个账号触发的?
- 哪些消息、工具调用、结果可以被其他用户看到?
MNote AI Session 的 id 可以稳定暴露给前端,例如 `mnote_{documentId}_{traceId}`,但它不是 ACP runtime 原生 session id。
### 1.2 ACP Runtime Session
ACP Runtime Session 是执行层对象,由 Hermes ACP 或 Reasonix ACP 子进程持有。
它回答:
- 某个 agent runtime 当前 prompt 属于哪个协议 session
- 运行时如何接收 `session/prompt`
- `session/update` 事件如何返回?
- 运行时内部是否有本地上下文、缓存、memory、tool state
ACP Runtime Session 不应作为产品层共享真相。它可以失效、重建、按用户隔离、按 runtime 隔离。
### 1.3 Run / Event / Tool Audit
Run 是一次用户触发的执行。
Event 是 runtime 或 mnote tool 在 run 中产生的结构化事件。
Tool Audit 是 mnote 侧工具执行与写入结果的审计记录,必须以触发账号为准,而不是 session owner 或 runtime owner。
---
## 2. 当前实现边界
当前代码可接受的最小边界:
- `POST /api/hermes/client/sessions` 在 ACP 默认路径下创建 SQLite control-plane runtime session 索引;Convex 仅保留显式 legacy compat。
- `POST /api/hermes/client/runs` 创建 run 并默认持久化到 SQLite control-plane runtime store。
- `GET /api/hermes/client/events/{runId}` 将 ACP event 转成页面 SSE,并追加 runtime event。
- `GET /api/hermes/client/gateway/health` 在 ACP 默认路径下返回 `transport=acp`,不再探测旧 Hermes HTTP gateway。
当前不应声称已完成:
- 跨账号共享会话。
- 多人共同编辑同一个 AI session。
- 复制会话后的上下文重建。
- 完整消息历史作为产品层真相。
- Hermes ACP 与 Reasonix ACP 的统一长期 resume 语义。
- 分享链接、权限继承、脱敏导出。
---
## 3. 数据模型草案
### 3.1 `ai_sessions`
后续需要从当前 `acp_runtime_runs` 中抽出产品层 session 表。默认建议本地全文 + 控制面 metadata 的双层模型:
- 会话全文默认落本地 `ai-sessions/private/*.jsonl``ai-sessions/shared/*/*.jsonl`
- 控制面只保存 metadata、分享关系、审计索引、同步状态
- 只有显式开启同步或分享时,才把必要副本推到远端
建议字段:
| 字段 | 说明 |
|---|---|
| `id` | 控制面记录 id |
| `session_id` | MNote 产品层 session id |
| `owner_user_id` | 会话创建者 |
| `workspace_id` | 所属 workspace,可为空但必须显式 |
| `document_id` | 所属页面 / 文档 |
| `title` | 会话标题 |
| `visibility` | `private` / `workspace_read` / `shared_link` / `collaborative` |
| `source` | `page_ai` / `local_file_ai` / 其他 |
| `created_at` / `updated_at` / `deleted_at` | 生命周期 |
权限规则:
- 默认 `private`
- 任何 query/mutation 必须先按当前控制面 auth 解析 user,再判断 `owner_user_id`、membership 或 share grant。
- 不允许只凭客户端传入的 `userId` 授权。
### 3.2 `ai_session_members`
多人共同会话需要独立成员表,不应把共享用户塞进 session JSON。
| 字段 | 说明 |
|---|---|
| `session_id` | 产品层 session |
| `user_id` | 成员 |
| `role` | `owner` / `editor` / `commenter` / `viewer` |
| `added_by` | 添加者 |
| `created_at` | 加入时间 |
| `revoked_at` | 撤销时间 |
角色规则:
- `viewer` 只能读可共享消息和可共享 artifact。
- `editor` 可以继续发起 run,但每次 run 的 `actor_id` 必须是本人。
- `owner` 可以管理成员和删除会话。
### 3.3 `ai_runtime_bindings`
产品层 session 与运行时 session 的绑定必须独立建模。
| 字段 | 说明 |
|---|---|
| `binding_id` | 绑定 id |
| `session_id` | MNote 产品层 session |
| `run_id` | 当前 run,可选 |
| `user_id` | 运行时所属用户 |
| `runtime` | `hermes` / `reasonix` |
| `profile` | Hermes profile 或 Reasonix preset |
| `runtime_session_id` | ACP 对端 session id |
| `status` | `active` / `closed` / `expired` / `failed` |
| `created_at` / `expires_at` | 生命周期 |
关键规则:
- 同一个 MNote session 可以有多个 runtime binding。
- 不同用户默认不能复用同一个 runtime binding。
- 切换 Hermes / Reasonix runtime 时必须创建新 binding。
- binding 是执行层缓存,不是会话权限真相。
### 3.4 `ai_session_messages`
后续如需完整历史,应单独建消息表,而不是只依赖 runtime events。
| 字段 | 说明 |
|---|---|
| `message_id` | 消息 id |
| `session_id` | 产品层 session |
| `run_id` | 关联 run |
| `actor_user_id` | 触发者,可为空仅限系统消息 |
| `role` | `user` / `assistant` / `tool` / `system` |
| `content` | 可展示文本 |
| `visibility` | `private` / `members` / `owner_only` |
| `redaction` | 脱敏状态 |
| `created_at` | 创建时间 |
当前阶段可以先不落完整 messages,但必须保证已有 run/event store 能按 user 过滤。
### 3.5 `ai_session_events`
Runtime event 和 tool event 应保留原始结构,但查询时必须做权限过滤。
| 字段 | 说明 |
|---|---|
| `event_id` | 事件 id |
| `session_id` | 产品层 session |
| `run_id` | run |
| `actor_user_id` | 触发者 |
| `runtime` | `hermes` / `reasonix` |
| `event_type` | `message.delta` / `tool.started` / `tool.completed` 等 |
| `payload` | 原始或规范化 payload |
| `visibility` | 默认 `owner_only`,明确脱敏后才可扩大 |
| `created_at` | 创建时间 |
---
## 4. 权限模型
### 4.1 单用户私有会话
当前阶段只要求这个模式稳定。
规则:
- 创建 session 时使用当前 SQLite control-plane auth/session 解析出的真实 user / actor id。
- `userId` 只可作为服务端派生字段,不可由浏览器任意指定。
- run / event / tool audit 都必须与同一 actor 绑定。
- 当前请求无法解析用户时,应返回 `401` 或稳定错误,而不是创建匿名共享 session。
### 4.2 复制会话
复制不是共享同一个 runtime session。
复制语义:
- 生成新的 `session_id`
- 新 session 的 `owner_user_id` 是复制者。
- 可以复制已脱敏、可共享的消息、摘要、artifact 引用。
- 不复制底层 `runtime_session_id`
- 不复制另一个用户的 Hermes profile memory、Reasonix cache handle、tool permission state。
- 复制后第一次继续对话时,为复制者创建新的 runtime binding。
适用场景:
- 用户把某段 AI 分析作为模板继续改。
- 从只读分享页复制到自己的 workspace。
### 4.3 只读分享
只读分享只读产品层 session 的可共享投影。
必须隐藏:
- 本地路径、profile 配置路径、API key 状态。
- tool args 中含有的敏感字段。
- 未授权页面内容、选区内容、私有 workspace 信息。
- actor 的内部 user id,除非产品明确展示成员身份。
分享链接不能恢复 ACP runtime session。
### 4.4 多人共同会话
多人共同会话是后续能力,不能直接复用当前 runtime session。
推荐语义:
```text
共享 MNote AI Session
-> 每个用户按自己的权限发起 run
-> 每次 run 记录 actor_user_id
-> runtime binding 默认按 actor_user_id 隔离
-> 前端展示同一个产品层 transcript
```
允许的实现策略:
- **每用户 runtime binding**:最安全,默认方案。每个成员继续对话时由自己的 runtime 处理。
- **共享 transcript 重建上下文**runtime 不共享,只把已授权 transcript 作为 prompt context 注入。
- **共享 runtime binding**:默认禁止。只有当 runtime 明确是服务端多租户安全实例,并且 tool permission 已按 actor 隔离时才可考虑。
---
## 5. ACP Hermes 与 ACP Reasonix 差异
### 5.1 ACP Hermes
特点:
- Hermes profile、memory、skills、tools 往往绑定本机用户配置。
- `mnoteai` profile 可能包含特定用户偏好、工具开关、记忆文件。
- Hermes ACP 的底层 session 适合“当前用户私有 agent runtime”。
规则:
- 默认按用户隔离 runtime binding。
- 共享会话不能让其他用户复用 owner 的 Hermes profile memory。
- 复制会话时只复制可展示 transcript,不复制 Hermes profile 状态。
- 如果后续允许团队 Hermes profile,必须单独建 workspace-level profile 权限模型。
### 5.2 ACP Reasonix
特点:
- Reasonix 强调 cache-first / preset / project cache。
- 缓存可能跨 prompt 复用,隐私边界比普通 stateless model 更敏感。
- Reasonix 的 session 与 cache handle 不应默认跨账号共享。
规则:
- 默认按 `userId + workspaceId + documentId + preset` 隔离缓存可见性。
- 分享 transcript 不等于分享 Reasonix cache。
- 多人共同会话如果要共享 Reasonix 缓存,必须先设计 cache grant。
- 只读分享不得暴露 cache hit/miss 细节,除非确认无隐私风险。
### 5.3 统一抽象
MNote 不应把 Hermes ACP / Reasonix ACP 的内部 session 当成统一真相。
统一层只定义:
- 产品层 session。
- run 与 actor。
- runtime binding。
- event/message 投影。
- 权限与分享合同。
---
## 6. API 草案
当前阶段不实现以下 API,只冻结形状。
### 6.1 Session
```http
GET /api/hermes/client/sessions
POST /api/hermes/client/sessions
GET /api/hermes/client/sessions/{sessionId}
DELETE /api/hermes/client/sessions/{sessionId}
POST /api/hermes/client/sessions/{sessionId}/rename
POST /api/hermes/client/sessions/{sessionId}/auto-title
```
要求:
- 所有接口服务端解析当前用户。
- 查询默认只返回当前用户有权限访问的 session。
- 删除默认软删除,不删除 runtime audit。
### 6.2 复制
```http
POST /api/hermes/client/sessions/{sessionId}/copy
```
请求:
```json
{
"targetWorkspaceId": "ws_1",
"targetDocumentId": "doc_2",
"copyMode": "summary_and_visible_messages"
}
```
响应:
```json
{
"ok": true,
"sessionId": "mnote_doc_2_copy_1",
"sourceSessionId": "mnote_doc_1_original",
"runtimeBindingCopied": false
}
```
### 6.3 分享
```http
POST /api/hermes/client/sessions/{sessionId}/shares
GET /api/hermes/client/sessions/{sessionId}/shares
DELETE /api/hermes/client/sessions/{sessionId}/shares/{shareId}
```
分享 grant 必须包含:
- `scope`: `user` / `workspace` / `link`
- `role`: `viewer` / `editor`
- `expiresAt`
- `redactionPolicy`
### 6.4 多人会话成员
```http
POST /api/hermes/client/sessions/{sessionId}/members
GET /api/hermes/client/sessions/{sessionId}/members
DELETE /api/hermes/client/sessions/{sessionId}/members/{userId}
```
---
## 7. 不变量
这些规则后续实现必须写成测试。
1. 账号 A 创建的 private session,账号 B 不能读取列表、详情、事件、消息。
2. 账号 B 复制账号 A 分享给他的 session 后,得到新的 `sessionId` 和新的 owner。
3. 复制后的 session 不包含源 session 的 `runtime_session_id`
4. 多人共同会话中,账号 B 发起 run 时 `actor_user_id=B`,不能写成 owner A。
5. 只读 viewer 不能发起 run。
6. Hermes profile memory 不随 session 分享。
7. Reasonix cache handle 不随 session 分享。
8. tool args 默认 `owner_only`,只有经过脱敏的摘要可进入 shared transcript。
9. Convex store 写入失败时,run 创建必须失败或返回明确可恢复错误,不能静默创建匿名内存会话。
10. 本地文件 AI session 分享必须重新核验目标用户是否能访问对应 local root;默认不支持跨用户分享本地文件内容。
---
## 8. 实施阶段
### Phase A:当前阶段,只做约束固化
状态:当前主线。
- ACP 默认主链可用。
- session/run/event 必须账号作用域写入 Rust SQLite control-planeConvex 只作为显式 compat / sync replica。
- 旧 Hermes HTTP 主链进入 `recycle`
- 不实现复制、分享、多人会话。
- 文档与测试明确禁止内存降级绕过权限。
### Phase B:项目基本完成后,补产品层 session 表
目标:
- 增加 `ai_sessions` / `ai_session_members` / `ai_runtime_bindings`
- 将现有 `acp_runtime_runs` 从“运行态索引”升级为 session 下的 run 记录。
- UI 从 localStorage 历史逐步迁到 Rust control-plane session 列表。
### Phase C:复制与只读分享
目标:
- 实现 session copy。
- 实现只读分享投影。
- 建立脱敏策略。
- 不实现多人共同编辑。
### Phase D:多人共同会话
目标:
- 成员管理。
- 多 actor transcript。
- 每 actor runtime binding。
- 共享上下文重建策略。
### Phase Eruntime-specific 高级策略
目标:
- Hermes team profile 权限模型。
- Reasonix cache grant / cache visibility。
- workspace-level AI runtime policy。
---
## 9. 当前代码注意事项
当前代码中 `acp_runtime_runs` / `acp_runtime_events` 仍是运行态索引,不是完整产品层 session 模型。
因此后续修改时:
- 不要把 `ACP_RUN_PAYLOADS``ACP_ACTIVE_RUNS` 视为权限真相。
- 不要因为 Convex 写入失败就退回“可继续写”的内存 session。
- 不要把 `profile=mnoteai` 当成 user identity。
- 不要让 `acpRuntime=hermes` 自动表示可访问 Hermes profile memory;仍需当前账号授权。
- 不要把 Reasonix cache 命中结果写入共享 transcript,除非经过脱敏和权限确认。
---
## 10. 验收清单
后续实现本稿时,至少需要以下验证:
- 两账号隔离 smokeA 创建 sessionB 列表不可见。
- 分享 smokeA 授权 B viewerB 可读脱敏 transcript,不可 run。
- editor smokeA 授权 B editorB 可 runevent actor 为 B。
- copy smokeB 复制 A 的分享 session,产生新 session,新 owner 为 B。
- runtime binding smoke:复制后没有复用源 runtime session id。
- Hermes ACP smoke:共享后不暴露 owner profile memory。
- Reasonix ACP smoke:共享后不暴露 cache handle。
- 权限失败 smokeConvex auth 缺失或 user 不匹配时,API 返回 401/403,不创建内存会话。