diff --git a/README.md b/README.md
index 0de6c1e..aca4a57 100644
--- a/README.md
+++ b/README.md
@@ -1,233 +1,60 @@
# Solrac
-> A self-hosted, transparent, hackable Claude-Code-style agent that lives in a Bun process, listens to Telegram, and uses Anthropic's Claude Agent SDK for thinking and tool use. Think of it as your own open Claude — on your machine, with your filesystem, your MCP servers, your tools, your audit log.
+> A self-hosted, hackable personal Agent: free local Ollama by default, Claude Sonnet/Opus on demand via Anthropic's Claude Agent SDK. Reach it from Telegram or a browser; own every audit row, permission rule, and budget cap.
-## Is this for you?
-
-Solrac fits if **all** of the following are true:
-
-- You want a chat-driven agent that can read your code, run shell, and edit files — but you want to own every piece of the stack.
-- You're willing to trade convenience (no plug-and-play hosting, no UI) for transparency (a small, focused TypeScript codebase, four moving parts, full audit trail).
-- You're operating at one-user-or-few scale. If you need multi-tenancy or a UI, look elsewhere.
-
-If you'd rather use Claude Code's official Telegram plugin, that's a perfectly good choice — it's actively maintained and zero-setup. Solrac exists because we wanted custom permission rules, per-chat budget caps, an audit log we control, and a foundation extensible to email/Slack/scheduled jobs.
-
-**Operational dependencies:**
-
-- Bun ≥1.3.0 (runtime).
-- A Telegram bot token + your `from.id`.
-- An Anthropic API key (`@`/`!` paths).
-- A local **Ollama daemon + tools-capable model** for the recommended Ollama-default config (no-prefix routes to local). For Claude-only deploys, set `SOLRAC_DEFAULT_ENGINE=primary` instead.
-
-## Features
-
-- **Customizable persona via `SOUL.md` + `SOLRAC.md`** — two operator-editable markdown files at the launch directory. `SOUL.md` (voice, stance, safety) ships with the package and is read once at boot. `SOLRAC.md` (operator overlay: who runs it, channel posture, project context) is re-read every turn so live edits land on the next message without a restart. See [docs/USAGE.md#customizing-solrac-soulmd-and-solracmd](./docs/USAGE.md#customizing-solrac-soulmd-and-solracmd).
-- **Slash commands** — `/help`, `/status`, `/context`, `/clear`, `/compact` give the operator visibility and control over conversation context, spend, and session state without leaving Telegram. Both `/cmd` and `:cmd` invoke the same handler (`:` avoids Telegram's auto-link on bold text).
-- **Operator-defined skills** — drop a `SKILL.md` into `$SOLRAC_SKILLS_DIR//` and that filename becomes a slash command on the next boot. Tool-less single-turn prompts with `{{args}}` templating; tier defaults to `SOLRAC_DEFAULT_ENGINE` (free on Ollama deploys); cost-capped under the existing per-chat hourly budget. Optional `tool: true` frontmatter exposes the skill as a callable MCP tool to the local Ollama agent (Phase 1: `tier: ollama` only) so natural-language requests can route through your prompts. Off by default; enable with `SOLRAC_SKILLS_ENABLED=true`.
-- **Scheduled tasks** — drop a `TASK.md` into `$SOLRAC_TASKS_DIR//` and the prompt fires on its configured schedule (`every 1h`, `daily_at 09:00`, `at 2026-05-15T13:00:00Z`) into a configured chat. Engine inheritance (defaults to `config.defaultEngine`), per-task `max_cost_usd`, boot catch-up jitter; fires synthesize updates through the same turn queue so all existing safety machinery applies. `/tasks` lists loaded tasks with last + next fire; `/tasks run ` triggers on demand. Off by default; enable with `SOLRAC_TASKS_ENABLED=true`. See [docs/USAGE.md#scheduled-tasks](./docs/USAGE.md#scheduled-tasks).
-- **Multi-user, multi-chat** — gated by per-`from.id` allowlist.
-- **Three-tier permission policy** — auto-allow / auto-deny / Telegram-inline-keyboard-confirm. Configurable rule tables.
-- **Per-chat hourly cost cap** — sliding 60-minute window over the audit log. Default $1.00/chat/hour.
-- **Loop detector** — denies the third call to the same `(toolName, input)` within a turn. Order-insensitive over JSON keys.
-- **Persistent audit trail** — every turn (allowed, denied, queue-full) writes a SQLite row with prompt, response, tool calls, cost, tokens, session id, status, **and engine** (`claude:primary:` / `claude:secondary:` / `ollama:`).
-- **Local-first engine routing** — *Claude only when explicitly requested.* No-prefix messages route to local Ollama (free) by default; `@` escalates to Sonnet, `!` escalates to Opus. Pinable via `SOLRAC_DEFAULT_ENGINE` (`ollama` | `primary` | `secondary`) for Claude-only deploys. Boot validation rejects unreachable combinations.
-- **Local Ollama with tool support** — when `OLLAMA_TOOLS_ENABLED=true`, the local model (e.g. `gemma4:e4b`) calls the same `mcp__solrac__*` integrations the Claude tiers see. Multi-round tool loop with shared loop detector, broker UX, and iteration cap (`OLLAMA_MAX_TOOL_ITERATIONS=8`). Cross-engine context bridge means switching between local and Claude preserves the conversation thread.
-- **Dual-Claude tier routing** — `@` → primary tier (Sonnet by default), `!` → secondary tier (Opus by default). Each tier keeps its own SDK session id so prompt caching survives same-tier turns. Per-tier thinking-stub emoji (🙂 primary / 🤔 secondary) makes the routing visible in chat.
-- **Optional browser web UI** — a second `Bun.serve` instance on a configurable port serves a minimal vanilla-JS chat interface with the same agent loop, slash commands, engine routing, and tool-confirm UX as Telegram. Full markdown rendering (headers, lists, tables, fenced code) on both transports — Claude/Ollama responses get a server-side markdown→HTML pass for Telegram and the raw markdown to the browser. Off by default; enable with `SOLRAC_WEB_ENABLED=true` plus a token. See [docs/USAGE.md#web-ui-browser-interface](./docs/USAGE.md#web-ui-browser-interface).
-- **Session resume across restarts** — SDK session ids persisted per chat **and per tier**; conversations survive process death.
-- **Inline-keyboard confirm UX** — 60-second timeout, fail-closed on send failure, verdict stamped into chat history after tap.
-- **Bearer-gated `/stats` endpoint** — RSS, in-flight turns, 24h spend; `node:crypto.timingSafeEqual` constant-time auth.
-- **Daily cost report** — DM'd to allowlist's first entry; idempotent via meta-key check; UTC midnight window.
-- **Graceful shutdown** — SIGINT/SIGTERM aborts polling, drains in-flight turns (60s cap), checkpoints WAL, removes PID file, exits cleanly.
-- **Weekly auto-bounce** — systemd timer mitigates Bun long-uptime memory drift.
-- **DB-pollution defenses** — denial throttle (1 row per `from.id` per minute under flood), per-chat queue depth cap, prompt truncation with surrogate-pair safety.
-- **Sub-agent default-deny** — `Agent`/`Task` tools disabled at SDK + policy layers.
-- **Concurrency primitives** — per-chat `KeyedMutex`, global `Semaphore`, drain-aware `TurnTracker`.
-- **No HTTP framework, no Telegram framework runtime, no queue server, no Docker** — focused TypeScript, no hidden middleware.
+## Why Solrac
-## Quick start
+Solrac is a single-process Bun agent that bridges Telegram (and an optional browser UI) to a local Ollama model, escalating to Claude Sonnet or Opus only when you explicitly ask. It was built as part of [PNXStudios.com](https://pnxstudios.com) to manage a complex monorepo from anywhere — and, in doing so, to explore the mechanics of building a personal agent from first principles while enforcing hard cost controls and behavior auditing on every turn.
-### Install — packaged binary (macOS, Linux)
+It's deliberately smaller and narrower than other personal-assistant projects:
-```sh
-curl -fsSL https://cjus.dev/solrac/install.sh | sh
-```
+- **[OpenClaw](https://github.com/openclaw/openclaw)** — Node/TypeScript "Gateway" daemon with macOS/iOS/Android companion apps, Voice Wake, Live Canvas, and ~25 inbound channels.
+- **[Hermes Agent](https://github.com/NousResearch/hermes-agent)** (Nous Research) — Python, multi-provider, self-improving agent with seven execution backends and broad transport (Telegram/Discord/Slack/WhatsApp/Signal/Email/CLI).
+
+Both are broader and better-resourced. **Solrac's distinct value:**
-Drops a self-contained binary at `~/.solrac/bin/solrac` and (with sudo if needed) symlinks it onto `/usr/local/bin/solrac`. Add `ANTHROPIC_API_KEY`, `TELEGRAM_BOT_TOKEN`, and `ALLOWLIST_BOOTSTRAP` to `~/.solrac/.env`, then run `solrac`. Persona files (`SOUL.md`, `SOLRAC.md`) and the SQLite data dir land in `~/.solrac/` on first boot. Reinstalling never touches your customizations.
+- **Local-LLM-first economics.** No-prefix messages route to free Ollama; `@` and `!` are paid Claude escalations only on operator intent.
+- **Cost enforcement, not just visibility.** Sliding hourly USD caps that *deny* turns when hit, plus a daily cost-report DM.
+- **Audit-before-acting.** Every update (allowed, denied, queue-full) writes a row to one append-only SQLite table.
+- **Single-process minimalism.** No HTTP framework, no Telegram framework runtime, no queue server, no Docker, no sub-agents. A few thousand lines of TypeScript you can read in an afternoon and fork.
-Full reference: [docs/INSTALL.md](./docs/INSTALL.md).
+If you need multi-tenancy, voice wake, mobile companions, or 25 chat platforms, use OpenClaw or Hermes. If you want a small, cost-capped, fully audited foundation you can bend to your shape, Solrac fits.
-### Install — from source (developers)
+## Quick start
-If you have Bun and a Telegram bot:
+**Packaged binary** (macOS/Linux):
```sh
-git clone https://github.com/cjus/solrac.git
-cd solrac
-npm install
-cp .env.example .env # then fill in 3 required values
-npm run dev # starts on PORT (default 8443)
+curl -fsSL https://cjus.dev/solrac/install.sh | sh
```
-Then DM your bot. You should see a 🤔 stub within a second.
-
-If you don't have Bun, a Telegram bot, or an Anthropic API key — see [docs/SETUP.md](./docs/SETUP.md). Total walkthrough: ~20 minutes.
-
-**Engine routing — at a glance** (with the recommended `SOLRAC_DEFAULT_ENGINE=ollama`):
+**From source** (Bun required):
-| Prefix | Engine | Model env | Default |
-|--------|--------|-----------|---------|
-| (none) | Local Ollama (default) | `OLLAMA_MODEL` | `gemma4:e4b` (recommended) |
-| `@` | Primary Claude — escalate | `SOLRAC_PRIMARY_MODEL` | `claude-sonnet-4-6` |
-| `!` | Secondary Claude — heaviest | `SOLRAC_SECONDARY_MODEL` | `claude-opus-4-7` |
-
-There is no `>`-style escape prefix; a leading `>` is literal user text routed via the default engine. For Claude-only deploys, set `SOLRAC_DEFAULT_ENGINE=primary` (no-prefix → Sonnet) and `OLLAMA_ENABLED=false`. See [docs/USAGE.md#engine-routing-prefix-table](./docs/USAGE.md#engine-routing-prefix-table) and [docs/ARCHITECTURE.md#engine-routing](./docs/ARCHITECTURE.md#engine-routing).
+```sh
+git clone https://github.com/cjus/solrac.git
+cd solrac && npm install && cp .env.example .env
+npm run dev
+```
-**Optional — browser web UI.** Set `SOLRAC_WEB_ENABLED=true` and `SOLRAC_WEB_TOKEN=$(openssl rand -hex 32)` in `.env`; browse to `http://127.0.0.1:8080` for a chat interface with full markdown rendering. Bind `SOLRAC_WEB_HOST=0.0.0.0` to expose it on a LAN/Tailnet (token gates access). See [docs/USAGE.md#web-ui-browser-interface](./docs/USAGE.md#web-ui-browser-interface) and [docs/ARCHITECTURE.md#web-ui-transport-optional](./docs/ARCHITECTURE.md#web-ui-transport-optional).
+Need help with Bun, a Telegram bot, or an Anthropic API key? See [docs/SETUP.md](./docs/SETUP.md) (~20 min walkthrough). Full install reference at [docs/INSTALL.md](./docs/INSTALL.md).
## Documentation
| Doc | Audience | What it covers |
|-----|----------|---------------|
-| [docs/INSTALL.md](./docs/INSTALL.md) | Operators | curl-pipe install, `~/.solrac/` layout, upgrade & uninstall, building local binaries |
-| [docs/SETUP.md](./docs/SETUP.md) | First-time users | Bun install, Telegram bot creation, `from.id` lookup, Anthropic key, `.env`, first boot |
-| [docs/USAGE.md](./docs/USAGE.md) | Daily users | Concepts (turn / session / `from.id` vs `chat.id`), interaction patterns, permission UX, cost cap, loop detector |
-| [docs/CONFIG.md](./docs/CONFIG.md) | Operators | Full env-var reference: defaults, ranges, validation, secret-scrub rules |
-| [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) | Developers | Module map, data flow, SDK integration, concurrency, SQLite schema, three-tier policy, threat model, tricky seams |
-| [docs/OPERATIONS.md](./docs/OPERATIONS.md) | Operators | systemd deploy, `/health` and `/stats`, daily report, log events, audit queries, backups |
-| [docs/SCHEMA.md](./docs/SCHEMA.md) | Operators / debuggers | SQLite schema reference + query cookbook for debugging, forensics, performance, cross-engine analysis |
-| [docs/RUNBOOK.md](./docs/RUNBOOK.md) | On-call | Incident recovery: 409 conflict, drain timeout, runaway cost, OOM, db corruption, zombie poller, network drops |
-| [docs/GLOSSARY.md](./docs/GLOSSARY.md) | Everyone | Alphabetical reference for Solrac-specific terms |
-| [docs/ROADMAP.md](./docs/ROADMAP.md) | Maintainers | Step 9 webhook spec, 13 open questions, deferred enhancements |
-
-Auxiliary references in the source tree:
-
-- `docs/SDK_NOTES.md` — verified Claude Agent SDK surface, pinned to `0.2.119`
-- `deploy/systemd/README.md` — install commands for the three systemd units
-
-Web UI deep-dives are split across the existing docs:
-
-- [SETUP.md §11](./docs/SETUP.md#11-optional-enable-the-browser-web-ui) — turning it on, security notes
-- [USAGE.md "Web UI"](./docs/USAGE.md#web-ui-browser-interface) — feature parity with Telegram, markdown rendering
-- [CONFIG.md](./docs/CONFIG.md#variables) — the five `SOLRAC_WEB_*` env vars
-- [OPERATIONS.md "Web UI (optional)"](./docs/OPERATIONS.md#web-ui-optional) — boot verification, route reference, audit queries
-- [ARCHITECTURE.md "Web UI transport"](./docs/ARCHITECTURE.md#web-ui-transport-optional) — module map, markdown sidecar, anti-goal posture
-- [RUNBOOK.md "Web UI not reachable"](./docs/RUNBOOK.md#web-ui-issues) and ["streaming silent"](./docs/RUNBOOK.md#web-ui-stream-silent) — troubleshooting
-
-## Repository layout
-
-```
-solrac/
-├── README.md — you are here
-├── SOUL.md — voice + safety; canonical default copied to launch cwd
-├── SOLRAC.md — operator overlay template; copied to launch cwd
-├── package.json
-├── tsconfig.json
-├── .env.example — copy to .env
-│
-├── src/
-│ ├── log.ts JSON-to-stdout logger
-│ ├── config.ts env validation
-│ ├── db.ts bun:sqlite + prepared statements
-│ ├── allowlist.ts isAllowed / bootstrap
-│ ├── session.ts per-chat, per-tier session state
-│ ├── mutex.ts KeyedMutex with depth()
-│ ├── semaphore.ts counting global concurrency cap
-│ ├── turn-tracker.ts drain-aware in-flight set
-│ ├── queue.ts compose mutex + semaphore + tracker
-│ ├── telegram.ts raw fetch + tgCall + 429 retry
-│ ├── poll.ts long-poll + PID file + dedupe
-│ ├── policy.ts classifier, cost cap, broker, loop, hooks, engine-prefix parser
-│ ├── instance.ts SOUL.md / SOLRAC.md bootstrap + load
-│ ├── agent.ts Claude Agent SDK wiring (per-tier) + cross-engine bridge
-│ ├── ollama.ts local Ollama runner (default engine)
-│ ├── commands.ts slash command parser, dispatcher, handlers
-│ ├── skills.ts operator-defined SKILL.md discovery
-│ ├── skill-tools.ts expose tool:true skills to Ollama (ALS-propagated context)
-│ ├── scheduler.ts TASK.md discovery + tick loop
-│ ├── server.ts /health + /stats
-│ ├── lifecycle.ts graceful shutdown
-│ ├── daily-report.ts cost report cron
-│ ├── main.ts transport wiring + engine dispatch
-│ └── *.test.ts bun:test units
-│
-├── test/
-│ └── smokes/
-│ ├── harness.ts openTestDb / mkUpdate helpers
-│ ├── flood.ts db-pollution defense smoke
-│ └── ollama.ts live Ollama smoke
-│
-├── deploy/systemd/
-│ ├── solrac.service main long-running unit
-│ ├── solrac-bounce.service oneshot restart helper
-│ ├── solrac-bounce.timer weekly bounce schedule
-│ └── README.md install + verify
-│
-├── docs/ (this README's siblings)
-│ ├── SETUP.md
-│ ├── USAGE.md
-│ ├── CONFIG.md
-│ ├── ARCHITECTURE.md
-│ ├── OPERATIONS.md
-│ ├── RUNBOOK.md
-│ ├── GLOSSARY.md
-│ ├── ROADMAP.md
-│ └── SDK_NOTES.md
-│
-└── data/ gitignored
- ├── solrac.sqlite + WAL + SHM
- ├── solrac.pid PID file
- └── workspaces// per-chat agent cwd
-```
-
-## Stack
-
-- **Runtime:** [Bun](https://bun.sh) (≥1.3.0). Required for `bun:sqlite`, `bun:test`, and `Bun.serve`.
-- **Package manager:** npm.
-- **Agent SDK:** [`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) `0.2.119` (pinned exact, no caret).
-- **Database:** `bun:sqlite` with `journal_mode = WAL`.
-- **HTTP:** `Bun.serve` `routes` (no framework).
-- **Telegram client:** raw `fetch` + `tgCall` (no framework runtime; `@grammyjs/types` for types only).
-- **Process supervision:** systemd (`Type=simple`, `Restart=on-failure`, `TimeoutStopSec=90`, hardening via `NoNewPrivileges` / `ProtectSystem=strict` / `ProtectHome` / `PrivateTmp`).
-
-## Design philosophy
-
-Three commitments that shape every decision:
-
-1. **Own the host process.** No HTTP framework, no Telegram framework runtime, no queue server. Everything that touches a chat, a tool, or the database lives in this Bun process. We trade libraries for clarity at production-scale-of-one.
-2. **Audit before acting.** Every update — allowed, denied, queue-full — writes an `audit` row. The audit log is the source of truth for "what did the bot do today?"
-3. **Defense in depth.** Allowlist + three-tier classifier + cost cap + loop detector + db-pollution defenses + sub-agent default-deny. Each defense is independent.
-
-See [docs/ARCHITECTURE.md#philosophy](./docs/ARCHITECTURE.md#philosophy) for the full discussion.
-
-## What's intentionally not here
-
-See [docs/ARCHITECTURE.md#anti-goals](./docs/ARCHITECTURE.md#anti-goals) for the full list. Highlights:
-
-- No HTTP framework. No Telegram framework runtime. No queue server. No Docker.
-- No MarkdownV2 outbound (HTML's three escape characters beat MarkdownV2's twenty).
-- No Bedrock/Vertex auth (direct Anthropic only).
-- No sub-agents ([OQ#8](./docs/ROADMAP.md#oq8-sub-agent-enablement)).
-- No webhook transport ([Webhook transport](./docs/ROADMAP.md#webhook-transport)).
-
-If you want to revisit any of these, write the case in a PR description and treat it as an explicit reversal.
-
-## Testing
-
-```sh
-npm test # bun test
-npm run typecheck # tsc --noEmit
-npm run smoke:flood # synthetic db-pollution defense smoke
-npm run smoke:ollama # live Ollama smoke (requires Ollama on $OLLAMA_URL)
-```
-
-For live smokes against a dev bot, see [docs/RUNBOOK.md](./docs/RUNBOOK.md).
-
-## Origin
-
-Solrac was built as part of the [PNXStudios.com](https://pnxstudios.com) project to manage work on a complex monorepo from anywhere — Telegram in, code edits and shell out, with full audit and per-chat budget control. It's open-sourced as a complete, hackable foundation: the same TypeScript codebase that drives a real production workflow, with the building blocks — auditable agent loop, local-Ollama default with optional tool-calling, dual-Claude tier escalation, per-chat cost caps, three-tier permission policy, operator-defined skills — laid bare for anyone to read, run, fork, or extend to a different transport (email, Slack, scheduled jobs, in-house dashboards).
+| [docs/FEATURES.md](./docs/FEATURES.md) | Everyone | Complete feature list, grouped by theme |
+| [docs/INSTALL.md](./docs/INSTALL.md) | Operators | curl-pipe install, `~/.solrac/` layout, upgrade & uninstall |
+| [docs/SETUP.md](./docs/SETUP.md) | First-time users | Bun, Telegram bot, `from.id`, Anthropic key, first boot |
+| [docs/USAGE.md](./docs/USAGE.md) | Daily users | Concepts, interaction patterns, permission UX, cost cap, loop detector |
+| [docs/CONFIG.md](./docs/CONFIG.md) | Operators | Full env-var reference |
+| [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) | Developers | Module map, data flow, concurrency, schema, policy, threat model, philosophy, anti-goals |
+| [docs/OPERATIONS.md](./docs/OPERATIONS.md) | Operators | systemd deploy, `/health` & `/stats`, daily report, audit queries |
+| [docs/SCHEMA.md](./docs/SCHEMA.md) | Operators / debuggers | SQLite schema + query cookbook |
+| [docs/RUNBOOK.md](./docs/RUNBOOK.md) | On-call | Incident recovery: cost runaway, drain timeout, db corruption, … |
+| [docs/GLOSSARY.md](./docs/GLOSSARY.md) | Everyone | Solrac-specific terms |
+| [docs/ROADMAP.md](./docs/ROADMAP.md) | Maintainers | Open questions, deferred enhancements |
## Contact
diff --git a/docs/FEATURES.md b/docs/FEATURES.md
new file mode 100644
index 0000000..9c6a99c
--- /dev/null
+++ b/docs/FEATURES.md
@@ -0,0 +1,41 @@
+# Features
+
+The complete feature list, grouped by theme. See [../README.md](../README.md) for the project overview, motivations, and quick-start instructions.
+
+## Engines & routing
+
+- **Local-first engine routing** — *Claude only when explicitly requested.* No-prefix messages route to local Ollama (free) by default; `@` escalates to Sonnet, `!` escalates to Opus. Pinable via `SOLRAC_DEFAULT_ENGINE` (`ollama` | `primary` | `secondary`) for Claude-only deploys. Boot validation rejects unreachable combinations.
+- **Local Ollama with tool support** — when `OLLAMA_TOOLS_ENABLED=true`, the local model (e.g. `gpt-oss:20b`) calls the same `mcp__solrac__*` integrations the Claude tiers see. Multi-round tool loop with shared loop detector, broker UX, and iteration cap (`OLLAMA_MAX_TOOL_ITERATIONS=8`). Cross-engine context bridge means switching between local and Claude preserves the conversation thread.
+- **Dual-Claude tier routing** — `@` → primary tier (Sonnet by default), `!` → secondary tier (Opus by default). Each tier keeps its own SDK session id so prompt caching survives same-tier turns. Per-tier thinking-stub emoji (🦙 Ollama / 🙂 primary / 🤔 secondary) makes the routing visible in chat.
+
+## Persona, commands & extensions
+
+- **Customizable persona via `SOUL.md` + `SOLRAC.md`** — two operator-editable markdown files at the launch directory. `SOUL.md` (voice, stance, safety) ships with the package and is read once at boot. `SOLRAC.md` (operator overlay: who runs it, channel posture, project context) is re-read every turn so live edits land on the next message without a restart. See [USAGE.md#customizing-solrac-soulmd-and-solracmd](./USAGE.md#customizing-solrac-soulmd-and-solracmd).
+- **Slash commands** — `/help`, `/status`, `/context`, `/clear`, `/compact` give the operator visibility and control over conversation context, spend, and session state without leaving Telegram. Both `/cmd` and `:cmd` invoke the same handler (`:` avoids Telegram's auto-link on bold text).
+- **Operator-defined skills** — drop a `SKILL.md` into `$SOLRAC_SKILLS_DIR//` and that filename becomes a slash command on the next boot. Tool-less single-turn prompts with `{{args}}` templating; tier defaults to `SOLRAC_DEFAULT_ENGINE` (free on Ollama deploys); cost-capped under the existing per-chat hourly budget. Optional `tool: true` frontmatter exposes the skill as a callable MCP tool to the local Ollama agent (Phase 1: `tier: ollama` only) so natural-language requests can route through your prompts. Off by default; enable with `SOLRAC_SKILLS_ENABLED=true`.
+- **Scheduled tasks** — drop a `TASK.md` into `$SOLRAC_TASKS_DIR//` and the prompt fires on its configured schedule (`every 1h`, `daily_at 09:00`, `at 2026-05-15T13:00:00Z`) into a configured chat. Engine inheritance (defaults to `config.defaultEngine`), per-task `max_cost_usd`, boot catch-up jitter; fires synthesize updates through the same turn queue so all existing safety machinery applies. `/tasks` lists loaded tasks with last + next fire; `/tasks run ` triggers on demand. Off by default; enable with `SOLRAC_TASKS_ENABLED=true`. See [USAGE.md#scheduled-tasks](./USAGE.md#scheduled-tasks).
+
+## Transport
+
+- **Optional browser web UI** — a second `Bun.serve` instance on a configurable port serves a minimal vanilla-JS chat interface with the same agent loop, slash commands, engine routing, and tool-confirm UX as Telegram. Full markdown rendering (headers, lists, tables, fenced code) on both transports — Claude/Ollama responses get a server-side markdown→HTML pass for Telegram and the raw markdown to the browser. Off by default; enable with `SOLRAC_WEB_ENABLED=true` plus a token. See [USAGE.md#web-ui-browser-interface](./USAGE.md#web-ui-browser-interface).
+- **Multi-user, multi-chat** — gated by per-`from.id` allowlist.
+
+## Safety & audit
+
+- **Three-tier permission policy** — auto-allow / auto-deny / Telegram-inline-keyboard-confirm. Configurable rule tables.
+- **Per-chat hourly cost cap** — sliding 60-minute window over the audit log. Default $1.00/chat/hour.
+- **Loop detector** — denies the third call to the same `(toolName, input)` within a turn. Order-insensitive over JSON keys.
+- **Persistent audit trail** — every turn (allowed, denied, queue-full) writes a SQLite row with prompt, response, tool calls, cost, tokens, session id, status, **and engine** (`claude:primary:` / `claude:secondary:` / `ollama:`).
+- **Session resume across restarts** — SDK session ids persisted per chat **and per tier**; conversations survive process death.
+- **Inline-keyboard confirm UX** — 60-second timeout, fail-closed on send failure, verdict stamped into chat history after tap.
+- **Sub-agent default-deny** — `Agent`/`Task` tools disabled at SDK + policy layers.
+- **DB-pollution defenses** — denial throttle (1 row per `from.id` per minute under flood), per-chat queue depth cap, prompt truncation with surrogate-pair safety.
+
+## Operations
+
+- **Bearer-gated `/stats` endpoint** — RSS, in-flight turns, 24h spend; `node:crypto.timingSafeEqual` constant-time auth.
+- **Daily cost report** — DM'd to allowlist's first entry; idempotent via meta-key check; UTC midnight window.
+- **Graceful shutdown** — SIGINT/SIGTERM aborts polling, drains in-flight turns (60s cap), checkpoints WAL, removes PID file, exits cleanly.
+- **Weekly auto-bounce** — systemd timer mitigates Bun long-uptime memory drift.
+- **Concurrency primitives** — per-chat `KeyedMutex`, global `Semaphore`, drain-aware `TurnTracker`.
+- **No HTTP framework, no Telegram framework runtime, no queue server, no Docker** — focused TypeScript, no hidden middleware.