<!-- source: openspec/current/muse.md -->

---
title: Muse
status: stable
owner: pavel
audience: [pavel, ai, engineer]
tldr: The AI substrate — one personality, four modes (Canvas, Scout, Architect, Builder). Architect and Builder modes active in v1.
---
# Muse

> The AI substrate. One personality, multiple modes.

---

## Status

**Architect mode active in v1; Builder mode active in v1.2** (RAP-48 Waves 1–3 shipped 2026-05-04 → 2026-05-05; `status` and `cancel` shipping under `openspec/archive/2026-05-09-sync-forge-builder-spec/`). Scout and Canvas remain deferred per `ROADMAP.md` layer ordering — see § Why other modes are deferred below. The four-mode vision is intact; layers activate in order.

The "Digital Pavel" Telegram bot at `@rapoport_studio_bot` uses Claude directly via `/api/studio/command` — that is **not** Muse. It's a focused standalone agent for Pavel's personal Linear journal, with its own slash-command surface (`/note`, `/task`, `/done`).

The **per-canvas Telegram surface** at `@rs_canvas_bot` (route `/api/studio/telegram/webhook`) IS Muse — running headlessly via `runMuseHeadless`, server-loaded contexts, propose-only mutations from TG, owner-gated execution. See `telegram.md` for the canvas-surface capability (applied 2026-05-09 from `add-tg-canvas-surface`).

---

## Vision

Muse is one AI personality with four modes. Each mode is a different system prompt, tool set, and output shape — but the personality, voice, and core values are consistent.

| Mode | Surface | Purpose |
|---|---|---|
| **Canvas** | Public site | Talks to clients during intake; extracts domain entities live |
| **Scout** | Studio CLI | Scans existing codebases; extracts entities and specs |
| **Architect** | Studio + Forge | Generates change proposals (proposal/design/tasks) from Linear issues |
| **Builder** | Forge CLI | Executes approved plans; writes code; commits; opens PRs |

Outside the studio: clients interact with Muse (Canvas mode).
Inside the studio: Pavel works with Muse (Scout / Architect / Builder).

---

## Architect mode (v1)

> Pavel's authoring surface for OpenSpec change proposals. Internal-only, single-user, three-column canvas UI in `apps/studio`. The first activated Muse mode.

### Status and scope

Architect mode is the v1 layer of the platform per `ROADMAP.md`. It ships an authoring surface inside `apps/studio` where Pavel converses with Muse to draft `proposal.md` / `design.md` / `tasks.md` for an OpenSpec change, then promotes the triplet to `openspec/changes/<slug>/` on disk via a single action. v1 ships:

- One mode at v1 launch (Architect). Builder shipped in v1.2 as a Forge engine extension (RAP-48 — see `forge.md § Builder mode`); Scout deferred to v1.3; Canvas to v2 per `ROADMAP.md` layer ordering.
- One user (Pavel). No invitations, no multi-tenancy, no collaborator access. The whole stack is single-user by design.
- Four routes at the studio subdomain. See `studio-workspace/spec.md § Surface area` for the table.
- Text input only. Voice and mobile arrive in later layers.

The agent definition, system prompt, and conversation runner live in `packages/muse/`. The UI surface lives in `apps/studio`. Persistence lives in Supabase. See § Package home and system prompt for the package layout.

### Surface

Architect mode surfaces in `apps/studio` at the auth-walled subdomain `app.rapoport.studio`. Two routes are load-bearing for the mode:

| Route | Purpose |
|---|---|
| `/canvases` | Canvas list — recent canvases, filters, "New canvas" action |
| `/spec/[id]` | Architect mode chat + proposal/design/tasks artifact panel |

The `/` (dashboard) and `/settings` routes are Studio-shell concerns that frame Architect mode but don't host the conversation directly. See `studio-workspace/spec.md § Surface area` for the full v1 four-route table and the host-vs-route framing.

Input is a text chat textarea. Voice input, mobile input, and rich-media uploads are explicitly deferred (see § What v1 explicitly defers). Pavel's flow is keyboard-first: type into the composer, send, read the response, iterate.

### UI surface

The `/spec/[id]` route renders a three-column layout. This section describes the structural contract — pixel-precise tokens, motion, and component implementations live in `ui.md` and `design-system.md` once those specs add the studio variant.

```
┌─────┬───────────────────────────┬───────────────────────────┐
│     │                           │                           │
│  R  │       Chat column         │    Artifact panel         │
│  a  │                           │                           │
│  i  │  - title bar              │  [proposal] [design] [tasks]
│  l  │  - message stream         │                           │
│     │  - composer               │   <markdown content>      │
│     │  - smart-pill quick acts  │                           │
│     │                           │   ──────────────────────  │
│     │                           │   Promote to /openspec/   │
└─────┴───────────────────────────┴───────────────────────────┘
```

**Mode rail** (left, 56px wide). Brand mark `RS` at the top, then three navigation icons mirroring the v1 studio routes:

| Icon | Route | Purpose |
|---|---|---|
| **D** | `/` | Dashboard |
| **C** | `/canvases` | Canvas list |
| **S** | `/settings` | Profile, API key, appearance |

The detail route `/spec/[id]` is reached *through* the Canvases list and does not get its own rail icon. The rail is a compact studio sidebar — not a Muse-modes display. Builder, Scout, and Canvas-mode icons do not appear in v1; the Architect mode itself is implicit (always active when on a `/spec/[id]` route).

**Middle column** (chat). Top-to-bottom:

- **Title bar** — canvas slug + status (e.g., "draft" / "promoted").
- **Message stream** — turn-by-turn conversation history. User messages right-aligned, agent messages left-aligned. Markdown renders inline. Persisted to `canvas_sessions` (see § Persistence).
- **Composer** — multi-line textarea + send button. Enter sends, Shift-Enter inserts newline.
- **Smart-pill quick actions** — three pills below the composer, in order
  (D-SP-4 in `consolidate-spec-page-surface`):
  - **"Templates"** — opens the studio-templates picker over the composer.
    Source contract: `add-muse-knowledge-system` § Studio templates tier.
  - **"Suggest a question"** — agent proposes the next question to ask.
    Hides once a complete artifact triplet is staged (the agent's job at
    that point is to answer iteration questions, not generate them).
  - **"Pin the brief"** — pins the canvas's intake brief (when present)
    as a sticky reference at the top of the chat surface. Source contract:
    `add-onboarding-brief` § Brief reference surface.

  > Historical: the `Promote to spec` pill was the second of two pills in
  > this list until 2026-05-16. It moved into the right rail's **Change**
  > block per D-CCP-3 (`reframe-canvas-change-pipeline`). The composer no
  > longer carries Promote — discoverability is preserved through the
  > Change block's `Promote` button. Page-level composition for the
  > composer lives at `studio-workspace/spec.md § Spec page surface`.

**Right column** (artifact panel). Top-to-bottom:

- **Three tabs** — `proposal.md` / `design.md` / `tasks.md`. Tab content renders the latest staged version of each artifact as read-only markdown. Editing happens through chat ("rewrite the design's section 3 to..."), not through direct text-area edits in this panel. The constraint keeps a single source of truth (the agent's last response) and avoids merge ambiguity in v1.
- **Empty state** per tab when the artifact has not yet been generated for the session.
- **Primary CTA** — "Promote to /openspec/changes" pinned to the panel bottom. Identical action to the smart-pill version in the chat column; same enabled/disabled rules. Two entry points for the same action is intentional — the pill is for keyboard flow, the panel CTA is for visual scan.

The three columns are co-visible at desktop widths (≥ 1024px). Below that, the panel collapses to a tab-bar above the chat; mode-rail collapses to a top-nav. Exact responsive thresholds belong to `ui.md`.

No motion contract is fixed in this spec — entry, transitions, and hover states inherit Studio defaults from `design-system.md § Surfaces › Studio (dark variant)` once that section deepens. The reduced-motion fallback per `design-system.md` applies.

### Conversation lifecycle

A canvas moves through three states: `empty`, `drafting`, `promoted`. The lifecycle:

```
[empty]                         [drafting]                       [promoted]
   │                               │                                 │
   ├─► User types prompt           ├─► User iterates                  ├─► (terminal)
   │   or pastes Linear issue body │   ("rewrite design § 3...")     │
   │                               │                                 │
   ├─► Agent asks clarifying Q's   ├─► Agent updates artifacts       │
   │                               │                                 │
   ├─► Agent emits initial         ├─► User clicks Promote            │
   │   proposal/design/tasks       │   → triplet writes to disk       │
   │   draft                       │                                 │
   │                               │                                 │
   └─► canvas state: drafting      └─► canvas state: promoted ───────┘
```

**Empty** — the canvas exists with metadata (slug, owner=Pavel, created_at) but holds no chat or artifacts. When the canvas's project is linked to a GitHub repo, the Phase 6 init turn (RAP-837) fires automatically on canvas open: Architect surveys the repo and introduces itself, transitioning the canvas through an `introspecting` intermediate state before the first user-initiated turn. Transitions to `drafting` on the first agent reply that emits any of the three artifacts.

**Drafting** — the canvas has chat history and at least one staged artifact. The agent's job is to (a) ask clarifying questions to complete the triplet, (b) iterate on existing artifacts when Pavel requests changes ("expand task 4"), (c) maintain coherence across the three documents (a change in `design.md § 3` should propagate to `tasks.md` if relevant). Multiple drafting cycles are normal — most canvases will iterate 5–20 turns before promotion.

**Promoted** — Pavel has clicked "Promote to spec." The triplet has been written to `openspec/changes/<slug>/` in the working tree. The canvas record is preserved (chat history + last-staged artifacts) for reference, but further changes happen via git workflow, not by re-prompting the canvas. Re-promoting an already-promoted canvas is not supported in v1 — see § Promote to spec for the one-way constraint and § Open questions for v1.1+ for re-prompt as future work.

The agent's system prompt and conversation runner enforce these states by emitting structured tool calls (markdown artifacts) only during `drafting`, and by refusing edit instructions on a `promoted` canvas. Implementation details live in the v1 Linear epic.

#### Convert to project (RAP-69, supersedes Promote for new canvases)

The `complete-canvas-file-lifecycle` epic introduced an orthogonal `canvas_sessions.mode` axis (`'draft' | 'project' | 'legacy_promoted'`) that supersedes the v1 Promote flow for any canvas created from RAP-44 onward:

- **`draft`** — new canvases start here. Behavior matches the old v1 `drafting` state; the file-tree pane (RAP-74) shows pending `canvas_files` rows; a `[Convert to project]` button sits in the canvas header strip.
- **`project`** — `convertToProjectAction` (RAP-73) creates a real GitHub branch `canvas/<slug>` under `projects.github_repo` (or `STUDIO_DEFAULT_REPO_FULL_NAME`), commits the current `canvas_files` rows as the initial tree, and flips `canvas_sessions.mode='project'` + populates `github_branch`, `base_sha_at_conversion`, `converted_to_project_at`. Subsequent edits flow through `saveFileAction` / `saveAllPendingAction` (real GitHub commits with optimistic concurrency on `base_sha`) and `openPullRequestAction` (idempotent on 422 "already exists"). The canvas remains live and editable — every save is a real commit on the canvas branch.
- **`legacy_promoted`** — the seven canvases that pre-date RAP-44. RAP-44 backfill marked every migrated `canvas_files` row `is_pending=true`, but the runtime renders these as saved (read-only) so the legacy "Promoted" UX is preserved without a separate code path.

The "Promote to /openspec/changes" CTA is **deprecated for new canvases** — Convert-to-project replaces it. Promote stays in the codebase for back-compat with the seven `legacy_promoted` canvases and is functionally inert for `mode='project'` canvases. New work should never trigger Promote.

### Inputs and outputs

**Inputs** to an Architect mode session:

| Input | Source | Purpose |
|---|---|---|
| Chat messages | Composer textarea | Drives the conversation; primary input |
| Pasted Linear issue body | User pastes into composer | Gives the agent the requested-change context |
| Spec path references | Mentioned by user (e.g., "see `forge.md § Architecture`") | Agent reads the file at that path to ground the proposal |
| Prior canvas content | Loaded from `canvas_sessions` on `/spec/[id]` mount | Resume an in-progress drafting session |

**Outputs** from an Architect mode session:

| Output | Where it lands | Format |
|---|---|---|
| Chat response | Message stream + persisted to `canvas_sessions` | Markdown |
| `proposal.md` artifact | Right-panel tab + persisted to `canvas_sessions` | Markdown, OpenSpec-shaped |
| `design.md` artifact | Right-panel tab + persisted to `canvas_sessions` | Markdown, OpenSpec-shaped |
| `tasks.md` artifact | Right-panel tab + persisted to `canvas_sessions` | Markdown, OpenSpec-shaped |
| `verification.md` artifact | Written by `muse draft <RAP-NN>` CLI (fourth artefact) | Markdown, four sections (see § muse draft verification.md below) |
| Promoted change folder | `openspec/changes/<slug>/{proposal,design,tasks}.md` on disk | Markdown files, written via Promote |

Linear issue bodies enter v1 via manual paste — the agent does not call the Linear API directly. GitHub PR creation is also out of scope for v1; Pavel commits the promoted change folder and opens the PR through the normal git workflow. See § Open questions for v1.1+ for the Linear-API-integration question.

### Staged artifacts

The three artifacts (`proposal.md`, `design.md`, `tasks.md`) are stored as named markdown blobs on the canvas record. Each is **versioned by chat turn** — every agent response that emits or updates an artifact creates a new version, persisted alongside the chat message that produced it.

In v1, the artifact panel renders only the **latest** version of each artifact. Earlier versions are persisted but not exposed via UI. Inline diff between versions is explicitly out of scope for v1 — manual re-read is the review mechanism. The decision keeps the panel UI minimal and avoids designing a diff component before the editing flow proves out. A future version-browser is captured in § Open questions for v1.1+.

The OpenSpec shape conventions (proposal sections: Why / What changes / Impact / Out of scope; design as numbered deltas; tasks as numbered checklist) come from the system prompt and the structural references in `forge.md § openspec/ relationship` and `openspec/archive/` exemplars (e.g., `2026-05-01-refactor-forge-consumer-registry/`).

### Promote to spec

The "Promote to /openspec/changes" action is the load-bearing v1 boundary between staged drafts and canonical OpenSpec changes.

**Trigger** — Pavel clicks either the chat-column smart pill or the panel CTA. Both routes call the same backend handler.

**Action** — the handler writes the latest staged version of each artifact to:

```
openspec/changes/<slug>/proposal.md
openspec/changes/<slug>/design.md
openspec/changes/<slug>/tasks.md
```

The `<slug>` is derived from the canvas record (the canvas's own slug is set at creation time and is human-editable in v1.1+ but immutable at promotion time in v1). The write happens via filesystem API in the studio app's runtime — the studio app and the working tree share a filesystem in the v1 single-user, single-host deployment. Multi-host deployments (where studio runs in a Worker but git operates on a developer machine) are explicitly deferred — see § What v1 explicitly defers and § Open questions for v1.1+.

**Post-promote state** — the canvas record's `status` flips to `promoted`. The canvas remains visible at `/spec/[id]` for reference; chat history and artifacts persist. Further edits to the change happen through git (Pavel commits the new files, opens a PR, iterates on review feedback in normal git workflow). Re-promoting an already-promoted canvas is not supported in v1 — the action is one-way.

**Failure modes** — if the target directory `openspec/changes/<slug>/` already exists, the handler returns an error and the canvas stays in `drafting`. The agent is instructed via system prompt to suggest a different slug if conflict is reported. If a write fails partway (e.g., disk full), the handler aborts and surfaces the error; the user retries. There is no transactional rollback — partial writes are an operational concern, not a v1 product concern.

**Why one-way and disk-write rather than ephemeral preview** — the OpenSpec workflow already has a strong "change folder on disk → PR → review → archive" loop (see `forge.md § openspec/ relationship` and the archive convention in `CLAUDE.md § Hard rules #7`). v1 plugs into that loop at the boundary where a draft becomes a real change folder. Anything else (in-canvas review, multi-stage approval, signoff workflow) is added work for problems v1 doesn't have yet.

### Grounding

Architect grounds every draft in current repo + spec state via four read-only tools: `read_openspec_index`, `read_openspec`, `list_repo_tree`, `read_repo_file`. The repo is the GitHub repository bound to the canvas's project via `project_connections.kind = 'github'`; the OpenSpec content is served from the bundled module shipped with `apps/studio`. Tools are read-only by contract — repo writes go through Forge, never through Architect. When the project is not linked, repo tools return a structured `not_linked` error and the agent falls back to asking the user to paste the relevant snippet.

The agent's prompt requires it to identify the spec file it will ground against in the opening turn, and to cite `file:line` anchors when asserting current state. Read budget: 8 tool calls per turn.

### Interview discipline

Architect interviews follow three discipline rules:

- **Recommended answer.** Every clarifying question includes the agent's recommended answer and a one-sentence reason grounded in spec or code. Never an open-ended ask. Inspired by Matt Pocock's `grill-with-docs` skill (`mattpocock/skills`); the underlying principle is that the agent has more context-gathering cycles than the user and should think first, not punt cognitive load.
- **One question at a time.** Sequential, not batch — wait for the answer before asking the next. Tightly coupled question pairs may be posed together as a single recommended pair.
- **Codebase trumps question.** If the answer is readable from the repo or `openspec/current/`, the Architect reads instead of asks. Relies on the four read-tools introduced by the `add-muse-architect-repo-grounding` change (`read_openspec_index`, `read_openspec`, `list_repo_tree`, `read_repo_file`).

The discipline closes the most common failure mode observed in real canvas sessions: open-ended questions that push cognitive load onto Pavel instead of accelerating the turn. The system prompt at `packages/muse/src/modes/architect/prompts/system.ts § INTERVIEW DISCIPLINE` is the operative location; this subsection is the capability-spec mirror.

### Evaluation

Architect output quality is graded against archived ground-truth change triplets from `openspec/archive/*/`. The harness lives at `tools/muse-evals/`; the DSPy compile tooling at `tools/muse-evals/dspy-compile/`.

**Rubric (five dimensions, each 0–5; ship-quality ≥ 20/25):**

1. **Grounding** — every concrete `file:line` reference is grep-able against the repo snapshot at archive time; cited capability specs exist in `openspec/current/`. Score 5: all references are verifiable. Score 0: hallucinated paths or invented spec sections.
2. **Boundary correctness** — all proposed touches respect the package boundaries in `CLAUDE.md § Hard rules`. Score 5: no disallowed imports proposed. Score 0: any package boundary violation.
3. **Completeness** — all `Q-*` open questions are either answered or explicitly flagged for Pavel; all design.md section headers from the ground truth are represented. Score 5: structurally complete. Score 0: missing critical sections or silently resolved open questions.
4. **Stack fidelity** — the candidate names the studio's actual stack (Next.js 16 App Router, Supabase, OpenNext on Cloudflare Workers) and not a hallucinated alternative. Score 5: correct throughout. Score 0: any wrong-framework instance (see `muse_architect_astro_hallucination.md`).
5. **Acceptance clarity** — each phase in `tasks.md` ends with a falsifiable checkpoint. Score 5: every phase has a verifiable exit criterion. Score 0: vague "implement X" tasks with no exit criterion.

**Dataset source:** `openspec/archive/*/` filtered to entries that (a) have all three artifacts (`proposal.md`, `design.md`, `tasks.md`), (b) carry a recoverable Linear issue ID, and (c) are not `superseded`. Dataset is rebuilt by `pnpm muse:eval:build-dataset` and committed to `tools/muse-evals/fixtures/architect-cases.json`.

**CI regression gate:** `.github/workflows/muse-evals.yml` fires on PRs touching `packages/muse/src/modes/architect/prompts/**`, `packages/muse/src/modes/architect/runner.ts`, `packages/muse/src/modes/architect/tools.ts`, or `tools/muse-evals/**`. The job runs `pnpm muse:eval --against=current-prompt --gate --threshold=5` against the held-out test split (25 most-recent cases); it fails if the aggregate score drops more than 5 points below the committed baseline at `tools/muse-evals/reports/baseline.json`.

**Judge model and escalation:** Default judge is Sonnet 4.6 (cheap enough for full-corpus runs at < $1/run). One-time validation: Pavel labels 15 archived cases as `[ship-quality | needs-rewrite]`; the judge must agree on ≥ 12/15 (≥ 80%). If agreement falls below 80%, escalate to Opus 4.7 and re-validate before any further harness use.

**Compiled prompt rule:** The compiled system-prompt artifact (`packages/muse/src/modes/architect/prompts/system.compiled.json`) is produced exclusively by `tools/muse-evals/dspy-compile/` running MIPRO over the training split. The file is **not hand-edited**. Direct edits to `packages/muse/src/modes/architect/prompts/system.ts` are limited to the loader logic and the fallback `SYSTEM_PROMPT` constant — never to the compiled artifact's content.

**Baseline-update procedure:** When the compiled prompt improves over the current baseline, the update lands in a stand-alone PR titled `muse-evals: update baseline (<justification>)`. The PR runs `pnpm muse:eval:update-baseline` against the test split, commits the new `tools/muse-evals/reports/baseline.json`, and documents the delta.

### Persistence

Architect mode persists session state to a Supabase table named `canvas_sessions`. The table holds one row per canvas; each row stores:

- Canvas metadata (slug, status, created/updated timestamps, owner = Pavel).
- Chat history (turn-by-turn user/agent messages with timestamps).
- Staged artifact triplet (latest version of each, plus version history per § Staged artifacts).

Exact column shape, indexing strategy, and RLS policy are owned by the v1 implementation Linear epic and are not fixed in this spec. The implementation epic lands the schema migration and runtime queries.

The table lives in the studio's Supabase project (`nifagnmgwoqkplegsicy`, eu-central-2 — see `db/README.md`). Authentication is the studio's standard magic-link session — see `auth.md § Authentication flow`. RLS reduces to "the requesting user matches the canvas owner" since v1 is Pavel-only; the policy is a one-line check that will need to expand when multi-user lands in v2+.

There is no Redis, no in-memory store, no local file cache for canvas state in v1. Every read goes to Supabase. Performance budget for canvas load is governed by the studio app's general latency target — exact numbers are an implementation epic concern.

### Package home and system prompt

Architect mode lives in `packages/muse/`. The package layout as it ships today:

```
packages/muse/
├── src/
│   ├── index.ts                  # barrel — root subpath export
│   ├── mirror/                   # mirror schema + loader + authoring tools (RAP-32 / RAP-33 / RAP-34)
│   ├── modes/
│   │   └── architect/            # system prompt + conversation runner + tool definitions (v1)
│   ├── orchestration/            # runMuseWithTools generator + ToolUseLoopContext factory (tool dispatch, cost accounting)
│   ├── personas/                 # viewer / persona derivation + role helpers (architect-only today; builder/scout add later)
│   ├── shared/                   # cross-mode utilities
│   ├── templates/                # reusable scaffolds (proposal/design/tasks shapes; `./templates` subpath)
│   ├── tools/                    # tool definitions + execution adapters consumed by runMuseWithTools (mirror-authoring + research tools)
│   └── __tests__/                # vitest suites
└── package.json                  # 4 subpath exports: ., ./modes/architect, ./mirror, ./templates
```

Scout and (public) Canvas modes are **not** in `src/modes/` today — they're placeholders to be populated by their respective layer epics (v1.3 / v2). The directories themselves don't exist on disk yet; they'll land as new subdirectories under `modes/` when their owning sprints kick off — see § Relationship to Forge for the v1.2 → v2 progression. Builder mode shipped via Forge engine extension rather than as a `modes/builder/` subdirectory: its system-prompt template lives at `packages/muse/src/builder-prompt.ts` and is consumed by `forge build` via Anthropic Agent SDK preset-append (`forge.md § Builder mode › Builder prompt`).

The Architect mode prompt is sourced from existing material in `packages/forge/src/commands/audit/prompts/architect.md` and the spec-fix prompt at `packages/forge/src/commands/spec/prompts/fix-proposal.md`. The migration is a content move — the conceptual personality stays the same; the runner code (conversation orchestration, tool dispatch, message persistence) is new.

Forge keeps its own Architect-as-audit-agent role (the agent that reviews PRs in the audit pipeline). The `packages/muse/` Architect mode is a different surface — it authors changes from scratch, where Forge's Architect reviews diffs against existing code. Same conceptual personality, different system prompts, different invocation patterns. See `forge.md § Architecture` for the audit-pipeline architecture and § Relationship to Forge below for the v1/v1.2/v2 progression.

### Internal-only boundary

Architect mode in v1 is single-user — Pavel only. Several constraints follow from this:

- **No anonymous surface.** Every route is auth-walled (see `studio-workspace/spec.md § Surface area`). No public Architect-mode chat exists in v1; the public Canvas-mode chat is v2 work per `ROADMAP.md § MVP v2 — Canvas mode (public)`.
- **No multi-tenancy.** The `canvas_sessions` table is keyed by user_id but the policy reduces to "user matches" since there's only one user. Multi-user expansion is v2+ and will require schema and RLS work — see § Open questions for v1.1+.
- **No rate limiting beyond cost guard.** Pavel is a trusted user; the only throttle is cost-based (see § Cost guard).
- **Conversational language follows `user_settings.appearance.locale`.** Architect and Maestro modes reply in the user's chosen language (`en` / `ru` / `ro`); `auto` resolves against the request's `Accept-Language` header with `en` as the fallback. Persisted artifacts (`proposal.md` / `design.md` / `tasks.md` / `openspec/brands/<slug>/identity.md`) and tool-call inputs remain English regardless of conversation language — the repo is English. See § Architect mode › Conversation language for the runtime contract.
- **No invitations, sharing, or guest access.** The canvas is private to its owner and remains so until multi-user lands.
- **Cookie domain shared with marketing.** Auth uses the same `.rapoport.studio` cookie domain pattern as the marketing apex — see `auth.md § Cookie domain handling`. This is a present constraint (not a deferred decision) because both subdomains share the auth package.

### Untrusted input boundary

Untrusted text inputs (Linear bodies, PR review comments, PR-branch repo content, Telegram messages, downstream API responses) are wrapped in `<untrusted_input source="...">` sentinel tags at prompt-build time. The agent's system prompt forbids following instructions inside the block. Convention is documented in [`openspec/_methodology/security/untrusted-input-convention.md`](../_methodology/security/untrusted-input-convention.md).

### Conversation language

Architect and Maestro modes honor the canvas owner's `user_settings.appearance.locale` (RAP-419, applied 2026-05-11). The runtime contract:

- **Source of truth:** `user_settings.appearance.locale` for the canvas owner. `en` / `ru` / `ro` win unconditionally; `auto` reads the request's `Accept-Language` header and matches the highest-quality tag against `{en, ru, ro}`, falling back to `en` when no header is present or no tag matches.
- **Resolution layer:** `resolveChatLocale(settingsLocale, acceptLanguageHeader)` in `apps/studio/src/lib/user-settings.ts`. Pure function; the turn route owns the call.
- **Plumbing:** turn route → `RunMuseTurnArgs.userLocale` (required) → `runMuseWithTools({ userLocale })` → `buildSystemPrompt({ userLocale })`. Maestro mode uses the same path via `runMaestro`. A forgotten plumbing site is a type error at the studio boundary, not an English default.
- **Directive placement:** `# Response language` block sits in `buildSystemPrompt` after persona/mode framing and before project/methodology / instruction blocks. The model reads it as part of the personality framing, not as overridable turn instruction.
- **Carve-outs (always English regardless of conversation language):** code blocks, file paths, identifiers, CLI commands, URLs, OpenSpec section refs, Linear keys, GitHub PR titles, and markdown emitted via `update_proposal` / `update_design` / `update_tasks` / `write_mirror_field`. Persisted artifacts are repo content.
- **Mode coverage:** Architect + Maestro (both go through `buildSystemPrompt`). Builder mode (Forge CLI) is unaffected — Builder writes code; English by repo convention. Telegram canvas surface (`@rs_canvas_bot`, `runMuseHeadless`) inherits the same plumbing in a follow-up; today it runs without `userLocale` and replies in English.
- **Enforcement:** prompt-only. No runtime character-set validation on tool inputs — false positives (Russian proper nouns, Romanian diacritics) outweigh the cost of an occasional non-English artifact slipping into a PR diff where Pavel catches it at review.

### Cost guard

Architect mode token spend is governed by the same patterns Forge uses. Concrete primitives:

- **Per-call instrumentation** — every Anthropic API call records input tokens, output tokens, model, and per-call cost. See `forge.md § Cost tracking` for the canonical pricing table and the `MODELS.opus` / `MODELS.sonnet` constants.
- **Pre-flight estimation** — the system can project cost for a chat turn before sending the request. The pattern mirrors `forge.md § Cost tracking` § "Pre-flight `--estimate` mode" (the audit/spec/review/estimate pre-flight that prints projected cost without making API calls).
- **Confirmation threshold** — if a projected turn cost exceeds a configured threshold, the surface prompts before sending. The pattern mirrors `forge.md § Cost guard` (the orchestrated review's "$5 confirmation prompt" at `forge.md § Review orchestration › Cost guard`).

Concrete dollar thresholds, model defaults, and the exact UX of the confirmation prompt are not fixed in this spec — they belong to the v1 implementation Linear epic. The principle is consistency with Forge: a single source of truth for pricing (Forge's table) and a single threshold philosophy (warn-on-large, don't-block-on-small).

### What v1 explicitly defers

| Item | Activates in |
|---|---|
| Scout mode | v1.3 — implementation design deferred per `ROADMAP.md` |
| Canvas mode | v2 — see `ROADMAP.md § MVP v2 — Canvas mode (public)` |
| Inline diff between artifact versions | v1.1+ (manual re-read in v1) |
| Multi-user, invitations, sharing, RLS expansion | v2+ |
| Real-time collaborative editing | v2+ |
| Linear API integration in Muse (auto-fetch issue body) | TBD per `ROADMAP.md`; manual paste in v1 |
| GitHub API integration (auto-PR after Promote) | v1.1+ at earliest |
| Voice input | not roadmapped |
| Mobile input / responsive optimization for narrow viewports | v1.1+ |
| Pricing, billing, subscriptions | v2+ |

### Open questions for v1.1+

- **Linear API integration shape.** Auto-fetch issue body when the user pastes a Linear URL? Auto-update issue with the promoted PR link? Or stay manual indefinitely? `ROADMAP.md` flags this as TBD and notes uncertainty whether the integration is ever needed. The first v1 dogfooding cycle will tell.
- **Auto-open PR after Promote.** Today Promote writes to disk; Pavel commits and opens the PR manually. v1.1+ could chain a `gh pr create` step (or use the GitHub API) to take that final manual step out. The decision depends on whether the friction is real in practice.
- **Canvas archive / delete semantics.** v1 has no archive button. As `canvas_sessions` accumulates, the list at `/canvases` will grow indefinitely. v1.1+ adds archive (hide from main list, retain) and possibly hard-delete (with confirmation). Soft-delete vs hard-delete is the question.
- **Post-promotion artifact viewing.** A promoted canvas's artifact panel shows the last-staged versions but those may diverge from the on-disk files once Pavel has committed and iterated in git. Should the promoted canvas re-read the files from disk on mount? Or stay frozen at promote time? Frozen is simpler; re-read is more honest.
- **Mode rail growth when Scout activates.** The v1 rail has D / C / S. Builder mode shipped via the Forge CLI rather than as a Workspace rail icon (RAP-48 ships no Workspace surface; `forge build` is the canonical entry point — see `forge.md § Builder mode`). Whether v1.3 adds an S-rail rearrangement when Scout activates remains open.
- **Re-prompt a promoted canvas.** v1 makes Promote one-way. Should v1.1+ allow "fork from promoted" to start a new canvas with the promoted artifacts as a starting point? Useful for amendments; complicates the "promoted is terminal" mental model.

### Untrusted input boundary

Untrusted text inputs (Linear bodies, PR review comments, PR-branch repo content, Telegram messages, downstream API responses) are wrapped in `<untrusted_input source="...">` sentinel tags at prompt-build time. The agent's system prompt forbids following instructions inside the block. Convention is documented in [`openspec/_methodology/security/untrusted-input-convention.md`](../_methodology/security/untrusted-input-convention.md).

---

## Research tools + persona system (applied 2026-05-03 from change `add-muse-research-tools-with-personas`)

### Tool architecture

Muse engine gains agentic capability via Anthropic's `tool_use` API:

- Tools registered in `@rapoport-studio/muse` package (path: `packages/muse/`, **not** `packages/muse-engine` as initially proposed — convention deviation).
- Tool execution loop with two hard limits:
  - Max 5 tool calls per assistant turn.
  - Max 10 loop iterations (guards against pathological one-call-per-iteration spinning).
- Cost guard: $50 per canvas budget (5000 cents) — accumulated in `canvas_sessions.cost_cents`. **Updated 2026-05-04 (column renamed, cap raised 50 → 500). Updated 2026-05-16 (cap raised 500 → 5000) — see § Token cost credited to canvas budget and § Canvas cost cap raised to $50 below.**
- Streaming-aware: `runMuseWithTools` async generator yields both Anthropic stream events and custom `ToolStatusEvent` to the client.

### Foundation tools (Tier 1)

| Tool | Description | Permission | Cost |
|---|---|---|---|
| `web_search` | Anthropic `web_search_20250305` server tool wrapper | all roles | $0.01 |
| `fetch_url` | HTTPS fetch + HTML→markdown + 8K truncation | all roles | $0.01 |
| `read_openspec` | Reads `openspec/current/*.md`. Live `node:fs/promises` on Node; build-time bundle on Workers. | owner + collaborator | free |
| `read_archive` | Reads `openspec/archive/*`. Live `node:fs/promises` on Node; build-time bundle on Workers. | owner + collaborator | free |

**Runtime selection.** `read_openspec` and `read_archive` use a three-branch selector. (1) Build-time bundle via `ToolExecutionContext.openspecBundle` — embedded by `tools/build-openspec-bundle.mjs` and supplied by the studio runtime in Cloudflare Workers. Preferred when present; the bundle being supplied is treated as an explicit caller signal. (2) Live filesystem via dynamic `node:fs/promises` — used when no bundle is supplied (Forge CLI, local Node tests). Reads against `MUSE_OPENSPEC_PATH ?? "openspec"`. (3) Neither — returns `{ ok: false, error: 'not_available_in_runtime' }` (defensive; Node and Workers cover both intended runtimes). The bundle keys are openspec-relative POSIX paths (e.g. `current/auth.md`, `archive/<change>/proposal.md`); both branches return identical `content` and `metadata` shapes so callers cannot tell which path produced their result. The `MUSE_OPENSPEC_PATH` env var continues to override the fs base path for tests and non-default Node deployments; it has no effect on the Workers bundle path. Bundle freshness is tied to deploy cadence — every merge to `main` regenerates the bundle as part of the studio build. The bundle-first ordering exists because the Workers `nodejs_compat` flag makes `node:fs/promises` importable but the openspec/ tree is not in the Worker bundle — an fs-first ordering would always throw on Workers and never reach the bundle branch.

### Specialized tools (Tier 2)

| Tool | Description | Permission | Cost |
|---|---|---|---|
| `domain_research` | Multi-step composition: `web_search` + `fetch_url` ×2-3 + LLM extraction → `DomainPrimer` JSON | owner + collaborator | sub-calls accumulate (~$0.05 actual) |

**Cost-tracking limitation.** `domain_research.execute` calls sub-tools directly, not through `executeToolWithGuards`. Sub-call costs are not credited to the canvas budget. Conservative under-counting (no surprise overruns) is accepted for v1. Tracked for future change `add-muse-tier2-budget-guards`.

### Persona system

Five personas describe research style and output preferences (orthogonal to role):

| Persona | Tool category preference | Output emphasis |
|---|---|---|
| `architect` | All categories | Phase-organized full report |
| `domain` | foundation, domain, pain, competitive | Vocabulary + workflows + scenarios |
| `tech` | foundation, technical, validation | Architecture + risks + patterns |
| `business` | foundation, identity, competitive, validation | Market map + projections |
| `ops` | foundation, domain | Process maps + integrations |

- Persona stored on `canvas_invitees.persona` (CHECK enum, default `'domain'`).
- Owner persona stored in `auth.users.raw_app_meta_data.default_persona` (default `'architect'` for `platform_owner`, `'domain'` otherwise).
- Per-message persona inference: the current author's persona drives the turn.
- System prompt builder composes base prompt + persona section (description + preferred categories + output template) + optional canvas context.
- Output format applied via prompt-based guidance (not post-processing).

### UI surface

- `InvitationModal`: persona dropdown with 5 options + descriptions, default `'domain'`, resets on close, persists via `inviteToCanvasAction`.
- `CanvasParticipants`: avatar tooltip extended to "Name · Role · Persona".
- `ToolStatusPill`: animated pill component renders during tool execution (running / completed / failed states); the chat column maintains an active-tools map.
- Markdown footnote rendering: GFM footnotes via `remark-gfm`; the «Footnotes» heading is renamed to «Sources»; superscripts are styled in the accent color.

### End-to-end activation status

✅ As of `wire-muse-runtime-in-studio` (RAP-36, applied 2026-05-03): the studio canvas runtime calls `runMuseWithTools` end-to-end. `runArchitect` is no longer invoked from `apps/studio` (the package export is retained for parity testing; cleanup tracked as a backlog item). The infrastructure is live in production for both architect-only canvases and project-bound (Mirror-authoring) canvases. See `studio-workspace/spec.md § Mirror runtime in canvas` for the full runtime contract; see `wire-muse-runtime-in-studio` block below for the tool-registration delta.

---

## Architectural intent for the package

- Lives in `packages/muse/`.
- Exports: agent definitions per mode (system prompt + tools), conversation runners, output schemas.
- No coupling to specific surfaces; the same Architect mode is callable from the Studio UI (v1) and from Forge CLI (the Forge audit pipeline today; the Builder-mode prompt template `packages/muse/src/builder-prompt.ts` consumed by `forge build` since v1.2 — see `forge.md § Builder mode`).
- Stateless inside the package — conversation history is the responsibility of the surface that runs it (Studio persists to `canvas_sessions`; Forge persists to its own report files; future Telegram bot persists to its own session store).
- v1 ships agent definitions, prompt files, and runner code for the **Architect** mode; v1.2 adds the Builder-mode system-prompt template at `src/builder-prompt.ts` (consumed by `forge build`). The Scout and Canvas mode subdirectories exist as placeholders, populated by their respective layer epics.

---

## Why other modes are deferred

The four-mode framing is intact. v1 ships Architect because it is the cheapest mode to build to demonstrable value: the prompts already exist (in Forge today), the surface is internal-only (no public-quality polish required), the loop is short (one user types, one user reads, one user commits), and the output integrates with an existing workflow (OpenSpec change folders).

Order:

1. **v1 — Architect mode** ships in `packages/muse/` with `apps/studio` UI surface (this PR's spec target).
2. **v1.2 — Builder mode** shipped (RAP-48) as `forge build <issue-key>` in `@rapoport-studio/forge`. The system-prompt template lives at `packages/muse/src/builder-prompt.ts` and is composed with the plan, lessons, and repo context per `forge.md § Builder mode › Builder prompt`. `status` and `cancel` shipping next under `openspec/archive/2026-05-09-sync-forge-builder-spec/`.
3. **v1.3 — Scout mode**. Implementation design deferred to the v1.3 design loop per `ROADMAP.md § MVP v1.3 — Scout mode`. Could be single-agent or multi-agent — decision when v1/v1.2 dogfooding has revealed real codebase complexity.
4. **v2 — Canvas mode** — the conversational mode of Muse for live entity-extraction during client conversations. (The original React Flow visualization layer in `canvas.md` was deprecated 2026-05-03; the v2 visualization shape is TBD.) See `ROADMAP.md § MVP v2 — Canvas mode (public)` for the shipping plan.

Each layer ships before the next starts. No parallel layer development.

---

## Relationship to Forge

```
v1.2 (current — Architect + Builder):
  Studio UI (Architect)        ─→ Muse (packages/muse)              ─→ Claude API
  Forge CLI (Builder)          ─→ Muse builder-prompt + Agent SDK   ─→ Claude API
  Forge CLI (audit/spec/review/estimate/render/mirror-export)   ─→ Claude API directly (unchanged)

v2 (Canvas lands):
  Public chat (Canvas mode)    ─→ Muse (packages/muse)              ─→ Claude API
  Studio UI (Architect)        ─→ Muse (packages/muse)              ─→ Claude API
  Forge CLI (Builder)          ─→ Muse builder-prompt + Agent SDK   ─→ Claude API
  Telegram bot                 ─→ Muse Canvas mode                  ─→ Claude API
```

In v1, Architect-mode prompts migrate from `packages/forge/src/commands/audit/prompts/architect.md` and `packages/forge/src/commands/spec/prompts/fix-proposal.md` into `packages/muse/`. Forge keeps its Architect-as-audit-agent role for the audit pipeline (where the agent reviews PRs and emits findings) — that role is conceptually distinct from the Architect-as-author role v1 introduces. Same personality, different system prompts, different invocation patterns.

In v1.2 Forge gained the `forge build` command (RAP-48), which consumes the Builder-mode system-prompt template at `packages/muse/src/builder-prompt.ts` and runs it through the Anthropic Agent SDK with checkpoint gating. The Forge → Claude API direct line still covers `audit / spec / review / estimate / render / mirror-export`; `forge build` is the first command to route through Muse. `status` and `cancel` (Wave 4) reuse the same SDK + builder-prompt path.

The v1 migration was content-only (prompt files moved) plus new runner code in `packages/muse/`. The v1.2 addition is one new file (`src/builder-prompt.ts`) — no behavior changes to existing Architect-mode runners or Forge audit/spec/review/estimate pipelines.

---

## Out of scope for v1

- Scout mode (v1.3 — implementation design deferred per `ROADMAP.md`).
- Canvas mode (v2 — see `ROADMAP.md § MVP v2 — Canvas mode (public)`; the original visualization vision in `canvas.md` was deprecated 2026-05-03).
- React Flow domain map (v2).
- Inline diff between artifact versions (v1 uses manual re-read).
- Linear API integration in Muse (TBD; manual paste of issue body in v1, uncertainty per `ROADMAP.md` whether the integration is ever needed).
- GitHub API integration (PR creation post-promote stays in human/git workflow for v1).
- Voice and mobile input.
- Multi-user, invitations, sharing.
- Real-time collaborative editing.
- Pricing and billing.
- ru/ro long-form chat content (chrome translates; chat is English-only).

---

## Related capabilities

- `platform.md` — overall context; locates Muse within the platform's capability map.
- `studio-workspace/README.md` — the workspace surface that hosts Architect mode in v1; see `studio-workspace/spec.md § Surface area` for the four-route table.
- `design-system.md` — visual tokens, typography, animation, brand voice; addresses the conversational canvas explicitly under § Surfaces › Muse.
- `forge.md` — current home of the Architect prompts being migrated. Already a Muse consumer in v1.2: `forge build` reads `packages/muse/src/builder-prompt.ts` for the Builder-mode system prompt — see `forge.md § Builder mode › Builder prompt`.
- `mirror.md` — the client-perception spec layer that Muse populates during client conversations. (The earlier Canvas v2 visualization vision in `canvas.md` was deprecated 2026-05-03 — see tombstone for historical context.)
- `auth.md` — magic-link session that gates the Architect-mode surface.
- `db/README.md` — Supabase project that hosts the `canvas_sessions` table.
- `ROADMAP.md` — single source of truth for layer ordering (v1 Architect ✓ → v1.2 Builder ✓ → v1.3 Scout → v2 Canvas).


---

## Mirror authoring tool (applied 2026-05-03 from change `add-muse-mirror-authoring-tool`)

The Muse tool that authors Mirror fields during canvas chat. Pairs with the `loadMirror` reader from `add-mirror-schema-and-loader` to close the read/write loop on `openspec/brands/<slug>/identity.md`. Render integration ships separately in RAP-33; studio runtime wiring (replacing `runArchitect` with `runMuseWithTools`, threading `projectSlug` from canvas state, `canvas_sessions.project_slug` column, slug-selection UI) ships separately in RAP-36.

### Tool addition (Foundation tier)

| Tool | Description | Permission | Cost |
|---|---|---|---|
| `write_mirror_field` | Persists a Mirror field to `openspec/brands/<slug>/identity.md`. Scaffolds the file on first write. List fields appended (dedup-skipped by primary key); body sections replaced; `brand_anchors` shallow-merges. Slug from `ToolExecutionContext.projectSlug`; tool filtered out of the API tool list when no slug is bound. See `mirror.md § Authoring semantics`. | owner + collaborator | free |

### Cloudflare Workers caveat extension

`write_mirror_field` was the last fs-dependent foundation tool — `read_openspec` and `read_archive` were closed earlier by `add-mirror-runtime-fix` (build-time bundle path; see § Foundation tools). RAP-71 (Track A of `complete-canvas-file-lifecycle`) routed `write_mirror_field` writes through a `mirrorWriter` callback supplied by the caller via `ToolExecutionContext.mirrorWriter`. The studio runtime backs the callback with a `canvas_files` UPSERT keyed by `(canvas_id, openspec/brands/<slug>/<field>.md)`; unit tests and CLI harnesses pass either an in-memory `Map` writer or a node-fs writer. The Workers `not_available_in_runtime` failure mode is gone — the tool no longer touches `node:fs/promises` at all.

### Defense in depth

Three gates protect the file:

1. **DB RLS** (RAP-36) — `client`-role users cannot reach a canvas they aren't invited to and cannot reach other projects' canvases.
2. **Tool registry filter** — `getToolsForAnthropicAPI(role, projectSlug?)` filters `write_mirror_field` out of the API tool list when role is `client` (via `permission.roles`) OR when `projectSlug` is undefined. The LLM never sees the tool description it cannot call.
3. **Tool execute() guard** — the tool re-checks role and slug at execute time. If a `client`-role context somehow reaches the tool, the call returns `{ ok: false, error: 'role_not_authorized' }`. Belt + suspenders.

Layers 2 and 3 ship in this change. Layer 1 ships in RAP-36.

### System prompt extension

`buildSystemPrompt` now accepts an optional `projectSlug` argument. When set, the system prompt includes a "Mirror authoring" section describing the nine capturable fields, when-to-call cues, implicit per-field write semantics (lists append + dedup, `brand_anchors` shallow-merges, body sections replace), and the owner+collaborator permission. The section is suppressed when no slug is bound, so canvases without a Mirror context don't see authoring guidance for a tool that won't be in their available-tools list.

### Schema relaxation

`MirrorBodySchema` body fields (`mental_model`, `aspirational_tone`, `success_phrase`) relaxed from `z.string().min(1)` to `z.string()` so an in-progress Mirror with empty body sections passes validation. The loader now enforces section-header presence explicitly (`body.<section>: required section header missing`) so structural errors are still rejected. Renders (RAP-33) own the "fully populated for render" readiness check at render time.

---

## Architect emit tools as foundation tools (applied 2026-05-03 from change `wire-muse-runtime-in-studio`)

Closes the runtime swap so `runMuseWithTools` can drive an architect-mode canvas turn end-to-end. Three concerns:

### Tool registration

The architect emit tools — `update_proposal`, `update_design`, `update_tasks` — were previously available only via `runArchitect` (which passed them as `Anthropic.Tool[]` directly to `messages.create`). They are now registered into the foundation registry alongside the read/research/Mirror tools, with `permission.roles: ['owner', 'collaborator']`, `category: 'foundation'`, `costCents: 0`. Definitions live in `packages/muse/src/tools/architect_emit_tools.ts`; descriptions and input schemas are reused verbatim from `packages/muse/src/modes/architect/tools.ts ARCHITECT_TOOLS` so the source of truth doesn't fork.

`getToolsForAnthropicAPI(role, projectSlug?)` therefore returns the architect emit tools to every owner/collaborator turn (no slug gate — every v1 canvas is in architect mode), plus `write_mirror_field` only when `projectSlug` is bound. Clients see neither.

### Sink pattern (no fs side effects on emission)

`update_proposal/design/tasks` are emit-only — their job is to capture LLM artifact output, not write fs. The `ToolExecutionContext.architectArtifactSink` field is a per-turn mutable map the tools' `execute()` writes to. The studio runtime supplies a fresh sink per turn, drives `runMuseWithTools`, then reads the populated sink and persists via UPSERT-on-emit against `canvas_files` (UNIQUE `(canvas_id, path)`). The sink type was widened by RAP-70 from the original three-name shape to `Record<string, string>` so paths beyond the proposal/design/tasks triplet (Mirror perception, research files, copy operations) flow through the same sink without translation. When the sink is absent (test harnesses, future non-architect callers), the tools return `ok` with `metadata.captured = false` and emit nothing.

### Tool error surface

`ToolStatusEvent.tool_completed` gains an optional `error?: string` field carrying the tool's `ToolResult.error` string when `ok = false`. Lets the studio runtime collect tool errors out-of-band (not just inferred from the `tool_result` block injected back into the conversation). The Workers fs UI banner in `canvas-view.tsx` consumes this — see `studio-workspace/spec.md § Mirror runtime in canvas` for the `not_available_in_runtime` detection path.

### Helper extraction

`parseArtifactsFromMessage(content)` extracted from `runArchitect.parseResponse` into `packages/muse/src/modes/architect/parse_artifacts.ts`. Walks tool_use blocks named `update_proposal/design/tasks` and returns a partial `ArtifactTriplet`. Reused by `runArchitect` (no behavior change) and exported from the package barrel for any future non-sink callers.

---

## Token cost credited to canvas budget (applied 2026-05-04 from change `credit-token-cost-to-canvas-budget`)

Closes the v2 token-cost gap from `wire-muse-runtime-in-studio` (RAP-36). The canvas runtime now meters per-turn assistant token cost on the same accumulator as tool cost.

### Cost guard extension

`packages/muse/src/shared/cost-guard.ts computeCost` accepts two optional cache-token params (default 0): `cacheReadTokens` (priced at 10% of input) and `cacheCreationTokens` (priced at 125% of input — Anthropic 5-minute-tier rates). New helper `usageToCents(model, usage)` returns `Math.ceil(totalUsd * 100)` for direct credit against the integer DB column, defaulting `cache_read_input_tokens` and `cache_creation_input_tokens` to 0 to absorb null fields. Forge's mirrored cost util is intentionally unchanged — boundary preserved per RAP-33 risk #7.

### Cap and column rename

`CANVAS_BUDGET_CENTS` raised 50 → 500 ($5/canvas) to absorb token cost without choking ordinary Opus traffic. `canvas_sessions.tool_cost_cents` renamed to `cost_cents` to reflect the broadened meaning — one column, one cap, covers both tool and assistant token cost. **Subsequently raised 500 → 5000 ($50/canvas) by RAP-957 — see § Canvas cost cap raised to $50 below.**

### Per-turn flow in `runMuseWithTools`

After each `stream.finalMessage()`, `usage` is priced via `usageToCents(args.model, finalMessage.usage)` and credited through the same `updateCanvasCost` callback the tool path uses. The mutation lands on `wrappedContext.currentCanvasCostCents` so the in-loop `executeToolWithGuards` pre-checks see the updated total. A new `{ type: 'turn_cost'; cents; usage }` event is yielded for downstream observation.

### Refuse-next-turn semantics

`isCanvasBudgetExceeded(currentCents)` (exported from `tool_use_loop.ts`, returns `currentCents >= CANVAS_BUDGET_CENTS`) is checked at the top of `runMuseWithTools` before the Anthropic stream opens. On exceed, the generator yields `{ type: 'turn_complete', reason: 'budget_exceeded' }` and returns without an API call. In-flight turns always finish — token cost is post-hoc.

### Multi-turn budget actually enforced

Latent reload bug fixed in the same change: studio's `actions.ts` previously hardcoded `currentCanvasCostCents: 0` per turn, so the new pre-check would never have fired across turns. The studio runtime now reloads `cost_cents` from the loaded `canvas_sessions` row and threads it through `buildToolUseLoopContext`. Without this, the pre-flight is ornamental.

### UI surface deferred

The `turn_cost` event is observed but not surfaced. RAP-19 (tool-status streaming UI) consumes it directly when shipped — no further engine work needed.

---

## Knowledge system (applied 2026-05-04 from change `add-muse-knowledge-system`)

Adds DB-backed dynamic instruction context that flows into the Muse system prompt each turn. Two instruction surfaces: per-canvas project instructions (authored in a panel) and canvas instructions (sent via `/instruct` command). A third surface — studio prompt templates — is authored by platform owners and applied by collaborators via a modal.

### SupabaseLike boundary interface

`packages/muse/src/context/instruction_loader.ts` defines a minimal structural interface `SupabaseLike` for the PostgREST query chain, avoiding a `@supabase/supabase-js` import into `packages/muse`. The interface exposes only `from(table)` with chainable `select/eq/order/limit` returning a `Promise<{ data: T[] | null; error: ... | null }>`. The studio runtime casts the real `SupabaseClient` through `as unknown as SupabaseLike` at the single call site in `turn/route.ts`.

### Instruction loader API

`loadProjectInstructions(supabase, canvasId, limit?)` — fetches `canvas_project_instructions` for a canvas, ordered pinned-first then by `created_at DESC`. Returns `ProjectInstruction[]`.

`loadCanvasInstructions(supabase, canvasId, limit?)` — fetches `canvas_messages` where `kind='instruction'`, last N, ordered by `created_at DESC`. Returns `CanvasInstruction[]`.

`resolveInstructionContext(project, canvas)` — applies priority order: pinned owner > non-pinned owner > collaborator. Returns `ResolvedInstructions { projectInstructions, canvasInstructions }`.

All three are exported from the `@rapoport-studio/muse` barrel. `SupabaseLike` and the return types are also exported as types.

### System prompt section

`buildSystemPrompt` receives an optional `instructionContext?: ResolvedInstructions` in `SystemPromptArgs`. When present and non-empty, renders a `# Knowledge system` section after the project methodology section. Format:

```
# Knowledge system

## Project instructions
<pinned-owner instructions first, then others>

## Canvas instructions (recent)
<last 10, newest first>
```

### Priority order (documented, not enforced)

System > Studio templates > Project instructions > Canvas instructions  
Within tier: Owner > Collaborator > Client

Conflict detection is deferred to v2. The knowledge dashboard (`/spec/[id]/knowledge`) displays the order as static text.

### Engine wiring

`RunWithToolsArgs` and `RunMuseTurnArgs` both carry `instructionContext?: ResolvedInstructions`. In `turn/route.ts`, `tryLoadInstructions(canvasId, supabase)` loads both instruction types in parallel with `tryLoadProjectContext`, wraps the combined call in try/catch (instructions are additive; failure is non-fatal), and passes the result through the turn generator.

## Linear read tools (applied 2026-05-05 from change `add-muse-linear-read-tools`)

Adds four read-only Linear tools to the foundation tool set. Closes RAP-54.

### Tools

| Name | Purpose |
|---|---|
| `linear_list_issues` | List issues for a team filtered by state, label, assignee email, or priority. |
| `linear_get_issue` | Single issue with description, comments, labels, and relations. Accepts identifier (`RAP-43`) or UUID. |
| `linear_search` | Full-text search across issues. |
| `linear_list_cycles` | List most recently updated cycles for a team. |

### Shared client

All four route through `packages/muse/src/shared/linear-client.ts` — a thin GraphQL wrapper over Linear's `https://api.linear.app/graphql` endpoint. Auth is the studio bot identity `LINEAR_BOT_API_KEY` provisioned in RAP-47. Token reader probes `getCloudflareContext().env` first, falls back to `process.env` — same pattern as `github-client.ts`.

`@linear/sdk` was deliberately rejected (~700 KB bundle delta for four queries we own outright). The shared client is ~280 lines including narrowed types for the four response shapes; mutations join the same file when RAP-61 (write tools) lands.

### Permissions and category

All four tools are `category: 'identity'`, `costCents: 1`, and `permission.roles: ['owner', 'collaborator']`. **Client role is hard-denied** — Linear is studio-internal infrastructure; the client persona never sees raw issue state. Aligns with the existing `identity` soft-routing in `personas/definitions.ts § PERSONA_PREFERENCES` which already deprioritizes `identity` for clients.

### Error taxonomy

Errors return as `{ ok: false, error: '<code>' }`:

- `invalid_input` — required param missing or malformed
- `linear_api_key_unavailable` — env var not loaded
- `linear_rate_limited` — HTTP 429 from Linear
- `linear_http_<NNN>` — other HTTP error
- `linear_graphql_error` — non-empty `errors` in response body
- `not_found` — issue / team does not exist
- `network` — fetch threw

### Out of scope

Write operations (RAP-61), Linear Documents API, project-context auto-resolution from `projects.linear_team_id` (v1 requires explicit `team`), and system-prompt routing prose ("when to use Linear lookup vs OpenSpec recall") — all deferred to follow-up changes.

---

## Mirror writer callback + widened architect sink (applied 2026-05-05 from change `complete-canvas-file-lifecycle`)

Closes the Workers `node:fs` gap on `write_mirror_field` and removes the implicit three-name shape from the architect emit sink. Both deltas land alongside the studio-side runtime rewire (RAP-70/74) so the canvas surface matches the new contract end-to-end.

### `write_mirror_field` migrates from fs to `mirrorWriter` callback (RAP-71)

The tool no longer reaches for `node:fs/promises`. `ToolExecutionContext` gains:

```ts
mirrorWriter?: (
  path: string,
  transform: (currentContent: string | null) => string | null,
) => Promise<void>;
```

Implementations must:

1. Read the current content for `path`, or `null` if no row/file exists.
2. Call `transform(currentContent)`.
3. If `transform` returns a string, persist it (UPSERT semantics on path). If it returns `null`, skip persistence — the tool decided no write is needed (dedup-skip on an existing file, or a validation gate failed inside the closure).

Studio runtime backs this with a `canvas_files` UPSERT keyed by `(canvasId, path)` (`apps/studio/.../spec/[id]/tool-use-context.ts makeCanvasFilesMirrorWriter`). Local Node runs (Forge CLI, monorepo tests, `pnpm dev`) supply an `fs`-backed implementation. When the callback is undefined the tool returns `mirror_writer_unavailable` rather than reaching for `fs` — Workers stop being a special case.

The `not_available_in_runtime` error string the tool used to emit is **gone**. The studio-side banner that watched for it (`canvas-view.tsx`) was removed by Track E Part B. Any v1 reference to "Mirror authoring requires local dev" is stale documentation.

### `ArchitectArtifactSink` widened to `Record<string, string>` (RAP-70)

The sink narrowed-by-keys shape (`{ 'proposal.md'?, 'design.md'?, 'tasks.md'? }`) is widened to `Record<string, string>`. The three architect emit tools (`update_proposal`, `update_design`, `update_tasks`) still write the literal triplet filenames; the wider type leaves room for future emit tools (e.g. arbitrary-path Mirror or methodology emissions) to contribute without another cross-package type swap.

The studio runtime threads the sink straight into the `canvas_files` UPSERT path keyed by `(canvas_id, path)` — the matching column shape on the DB side. Test harnesses that supplied `{} as ArchitectArtifactSink` continue to compile; production callers iterate `Object.keys(sink)` so a generalised path domain works without code changes.

### Foundation tools surface unchanged

`getToolsForAnthropicAPI(role, projectSlug?)` returns the same set as before. The mirror writer migration is a tool-implementation detail — `permission.roles` and the slug gate haven't moved. `read_openspec` / `read_archive` continue to use the build-time openspec bundle on Workers; no fs-bundle drift was introduced by this change.

---

## Discovery wizard preamble (applied 2026-05-08 from change `add-canvas-discovery-wizard`)

`buildSystemPrompt` accepts an optional `wizardPreamble?: WizardPreamble` argument. When set, the system prompt prepends a `# Discovery wizard state` block at the **top of the system message**, before any persona / mode framing. The block surfaces the user's already-answered wizard fields so Muse skips re-asking them on every fresh canvas.

### Three branches

- **`undefined`** — block suppressed entirely. Caller didn't load wizard state (e.g. `tryLoadWizardPreamble` failed); Muse falls back to its existing conversational behaviour.
- **`{ filled: [] }`** — single conversational-fallback hint line: `_Empty. Use the conversational discovery flow._`. Wizard exists but no fields are filled yet; Muse should still ask the discovery questions.
- **`{ filled: [{label, value}, ...] }`** — bullet list of `**Label:** value` entries with the framing *"These fields are already answered. Do not relitigate them — incorporate them into your turn."* Caller-supplied order is preserved.

### Mode coverage

The studio runs every Muse mode (Architect / Builder / Research) through the single shared `buildSystemPrompt` (called from `runMuseWithTools`). The wizard preamble lands once at the top of the system prompt and applies uniformly across modes. **Muse must not re-ask questions already answered in the wizard preamble**, regardless of which mode the turn is in.

### Cross-package boundary

`@rapoport-studio/muse` deliberately does NOT import from `@rapoport-studio/canvas/wizard`. The studio bridges:

```
canvas_sessions.wizard_state          (DB row, JSONB)
  → loadWizardState                    (studio server action)
  → filledWizardFields + LABELS        (canvas package)
  → WizardPreamble { filled: [...] }   (studio bridge in turn/route.ts)
  → runMuseTurnStream                  (studio runner)
  → runMuseWithTools                   (muse package)
  → buildSystemPrompt                  (muse package)
  → renderWizardBlock                  (muse package, this contract)
```

Each layer owns its concern; muse stays canvas-package-agnostic.

### Token cost

Six fields × ~100 tokens each + framing ≈ 700 tokens upper bound on a fully-filled wizard. Empty wizard adds ~25 tokens of hint. On Opus pricing this is ~$0.01 per turn baseline at the upper bound, acceptable for v1. If the cost guard surfaces concerns later, the prompt-block can be filtered to `filled.length > 0` only and skipped entirely on empty wizards (caller-side change in the studio bridge; muse contract stays).

### Why no `read_wizard_state` tool

System-prompt injection covers the 95% case (Muse needs awareness, not mid-turn structured access). The 5% case (Architect mode generating proposal.md from wizard answers mid-turn) is rare in v1 and lands as a follow-up `add-muse-wizard-read-tool` change scoped to that need. Per `openspec/archive/2026-05-08-add-canvas-discovery-wizard/design.md § 5`.

---

## TG canvas surface as Muse — Headless mode (applied 2026-05-09 from change `add-tg-canvas-surface`)

Muse runs headlessly when invoked from a Telegram canvas group via `runMuseHeadless`
(`packages/muse/src/orchestration/run_headless.ts`). The runtime is identical to
the Studio canvas turn (same tool registry, same personality, same model selection)
with three differences:

1. **No browser session** — viewer/canvas/instruction context loaded server-side via
   dependency-injected loaders.
2. **Propose-only mutations** — mutator tools (`MUTATOR_TOOLS` allowlist) write to
   `canvas_proposals` instead of executing; owner approves via `/yes`.
3. **Daily cost cap** enforced Worker-side pre-turn (`canvas_sessions.daily_cap_cents`,
   default 300 cents = $3). In-flight turn finishes; next turn rejected on cap hit.

`runMuseWithTools` itself is unchanged. The wrapper translates yielded events into
callbacks and runs the existing tool-use loop. Studio canvas turn route is untouched.

See `telegram.md` for full surface spec, bot identity (D-TCS-12), voice pipeline
(D-TCS-5), identity binding (D-TCS-6), and engagement opt-in (D-TCS-11).

---

## Maestro added as fifth mode (applied 2026-05-09 from change `amend-muse-forge-for-maestro`)

`packages/muse/` gains a fifth mode at `packages/muse/src/modes/maestro/` — peer
to Architect / Builder / Scout / Canvas — per `add-philosophy-foundation §
Decisions captured` D-7. The five-mode roster supersedes the four-mode framing
in § Vision; that section is preserved as the v1 mental model and remains
accurate for the four originally-planned client-facing modes. Maestro is the
orchestra coordinator (route work between agents, audit cross-agent state,
surface decision points to Pavel) and is invoked through the same internal
Muse runtime, not through any of the existing public surfaces.

Maestro inherits Muse infrastructure: tool loop (`runMuseWithTools`), cost
guards (`packages/muse/src/shared/cost-guard.ts`), persona system
(`packages/muse/src/personas/`), model selection (`MODELS.opus` /
`MODELS.sonnet` from the existing pricing constants), and instruction-context
resolution (`packages/muse/src/context/instruction_loader.ts`). The mode does
not reimplement these primitives; it ships as a system prompt + tool-permission
allowlist + persona augmentation that loads into the existing runtime. Cross-
cutting concerns (cost accounting, tool registry, persona priorities) stay
in one place.

Tool registry filtering enforces Maestro's read-only access scope per
[`agents/maestro.md § Access scope`](agents/maestro.md). The runtime
mechanism is the existing `getToolsForAnthropicAPI(role, projectSlug?)` filter
in `packages/muse/src/tools/`. Maestro's allowed tools (`read_file`,
`list_directory`, `web_search`, `linear_get_issue`, `linear_list_issues`,
`linear_search`, `linear_list_cycles`, `read_openspec`, `read_archive`) are
visible at the API boundary; write tools (`write_file`, `edit_file`, `bash`,
`write_mirror_field`, `update_proposal`, `update_design`, `update_tasks`) are
not registered for the Maestro role and the model never sees them in the
available-tools list. The allowlist is part of the spec contract; a future
runtime regression that exposes a write tool to Maestro is a spec violation,
not just a security finding.

**Substrate-pending.** The `packages/muse/src/modes/maestro/` directory does
not exist on disk yet — this amendment describes the contract, not the
implementation. Runtime code (system prompt, mode entry point, persona
definition) lands in a separate change after this amendment merges. The
canonical capability spec is at [`agents/maestro.md`](agents/maestro.md)
(landed via [RAP-150](https://linear.app/rapoport-studio-slr/issue/RAP-150) /
PR #428). This amendment closes the forward-reference flagged in
[`agents-overview.md § Maestro as fifth Muse mode (forward dependency)`](agents-overview.md).

## Workflow timeline cross-reference (applied 2026-05-12 from change `establish-workflow-spec`)

Architect mode owns Stages 1–2 (Capture → Frame) of the workflow timeline; Maestro mode is the workflow timeline controller per [`workflow.md § 4 Maestro control loop`](workflow.md). See `agents/maestro.md § Function` for routing scope.

## muse draft verification.md — fourth artefact (applied 2026-05-14 from change `add-spec-acceptance-gate`)

`muse draft <RAP-NN>` now produces **four** output files in `openspec/changes/<slug>/`:

```
openspec/changes/<slug>/
├── proposal.md       (existing)
├── design.md         (existing)
├── tasks.md          (existing)
└── verification.md   (NEW — four-section template, generated by `verifier` role)
```

### `verification.md` shape (four sections, in order)

```markdown
# Verification — <change-slug>

> Companion to proposal.md / design.md / tasks.md.

## Spec alignment
Prose (1-2 paragraphs). What "done" means at the capability-spec level.

## Hallucination risks
Bulleted list of MUST appear / MUST NOT appear paths and symbols.

## Pattern references
Bulleted list of existing files the diff should mimic.

## Acceptance
One deterministic assertion per line. Six allowed shapes (see below).
```

### `verifier` role in cli-core router

`packages/cli-core/src/llm/router.ts` resolves role `'verifier'` to Sonnet (`claude-sonnet-4-6`). Synthesis difficulty is comparable to `planner` / `storyteller`; Opus is overkill for verification.md generation. The role is a fourth LLM call in `muse draft` after proposal / design / tasks; the call is parallel-eligible with the other three.

### Two mandatory boilerplate gates

Every generated `verification.md` includes these two assertions as the last two lines in the `## Acceptance` section:

```
- Done when: gh pr list --head <branch-for-this-change> returns ≥1
- Done when: jq -r '.outcome' ~/.forge/state/<RAP-NN>.json returns 'completed-with-pr'
```

These cover the two most common silent-failure modes from the build orchestrator: PR not actually created (`failed-pr` state) and outcome field lying about success (RAP-392 finding).

### Six acceptance assertion shapes

See `openspec/current/forge/entities.md § Six assertion shapes` for the full table and TypeScript type definitions. `verification.md` uses the surface syntax column; `core/acceptance.ts` implements the checkers.

## Architect Extended Thinking (applied 2026-05-16 from change `wire-architect-extended-thinking`)

The Muse architect turn runs with Claude Extended Thinking enabled. Both
architect runners pass `thinking: { type: "enabled", budget_tokens: 6000 }`
on the Anthropic call:

- `runMuseWithTools` (`packages/muse/src/orchestration/run_with_tools.ts`) —
  the streaming tool-use loop behind the canvas SSE turn route. Extended
  Thinking is opt-in via an optional `thinking` arg on `RunWithToolsArgs`;
  the architect caller (`muse-runner.ts`) sets it, other callers omit it.
- `runArchitect` (`packages/muse/src/modes/architect/runner.ts`) — the
  non-streaming single-turn driver.

`max_tokens` on the architect path is raised to 16000 — the thinking budget
counts toward `max_tokens`, so the ceiling must cover both the budget and
the response (which can be a full artifact-emission `tool_use` block).

Each runner walks the **final** response's `thinking` content blocks
(intermediate tool-loop iterations' thinking is discarded — D-XT-5),
concatenates them into `TurnReasoning.details`, and measures the loop's
wall-clock as `durationMs`. `TurnReasoning` (`{ summary, details, durationMs? }`,
exported from `@rapoport-studio/muse`) is `undefined` when the turn produced
no thinking. `summary` is the empty string in v1 — Claude `thinking` blocks
carry no summary field.

When Extended Thinking runs with tool use, the assistant turn's
`finalMessage.content` (including `thinking` blocks with their signatures) is
pushed back verbatim on tool-loop continuation, as the API requires.

Reasoning is surfaced to canvas chat as a `reasoning` content block (see
`canvas.md`) — **never persisted** (live-only). Source:
`openspec/archive/2026-05-16-wire-architect-extended-thinking/`.

## Canvas surface — Change · Spec · Code pipeline (applied 2026-05-16 from change reframe-canvas-change-pipeline)

> Deep contract for the **right rail** pane of the `/spec/[id]` page.
> Page-level composition lives at `studio-workspace/spec.md § Spec page
> surface`. Block stacking order, the per-block caption header, and the
> empty-state banner position (inside the relevant block, below the tab
> strip) are ratified by D-SP-3.

The `/spec/[id]` right column — described in § UI surface as a single
"Artifact panel" of `proposal.md` / `design.md` / `tasks.md` tabs — is
reframed as a **maturity pipeline**. The canvas is the surface where an
idea matures along a fixed, one-directional path that every collaborator
reads the same way: *idea → Change → Spec → Code*.

The right rail is one column of three blocks, stacked top-to-bottom by
maturity:

| Block | Shows | Backed by |
|---|---|---|
| **Change** | The `proposal` / `design` / `tasks` triplet Muse is drafting for this canvas — work in flight, plus the Promote action. | `ArtifactPanel` tabs. |
| **Spec** | The project's accepted specification — a read-only browser over every `openspec/current/*` capability file. | `<SpecBlock>`, sourced from the bundled `openspec-bundle.ts` (D-CCP-4). |
| **Code** | The bound GitHub repository's file tree, read-only. | `RepoFileTree`. |

Block labels are deliberately non-technical — **Spec**, not "OpenSpec" — so
a first-time user reads the rail as a scenario without onboarding. The
`Canvas` entity in code / routes / DB is unchanged; only the visible block
labels are renamed.

The left **Threads** rail is reframed in the same change from a
viewer-scoped flat canvas list into a project-scoped chronological change
timeline — see `studio-workspace/spec.md § Threads rail — project change
timeline`.

Ratified decisions: D-CCP-1 (maturity-pipeline model), D-CCP-2 (three
vertical blocks), D-CCP-3 (Change · Spec · Code naming), D-CCP-4 (Spec
block source + whole-spec slice). The approve mechanism (D-CCP-6 /
Q-CCP-1) ships as a visual placeholder only; a real signoff workflow is
deferred.

Source: `openspec/archive/2026-05-16-reframe-canvas-change-pipeline/`.

---

## Canvas cost cap raised to $50 (applied 2026-05-16 from change `raise-canvas-cost-cap`)

`CANVAS_BUDGET_CENTS` raised 500 → 5000 ($5 → $50/canvas). The $5 cap was tripping on ordinary repo-intake sessions (RAP-957 reported real sessions reaching the limit before useful work completed). Rationale: the cap is a safety rail, not a pricing gate — it should allow a full productive session, not a truncated one.

### Decision record

- **D-CCC-1**: Cap is $50 (5000¢). One global constant; no per-canvas or per-tier variant.
- **D-CCC-2**: Three independent copies of the constant are kept in lockstep — `CANVAS_BUDGET_CENTS` in `packages/muse/src/orchestration/tool_use_loop.ts` (canonical), `costCents >= 5000` literal in `apps/studio/src/app/(auth-walled)/(canvas)/spec/[id]/turn/route.ts` (turn pre-check), and `COST_CAP_CENTS` in the chat machine. Deduplication into a shared export is deferred (crosses package boundaries).
- **D-CCC-3**: Warning-threshold ratio (80%, = 4000¢) unchanged.

Source: `openspec/archive/2026-05-16-raise-canvas-cost-cap/`.

## Operational modes vs SDLC personas (applied 2026-05-18 from change `clarify-muse-modes-framing`)

Canvas, Scout, Architect, and Builder are operational **modes** of one authoring agent — states, not personas. Muse has one personality and one set of core values; a mode differs from a mode only in system prompt, tool set, and output shape. Which mode is active is selected by *what surface the agent is on and what artifact it is producing* — public intake on the Canvas surface, a codebase scan on the Scout surface, a change proposal on the Architect surface, a code PR on the Builder surface. A mode is a configuration of the same agent for the work in front of it, not a distinct character.

This is deliberately **not** BMAD-style persona simulation. The widely-criticized "19-agent trap" is the pattern where an agent framework simulates a large cast of fake SDLC-role personas — a PM, an architect, a QA engineer, a scrum-master, a business analyst, and so on — as separate agents, and the user is left orchestrating an imaginary org chart. The critique is that this is theatre: persona count is not capability, and a sprawling cast adds coordination overhead without adding real planning power. Muse's modes are the opposite shape — a small finite state machine over a single agent, with a handful of well-defined states and explicit transitions between them. It is a state machine, not an org chart.

The point is about the *nature* of a mode, so it holds regardless of the mode count. Maestro landed as a fifth mode (see § Maestro added as fifth mode above) without changing the framing: a fifth state on the machine is still a state of one agent, not a fifth persona. If the roster ever grows again, the same rule applies — each addition is a new state of the one authoring agent, justified by a real surface and a real artifact, never a simulated role added for its own sake.


## Complete idea-to-Forge loop (applied 2026-05-24 from change complete-idea-to-forge-loop)

**RAP-1202 epic.** Closes the two missing edges of the idea → dispatch → Forge → idea loop. The `muse draft <RAP-NN>` CLI is rewired to persist artefacts through the studio substrate (`ideas` + `files` tables) rather than writing to disk directly. The CLI is now stage-only by default; `--convert` and `--open-pr` chain the studio's convert + openPR semantics via `@rapoport-studio/forge/core`'s `GitHubClient` against the same substrate.

Substrate fields the CLI consumes:

- `ideas.rap_id` — Linear identifier the CLI looks up to resolve-or-create the idea row. Format guard mirrors the SQL `^RAP-[0-9]+$` CHECK. Two CLI runs against the same RAP-id resolve the same row (D-IFL-5).
- `files` UPSERT by `(idea_id, path)` at `status='accepted'` (D-IFL-4) — CLI-emitted artefacts bypass the `proposed → accepted` user-acceptance step since there is no human in the loop.
- `ideas.{forge_state_url, forge_last_checkpoint, forge_outcome}` — populated by the Forge dispatch trigger workflow and the studio's poll-build-state action; not written by the CLI directly.

The CLI exits non-zero when `SUPABASE_URL` / `SUPABASE_SERVICE_ROLE_KEY` are absent — substrate writes are the only emission path. The historical disk-write to `openspec/changes/<slug>/` is gone; `convertToProjectAction` (studio) or `muse draft --convert` (CLI) owns the change-folder branch creation.

`SPEC_DRAFTED` event payload widened with optional `ideaId` so `maestro lineage RAP-NN` can correlate the drafted event with the studio's `/spec/[id]` surface for the same idea.

Source: `openspec/archive/2026-05-24-complete-idea-to-forge-loop/`.

## Architect prompt teaches canonical proposal.md header (applied 2026-05-26 from change `teach-muse-proposal-header`)

The Architect mode system prompt (`packages/muse/src/modes/architect/prompts/system.ts § proposal.md`) now teaches the canonical `proposal.md` header — H1 title in sentence case followed by a leading Rapoport-Prose blockquote with the `**Affects capability spec:**` field — alongside the four canonical body sections (`## Why / What Changes / Impact / Out of scope`). The convention is documented in full at [`openspec/AGENTS.md § proposal.md canonical shape`](../AGENTS.md), including the `none directly` sentinel for deliberate zero-scope changes and the no-backticks rule for that literal.

The studio's spec-preview surface ([apps/studio/src/app/(auth-walled)/(spec)/spec/[id]/loaders/spec-preview.ts](../../apps/studio/src/app/%28auth-walled%29/%28spec%29/spec/%5Bid%5D/loaders/spec-preview.ts) `parseProposalHeader`) reads the H1 + blockquote to determine which capability specs each change touches. Emitting the canonical header is mandatory — without it the panel falls back to an `affects-undeclared` warning and shows 0 AFFECTED.

A drift-guard test at `packages/muse/src/modes/architect/prompts/system.test.ts` asserts the prompt continues to teach the canonical header. The locked snapshot at `__snapshots__/system.test.ts.snap` captures the full template text; regen via `pnpm --filter @rapoport-studio/muse test -u` after intentional edits, never blindly.

Source: `openspec/archive/2026-05-26-teach-muse-proposal-header/` (PR-B #1804). Decisions D-MPH-7, D-MPH-8 ratified.

## Binary deterministic eval signals — zero-tolerance pattern (applied 2026-05-26 from change `teach-muse-proposal-header`)

Amends `**CI regression gate**` (above): binary deterministic signals (header-presence and successors) follow the zero-tolerance pattern, tracked **independently of the aggregate 5-point band**. A binary fail blocks the run regardless of aggregate score. No threshold knob; no aggregate weight; no warn-then-gate toggle.

**Recovery for non-quality fails** is operator-level (retry once → annotate as `infrastructure-failure` in the run report → escalate to re-ratify the originating decision after > 3 recurrences in a 2-week window); **never** an implementation toggle. Downgrade requires re-opening the underlying decision, not flipping a flag.

**First binary signal: `header-presence`** (D-MPH-9 / Q-MPH-1 resolved as hard-gate day-one). For each architect-cases.json entry, the eval runner parses the generated `proposal.md` via `parseProposalHeader` (mirror at `tools/muse-evals/lib/parse-proposal-header.ts`); pass condition is `affectsDeclaration !== 'absent'`. ANY case where the architect emits a `proposal.md` without a parseable Affects declaration fails the entire run with exit code 1.

Future binary signals follow this pattern: zero-tolerance, independently tracked, recovery is procedural not toggleable.

Source: `openspec/archive/2026-05-26-teach-muse-proposal-header/` (PR-B #1804). Decision D-MPH-9 ratified; Q-MPH-1 closed.

---

## Outbound prompt secret redaction (applied 2026-05-26 from change `add-muse-prompt-secret-redaction`)

Every Muse Anthropic call passes through `wrapAnthropicWithSecretScrub` (`@rapoport-studio/cli-core/security`) at the single chokepoint in `packages/muse/src/shared/client.ts § createClient`. The wrap is a Proxy around the `Anthropic` instance — `messages.create` and `messages.stream` invocations scrub `system` + each `messages[].content` (text blocks only — image / tool_use / tool_result variants are not secret-shaped) before transmission, replacing known-secret patterns with `[REDACTED-<pattern-name>]` placeholders. Empty-redactions path is byte-identical to the pre-wrap behaviour; non-empty path emits one `prompt.secret_redaction` event per call carrying pattern + count + role + model — **never the value** (D-FPSR-2 carried forward from the Forge ship).

Coverage:
- All call sites that resolve their client via `createClient(options)`: `tools/web_search.ts`, `tools/competitor_research.ts`, `tools/domain_research.ts`, `modes/architect/runner.ts`, `modes/architect/translation.ts`, `orchestration/run_with_tools.ts` (via the tool-loop's stored client), and every other consumer of the factory.
- Maestro headless: `modes/maestro/headless.ts § defaultCreateAnthropicClient` was refactored to apply the same wrap with `cli: 'maestro'` after its dynamic-import dance. The dynamic-import is kept for bundle-slim reasons (the studio bundle does not eagerly load `@anthropic-ai/sdk` for code paths that never call `runMuseHeadlessMaestro`).

Pattern table + helpers live in `@rapoport-studio/cli-core/security` per the rule-of-three lift documented at `cli-core.md § Security primitives`. Forge's local `packages/forge/src/core/secret-scrub.ts` is unchanged in this PR — a follow-up cleanup converts it to a re-export from cli-core (out of scope here).

**Threat closed (Muse half): AT-07** in [`openspec/_methodology/security/agents-threat-model.md`](../_methodology/security/agents-threat-model.md) — Q-FPSR-3 from the original Forge ship. Forge half shipped 2026-05-26 (PR #1792). Source-of-truth: [`openspec/archive/2026-05-26-add-muse-prompt-secret-redaction/`](../archive/2026-05-26-add-muse-prompt-secret-redaction/).

**Follow-ups (recorded explicitly, out of scope here):**
- `cli-core/llm/client.ts § callClaude` wire-in — different code path (Vercel AI SDK), separate PR.
- Forge's local `secret-scrub.ts` → re-export from cli-core — incidental cleanup PR.
- Studio Telegram-webhook standalone Anthropic call (`apps/studio/src/app/api/studio/telegram/webhook/route.ts:346`) — separate apps/studio surface.

## Title emission on `update_proposal` (applied 2026-05-26 from change `add-title-to-ideas-and-specs`, RAP-1226)

Muse Architect's `update_proposal` tool call now carries a `title: string` field alongside the proposal payload (Zod-validated `min(1).max(140)` at `packages/muse/src/tools/architect_emit_tools.ts`). The system prompt at `packages/muse/src/modes/architect/prompts/system.ts` instructs:

- Derive the title from the proposal's H1 or first sentence.
- Verb-led (e.g. "Add cookie-compliance banner", not "Cookie compliance").
- ≤ 140 characters.
- No metadata prefix (no "Idea:", "Proposal:", "Draft:").

The server action writes `ideas.title = args.title` in the same transaction as the proposal update. If Muse omits the field, the action logs a "missing-title" warning and the UI falls back to `idea.slug`. Source: [`openspec/archive/2026-05-26-add-title-to-ideas-and-specs/`](../archive/2026-05-26-add-title-to-ideas-and-specs/).
