Stop maintaining a folder of
agent.mdfiles. Agents should be long-lived services — pointed at a workspace, sandboxed, watched, backed up, audited — like any other piece of infrastructure on your box. ritsu is the multi-agent server that makes them that.
Name: 律 (ritsu), from 自律 (jiritsu, "autonomous / self-governing").
Important
ritsu is built for your lab — not the open internet, yet. Every /admin
route is gated by a bearer admin token (bootstrapped on first run,
mode 0600 on disk), and MCP is gated by rt_* bearer tokens or full
OAuth 2.1 + DCR + PKCE — the auth is real. But the threat model still
assumes you're behind a private network boundary (Tailscale, WireGuard,
your home LAN); the network ACL is defence-in-depth on top of the
bearer auth, not a substitute for it.
For a remote-but-still-authenticated path, front the admin port with
Tailscale Funnel, Cloudflare Tunnel + Access, or a reverse proxy that
enforces auth on its own. Don't bind 0.0.0.0 on the admin port
without thinking about what's in front of it.
Most AI-agent tooling treats agents as ephemeral. You hand them a prompt, they do a task, they vanish. Fine for one-shot scripts. Falls apart the moment you want an agent that persists — one that remembers what you told it last week, watches its own folder for new inputs, and runs in the background while you do other things.
ritsu treats agents as infrastructure. Each one is a long-running service on your box, with:
- A real working directory. Point an agent at a folder on your NAS, snapshot that folder nightly, and even a misbehaving agent can't take you down — the systemd sandbox + per-tool permission gate fail closed before damage spreads. Workspace contents stay backed up like any other directory.
- Real long-term memory. Not a
.mdfile you re-paste into every conversation — a SQLite-backed store you can inspect, edit, supersede, and audit from the admin UI. Agents remember preferences and decisions across sessions because the storage outlives the chat window. - Hierarchy. Agents with the
manage_agentscapability can mint, edit, or reload other agents. Agents withmonitor_agentsget read-only inspection across the whole swarm — every conversation, every memory, every active thread. Build supervisor / sub-agent patterns the same way you'd compose any other set of long-running processes. - MCP from anywhere. Any client that speaks the Model Context Protocol can talk to any agent. Claude Code, Claude Desktop, Cursor, curl. OAuth 2.1 + DCR + PKCE + RFC 8707 audience binding for spec-compliant clients;
rt_*bearer tokens for header-based clients.
What ritsu kills:
- The
agents/*.mdfolder you re-paste into prompts every conversation - Memory that evaporates on tab close
- Agents running with your full filesystem permissions
- Agent-to-agent coordination via copy-paste
What's baked in:
- Per-agent isolation enforced before tools fire.
tools_allowlist+ per-path workspace permissions go through the SDK'scanUseToolcallback. An agent with noBashand no writable workspace can't exfiltrate files even if perfectly socially-engineered. - AES-256-GCM secrets at rest. Bot tokens, API keys; master key separable from the DB dir.
- Strict CSP, audit log, OAuth 2.1 + DCR + PKCE + RFC 8707 — full posture in
docs/threat-model.md. - Two runtimes.
claude-sdk(Max plan via@anthropic-ai/claude-agent-sdk, $0 per turn) orritsu-agent(your API key against any OpenAI-compatible provider — OpenAI, OpenRouter, local LiteLLM, etc.). Same tools, same memory, same UI.
npm install
claude login # one-time, reads ~/.claude/.credentials.json
cp .env.example .env # sets RITSU_ADMIN_TOKEN_FILE=./data/.admin-token for dev
mkdir -p data # bootstrap path; the server refuses to start without it
npm run dev # MCP on :7333, admin on :7334On first start the server mints a bootstrap admin token and writes the
plaintext to ./data/.admin-token (mode 0600). Read it:
cat data/.admin-tokenThen open http://127.0.0.1:7334/admin, paste that token when prompted, and you're in. From there you can mint per-client MCP tokens in the Tokens tab.
# health
curl -s http://localhost:7333/healthz
# call an agent (use the rt_ token you minted, not the admin bootstrap one)
curl -s -X POST http://localhost:7333/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Authorization: Bearer rt_YOUR_MCP_TOKEN' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ask_agent","arguments":{"agent_id":"hello-world","message":"hi"}}}'For the full walkthrough (create a real agent, wire Claude Code to it,
inspect the conversation from the admin UI), see
docs/getting-started.md. For drop-in
agent definitions you can paste in, see docs/examples/.
docker compose up --build # ritsu only
docker compose --profile litellm up # adds LiteLLM sidecar on :4000Bind-mounts the host's ~/.claude/ so the Max-plan dispatcher works without an API key. State persists in the ritsu-data named volume. Both ports bound to 127.0.0.1 by default.
| Surface | What it does |
|---|---|
GET /mcp (port 7333) |
Real @modelcontextprotocol/sdk Streamable HTTP transport. Bearer-token-gated (rt_*). |
POST /oauth/{register,authorize,token} |
RFC 7591/8414/9728/8707 OAuth 2.1 + DCR + PKCE for spec-compliant clients (claude.ai web, Claude Desktop Connectors). |
/admin (port 7334) |
Tabbed UI: Dashboard, Agents, Workspaces, Memories, Conversations, Tools, MCP, Channels, Tokens, API Keys, OAuth Clients, Logs, Audit. |
GET /healthz, /readyz, /version |
Liveness / readiness. |
GET /metrics |
Prometheus exposition. |
ritsu CLI |
Operator commands: service, env, path, token, admin-token, url, doctor. Full reference: docs/cli.md. |
list_agents, ask_agent, read_agent_memory, create_agent, update_agent, reload_agent
Each agent additionally has these in-process per-agent MCP tools:
mcp__memory__{remember,update_memory,forget,list_memories}— agent-scoped CRUDmcp__agent_comms__{ask_agent,list_agents}— gated bycan_callallowlist, depth-3 loop guardmcp__agent_admin__*— only if the agent has themanage_agentscapabilitymcp__agent_monitor__*— only if the agent has themonitor_agentscapability
Telegram is the only kind today. One bot ↔ one operator agent ↔ one bound chat (no group spam). Bot tokens are AES-256-GCM encrypted at rest. Discord / Slack land later; the kind enum is the extension point.
src/
index.ts two ports, schema bootstrap, admin token bootstrap
db.ts openDatabase + additive migrations
mcp-server.ts MCP HTTP surface (SDK transport, OAuth, bearer auth)
agent-host.ts live agent map; addOrReplace/remove
agent-definition-store.ts CRUD over agent_definitions
workspace-store.ts per-agent filesystem roots + permissions
memory-store.ts, conversation-store.ts
channels/ channel registry + telegram impl
auth/ token-store, api-key-store, oauth-store, oauth-routes
admin/ express CRUD + UI (ui.html / app.js / app.css)
agents/ AgentBase, GenericAgent, type registry
model/ dispatcher interface, claude-direct, litellm, ritsu-agent
tools/
mcp-internal/ per-agent in-process MCP servers (memory + comms + admin + monitor)
ritsu-agent/ native FS/process/network tools for the ritsu-agent runtime
permissions.ts shared tool→permission map + checkToolUse
cli/ `ritsu` operator CLI
util/ logger (with redaction), dotenv-lite, secret-crypto (AES-256-GCM)
systemd/ ritsu.service + litellm-proxy.service
scripts/ install.sh / configure.sh / update.sh / bootstrap-remote.sh
npm test # node:test, in-memory SQLite, fake dispatcher (152 tests)
npm run typecheck
npm run lint # ESLint v9 flat config, type-aware
npm run build
npm run test:coverage # c8 → coverage/lcov.info (consumed by SonarCloud)See DEPLOY.md. Two paths:
scripts/bootstrap-remote.sh— drive an install over SSH from your laptop. Recommended.scripts/install.sh— run on the target host directly.
Targets a Linux + systemd box. Footprint ~200 MB working set. Tailscale-anchored is the documented happy path; the tailnet ACL is the outer auth boundary.
See SECURITY.md (reporting + posture summary) and docs/threat-model.md (deep-dive on the adversary model) for the full posture. One-line summary of what's baked in:
- No public exposure by default (
127.0.0.1binds; tailnet ACL is the outer auth boundary). - Scoped bearer tokens (
rt_*MCP,rat_*admin) — sha256 at rest, optionalexpires_at, audit-logged. - OAuth 2.1 + DCR + PKCE + RFC 8707 audience binding for spec-compliant clients.
- AES-256-GCM secrets at rest with master key separation (
RITSU_MASTER_KEYenv or/etc/ritsu/master-key). - Per-agent isolation enforced by the SDK's
canUseToolBEFORE tools fire. - Strict CSP (
script-src 'self'; style-src 'self'),X-Frame-Options: DENY, body-size + per-IP rate limits. - Full audit trail: per-tool MCP calls in
mcp_token_usage, per-admin-action mutations inadmin_auditwith body sha256.
Reporting: see SECURITY.md#reporting.