Ctrl K
ComunidadGuías…

constitution.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# muselab Constitution

> [简体中文](constitution_zh.md)
>
> The single source of truth for **how muselab is built and may change**.
> Specs and AI-generated code derive from this document; when code and this
> document disagree, this document wins and the code is the bug.
>
> Scope: this governs *engineering invariants*, not feature wishlists. Feature
> intent lives in per-change specs; product roadmap and known issues live on
> [GitHub Issues](https://github.com/hesorchen/muselab/issues).

- **Version:** 6.0.0
- **Ratified:** 2026-05-31
- **Last amended:** 2026-08-25
- **Derived from:** `docs/architecture.md`,
  `CONTRIBUTING.md`, `SECURITY.md`, `pyproject.toml`, and the backend/frontend
  source as of 2026-08-25.

Normative keywords **MUST / MUST NOT / SHOULD / MAY** follow RFC 2119.

---

## 1. Core Principles

### P1 — Readable over clever, small over featureful
muselab is intentionally small so the whole codebase stays human-readable.
Contributions MUST prefer clarity over cleverness. Any change that materially
increases conceptual surface area MUST justify itself against this principle.

### P2 — Clone-and-run: no build step, ever
The frontend MUST remain runnable by editing a file and refreshing the browser.
- No bundler / transpiler / npm install step MAY be introduced (webpack, vite,
  esbuild, tsc, etc.).
- Third-party browser libs MUST be vendored under `frontend/vendor/` with their
  license recorded in `THIRD_PARTY_LICENSES.md`.
- Frontend code MUST be written in the dialect Alpine.js v3 + modern browsers
  understand directly — no compile step is allowed to bridge the gap.

### P3 — SDK over raw API
muselab drives Claude through the **Claude Agent SDK** (the same engine as
Claude Code), never the raw Messages API directly. This is what makes MCP,
Skills, Subagents, plan mode, and `CLAUDE.md` auto-load behave uniformly across
every provider. New capabilities MUST be expressed through SDK-native mechanisms
rather than bypassing the SDK.

### P4 — Workspaces belong to the user; the repo never touches them
The install root and workspace roots remain separate (see §2). Code MUST NOT
write its own state into a user workspace except under the default workspace's
reserved `<MUSELAB_ROOT>/.muselab/` path and each workspace's recoverable
trash. Installation, upgrade, and migration MUST NOT depend on user files.

### P5 — Whole-file as the unit of input
The assistant reaches user files on demand via Read / Grep / Edit. muselab MUST
NOT pre-embed, pre-index, or RAG-chunk user workspaces. Context comes from the
SDK-discovered `CLAUDE.md` hierarchy for the active cwd plus on-demand reads.

### P6 — Personal data is radioactive in shipped artifacts
This is an open-source repo. No real personal data MAY appear in code, docs,
commits, test fixtures, examples, or README copy. Tests MUST run against a
throwaway workspace directory.

---

## 2. Architecture Invariants

These are non-negotiable structural facts. A change that violates one is an
architecture change and MUST amend this constitution (see §8) before landing.

### A1 — Install root and workspace roots stay separate
| Root | What | Backups |
|---|---|---|
| **repo** (`muselab/`) | code + per-install state (`.env`, `sessions/`) | with the install |
| **default workspace** (`MUSELAB_ROOT`) | user files + `.muselab/` application state | independently, without touching the install |
| **other registered workspaces** | additional user files and workspace trash | separately |

`backend/settings.py` owns `ROOT` (the default workspace), while
`backend/workspaces.py` manages other registered roots. Each session stores its
cwd and the SDK discovers `CLAUDE.md` from the corresponding hierarchy.

### A2 — Layered backend, one router per concern
The backend is FastAPI, mounted in `backend/main.py`. Each domain is one module
exposing an `APIRouter`. New surface area MUST follow this one-router-per-concern
shape rather than growing a god-module:

| Module | Owns |
|---|---|
| `main.py` | app factory, uvicorn entry, route mounting, static frontend, log token-redaction, asset-version stamping |
| `auth.py` | `X-Auth-Token` guard (header or `?token=`) |
| `chat.py` | `/api/chat/*` — SDK client pool + SSE turn loop |
| `endpoints.py` | provider `CATALOG` + per-request env wiring |
| `files.py` | `/api/files/*` — safe-resolve read/write/grep + dustbin |
| `workspaces.py` | workspace registration, selection, and directory boundaries |
| `sessions.py` | session index, cwd, sidecars, queue |
| `terminal.py` / `terminal_worker.py` | `/api/terminals/*`, profiles, WebSocket tickets, and PTY workers |
| `scheduler.py` / `api_scheduler.py` | asyncio cron loop + its API |
| `push.py` / `api_push.py` | Web Push / VAPID + its API |
| `activity.py` / `activity_api.py` | activity-center state + its API |
| `transcript_index.py` | transcript index |
| `api_settings.py` | `/api/settings` — hot-rewrite `.env` + `os.environ` |
| `prompts.py` | short starter messages for self-contained UI workflows |
| `ask_user_question.py` | browser state bridge for native `AskUserQuestion` |
| `permission_request.py` | tool-permission and interactive-question round-trip |
| `settings.py` | `ROOT` / `PORT` / `HOST`, `atomic_write_text`, `env_int` |

### A3 — Per-session env override with config isolation
Third-party providers are wired by setting, per request,
`ANTHROPIC_BASE_URL` + `ANTHROPIC_API_KEY` + an **isolated** `CLAUDE_CONFIG_DIR`.
The isolated config dir is mandatory: it blocks the CLI from silently falling
back to Pro OAuth and billing third-party traffic to the user's Anthropic
account. Any new provider path MUST preserve all three.

### A4 — Client pool keyed by `(session_id, model, effort, service_tier)`
`chat.py` pools `ClaudeSDKClient` instances on exactly this key. The default LRU
cap is 3 and may be adjusted through supported runtime configuration. Each
assistant message stores its own `model` so badges stay accurate after reload.
Effort and service tier are SDK/Gateway process-launch contracts; changing
either MUST rebuild the matching session runtime. Changing the pool key is an
architecture change (it interacts with MCP spawning — see A6).

### A5 — The UI protects model continuity by default
The first real turn establishes the session model. For a non-empty session, the
frontend MUST fork when switching to an incompatible model, avoiding
cross-vendor thinking-block signatures that can produce unrecoverable `400`
errors. The management API MAY explicitly update a session model, but callers
own transcript-compatibility risk. Sessions created before a provider existed
self-heal to a configured model on first send.

### A6 — MCP: attribute-driven, gated, default-zero
- The shipped default configures **zero** MCP servers; every connector is
  opt-in. Interactive questions use the SDK-native `AskUserQuestion`; no MCP
  server may be registered solely to duplicate that built-in capability.
- Every server (preset or user-added) is stored in `mcp.json` by **attributes**
  (`transport`, `disabled`, pinned `version`), never a hard-coded catalog.
- Versions MUST be pinned. Shipped config MUST NOT use `npx -y latest` / unpinned
  `uvx`.
- The backend **readiness gate** (`_await_mcp_ready` in `chat.py`) MUST hold
  turn 1 open until every enabled external MCP server reaches a terminal state,
  to prevent mid-turn toolset changes from wedging the session. The gate MUST be
  skipped when no external MCP is enabled (`_has_enabled_external_mcp`).
- MCP is for **capabilities the built-in tools lack** (external auth'd systems).
  Anything `Read`/`Edit`/`Grep`/`Glob`/`Bash`/`WebFetch` already covers MUST NOT
  become an MCP server.

### A7 — MCP vs Skill boundary
If the hard part is *connecting & authenticating to an external system* → MCP.
If the hard part is *encoding how to do a task well* (connection is just an API
key) → Skill (a folder with `SKILL.md` + optional assets, progressive
disclosure). New extensions MUST be classified by this rule.

### A8 — Transcripts are owned by the CLI, not muselab
Conversation transcripts are owned by the Claude CLI. Claude sessions normally
use its default configuration root, while third-party-provider sessions may use
isolated configuration roots. muselab's `sessions/` holds only the sidecar
metadata layered on top (name, cwd, per-message model badge, cost, attachments).
muselab MUST NOT duplicate or fork ownership of the transcript itself.

### A9 — Native instruction ownership
muselab MUST NOT inject a global or per-session custom system prompt. Persistent
identity, response preferences, and personal context belong in the
SDK-discovered `CLAUDE.md` hierarchy. Reusable task workflows belong in Skills,
and tool-specific behavior belongs in tool descriptions and enforced permission
configuration. UI starter prompts MAY describe a self-contained task without
creating another instruction layer. Runtime modes MUST NOT inject a parallel
identity, persona, or reusable workflow through system prompts or transient
hook context; reusable workflows remain in user, workspace, plugin, generated,
or repository-extension Skills.

---

## 3. Technology Stack & Constraints

- **Language/runtime:** Python `>=3.12`. Dependency + venv management via `uv`.
- **Web:** FastAPI on `starlette>=1.3.1`, served by `uvicorn[standard]`.
- **Agent:** `claude-agent-sdk>=0.2.120,<0.3` — the only path to the model. No direct
  `anthropic` SDK / raw HTTP to model endpoints in app code.
- **Frontend:** vanilla HTML + Alpine.js v3 + CSS. No framework, no build (P2).
- **Persistence:** flat files (JSON sidecars, JSONL transcripts owned by CLI).
  No database dependency MAY be introduced for core function.
- **Adding a dependency** MUST be justified (new capability, not convenience),
  pinned with a floor version, and — if it pulls a runtime binary (`npx`/`uvx`)
  — detected by the install scripts with a warning (CONTRIBUTING checklist).
- **Provider integrations** MUST expose an Anthropic-compatible Messages
  endpoint and land as a single `CATALOG` entry in `endpoints.py`. OpenAI-only
  protocols are out (the SDK expects Anthropic-compatible).

---

## 4. Code Conventions

### Python
- PEP 8. **No formatter is enforced** — do not introduce `black`/auto-format
  that would churn the existing terse style.
- Lint: `ruff` with `select = ["E","F","W"]`, `ignore = ["E501"]`. New code MUST
  pass `ruff check backend/ tests/`. Opinionated families (B, I, N, UP) MUST NOT
  be enabled — they'd churn working code for marginal gain.
- Type hints on **public** functions; not required everywhere.
- Writes to disk MUST go through `atomic_write_text` (or an equally atomic path)
  — never a bare truncating write to a user-visible file.

### JavaScript / CSS
- No transpiler. Write the dialect Alpine v3 + modern browsers run directly.
  Match the semicolon style of neighbouring code.
- CSS: per-component sections with a comment header. Theme via CSS variables
  (`--c-*`, `--sp-*`); colors MUST NOT be hardcoded.
- Editing `app.js` / `styles.css` / `index.html` / `i18n/index.js` /
  `data/constants.js` participates in asset-version stamping — keep them in the
  candidate list when adding split-out modules so clients refetch.

### Internationalization
User-facing UI strings MUST exist in both `en` and `zh` tables in
`frontend/i18n/index.js`. Docs that ship a translation use the `_zh.md` sibling
convention.

---

## 5. Security Requirements

(Authoritative detail in `SECURITY.md`; these are the constitution-level
invariants that code reviews and specs MUST enforce.)

- **Auth on every request.** Normal APIs use `X-Auth-Token`. Browser real-time
  connections MUST first obtain a short-lived, single-use ticket through an
  authenticated request; compatibility query tokens are reserved for resource
  requests that cannot send headers. New endpoints MUST NOT bypass the
  corresponding authentication dependency.
- **Files API path traversal is closed.** Every browser file path MUST resolve
  through the safe-resolve logic in `files.py` and stay within the selected
  registered workspace. Write/upload/rename/copy MUST refuse the
  `.muselab-dustbin/` path (`_guard_not_trash`); deletes are soft (move to
  dustbin), with restore/purge as separate endpoints.
- **No secret leakage in logs.** The uvicorn access-log `token=` redaction
  filter (`_TokenFilter` in `main.py`) MUST stay in place; new log surfaces that
  could carry tokens/keys MUST be scrubbed the same way.
- **Local MCP consent.** Adding a stdio server MUST show the exact untruncated
  command, warn it runs with app privileges, flag dangerous patterns
  (`sudo`, `rm -rf`, `curl` to home/SSH paths), and require explicit approval.
- **Local HTTP servers** MUST bind `127.0.0.1`, validate `Origin`
  (DNS-rebinding), and require a token. Prefer remote HTTP connectors over `npx`
  commands (fewer supply-chain risks).
- **Permission boundaries MUST be described accurately.** Files API access is
  restricted to the selected registered workspace. Agent tools and real
  terminals run with service-user permissions and MUST NOT be described as the
  same path sandbox. Terminals MUST use same-origin checks, single-use tickets,
  and a safe environment allowlist.
- **No secrets** in code, commits, or test fixtures. `.env` and `sessions/`
  remain gitignored and MUST NOT be added.

---

## 6. Testing & Quality Gates

A change is not done until these pass (CONTRIBUTING checklist is the enforced
form):

- [ ] `uv run pytest tests/` green.
- [ ] `uv run ruff check backend/ tests/` clean (CI blocks merge on lint fail).
- [ ] `bash scripts/lint.sh` clean (encoding / BOM / class-collision).
- [ ] **Every backend change adds or updates a test** in `tests/`. Bug fixes
      MUST ship a regression test.
- [ ] Security-relevant changes (auth, path resolution, MCP) extend
      `test_security.py` / `test_files.py` / `test_mcp_gate.py` as applicable.
- [ ] Frontend visual changes documented with a before/after note in the PR
      (no visual-regression harness yet).
- [ ] No secrets; no additions to `.env` / `sessions/`.
- [ ] Tests MUST pass against a throwaway workspace — never real personal data.

---

## 7. Scope Boundaries (Non-Goals)

muselab is a **self-hosted, workspace-bound Agent workbench**, not a generic
hosted AI platform. The
following MUST be declined absent an explicit constitution amendment:

- A build step of any kind (violates P2).
- A document-RAG / crawled-content pipeline over user workspaces (violates P5).
- Generic chat-UI features unrelated to workspace execution (hosted accounts,
  social feeds, etc.).
- OpenAI-only protocol providers (violates §3 / A3).
- Presetting heavy/developer-only or write/trade-capable MCP servers by default
  (violates A6 — e.g. GitHub MCP, DB-write, brokerage).
- Any feature that requires real personal data to test (violates P6 / §6).

---

## 8. Governance

- **This document outranks code, comments, and habit.** A PR that contradicts an
  invariant here is wrong until either the PR changes or this document is amended
  in the same PR with reviewer sign-off.
- **Specs derive from this constitution.** Each feature/refactor SHOULD carry a
  short spec (what changes, boundaries, acceptance criteria in EARS form: "When
  <trigger>, the system shall <behavior>"). Specs MUST NOT restate invariants —
  they reference them.
- **Amending the constitution:** bump the version (semver — MAJOR for removing/
  redefining an invariant, MINOR for adding one, PATCH for clarifications),
  update *Last amended*, and note the rationale. Architecture changes (§2) MUST
  amend before merge, not after.
- **Drift check:** when reverse-engineered understanding contradicts this
  document, treat the contradiction as a finding — fix the code, or amend the
  doc with rationale. Never silently let them diverge.

---

## 9. Amendment history

- **6.0.0 (2026-08-25):** Replaced the always-on in-process question MCP with
  the SDK-native `AskUserQuestion` browser bridge after upgrading to Agent SDK
  0.2.144 / Claude Code CLI 2.1.239 and verifying native answer delivery across
  default, plan, acceptEdits, and bypass permission modes. The shipped MCP
  topology is now truly default-zero; connectors remain opt-in.
- **5.0.0 (2026-08-15):** Removed the bundled Skill distribution and the
  runtime-mode Skill activation exception from A9. Skills remain an SDK-native
  extension mechanism for user, workspace, plugin, reviewed generated, and
  repository-extension workflows; runtime modes retain only their transport
  and resource-bound contracts.
- **4.0.0 (2026-07-31):** Expanded A4's exact client-pool key with
  `service_tier` because Fast is fixed in the Gateway header contract at SDK
  client launch; amended A9 so an explicitly selected runtime mode may activate
  a bundled Skill through transient `UserPromptSubmit.additionalContext`, while
  preserving the canonical prompt/transcript and keeping the workflow in
  `SKILL.md` rather than a custom system prompt.
- **3.0.0 (2026-07-31):** Repositioned muselab from its former personal-domain
  framing to a workspace-bound Agent workbench; removed domain-specific
  assumptions while retaining local-file, self-hosting, and safety invariants.
- **2.0.0 (2026-07-23):** Replaced the single-archive model with registered
  workspaces; added the real terminal, short-lived real-time tickets, and
  Activity modules; defined model continuity as frontend fork-by-default with
  an explicit management-API override; and fixed native instruction ownership
  across CLAUDE.md, Skills, and tool permissions.

---

*A spec tells the AI what to build next. This constitution tells it what must
stay true no matter what it builds.*