# muselab 宪法 > [English](constitution.md) > > 关于 **muselab 如何构建、如何允许变更** 的唯一事实源。 > 规格(spec)与 AI 生成的代码都从本文派生;当代码与本文冲突时,**以本文为准,代码即 bug**。 > > 范围:本文约束的是 *工程不变量*,不是功能愿望清单。功能意图写进每次改动的 spec,产品路线图与已知问题见 [GitHub Issues](https://github.com/hesorchen/muselab/issues)。 - **版本:** 6.0.0 - **批准日:** 2026-05-31 - **最近修订:** 2026-08-25 - **派生自:** `docs/architecture.md`、`CONTRIBUTING.md`、`SECURITY.md`、`pyproject.toml`,以及截至 2026-08-25 的前后端源码。 规范性关键词 **必须(MUST)/ 禁止(MUST NOT)/ 应当(SHOULD)/ 可以(MAY)** 遵循 RFC 2119。 --- ## 1. 核心原则 ### P1 — 可读优先于聪明,精简优先于功能堆砌 muselab 刻意保持小巧,让整个代码库始终人类可读。任何贡献**必须**优先选择清晰而非取巧。任何显著增大概念复杂度的改动,**必须**针对本原则给出正当理由。 ### P2 — Clone 即跑:永不引入构建步骤 前端**必须**保持「改一个文件、刷新浏览器即可」的运行方式。 - **禁止**引入任何打包器 / 转译器 / npm install 步骤(webpack、vite、esbuild、tsc 等)。 - 第三方浏览器库**必须** vendored 在 `frontend/vendor/` 下,并在 `THIRD_PARTY_LICENSES.md` 记录许可证。 - 前端代码**必须**用 Alpine.js v3 + 现代浏览器能直接理解的方言书写——不允许靠编译步骤来弥合差距。 ### P3 — 走 SDK,而非裸 API muselab 通过 **Claude Agent SDK**(与 Claude Code 同一引擎)驱动 Claude,绝不直接调用裸 Messages API。正是这一点让 MCP、Skills、Subagents、plan mode、`CLAUDE.md` 自动加载在所有 provider 上行为一致。新能力**必须**通过 SDK 原生机制表达,而非绕过 SDK。 ### P4 — 工作区属于用户,repo 永不染指 安装根与工作区根永久分离(见 §2)。代码**禁止**把自身状态写进用户工作区,唯一例外是默认工作区的保留路径 `/.muselab/` 与每个工作区的可恢复回收站;并且**禁止**让安装、升级、迁移依赖用户文件。 ### P5 — 以整文件为输入单位 助手按需通过 Read / Grep / Edit 触达用户文件。muselab **禁止**对用户工作区做预嵌入、预索引或 RAG 切块。上下文来自 SDK 按 cwd 自动发现的 `CLAUDE.md` 层级与按需工具读取。 ### P6 — 个人数据在发布物里是放射性的 这是一个开源仓库。**禁止**任何真实个人数据出现在代码、文档、commit、测试夹具、示例或 README 文案里。测试**必须**跑在一个用完即弃的工作区目录上。 --- ## 2. 架构不变量 以下是不可妥协的结构性事实。违反其中任一条即属架构变更,落地前**必须**先修订本宪法(见 §8)。 ### A1 — 安装根与工作区根刻意分离 | 根 | 内容 | 备份方式 | |---|---|---| | **repo**(`muselab/`) | 代码 + 每安装实例状态(`.env`、`sessions/`) | 随安装一起 | | **默认工作区**(`MUSELAB_ROOT`) | 用户自己的文件 + `.muselab/` 应用状态 | 独立备份,不动安装 | | **其他已登记工作区** | 额外的用户文件与工作区回收站 | 分别备份 | `backend/settings.py` 持有 `ROOT`(默认工作区),`backend/workspaces.py` 管理其他已登记根目录。会话固定保存 cwd,并由 SDK 从对应层级发现 `CLAUDE.md`。 ### A2 — 分层后端,一个 router 对应一个关注点 后端是 FastAPI,在 `backend/main.py` 挂载。每个领域是一个模块、暴露一个 `APIRouter`。新增的接口面**必须**遵循「一 router 一职责」的形态,而不是把一个上帝模块越养越大: | 模块 | 职责 | |---|---| | `main.py` | app 工厂、uvicorn 入口、路由挂载、静态前端、日志 token 脱敏、资源版本戳 | | `auth.py` | `X-Auth-Token` 守卫(header 或 `?token=`) | | `chat.py` | `/api/chat/*`——SDK client 池 + SSE 回合循环 | | `endpoints.py` | provider `CATALOG` + 每请求 env 装配 | | `files.py` | `/api/files/*`——safe-resolve 读写 / grep + 回收站 | | `workspaces.py` | 工作区登记、选择与目录边界 | | `sessions.py` | 会话索引、cwd、sidecar、队列 | | `terminal.py` / `terminal_worker.py` | `/api/terminals/*`、Profile、WebSocket ticket 与 PTY worker | | `scheduler.py` / `api_scheduler.py` | asyncio cron 循环 + 其 API | | `push.py` / `api_push.py` | Web Push / VAPID + 其 API | | `activity.py` / `activity_api.py` | 活动中心状态 + 其 API | | `transcript_index.py` | transcript 索引 | | `api_settings.py` | `/api/settings`——热改写 `.env` + `os.environ` | | `prompts.py` | 自包含 UI 工作流的简短启动消息 | | `ask_user_question.py` | 原生 `AskUserQuestion` 的浏览器状态桥接 | | `permission_request.py` | 工具权限与交互问答往返 | | `settings.py` | `ROOT` / `PORT` / `HOST`、`atomic_write_text`、`env_int` | ### A3 — per-session env 覆盖 + 配置隔离 第三方 provider 通过每请求设置 `ANTHROPIC_BASE_URL` + `ANTHROPIC_API_KEY` + 一个**隔离的** `CLAUDE_CONFIG_DIR` 来接入。隔离配置目录是强制的:它阻止 CLI 静默回退到 Pro OAuth、把第三方流量计费到用户的 Anthropic 账号上。任何新 provider 路径**必须**保留这三者。 ### A4 — client 池以 `(session_id, model, effort, service_tier)` 为 key `chat.py` 正是以这个 key 池化 `ClaudeSDKClient` 实例,默认 LRU 上限为 3,也可通过受支持的运行配置调整。每条助手消息存自己的 `model`,使刷新后徽标依旧准确。effort 与 service tier 都是 SDK/Gateway 进程启动契约;修改任一项都**必须**重建对应会话运行时。改动池的 key 属于架构变更(它与 MCP 进程派生相互作用——见 A6)。 ### A5 — UI 默认保护模型连续性 首个真实回合确定会话模型。前端在非空会话切换不兼容模型时**必须** fork 新会话,避免跨 vendor thinking-block 签名导致不可恢复的 `400`。管理 API 可以显式修改会话模型,但调用者必须承担 transcript 兼容性风险。在任何 provider 存在之前创建的会话,会在首次发送时自愈到某个已配置模型。 ### A6 — MCP:attribute 驱动、有门禁、默认零 - 出厂默认配置**零**个 MCP server;连接器全部是 opt-in。交互问答使用 SDK 原生 `AskUserQuestion`,不得为内置问答额外注册 MCP server。 - 每个 server(预置或用户添加)在 `mcp.json` 里都按**属性**存储(`transport`、`disabled`、钉死的 `version`),绝不用写死的 catalog。 - 版本**必须**钉死。出厂配置**禁止**用 `npx -y latest` / 未钉版本的 `uvx`。 - 后端**就绪门禁**(`chat.py` 的 `_await_mcp_ready`)**必须**把第 1 回合挂住,直到每个启用的外部 MCP server 到达终态,以防回合中途工具集变化把会话卡死。当没有启用外部 MCP 时门禁**必须**跳过(`_has_enabled_external_mcp`)。 - MCP 用于**内置工具够不到的能力**(需鉴权的外部系统)。凡是 `Read`/`Edit`/`Grep`/`Glob`/`Bash`/`WebFetch` 已覆盖的,**禁止**做成 MCP server。 ### A7 — MCP 与 Skill 的边界 若难点在于*连接并鉴权一个外部系统* → MCP。 若难点在于*把一件事做好的方法编码下来*(连接只是一个 API key)→ Skill(一个含 `SKILL.md` + 可选资产的文件夹,渐进式披露)。新扩展**必须**按此规则归类。 ### A8 — transcript 归 CLI 所有,不归 muselab 对话 transcript 由 Claude CLI 所有:Claude 会话通常位于默认配置根,第三方 Provider 会话可能位于隔离的配置根。muselab 的 `sessions/` 只持有叠加在其上的 sidecar 元数据(名称、cwd、每消息 model 徽标、成本、附件)。muselab **禁止**复制或分叉 transcript 本身的所有权。 ### A9 — 原生指令归属 muselab **禁止**注入全局或会话级自定义 system prompt。持久身份、回复偏好与个人上下文属于 SDK 自动发现的 `CLAUDE.md` 层级;可复用任务工作流属于 Skill;工具特有行为属于工具描述与代码强制的权限配置。UI 可以用开场 Prompt 描述自包含任务,但**禁止**再建立一层行为指令体系。运行模式**禁止**通过 system prompt 或临时 hook context 注入另一套身份、persona 或可复用工作流;可复用流程只属于用户、workspace、plugin、经审核生成或仓库扩展 Skills。 --- ## 3. 技术栈与约束 - **语言 / 运行时:** Python `>=3.12`。依赖与虚拟环境用 `uv` 管理。 - **Web:** FastAPI,基于 `starlette>=1.3.1`,由 `uvicorn[standard]` 提供服务。 - **Agent:** `claude-agent-sdk>=0.2.120,<0.3`——通往模型的唯一路径。应用代码里**禁止**直接用 `anthropic` SDK / 裸 HTTP 打模型端点。 - **前端:** vanilla HTML + Alpine.js v3 + CSS。无框架、无构建(P2)。 - **持久化:** 扁平文件(JSON sidecar、由 CLI 所有的 JSONL transcript)。**禁止**为核心功能引入数据库依赖。 - **新增依赖**:**必须**给出正当理由(是新能力,而非图方便)、钉一个版本下限;若它拉入运行时二进制(`npx`/`uvx`),安装脚本**必须**检测到并发出警告(见 CONTRIBUTING 清单)。 - **provider 接入**:**必须**暴露 Anthropic 兼容的 Messages 端点,并以 `endpoints.py` 里一条 `CATALOG` 落地。OpenAI-only 协议出局(SDK 期望 Anthropic 兼容端点)。 --- ## 4. 代码约定 ### Python - PEP 8。**不强制 formatter**——不要引入会把现有紧凑风格搅乱的 `black` / 自动格式化。 - Lint:`ruff`,`select = ["E","F","W"]`,`ignore = ["E501"]`。新代码**必须**通过 `ruff check backend/ tests/`。带主观倾向的规则族(B、I、N、UP)**禁止**启用——会为边际收益搅动可用代码。 - **公开**函数上**必须**加类型标注;其他地方不强制。 - 写盘**必须**走 `atomic_write_text`(或同等原子路径)——绝不对用户可见文件做裸截断写。 ### JavaScript / CSS - 无转译器。写 Alpine v3 + 现代浏览器能直接跑的方言。分号风格跟随相邻代码。 - CSS:按组件分段,带注释头。主题用 CSS 变量(`--c-*`、`--sp-*`);**禁止**写死颜色。 - 编辑 `app.js` / `styles.css` / `index.html` / `i18n/index.js` / `data/constants.js` 会参与资源版本戳——新增拆分模块时记得把它加进候选列表,好让客户端重新拉取。 ### 国际化 面向用户的 UI 文案**必须**在 `frontend/i18n/index.js` 的 `en` 与 `zh` 两张表里都存在。带译本的文档遵循 `_zh.md` 同名兄弟文件约定。 --- ## 5. 安全要求 (权威细节在 `SECURITY.md`;以下是代码评审与 spec **必须**强制执行的宪法级不变量。) - **每请求鉴权。** 普通 API 使用 `X-Auth-Token`。浏览器实时连接必须先通过已鉴权请求取得短期、单次 ticket;兼容性 query token 仅限无法发送 header 的资源请求。新增端点**禁止**绕过相应鉴权依赖。 - **Files API 路径穿越已封死。** 浏览器文件接口的所有路径**必须**经过 `files.py` 的 safe-resolve 逻辑,且停留在当前已登记工作区内。写 / 上传 / 重命名 / 拷贝**必须**拒绝 `.muselab-dustbin/` 路径(`_guard_not_trash`);删除是软删除(移入回收站),恢复 / 清除作为独立端点。 - **日志不泄密。** uvicorn 访问日志的 `token=` 脱敏过滤器(`main.py` 的 `_TokenFilter`)**必须**保留;新增可能携带 token / key 的日志面**必须**同样脱敏。 - **本地 MCP 需同意。** 添加 stdio server **必须**展示未截断的确切命令、警告它以 app 权限运行、标出危险模式(`sudo`、`rm -rf`、向 home/SSH 路径 `curl`),并要求明确批准。 - **本地 HTTP server** **必须**绑定 `127.0.0.1`、校验 `Origin`(防 DNS 重绑定)、并要求 token。优先用远程 HTTP 连接器而非 `npx` 命令(更少供应链风险)。 - **权限边界必须准确表达。** Files API 限定在选中的已登记工作区;Agent 工具和真实终端以服务用户权限运行,不得被描述成同一条路径沙箱。终端必须使用同源校验、单次 ticket 与安全环境变量集合。 - **零密钥** 进代码、commit 或测试夹具。`.env` 与 `sessions/` 保持 gitignored,**禁止**被添加进库。 --- ## 6. 测试与质量门禁 下列全过之前,改动不算完成(CONTRIBUTING 清单是其强制形式): - [ ] `uv run pytest tests/` 全绿。 - [ ] `uv run ruff check backend/ tests/` 干净(CI 在 lint 失败时阻断合并)。 - [ ] `bash scripts/lint.sh` 干净(编码 / BOM / 类名冲突)。 - [ ] **每个后端改动新增或更新一个 `tests/` 里的测试。** bug 修复**必须**带回归测试。 - [ ] 安全相关改动(鉴权、路径解析、MCP)按需扩展 `test_security.py` / `test_files.py` / `test_mcp_gate.py`。 - [ ] 前端视觉改动在 PR 里附 before/after 说明(暂无视觉回归套件)。 - [ ] 零密钥;不向 `.env` / `sessions/` 添加内容。 - [ ] 测试**必须**跑在用完即弃的工作区上——绝不用真实个人数据。 --- ## 7. 范围边界(非目标) muselab 是一个**自托管、以工作区为边界的 Agent 工作台**,不是通用托管式 AI 平台。以下事项在没有明确修宪之前**必须**拒绝: - 任何形式的构建步骤(违反 P2)。 - 对用户工作区做文档 RAG / 爬取内容的流水线(违反 P5)。 - 与工作区执行无关的通用聊天 UI 功能(托管账号体系、社交信息流等)。 - OpenAI-only 协议的 provider(违反 §3 / A3)。 - 默认预置重型 / 仅开发者 / 具写入或交易能力的 MCP server(违反 A6——如 GitHub MCP、DB 写、券商)。 - 任何需要真实个人数据才能测试的功能(违反 P6 / §6)。 --- ## 8. 治理 - **本文凌驾于代码、注释与习惯。** 一个与此处不变量冲突的 PR 即属错误,要么改 PR,要么在同一 PR 里修订本文并经评审签字。 - **spec 从本宪法派生。** 每个功能 / 重构**应当**带一份简短 spec(改什么、边界、用 EARS 句式写验收标准:「当 <触发>,系统应当 <行为>」)。spec **禁止**复述不变量——引用即可。 - **修订宪法:** 升版本号(semver——移除 / 重定义不变量为 MAJOR,新增不变量为 MINOR,澄清为 PATCH),更新 *最近修订*,并记录理由。架构变更(§2)**必须**先修订后合并,而非事后。 - **漂移检查:** 当对代码的反推理解与本文冲突时,把冲突当作一个发现——要么修代码,要么带理由修订本文。**绝不**让两者悄悄背离。 --- ## 9. 修订记录 - **6.0.0(2026-08-25):** 升级至 Agent SDK 0.2.144/Claude Code CLI 2.1.239,并在 default、plan、acceptEdits 与 bypass 权限模式下验证原生答案传递后,用 SDK 原生 `AskUserQuestion` 浏览器桥接替代常驻的进程内问答 MCP。出厂 MCP 拓扑改为真正的默认零配置,连接器继续全部 opt-in。 - **5.0.0(2026-08-15):** 移除预装 Skill 分发以及 A9 中运行模式激活 Skill 的例外。Skills 继续作为 SDK 原生扩展机制,支持用户、workspace、plugin、经审核生成与仓库扩展工作流;运行模式只保留传输与资源边界契约。 - **4.0.0(2026-07-31):** Fast 在 SDK client 启动时即固定进 Gateway header 契约,因此为 A4 的精确 client-pool key 加入 `service_tier`;同时修订 A9,允许用户显式选择的运行模式通过临时 `UserPromptSubmit.additionalContext` 激活内置 Skill,但必须保持规范 prompt/transcript 不变,并把工作流留在 `SKILL.md`,而不是注入自定义 system prompt。 - **3.0.0(2026-07-31):** 将 muselab 从原有个人领域定位调整为以工作区为边界的 Agent 工作台;移除领域预设,同时保留本地文件、自托管与安全不变量。 - **2.0.0(2026-07-23):** 将单 archive 模型更新为多工作区;纳入真实终端、短期实时连接 ticket 与 Activity 模块;把模型连续性明确为前端默认 fork、管理 API 显式覆盖;固化 CLAUDE.md/Skills/工具权限的原生指令归属。 --- *spec 告诉 AI 下一步该造什么。本宪法告诉它:无论造什么,什么必须始终为真。*