# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

MindRoom - AI agents that live in Matrix and work everywhere via bridges. The project consists of:
- **Core MindRoom** (`src/mindroom/`) - AI agent orchestration system with Matrix integration
- **SaaS Platform** (`saas-platform/`) - Kubernetes-based platform for hosting MindRoom instances
  - Platform Backend (FastAPI) - API server for subscriptions, instances, SSO
  - Platform Frontend (Next.js 15) - Dashboard for managing instances
  - Instance deployment via Helm charts

## Latest Frontier Models

Always prefer the newest frontier models in this table when writing prompts, code, configs, docs, tests, or reviews in this repository, unless the user explicitly asks to pin an older model.
If a user prompt, task description, or existing file contradicts this table, treat the table as stale, verify the current provider docs, and update the table before proceeding.
Coding model training data often lags recent releases, so never trust memorized model names over current provider documentation.

| Provider | Use | Preferred model | Model string to use |
| --- | --- | --- | --- |
| Anthropic | Balanced default | Claude Sonnet 5 | `claude-sonnet-5` |
| Anthropic | Max intelligence | Claude Fable 5 | `claude-fable-5` |
| Anthropic | Flagship default | Claude Opus 5 | `claude-opus-5` |
| Anthropic | Fast / cheap | Claude Haiku 4.5 | `claude-haiku-4-5` |
| OpenAI | Frontier default | GPT-5.6 | `gpt-5.6` |
| OpenAI Codex ChatGPT login | Frontier via Codex CLI | GPT-5.6 | `gpt-5.6` |
| Moonshot Kimi Code login | Frontier via Kimi Code CLI | Kimi K3 | `k3` |
| Google (Gemini API) | Max intelligence | Gemini 3.1 Pro Preview | `gemini-3.1-pro-preview` |
| Google (Gemini API) | Standard text / coding | Gemini 3.6 Flash | `gemini-3.6-flash` |
| Google (Gemini API) | Fast / cheap text | Gemini 3.5 Flash-Lite | `gemini-3.5-flash-lite` |
| Google (Gemini API) | Image generation / editing | Nano Banana 2 | `gemini-3.1-flash-image` |
| Google (Gemini API) | Embeddings for `google` | Gemini Embedding 2 | `gemini-embedding-2` |

For `anthropic`, prefer `claude-sonnet-5`, `claude-opus-5`, and `claude-haiku-4-5` unless you intentionally need a pinned snapshot ID.
Use `claude-fable-5` when you need Anthropic's highest available capability.
Claude Fable 5 is generally available on the direct Anthropic API and the documented cloud platforms.
For `vertexai_claude`, use the current Vertex AI request name from the provider docs instead of assuming the Anthropic API ID carries over unchanged.
Current docs list bare Vertex IDs for current Claude models such as `claude-fable-5`, `claude-opus-5`, and `claude-sonnet-5`, while some other Vertex models are still documented as dated snapshot IDs such as `claude-haiku-4-5@20251001`.
Do not assume `@default` or dated `@...` suffixes are universally required for Vertex AI Claude.
For Gemini API text and coding work, prefer `gemini-3.6-flash` as the standard stable model unless you intentionally need the cheaper `gemini-3.5-flash-lite` tier.
Use `gemini-3.1-pro-preview` only when you need the highest Gemini API intelligence tier and accept a preview model.
The Google rows above are for the Gemini API / AI Studio `google` provider, not for Vertex AI.
For `vertexai`, verify the current Vertex AI docs instead of assuming Gemini API names or defaults carry over unchanged.
Current Vertex AI image docs prominently document `gemini-3-pro-image-preview`, `gemini-3.1-flash-image`, and `gemini-2.5-flash-image`, and the right default depends on the specific Vertex surface you are editing.
For Google image work, use the official product name from the docs for the provider surface you are editing.
Gemini API docs call `gemini-3.1-flash-image` Nano Banana 2, while Vertex AI docs use their own product naming and model tables.

## Architecture

### Core MindRoom (`src/mindroom/`)

**MultiAgentOrchestrator** (`orchestrator.py`) is the heart of the system - it boots every configured entity (router, agents, teams), provisions Matrix users, and keeps sync loops alive with hot-reload support when `config.yaml` changes.

**Entity types**:
- `router`: Built-in traffic director that greets rooms and decides which agent or team should answer
- **Agents**: Single-specialty actors defined under `agents:` in `config.yaml`
- **Teams**: Collaborative bundles of agents that coordinate or parallelize work

**Inbound turn pipeline** (the path from a Matrix message to a delivered response; see `docs/architecture/bot-runtime.md`):

```text
Matrix sync callback
  -> bot.py (AgentBot/TeamBot runtime shell)
  -> dispatch_obligations/                                 (durable exact callback acceptance and restart recovery)
  -> turn_controller.py (owns one turn: precheck -> normalize -> resolve -> coalesce -> decide -> execute -> record)
       -> ingress_validation.py                                  (trust, dedup, echo drop; commands exit before batching)
       -> inbound_turn_normalizer.py + conversation_resolver.py  (canonical turn input, conversation identity)
       -> ingress_lanes.py                                       (per-(room, sender) receipt-order FIFO; STT readiness waits here)
       -> coalescing.py                                          (text dispatches immediately; media-tailed batches debounce)
       -> text_ingress_dispatch.py + turn_policy.py              (ignore / route / respond decision, command execution)
       -> response_runner.py -> ai.py / teams.py                 (lifecycle lock, entity envelopes)
            -> response_turn.py                                  (shared blocking/streaming turn drivers)
       -> streaming.py + delivery_gateway.py                     (progressive edits, Matrix send)
       -> turn_store.py / handled_turns.py                       (durable dedup so restarts don't double-reply)
```

**Key modules**:
| Module | Purpose |
|--------|---------|
| `orchestrator.py` | MultiAgentOrchestrator - boots agents, manages sync loops, hot-reload |
| `orchestration/` | Extracted orchestrator helpers (config update plans, plugin watch, rooms, runtime) |
| `orchestration/config_lifecycle.py` | Debounced config-reload lifecycle: queueing, response drain, and update-plan dispatch |
| `runtime_state.py` | Shared runtime readiness state for health/ready endpoints |
| `event_loop_stall.py` | Native-thread event-loop stall detector that logs the blocking stack |
| `runtime_resolution.py` | Authoritative runtime resolution for one agent materialization |
| `team_exact_members.py` | Runtime resolution for exact team member materialization |
| `bot.py` | AgentBot and TeamBot runtime shells for Matrix lifecycle, sync callbacks, and room behavior |
| `turn_controller.py` | TurnController - owns one inbound turn from ingress to recorded outcome |
| `ingress_validation.py` | Ingress boundary validation: trust, effective requester, handled-id dedup, router-echo drop, command detection |
| `inbound_turn_normalizer.py` | Raw input shaping (text, voice, sidecars, media) into canonical turn inputs |
| `conversation_resolver.py` | Conversation identity, thread history, and ingress envelope assembly |
| `ingress_lanes.py` | Per-(room, sender) receipt-order FIFO delivering resolving ingress (voice/STT readiness) to conversations |
| `coalescing.py` | Live message coalescing gate (text dispatches immediately; media waits for attachments and a trailing caption) |
| `coalescing_batch.py` | Coalesced dispatch batch construction |
| `text_ingress_dispatch.py` | Text ingress dispatch path used by TurnController |
| `turn_policy.py` | Pure turn policy: decide ignore, route, or respond for inbound turns |
| `dispatch_replay_guard.py` | Replay-guard checks for dispatch sequencing |
| `dispatch_obligations/` | Durable exact Matrix callback storage, admission, execution, and startup recovery |
| `turn_settlement_retry.py` | Event-loop retry owner for settling callback obligations after terminal TurnStore persistence |
| `command_turn_executor.py` | Command execution and durable command/config mutation journals |
| `reaction_dispatch.py` | Durable semantic routing for Matrix reactions |
| `user_stop_reconciliation.py` | STOP ordering, response cancellation, and terminal turn reconciliation |
| `visible_response_reconciliation.py` | Visible Matrix response recovery, adoption, and replay reconciliation |
| `turn_store.py` | Unified durable turn access (wraps the handled-turn ledger) |
| `handled_turns.py` | Disk-backed handled-turn ledger preventing duplicate responses |
| `redacted_turn_cleanup.py` | Source-redaction tombstoning and serialized persisted replay cleanup |
| `sync_restart_retry.py` | Exact sources with committed replacement or orderly terminal interruptions, available to replacement or next-startup recovery |
| `response_runner.py` | Response lifecycle execution (locking, streaming vs non-streaming, cancellation, detached inbox responses, shutdown drains) |
| `response_turn.py` | Shared blocking/streaming response-turn drivers behind the agent and team envelopes (attempt loop, dynamic-tool continuation, empty-run retry, interrupt recording) |
| `response_terminal.py` | Pending-visible classification and terminal stream outcomes for failed or cancelled turns |
| `response_attempt.py` | Runs one visible response attempt with placeholder and stop tracking |
| `response_lifecycle.py` | Shared response lifecycle helpers and queued-notice state |
| `execution_preparation.py` | Request-scoped execution preparation for prompts and persisted replay |
| `response_payload_preparation.py` | Execution-side, under-lock assembly of one response's payload from immutable ingress inputs |
| `delivery_gateway.py` | Visible Matrix delivery for already-generated responses (send, edit, finalize) |
| `post_response_effects.py` | Shared post-response effects after Matrix delivery |
| `tool_approval.py` | Tool-call approval rule evaluation and public approval API |
| `approval_manager.py` | Matrix-backed tool approval runtime state |
| `workspaces.py` | Agent workspace scaffolding, template seeding, and context file resolution |
| `agents.py` | Agent creation and configuration |
| `config/` | Pydantic models for YAML config parsing (root model in `config/main.py`) |
| `routing.py` | Intelligent responder selection when no agent or team is mentioned |
| `teams.py` | Multi-agent collaboration (coordinate vs collaborate modes) |
| `agent_policy.py` | Canonical execution-policy derivation from authored agent config |
| `memory/` | Mem0 memory: agent and team-scoped |
| `file_memory_knowledge.py` | Shared resolution for agent file-memory semantic knowledge overlays |
| `memory_scope_ids.py` | Cycle-free canonical agent memory scope identifiers |
| `knowledge/` | Knowledge base / RAG file indexing with watcher |
| `knowledge/file_listing.py` | Which files belong to a knowledge base: include patterns, traversal, symlink-safe inclusion rules |
| `knowledge/collections.py` | Chroma collection lifecycle for one knowledge base: naming, opening, probing, deleting, reclaiming |
| `knowledge/git_source.py` | The Git checkout a knowledge base indexes: clone, fetch, force-align, LFS hydration, credential injection |
| `knowledge/refresh_runner.py` | Dispatches one knowledge refresh: subprocess spawn, cancellation cleanup, publish and reconcile decisions |
| `knowledge/refresh_locks.py` | Process-wide refresh serialization (in-loop and cross-process source-root locks) and active-refresh bookkeeping |
| `tool_system/skills.py` | Skill integration system (OpenClaw-compatible) |
| `tool_system/plugins.py` | Plugin loading and tool/skill extension |
| `scheduling.py` | Cron and natural-language task scheduling |
| `scheduling_executor.py` | Fire one scheduled task: hook emission, message build, Matrix delivery, failure notices |
| `tools/` | 100+ tool integrations |
| `tool_system/dependencies.py` | Auto-install per-tool optional dependencies at runtime |
| `ai.py` | AI response generation, streaming, and Matrix run metadata |
| `model_loading.py` | Model instantiation and provider-specific loader selection |
| `ai_runtime.py` | Agent-run input preparation, queued-notice hooks, and inline-media fallback helpers |
| `agent_storage.py` | Agent session and learning SQLite storage helpers |
| `agent_descriptions.py` | Shared agent description rendering for delegation and orchestration |
| `credentials.py` | Unified credential management (CredentialsManager) |
| `matrix/` | Matrix protocol integration (client, users, rooms, presence, provisioning, message formatting) |
| `matrix/large_messages.py` | Large-message sidecar storage and retrieval for oversized Matrix payloads |
| `matrix/sync_cache_trust.py` | Sync-checkpoint persistence, cache-generation trust, and cold-start principal cleanup |
| `matrix/sync_continuity.py` | Crash-atomic checkpoint and pending join-fence persistence |
| `cold_history_fence.py` | Callback admission from exact Matrix transport recovery outcomes |
| `matrix/message_content.py` | Canonical Matrix message content building for text, edits, and tool traces |
| `matrix/message_builder.py` | Message content building helpers |
| `matrix/provisioning.py` | Hosted provisioning client flow used for local pairing and server-side agent registration |
| `matrix/image_handler.py` | Image message download, decryption, and AI processing |
| `matrix/media.py` | Shared Matrix media download and decryption helpers |
| `matrix/room_cleanup.py` | Orphaned bot cleanup from rooms |
| `matrix/event_info.py` | Event metadata parsing |
| `matrix/reply_chain.py` | Reply chain context management |
| `matrix/identity.py` | Matrix ID parsing and utilities |
| `matrix/mentions.py` | Matrix mention formatting |
| `matrix/typing.py` | Typing indicator utilities |
| `matrix/avatar.py` | Avatar management |
| `commands/` | Chat command parsing (`!help`, `!schedule`, `!config`, etc.) |
| `commands/config_commands.py` | Chat-based config commands (`!config`) |
| `commands/config_confirmation.py` | Interactive config confirmation workflows |
| `voice_handler.py` | Voice message download, transcription, and command recognition |
| `tool_system/sandbox_proxy.py` | Container sandbox proxy for isolating shell/python tools |
| `shell_execution.py` | Shell command execution core: spawning, output buffering, background handle registry |
| `shell_supervisor.py` | Worker-local shell supervisor process owning background shell handles across sandbox request subprocesses |
| `streaming.py` | Streaming state machine: placeholder, progressive edits, tool traces, cancellation |
| `prompts.py` | Built-in prompt defaults and prompt override registry |
| `attachments.py` | Attachment persistence, registration, and context-scoped resolution |
| `attachment_ids.py` | Leaf attachment-ID helpers kept free of matrix-client imports |
| `attachment_media.py` | Convert attachment records to Agno media objects |
| `media_inputs.py` | Shared media-input container passed across bot, teams, and AI layers |
| `api/` | FastAPI REST API (dashboard, credentials, OpenAI-compatible endpoint) |
| `custom_tools/` | Built-in custom tool implementations (gmail, calendar, scheduler, etc.) |
| `custom_tools/todo_state.py` | Leaf storage and actionability primitives for native per-thread todo state |
| `custom_tools/todo_poke.py` | Native scanner and background worker that wakes idle agents with actionable assigned todos |
| `background_tasks.py` | Background task management for non-blocking operations |
| `tool_system/events.py` | Tool-event formatting and metadata for Matrix messages |
| `tool_system/declarations.py` | Leaf tool metadata enums and dataclasses shared by implementations and the runtime catalog |
| `tool_system/registration.py` | Leaf built-in and plugin tool registration surface |
| `tool_system/metadata.py` | Runtime tool lookup, validation, plugin resolution, and instance construction |
| `tool_system/runtime_context.py` | Shared runtime ContextVar for tool calls (including attachment scope) |
| `constants.py` | Shared constants, paths, and environment variable defaults |
| `error_handling.py` | User-friendly error message extraction |
| `authorization.py` | Sender and per-agent authorization checks |
| `thread_utils.py` | Thread analysis and agent detection |
| `session_ids.py` | Leaf helpers for the canonical persisted room/thread session ID |
| `thread_models.py` | Durable per-thread model overrides backing `!model` and the `thread_model` tool |
| `file_watcher.py` | File change detection for config hot-reload |
| `interactive.py` | Interactive Q&A system via Matrix reactions |
| `stop.py` | StopManager for cancelling in-progress responses |
| `topic_generator.py` | AI-generated room topics |
| `cli/main.py` | Main CLI entry point (Typer app) |
| `cli/banner.py` | CLI startup banner |
| `cli/config.py` | Config subcommand logic |
| `cli/connect.py` | `mindroom connect` pairing helpers and owner placeholder replacement |
| `cli/doctor.py` | Doctor command implementation |
| `cli/local_stack.py` | Local stack setup command |
| `credentials_sync.py` | Shared provider/bootstrap env to credentials sync |
| `logging_config.py` | Structured logging setup |
| `knowledge/utils.py` | Multi-knowledge-base vector DB utilities |

**Persistent state** lives under `mindroom_data/` (next to `config.yaml`, overridable via `MINDROOM_STORAGE_PATH`):
- `sessions/` – Per-agent SQLite event history for Agno conversations
- `learning/` – Per-agent Agno Learning preference data
- `chroma/` – ChromaDB storage backing the memory system
- `knowledge_db/` – Knowledge base vector stores for file-backed RAG
- `tracking/` – Durable handled-turn ledger plus exact callback obligations and compact terminal tombstones
- `credentials/` – JSON secrets synchronized from `.env`
- `encryption_keys/` – Matrix E2E encryption keys
- `sync_continuity/` – Crash-atomic Matrix checkpoints and pending join decrypt fences
- `culture/` – Shared culture state
- `logs/` – Log files
- `matrix_state.yaml` – Matrix sync state

### SaaS Platform (`saas-platform/`)
- **Platform Backend**: Modular FastAPI app with routes in `saas-platform/platform-backend/src/backend/routes/`
- **Platform Frontend**: Next.js 15 with centralized API client in `saas-platform/platform-frontend/src/lib/api.ts`
- **Authentication**: SSO via HttpOnly cookies across subdomains
- **Deployment**: Kubernetes with Helm charts, dual-mode support (platform/standalone)
- **Database**: Supabase with comprehensive RLS policies

### Repo Layout

| Path | Purpose |
|------|---------|
| `src/mindroom/` | Core agent runtime (Matrix orchestrator, routing, memory, tools) |
| `frontend/` | Core MindRoom dashboard (Vite + React) |
| `saas-platform/platform-backend/` | SaaS control-plane API (FastAPI) |
| `saas-platform/platform-frontend/` | SaaS portal UI (Next.js 15) |
| `saas-platform/supabase/` | Supabase migrations, policies, seeds |
| `cluster/` | Terraform + Helm for hosted deployments |
| `local/` | Docker Compose helpers for local dev stacks |

### Ecosystem Repositories

MindRoom also maintains related repositories under `github.com/mindroom-ai`:
- `mindroom-element` - our Element fork for MindRoom message UX: collapsible tool-trace rendering, `!` command autocomplete synced from backend commands, long-text sidecar hydration, AI run metadata tooltip, and MindRoom branding/thread-first defaults. See `README.md` and `FORK_CHANGES.md` in that repo.
- `synapse` - our Synapse fork for MindRoom streaming workloads: optional compact-edit collapsing for superseded `m.replace` events across `/sync`, Sliding Sync, pagination, and context responses, plus `/versions` advertisement via `org.mindroom.compact_edits` and fork-owned Docker/CI flows. See `README.md` and `FORK_CHANGES.md`.
- `mindroom-librechat` - our LibreChat fork that parses MindRoom inline `<tool>` / `<tool-group>` tags into native `ToolCall` cards so tool execution stays server-side; also includes fork Docker CI and MindRoom-specific UX additions. See `README.md` and `.mindroom/` docs (`fork-context.md`, `tool-tag-rendering.md`).
- `mindroom-chat` - our AI-native Matrix client, built on Cinny and optimized for MindRoom agent workflows with MindRoom branding/default homeserver config, subpath deployment support (runtime/build base path for `/mindroom`), and thread/auth/sidebar UX refinements. See `README.md` and `FORK_CHANGES.md`.
- `mindroom-stack` - a full Docker Compose reference stack that boots the published MindRoom backend/frontend, a Tuwunel Matrix homeserver, and the MindRoom client together, including first-login and model/API-key setup guidance.

In this dev environment, many of these repositories are cloned in the parent directory (`../`) and can be inspected directly.

### Configuration Model

The authoritative config is `config.yaml`, loaded via Pydantic models in `src/mindroom/config/` (root model in `src/mindroom/config/main.py`):

```yaml
agents:
  code:
    display_name: CodeAgent
    role: Generate code, manage files, execute shell commands
    model: sonnet
    tools: [file, shell]
    instructions:
      - Always read files before modifying them.
    rooms: [lobby, dev]
    knowledge_bases: [engineering_docs]

defaults:
  tools: [scheduler]
  markdown: true
  enable_streaming: true

memory:
  backend: mem0

plugins: []

room_models: {}

bot_accounts: []

models:
  default:
    provider: anthropic
    id: claude-sonnet-5
  sonnet:
    provider: anthropic
    id: claude-sonnet-5

router:
  model: default

teams:
  super_team:
    display_name: Super Team
    role: Collaborative engineering assistant
    agents: [code]
    mode: collaborate

cultures:
  engineering:
    description: Follow clean code principles and write tests
    agents: [code]
    mode: automatic

knowledge_bases:
  engineering_docs:
    path: ./knowledge_docs
    watch: true

voice:
  enabled: false
  stt:
    provider: openai
    model: gpt-4o-transcribe

mindroom_user:
  username: mindroom_user
  display_name: MindRoomUser

matrix_room_access:
  mode: single_user_private
  multi_user_join_rule: public
  publish_to_room_directory: false
  invite_only_rooms: []
  reconcile_existing_rooms: false

authorization:
  default_room_access: false
  global_users:
    - __MINDROOM_OWNER_USER_ID_FROM_PAIRING__
  agent_reply_permissions:
    "*":
      - __MINDROOM_OWNER_USER_ID_FROM_PAIRING__

timezone: America/Los_Angeles
```

**Hot reloading**: `config.yaml` changes are watched at runtime. The orchestrator diffs configs, gracefully restarts affected agents, and rejoins rooms without bringing down the stack.

### Memory System

Mem0 memory (`src/mindroom/memory/functions.py`):
- **Agent memory** (`agent_<name>`) – Personal preferences, coding style, tasks
- **Team memory** – Shared context for team collaboration

### Teams & Collaboration

Teams (`src/mindroom/teams.py`) let multiple agents work together:
- **coordinate**: Lead agent orchestrates others
- **collaborate**: All members respond in parallel with consensus summary

## 1. Core Philosophy

- **Embrace Change, Avoid Backward Compatibility**: This project has no end-users yet. Prioritize innovation and improvement over maintaining backward compatibility.
- **Simplicity is Key**: Implement the simplest possible solution. Avoid over-engineering or generalizing features prematurely.
- **Focus on the Task**: Implement only the requested feature, without adding extras.
- **Functional Over Classes**: Prefer a functional programming style for Python over complex class hierarchies.
- **Keep it DRY**: Don't Repeat Yourself. Reuse code wherever possible.
- **Be Ruthless with Code Removal**: Aggressively remove any unused code, including functions, imports, and variables.
- **Prefer dataclasses**: Use `dataclasses` that can be typed over dictionaries for better type safety and clarity.
- **Documentation Line Style**: In Markdown docs, write one sentence per line, and never split a single sentence across multiple lines.
- Do not wrap things in try-excepts unless it's necessary. Avoid wrapping things that should not fail.
- NEVER put imports in the function, unless it is to avoid circular imports or to keep a heavy import (for example a provider SDK) out of module import time. Prefer an explicit function-level `from x import Y` with `# noqa: PLC0415` over dynamic `import_module` indirection. Imports should be at the top of the file.
- `tests/test_import_graph.py` pins the complete third-party import surface of slim CLI, config, tool-registry, and sandbox entry points, and bans heavy optional provider, storage, and ML/data dependencies from the primary runtime. If it fails on your change, defer the new import to first use; extend the allowlist only when the dependency is genuinely needed at import time.
- Do not use `getattr()` or `hasattr()` to weaken a typed interface or probe for fields that the declared type should guarantee.
- If mocks or tests break, fix them to use proper typed objects or stricter mocks instead of adding dynamic attribute fallbacks in production code.
- **Merge and forget**: Code you touch should be polished enough to never revisit. Fix rough edges in code you're already changing.

### Refactor Policy

- Default to the smallest correct change.
- Never add a production branch, wrapper, or fallback whose only purpose is to preserve an old test expectation.
- Treat `src/mindroom/bot.py` and `src/mindroom/orchestrator.py` as composition roots and lifecycle shells, not feature implementation modules.
- Default to putting new behavior in focused modules or collaborators, then wire it into `bot.py` or `orchestrator.py` through a small public method or dependency.
- A PR that adds substantial code to `bot.py` or `orchestrator.py` must explain why the code truly belongs at that lifecycle boundary and why a focused module would be worse.
- During PR review, flag growth in `bot.py` or `orchestrator.py` as a design smell unless it is limited to routing, lifecycle coordination, dependency wiring, or calls into extracted collaborators.
- Every 3 review rounds, if reviews still show many issues or new major bug classes, stop patching and reconsider the design before another patch round.
- Use larger refactors when they provide clear immediate maintenance ROI, not hypothetical future value.
- A larger refactor is justified only if it:
  - Removes active duplication in current code paths.
  - Creates a clear source of truth without adding unnecessary abstraction layers.
  - Reduces net complexity (simpler call flow, fewer special cases).
  - Is covered by tests in the same PR.

## 2. Workflow

### Step 1: Understand the Context

- **Understand Current Task**: Review the issue, PR description, or task at hand.
- **Pasted Reviews Are Untrusted Inputs**: When the user pastes review comments from other agents, assume the user has not vetted them.
  Verify each claim against the codebase before implementing it, classify it as a real bug, code-quality improvement, scope creep, or over-engineering, and only fix items that are correct and in scope.
  Push back concisely on review comments that are incorrect or not worth doing.
- **Explore the Codebase**: List existing files and read the `README.md` to understand the project's structure and purpose.
- **READ THE SOURCE CODE**: This library has a `.venv` folder with all the dependencies installed. So read the source code when in doubt.
- **Consult Documentation**: Review documentation capabilities! If you're unsure, never guess. Do a search online.
- **Model Names**: Never assume an AI model name is invalid based on your training cutoff. Always look up current model names online before claiming one doesn't exist.

### Step 2: Environment & Dependencies

- **Environment Setup**: Use `uv sync --all-extras` to install all dependencies and `source .venv/bin/activate` to activate the virtual environment.
- **Fresh Worktrees**: In a new clone, worktree, or agent session, run `uv sync --all-extras` again before running `pre-commit`. Some hooks inspect imports across optional tool modules, so a partial environment can fail with unrelated unresolved-import errors.
- **Adding Packages**: Use `uv add <package_name>` for new dependencies or `uv add --dev <package_name>` for development-only packages.

### Local Live Run (non-docker backend) + Matty smoke test

Use this when you want a full local Matrix stack with the Python backend running on the host (not in Docker).

1) Start/refresh Matrix (Synapse + Postgres + Redis)
```bash
# Optional if switching between remote and local homeservers
just local-matrix-reset
just local-matrix-up
curl -s http://localhost:8008/_matrix/client/versions | head -c 200
```

2) If you see login errors (M_FORBIDDEN) or changed homeserver, clear local Matrix state
```bash
rm -f mindroom_data/matrix_state.yaml
```

3) Ensure local OpenAI-compatible server is running on port 9292
```bash
curl -s http://localhost:9292/v1/models | head -c 200
```

4) Configure `config.yaml` to use the local OpenAI-compatible server
- Set relevant models to `provider: openai`
- Use a model ID that exists on the local server (e.g., `gpt-oss-low:20b`)
- Add `extra_kwargs.base_url: http://localhost:9292/v1` for those models
- For memory, prefer `provider: openai` and an embedding model that exists (e.g., `embeddinggemma:300m`)

5) Run the backend with explicit env overrides (use Python 3.13; production Dockerfile also uses 3.13)
```bash
MATRIX_HOMESERVER=http://localhost:8008 \
MATRIX_SSL_VERIFY=false \
OPENAI_BASE_URL=http://localhost:9292/v1 \
OPENAI_API_KEY=sk-test \
UV_PYTHON=3.13 \
uv run mindroom run
```

6) Wait for API health, then for rooms to appear (room creation uses AI topics)
```bash
curl -s http://localhost:8765/api/health
MATRIX_HOMESERVER=http://localhost:8008 MATRIX_SSL_VERIFY=false \
uv run --python 3.13 matty rooms
```

7) Matty smoke test (agent reply in a thread)
```bash
MATRIX_HOMESERVER=http://localhost:8008 MATRIX_SSL_VERIFY=false \
uv run --python 3.13 matty send "Lobby" "Hello @general please reply with pong."

MATRIX_HOMESERVER=http://localhost:8008 MATRIX_SSL_VERIFY=false \
uv run --python 3.13 matty threads "Lobby"

MATRIX_HOMESERVER=http://localhost:8008 MATRIX_SSL_VERIFY=false \
uv run --python 3.13 matty thread "Lobby" t1
```

### Hosted Matrix Run (`uvx`) + Pairing

Use this when Matrix + chat UI are hosted and only the MindRoom backend runs locally.

1) Initialize local config with hosted defaults
```bash
uvx mindroom config init --profile public
```

2) Add at least one model provider key in `~/.mindroom/.env` (for example `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`)

3) Generate a pair code in `https://chat.mindroom.chat` (`Settings -> Local MindRoom`) and pair locally
```bash
uvx mindroom connect --pair-code ABCD-EFGH
```

4) Start MindRoom
```bash
uvx mindroom run
```

`mindroom connect` writes `MINDROOM_LOCAL_CLIENT_ID` and `MINDROOM_LOCAL_CLIENT_SECRET` to `~/.mindroom/.env` by default (unless `--no-persist-env` is used) and auto-replaces owner placeholder tokens in `config.yaml` and every file it pulls in via `!include` when `owner_user_id` is returned.

### SaaS Platform Commands

#### Development
```bash
# Platform Backend
cd saas-platform/platform-backend
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Platform Frontend
cd saas-platform/platform-frontend
bun install && bun run dev
```

#### Deployment
```bash
# Set kubeconfig path
export KUBECONFIG=./cluster/terraform/terraform-k8s/mindroom-k8s_kubeconfig.yaml

# Deploy platform
helm upgrade --install platform ./cluster/k8s/platform -f cluster/k8s/platform/values.yaml --namespace mindroom-staging

# Deploy instance - ALWAYS use the provisioner API:
./cluster/scripts/mindroom-cli.sh provision 1

# The provisioner handles everything:
# - Creates database records
# - Manages secrets securely
# - Deploys via Helm with proper values
# - Tracks status

# Manual Helm deployment (debugging only, not for production):
# helm upgrade --install instance-1 ./cluster/k8s/instance \
#   --namespace mindroom-instances \
#   -f values-with-secrets.yaml  # Never commit this file!

# Quick redeploy of MindRoom backend (updates all instances)
./saas-platform/redeploy-mindroom.sh

# Deploy platform frontend or backend
./saas-platform/deploy.sh platform-frontend  # Build, push, and deploy frontend
./saas-platform/deploy.sh platform-backend   # Build, push, and deploy backend

# Use the CLI helper for common operations
./cluster/scripts/mindroom-cli.sh status
./cluster/scripts/mindroom-cli.sh list
./cluster/scripts/mindroom-cli.sh logs 1
```

### Step 3: Development & Git

- **Check for Changes**: Before starting, review the latest changes from the main branch with `git diff origin/main | cat`. Make sure to use `--no-pager`, or pipe the output to `cat`.
- **Commit Frequently**: Make small, frequent commits.
- **Atomic Commits**: Ensure each commit corresponds to a tested, working state.
- **Preserve Review History**: Do not amend commits or force-push PR branches unless the user explicitly asks for it. Prefer follow-up commits so PR history stays inspectable.
- **Targeted Adds**: **NEVER** use `git add .`. Always add files individually (`git add <filename>`) to prevent committing unrelated changes.

### Step 4: Testing & Quality

- **Test Before Committing**: **NEVER** claim a task is complete without running `pytest` to ensure all tests pass.
- **Test at the Owning Seam**: New behavior tests target the collaborator seam that owns the behavior (`ResponseRunner`, `DeliveryGateway`, `TurnPolicy`, `TurnController`, `CoalescingGate`, `IngressValidator`, `EditRegenerator`), not the `AgentBot`/`TeamBot` facade.
  Fake collaborators through the conftest seam installers (`install_generate_response_mock`, `install_send_response_mock`, `replace_edit_regenerator_deps`) or the `*Deps` dataclasses, never by assigning mocks onto bot attributes.
  A test needing more than 3 patches is a smell that it is testing through the wrong seam.
  Genuinely end-to-end tests belong in the slim integration files (`test_multi_agent_bot.py`, `test_threading_error.py`), which stay small by design.
- **Run Pre-commit Hooks**: After `uv sync --all-extras`, run `uv run pre-commit run --all-files` before committing to enforce code style and quality.
- **Update Tach Boundaries in the Same PR**: If your PR changes a Tach-governed boundary, update `tach.toml` in the same PR, follow the guidance in the comment at the top of that file, and run `uv run tach check --dependencies --interfaces`.
- **Handle Linter Issues**:
  - **False Positives**: The linter may incorrectly flag issues in `pyproject.toml`; these can be ignored.
  - **Test-Related Errors**: If a pre-commit fix breaks a test (e.g., by removing an unused but necessary fixture), suppress the warning with a `# noqa: <error_code>` comment.

### Step 5: Refactoring

- **Be Proactive**: Continuously look for opportunities to refactor and improve the codebase for better organization and readability.
- **Incremental Changes**: Refactor in small, testable steps. Run tests after each change and commit on success.

### Step 6: Viewing the Widget

- **Taking Screenshots**: To view the dashboard without Jupyter, use `python frontend/take_screenshot.py` from the project root.
- **Manual Screenshot**: From the frontend directory, run `bun run dev` to start the development server, then run `bun run screenshot` in another terminal.
- **Screenshot Location**: Screenshots are saved to `frontend/screenshots/` with timestamps.
- **Use Cases**: This is helpful for visual verification, documentation, and sharing the dashboard appearance.

### Developer Automation (`justfile`)

Common `just` recipes for development:
```bash
# Local stacks
just local-matrix-up              # Boot Synapse + Postgres dev stack
just local-platform-compose-up    # Full SaaS sandbox

# Testing (IMPORTANT: enter the Node.js 24 `nix-shell shell.nix` first on NixOS hosts)
# If `uv run pytest` fails with 'module mindroom has no attribute bot',
# use the repo dev shell so `libstdc++.so.6` is available:
nix-shell shell.nix
# Then run commands normally inside the shell, for example:
uv run pytest tests/<file>.py -x -n 0 --no-cov -v
# If `<nixpkgs>` is unresolved, use:
nix-shell -I nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos shell.nix
# Or if the above works, you can also use:
just test-backend                 # Run pytest for core (may need nix-shell wrapper)
just test-saas-backend            # Run pytest for SaaS backend

# Deployment
just cluster-helm-template        # Render platform chart manifests
just cluster-helm-lint            # Lint platform chart
```

## 3. Critical "Don'ts"

- **DO NOT** manually edit the CLI help messages in `README.md`. They are auto-generated.
- **NEVER** use `git add .`.
- **NEVER** claim a task is done without passing all `pytest` tests.

## 4. Interacting with MindRoom Agents via Matty CLI

### Overview
Matty is a Matrix CLI client that allows you to interact with MindRoom AI agents. Use it to send messages and observe agent responses during development and testing.

### Prerequisites
```bash
# Matty is optional and is not installed by `uv sync --all-extras`
# Activate an environment where Matty is installed
source .venv/bin/activate
# If Matty is unavailable, use the authenticated raw Matrix client API
```

### Configuration
The Matrix credentials are already configured in the project's `.env` file. Matty will automatically use these credentials.

### Essential Commands for Agent Interaction

#### 1. List Rooms
```bash
matty rooms  # or: matty r
```

#### 2. View Messages (See Agent Responses)
```bash
matty messages "room_name" --limit 20  # or: matty m "room_name" -l 20
```

#### 3. Send Messages to Agents
```bash
# Direct message
matty send "room_name" "Hello @assistant!"

# Multiple agent mentions
matty send "room_name" "@research @analyst analyze this topic"
```

#### 4. Work with Threads (Agents respond in threads)
```bash
# List threads in a room
matty threads "room_name"

# View thread messages (where agents typically respond)
matty thread "room_name" t1  # View thread with ID t1

# Start a thread (agents will respond here)
matty thread-start "room_name" m2 "Starting discussion with agents"

# Reply in thread
matty thread-reply "room_name" t1 "@assistant continue"
```

### Typical Agent Testing Workflow
```bash
# 1. Find the test room
matty rooms

# 2. Send a message mentioning agents
matty send "test_room" "@assistant What can you do?"

# 3. Check for agent response (agents respond in threads)
matty threads "test_room"
matty thread "test_room" t1  # View the thread where agent responded

# 4. Continue conversation in thread
matty thread-reply "test_room" t1 "@research find information about X"
```

### Important Notes
- **Agents respond in threads**: Always check threads after sending messages
- **Use @mentions**: Tag agents with @ to get their attention
- **Message handles**: Use m1, m2, m3 to reference messages
- **Thread IDs**: Use t1, t2, t3 to reference threads (persistent across sessions)
- **Output formats**: Add `--format json` for machine-readable output
- **Streaming responses**: If you see "⋯" in agent messages, they're still typing. Agents stream responses by editing messages, which may take 10+ seconds to complete. Re-check the thread after waiting.

## 5. Quick Reference

```bash
# Preflight check before running
mindroom doctor

# Run the stack
uv run mindroom run --storage-path mindroom_data

# Pair local install with hosted provisioning
mindroom connect --pair-code ABCD-EFGH

# Bootstrap local Synapse + MindRoom Chat (Docker)
mindroom local-stack-setup --synapse-dir /path/to/mindroom-stack/local/matrix

# Update credentials
# Edit .env for provider/bootstrap keys and restart; configure tool credentials through the dashboard or persisted tool config

# Discover commands
# Send !help from any bridged room

# Debug logging
mindroom run --log-level DEBUG  # Surface routing decisions, tool calls, config reloads
```

Inspect agent traces: `mindroom_data/sessions/<agent>.db`

## 6. Releases

Use `gh release create` to create releases. The tag is created automatically.

```bash
# IMPORTANT: Ensure you're on latest origin/main before releasing!
git fetch origin
git checkout origin/main

# Check current version
git tag --sort=-v:refname | head -1

# Create release (minor version bump: v0.2.2 -> v0.3.0)
gh release create v0.3.0 --title "v0.3.0" --notes "release notes here"
```

Versioning:
- **Patch** (v0.2.2 -> v0.2.3): Bug fixes
- **Minor** (v0.2.3 -> v0.3.0): New features, non-breaking changes

Write release notes manually describing what changed. Group by features and bug fixes.

# Important Instruction Reminders
Do what has been asked; nothing more, nothing less.
NEVER create files unless they're absolutely necessary for achieving your goal.
ALWAYS prefer editing an existing file to creating a new one.
