Mori (森) is a shared memory layer for AI coding agents — one that compounds.Sessions feed a dream pipeline that distils activity into durable knowledge,so every instance starts informed rather than cold. One Mori, many agents —every session benefits from what every other session learned.
Works with any OpenAI-compatible provider. No homelab, no Anthropicaccount, no LLM Gateway required — though those all work too.
Multi-Instance Coherence
If you run Claude Code across multiple machines or profiles — one focused on theAPI layer, another on the frontend, a third on infrastructure — you already knowthe problem: each instance is brilliant in isolation, but none of them know whatthe others decided.
Instance B doesn't know that Instance A just changed the auth contract. Instance Cdoesn't know that Instance B's deployment assumptions shifted. They find out thehard way, mid-task, when something breaks.
Mori solves this.
Every CC instance sends its session events — prompts, tool calls, errors, decisions— to the shared Mori server. The dream pipeline distils those events from allinstances into a unified memory store. At the start of any session, /briefsurfaces what the other instances have been doing: the cross-cutting decisions,the architectural tensions, the gotchas one instance hit that another is aboutto repeat.
From turn one, each instance knows what the others know.
Real-time awareness via NATS
The dream pipeline runs on a schedule. For decisions thatcan't wait, NATS provides real-time cross-instance messaging:
# Instance A just changed the auth contract:
/nats pub "Auth contract changed — JWT now includes org_id claim. See memory: api-auth-contract"
# Instance B picks it up immediately:
/nats sub
→ [Instance A] Auth contract changed — JWT now includes org_id claim.
Any instance can publish, any instance can subscribe. Messages replay for 7 daysso instances that were offline don't miss decisions.
What gets shared
The dream pipeline captures and synthesises across instances:
- Architecture decisions — "Instance A moved to event-driven auth; Instance Bshould update its session handling assumptions"
- Cross-cutting gotchas — "This provider 429s under load; all instances shoulduse the fallback routing"
- Deferred decisions — "Instance C flagged a migration risk; nobody has resolved it yet"
- Conventions — patterns that emerge across sessions become shared standards
What doesn't get shared: one-off bugs, noise, anything recoverable from docs or git.The dream pipeline filters aggressively. You get signal, not a transcript.
Setup for multi-instance use
Point every instance at the same Mori server. That's it.
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://<your-mori-server>:8968/mcp"
}
}
}
Add the lifecycle hooks to each instance's settings.json so events flow in.Each instance gets a ?client=<hostname> tag so the dream pipeline knows whocontributed what. Attribution is preserved — you can always trace a memory backto the session and device that produced it.
Recommended dream cadence for multi-instance setups
| Instances | Recommended interval |
|---|---|
| 1–2 | 1 hour |
| 3–5 | 30 minutes |
| 5+ | 30 minutes + manual /dream after significant decisions |
The PreCompact hook triggers an immediate dream run before any instance'scontext is compressed — ensuring nothing is lost at the moment it matters most.
Core capabilities
1. Event Logging
Claude Code lifecycle hooks POST session events to POST /api/events/raw.These events feed the dream pipeline. The PreCompact hook posts toPOST /api/precompact and triggers an immediate synchronous dream.
# Minimal hook config — add to settings.json:
curl -sf -X POST 'http://localhost:8968/api/events/raw?client=my-hostname' \
-H 'Content-Type: application/json' -d @-
Every Claude Code session emits lifecycle events — tool calls, prompts, errors,stop reasons. Mori receives these via HTTP POST and stores them in an append-onlyevent log. This is the raw material everything else builds on.
What it captures:
PostToolUse— tool name, input, output, errorsPostToolUseFailure— tool call errors (high-value for dream distillation)PreCompact— session snapshot before context compression (triggers synchronous dream)UserPromptSubmit— the prompt textStop/SessionEnd— stop reason, model used- Session ID, client hostname, working directory, transcript path
Components required: Mori server only. No LLM provider needed.
Config: MORI_ADVISOR_API_KEY for auth (empty = no auth, only reachable viaTailscale LAN or localhost).
2. Persistent Memory
Memories live in a single SQLite database (memories.db) with:
- Versioning — every change creates a new version. View history, diff versions, rollback.
- Attribution — each memory tracks which session(s) and client(s) contributed.
- Protection — trusted dreamers write directly; others queue for approval.
- Tagging — memories are taggable (
security,architecture,decision) for filtering. - Search — keyword search across name, title, description, and body.
Components required: Mori server only. Memories persist in SQLite — noexternal dependencies.
3. Dream Phase
Session events are captured via Claude Code lifecycle hooks (PostToolUse,PostToolUseFailure, UserPromptSubmit, Stop, PreCompact). The dream pipelinereads events since the last watermark, sends them to a configurable LLM, andwrites extracted memories back to the store.
Hook fires → POST /api/events/raw → SQLite events table
↓
PreCompact → POST /api/precompact → dream_run() reads since watermark
↓
LLM distills events → structured memories
↓
memories written to store (with attribution)
↓
watermark advanced
Run it: /dream or mori-dream_run. Check state: /dream --status.
Stale knowledge & eviction
Dream produces three tiers of memory, each with a different lifecycle:
| Tier | Scope | Eviction |
|---|---|---|
| Ephemeral | Auto-generated session summaries | Auto-expire at session end unless explicitly saved |
| Working | Patterns, decisions, project context | Flagged for review after 30 days of no retrievals. Not deleted — surfaced via pensieve --since 30d for weekly triage |
| Canonical | Explicitly promoted by a trusted dreamer | Indefinite, but freshness-checked before injection via /brief |
The freshness check runs during /brief (session bootstrap). For eachcanonical memory about dependencies, infrastructure, or tooling, a lightweightvalidation prompt asks: "Based on current project state, is this still accurate?Answer YES, NO, or STALE." STALE responses suppress injection and queuethe memory for review.
Orphan scan — dream_run also tracks retrieval recency. Memories notretrieved in 30 days are flagged but preserved. Human reviews the queue,not a batch delete.
This avoids the classic "persistent memory" failure mode where a patchedcluster's stale workaround poisons sessions for months.
Components required: Mori server + LLM provider (for the distillation model).Config: MORI_DREAM_MODEL (defaults to MORI_MODEL, then deepseek/deepseek-v4-flash).
4. Session Context (/brief)
Mori uses session grounding rather than per-query RAG. /brief loadsshared memories, team standards, and dream pipeline state into context atsession start. From turn one, the model knows your security baseline,coding conventions, and current project state — no retrieval needed.
Unresolved /req items also surface via /brief — a sticky note, not a project board.No sync, no drift from JIRA or GH Projects.
# Starting a refactor — add a checklist:
/req add "Extract auth middleware" --project bifrost --pri high
/req add "Add rate limiting" --project bifrost --pri medium
/req add "Write migration guide" --project bifrost --pri low
# Check progress mid-session:
/req --project bifrost
→ 3 requirements, 1 in-progress, 2 pending
# Mark done as you go:
/req done req-bifrost-extract-auth-middleware
# Next session, /brief shows what's still open
When the standards corpus grows beyond one context window, run separateMori instances per namespace rather than adding a vector store:
# Retail team
docker run ... -e MORI_STANDARDS_DIR=/standards/retail -p 8970:8968
# Energy team
docker run ... -e MORI_STANDARDS_DIR=/standards/energy -p 8971:8968
Standards ingestion
Set MORI_STANDARDS_DIR to a directory of .md files:
/path/to/standards/
ethos/
values-and-ethical-principles.md
security/
security-baseline.md
pii-handling.md
coding/
python-style-guide.md
On startup, every .md file is imported as a protected memory with type: standardand tags from its subdirectory. Standards are read-only to non-trusted dreamers.
Update without restarting: mori-standards_reload (trusted dreamers only).
Components required: Mori server + /brief skill. Config: MORI_STANDARDS_DIR.
5. Strategic Code Review (/consult)
A configurable LLM receives your question plus optional file context andreturns strategic guidance. Supports focus areas (general, architecture,security, performance, style) and depth levels (quick, balanced, deep).
When a specific focus is given (--focus security), relevant team standardsare auto-injected from memory — the advisor checks against your own baseline,not generic advice.
Chain tool output into the advisor:
/consult "review this auth handler" --focus security --file src/auth.py --file snyk-report.json
Components required: Mori server + LLM provider. Config: MORI_MODEL(default moonshotai/kimi-k2.6).
6. Agent Delegation + NATS (/nats, /update)
Cross-device messaging (NATS)
Optional NATS JetStream integration for cross-device state-of-play messages.Each device publishes session summaries; any device can replay the last 7 days.Useful for awareness across a team or fleet of Claude Code instances.
Skill deployment (/update)
The mori-update tool generates install commands for skills and slash commandsacross devices. It knows each device's profile layout and produces the rightshell commands — no manual copy-paste across machines:
| Device | Profiles |
|---|---|
| Linux | .claude, .claude-sr, .claude-sub, .claude-api |
| Windows | Same paths via $env:USERPROFILE |
| NUC | .claude, .claude-jr, .claude-sub, .claude-api |
Command output is base64-encoded to avoid shell quoting issues:
/update --twiggy --nats
→ compact PowerShell block that deploys to all 4 profiles
→ ask approval then execute — no copy-paste needed
This means pushing an updated skill to every Claude Code instance is a single/update command away.
Components required: Mori server. NATS server for cross-device messaging(optional).
7. Governance — Memory Quality & Validity
Memories accumulate over time. Without safeguards, they drift, conflict, oraccumulate noise. Mori has several mechanisms to maintain quality:
Trusted Dreamers
Certain client hostnames are designated as trusted dreamers. Only they candirectly modify protected memories. Writes from other instances are queued aspending writes.
Configured via MORI_TRUSTED_DREAMERS env var (comma-separated hostnames)or in the dreamer_config table.
Protection
Any memory can be toggled protected via mori-memory_protect. When protected:
- Trusted dreamers write directly (no change in behaviour)
- Other instances' writes go to
pending_writesfor review mori-memory_pending_list,mori-memory_approve,mori-memory_rejectmanage the queue
Versioning & Rollback
Every write snapshots the previous state. You can:
- View history:
mori-memory_history(name) - Compare versions:
mori-memory_diff(name, from, to) - Roll back:
mori-memory_rollback(name, version_id)— rollbacks are themselvesversioned, so they can be reversed
Attribution
Every memory tracks its origin:
origin_session_ids— which sessions contributedorigin_clients— which hostnames contributedmori-memory_session_summary(session_id)— audit what a session produced
This means you can trace any memory back to the session and device that created it.
Export / Import
Memories can be exported to standard .md files and imported elsewhere. Thisserves as both backup and review — you can inspect the full corpus as flatfiles, edit them, and re-import.
Quickstart
1. Pick your platform
| Platform | Section | Recommended path | Complexity |
|---|---|---|---|
| Linux | 1a | Docker Compose or Podman Compose | Low |
| macOS | 1b | Docker Desktop | Low |
| macOS (dev) | 1c | Native Python | Low |
| Windows | 1d | Docker Desktop | Low |
| Windows (advanced) | 1e | WSL2 + Podman Compose | Medium |
| Cloud (any) | 1f | GCP Terraform | Medium |
1a. Linux — Docker Compose
Works with Podman Compose (podman compose) or Docker Compose (docker compose).
git clone https://github.com/fjwood69/mori.git
cd mori
cp deploy/homelab/.env.example deploy/homelab/.env
# Edit deploy/homelab/.env with your provider API key and model
docker compose -f deploy/homelab/docker-compose.yml up -d
curl http://localhost:8968/health
The compose file brings up Mori with a dream-cron sidecar that runs the dreampipeline on a schedule. Configure MORI_DREAM_INTERVAL in .env (default: 60 minutes).
1b. macOS — Docker Desktop
Install Docker Desktop then:
git clone https://github.com/fjwood69/mori.git
cd mori
cp deploy/homelab/.env.example deploy/homelab/.env
# Edit deploy/homelab/.env with your provider API key and model
docker compose -f deploy/homelab/docker-compose.yml up -d
curl http://localhost:8968/health
Docker Desktop handles the Linux container layer transparently — no extra setup needed.
1c. macOS — native Python
git clone https://github.com/fjwood69/mori.git
cd mori
pip install -r requirements.txt
cp deploy/homelab/.env.example deploy/homelab/.env
# Edit deploy/homelab/.env with your provider API key
set -a; source deploy/homelab/.env; set +a
python -m mori_advisor.main &
# Dream cron: add to crontab (runs every hour — adjust to match MORI_DREAM_INTERVAL)
# 0 * * * * cd /path/to/mori && python -m mori_advisor.dream_job
SQLite WAL mode works natively on macOS. No container needed.
1d. Windows — Docker Desktop
Install Docker Desktop (handles WSL2 backend automatically) then in PowerShell:
git clone https://github.com/fjwood69/mori.git
cd mori
copy deploy\homelab\.env.example deploy\homelab\.env
# Edit deploy\homelab\.env with your provider API key and model (Notepad works fine)
docker compose -f deploy\homelab\docker-compose.yml up -d
curl http://localhost:8968/health
No WSL knowledge required. Docker Desktop runs the Linux container transparently.Dream cron is handled inside the container — no Windows Task Scheduler needed.
1e. Windows — WSL2 + Podman
Follow the 1a Linux path inside WSL2 Ubuntu. DockerDesktop (1d) is the easier path for most users.
1f. Cloud — GCP Terraform
See deploy/gcp/ for Terraform configs. Creates a GCE e2-small VMwith Podman rootless, persistent disk, Tailscale, and GCP Secret Manager.
cd deploy/gcp
terraform init
terraform plan
terraform apply
2. Verify it's running
curl http://localhost:8968/health
# {"status":"ok","service":"mori-advisor"}
curl http://localhost:8968/api/events/health
# {"status":"ok","total_events":0}
curl http://localhost:8968/metrics
# Prometheus-formatted metrics
3. Connect Claude Code
Option A: .mcp.json (project root, most reliable)
Create a .mcp.json file in your project root:
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://localhost:8968/mcp"
}
}
}
Claude Code picks this up automatically when working in that project directory— no global config needed. For a remote Mori server (e.g. on another machineon the same Tailscale tailnet), use the Tailscale IP:
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://100.84.128.79:8968/mcp"
}
}
}
Option B: settings.json (global)
Add to ~/.claude/settings.json under mcpServers:
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://localhost:8968/mcp"
}
}
}
For user-global scope (works in VS Code extension):
claude mcp add mori --scope user --type http http://localhost:8968/mcp
4. Install slash commands
Copy the skill files from the skills/ directory:
# One-shot for all profiles:
SKILLS_DIRS=(".claude" ".claude-sr" ".claude-sub" ".claude-api")
for d in "${SKILLS_DIRS[@]}"; do
cp -r skills/* ~/$d/skills/
done
Each skill becomes a /command: /brief, /consult, /dream, /pensieve, /update, /nats, /req.
5. Enable event capture (required for dreams)
Add the hooks from examples/settings.json to your ~/.claude/settings.json.See Claude Code CLI Setup below for a complete walkthrough.
Claude Code CLI — Mori Setup
This section covers the Claude Code CLI-specific configuration that enablesthe full Mori experience: event capture, dream pipeline, and session grounding.
1. Connect Mori as an MCP server
Project-level (recommended — per repo):
// .mcp.json in your project root
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://localhost:8968/mcp"
}
}
}
User-global (available in all CC sessions):
claude mcp add mori --scope user --type http http://localhost:8968/mcp
For a remote Mori server on the same Tailscale tailnet:
{
"mcpServers": {
"mori": {
"type": "http",
"url": "http://mori.yourteam.ts.net:8968/mcp"
}
}
}
2. Enable event capture (required for dreams)
Mori's dream pipeline is fed by Claude Code lifecycle hooks. Add these to~/.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf -X POST 'http://localhost:8968/api/events/raw?client=$(hostname)' -H 'Content-Type: application/json' -H 'X-Api-Key: your-api-key' -d @- >/dev/null 2>&1; exit 0"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf -X POST 'http://localhost:8968/api/events/raw?client=$(hostname)' -H 'Content-Type: application/json' -H 'X-Api-Key: your-api-key' -d @- >/dev/null 2>&1; exit 0"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf -X POST 'http://localhost:8968/api/events/raw?client=$(hostname)' -H 'Content-Type: application/json' -H 'X-Api-Key: your-api-key' -d @- >/dev/null 2>&1; exit 0"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf -X POST 'http://localhost:8968/api/events/raw?client=$(hostname)' -H 'Content-Type: application/json' -H 'X-Api-Key: your-api-key' -d @- >/dev/null 2>&1; exit 0"
}
]
}
],
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf -X POST 'http://localhost:8968/api/precompact?client=$(hostname)' -H 'Content-Type: application/json' -H 'X-Api-Key: your-api-key' -d @- >/dev/null 2>&1; exit 0"
}
]
}
]
}
}
Notes:
- Replace
your-api-keywith the value ofMORI_ADVISOR_API_KEY - Replace
localhost:8968with your Mori server address if running remotely $(hostname)is evaluated at hook fire time — override with a fixed nameif your hostname is long or changes (e.g.?client=twiggy)PreCompactposts to/api/precompact— this triggers an immediatesynchronous dream run before context compression. Expect 10–30s delay.This is intentional — it preserves session knowledge before it is lost.- A full example
settings.jsonis in examples/settings.json
3. Install slash commands
# One-shot for all profiles:
SKILLS_DIRS=(".claude" ".claude-sr" ".claude-sub" ".claude-api")
for d in "${SKILLS_DIRS[@]}"; do
mkdir -p ~/$d/skills
cp -r skills/* ~/$d/skills/
done
Each skill becomes a /command in Claude Code:
| Command | What it does |
|---|---|
/brief |
Load shared memories + standards + dream state into context |
/dream |
Distil undreamed events into memories |
/dream --status |
Show dream pipeline state |
/dream --dry-run |
Preview without writing |
/consult "question" |
Strategic guidance from the advisor model |
/pensieve <query> |
Search memories by keyword |
/pensieve read <name> |
Read a specific memory |
/req |
Requirements dashboard |
/nats ping |
Check cross-device messaging |
/wrap |
Session close — summary, dream flush, NATS publish |
4. Session grounding with CLAUDE.md
Add a line to ~/.claude/CLAUDE.md to ensure every session starts withshared context:
At the start of every session, run /brief to load shared memories and
dream pipeline state before responding to the user.
This ensures the agent bootstraps from the Mori memory store automatically,without requiring a manual /brief invocation each time.
5. Trusted dreamer setup
The device that runs the dream pipeline and writes directly to protectedmemories is the trusted dreamer. Set it in your Mori server config:
MORI_TRUSTED_DREAMERS=your-hostname
To find your hostname:
hostname
On Windows:
$env:COMPUTERNAME
Multiple trusted dreamers (comma-separated):
MORI_TRUSTED_DREAMERS=macbook-pro,twiggy,nuc15pro
Writes from non-trusted devices are queued as pending writes for reviewvia mori-memory_pending_list.
6. Verify the full setup
# MCP tools available
claude mcp list
# Health check
curl http://localhost:8968/health
# → {"status":"ok","service":"mori-advisor"}
# Send a test event
curl -X POST 'http://localhost:8968/api/events/raw?client=test' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: your-api-key' \
-d '{"event_name":"UserPromptSubmit","prompt":"test"}'
# Check dream state
curl http://localhost:8968/metrics
Then start a Claude Code session and run /brief — if shared memoriesload into context, everything is working.
Dream cadence
Configure how often the dream pipeline runs in .env:
MORI_DREAM_INTERVAL=60 # minutes
| Team size | Recommended |
|---|---|
| Solo | 240 min (4 hours) |
| 1–4 people | 60 min (1 hour) |
| 5–10 people | 30 min |
The PreCompact hook fires an immediate dream regardless of cadence —so no session knowledge is lost at context compression time.
What you get
| Tool | What it does |
|---|---|
mori-memory_search/write/read/list/delete |
Full CRUD on shared memories |
mori-memory_export/import/export_all |
Portability between instances |
mori-memory_history/diff/rollback |
Versioning — track changes over time |
mori-memory_session_summary |
Attribution — see what a session produced |
mori-memory_pending_list/approve/reject/protect |
Governance — trusted dreamer workflow |
mori-consult_advisor |
Strategic guidance mid-task (configurable model + focus) |
mori-dream_run / dream_status |
Batch distills session events → memories |
mori-standards_reload |
Re-import team standards from disk |
mori-brief |
Session bootstrap — loads memories + standards + dream state |
mori-pensieve |
Search/browse the shared memory store |
mori-update |
Deploy slash command skills to devices |
mori-nats_pub/sub/ping |
Cross-device message bus (NATS optional) |
mori-memory_req |
Requirements tracking dashboard with status workflow |
mori-event_log |
HTTP event capture endpoint for dream pipeline |
Slash commands: /brief, /wrap, /consult, /dream, /pensieve, /update, /nats, /req
Quick Reference
| Command | Usage | What it does |
|---|---|---|
/brief |
/brief |
Load shared memories + standards + dream state into context |
/wrap |
/wrap |
Session wrap — writes summary to cc-share, publishes to NATS, runs dream |
/consult |
/consult "question" [--focus security] [--depth quick] [--file path] |
Get strategic guidance from the advisor model |
/dream |
/dream |
Distill undreamed events into memories |
/dream --status |
Show dream pipeline state (watermark, event counts) | |
/dream --dry-run |
Preview what would be produced without writing | |
/pensieve |
/pensieve <query> |
Search memories by keyword |
/pensieve read <name> |
Read a specific memory by its kebab-case name | |
/pensieve --type decision --since 30d |
Filter by type and recency | |
/pensieve --tag security |
Filter by tag | |
/req |
/req |
Show requirements dashboard grouped by project |
/req --project bifrost |
Filter by project | |
/req --project bifrost --status pending |
Filter by project and status | |
/req add "Title" --project bifrost --pri high |
Create a new requirement | |
/req done req-bifrost-<name> |
Mark a requirement complete | |
/nats |
/nats ping |
Check NATS connection status |
/nats sub |
Show recent cross-device messages | |
/nats pub "message" |
Publish a message to other devices | |
/update |
/update --device twiggy --skill nats |
Generate install commands for a skill on a device |
Architecture
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
MORI_PROVIDER_MODE |
bifrost |
direct or bifrost. New users without a custom gateway should set direct. |
MORI_API_KEY |
— | Provider key (required in direct mode) |
MORI_BASE_URL |
depends | OpenAI-compatible base URL |
MORI_MODEL |
moonshotai/kimi-k2.6 |
Advisor model |
MORI_DREAM_MODEL |
falls back | Dream pipeline model |
MORI_MCP_SERVER_NAME |
mori |
MCP tool prefix |
MORI_ADVISOR_DATA |
/data/mori-advisor |
SQLite DB location |
MORI_ADVISOR_API_KEY |
— | Event capture auth (empty = no auth) |
MORI_TRUSTED_DREAMERS |
— | Comma-separated hostnames for write approval bypass |
MORI_STANDARDS_DIR |
— | Path to team standards .md directory |
MORI_SKILLS_DIR |
— | Path to slash command skill files (for /update) |
MORI_DREAM_INTERVAL |
60 |
Dream pipeline interval in minutes |
MORI_BIFROST_TIMEOUT |
300 |
API timeout in seconds |
Dream interval
How often to run the dream phase depends on session density. The PreCompacthook fires on context compression regardless of schedule, so the cron is justa safety net for sessions that never compact.
Set via MORI_DREAM_INTERVAL in your .env file (used by the Docker Composedream-cron sidecar). For Podman/systemd deployments, set via the dream timer.
| Team size | Suggested interval | Rationale |
|---|---|---|
| Solo | 240 (4 hours) | Few events per session, low risk of losing context |
| 1–4 people | 60 (1 hour) | More events, catches cold restarts and short sessions |
| 5–10 people | 30 minutes | High event density, any session could be the last before the server goes down |
Ports
| Port | Service |
|---|---|
8968 |
MCP server (streamable HTTP) + event capture API |
Deployment
Deployment matrix
| Platform | Recommended path | Complexity |
|---|---|---|
| Linux | Docker Compose or Podman Compose | Low |
| macOS | Docker Desktop or native Python | Low |
| Windows | Docker Desktop | Low |
| Windows (advanced) | WSL2 + Podman Compose | Medium |
| Cloud (any) | GCP Terraform (deploy/gcp/) | Medium |
Docker Compose (all platforms — recommended)
The compose file in deploy/homelab/docker-compose.yml brings up Mori with adream-cron sidecar. Works with Docker Desktop (macOS/Windows), Podman Compose(Linux), and docker compose.
git clone https://github.com/fjwood69/mori.git
cd mori
cp deploy/homelab/.env.example deploy/homelab/.env
# Edit deploy/homelab/.env with your provider API key and model
docker compose -f deploy/homelab/docker-compose.yml up -d
curl http://localhost:8968/health
Homelab (Podman raw, Linux advanced)
Systemd user services for the dream timer and backup timer are in deploy/homelab/:
git clone https://github.com/fjwood69/mori.git
cd mori
podman build -t localhost/mori-advisor:latest .
podman run -d --name mori --restart=unless-stopped --network=host \
-v /data/mori-advisor:/data/mori-advisor:Z \
--env-file deploy/homelab/.env \
localhost/mori-advisor:latest
# Install systemd timers (user-level)
cp deploy/homelab/mori-dream.* ~/.config/systemd/user/
cp deploy/homelab/mori-backup.* ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mori-dream.timer
systemctl --user enable --now mori-backup.timer
GCP (GCE VM)
See deploy/gcp/ for Terraform configs. Creates:
- GCE e2-small VM (2 vCPU, 2GB RAM, 20GB persistent disk) — ~$12/month
- Ubuntu 24.04 LTS with Podman rootless
- GCS bucket for SQLite backups (daily backup, 90-day archive lifecycle)
- GCP Secret Manager for all secrets
- Tailscale join for access (no public ports)
- Systemd timers for dream and backup
cd deploy/gcp
terraform init
terraform apply
# If migrating from an existing homelab NUC:
# bash scripts/migrate-secrets.sh
# (Assumes you're on the NUC with ~/.claude/.secrets available.
# For a fresh GCP deployment without a NUC, create secrets manually
# in GCP Secret Manager and reference them in your Terraform variables.)
# SSH in and verify:
gcloud compute ssh mori-advisor
curl http://localhost:8968/health
Dual deployment (migration period)
During migration, both homelab and GCP instances can run in parallel pointingat separate databases. Claude Code points at either one via .mcp.json.
To copy memories from an existing instance:
- On the old instance:
mori-memory_export_all→ flat.mdfiles - On the new instance:
mori-memory_import→ loads into new DB - Verify with
mori-memory_list
No downtime — both instances serve during the cutover.
Observability endpoints
| Endpoint | Purpose | Response |
|---|---|---|
/health |
Liveness probe | 200 if process is alive |
/ready |
Readiness probe (HTTP endpoint, not the /ready slash command) |
200 if DB accessible, 503 otherwise |
/metrics |
Prometheus exposition format | Counts for memories, events, pending writes, eviction queue |
/api/events/health |
Legacy event endpoint | Event count |
Provider Policy
Mori routes all LLM inference through US and EU sovereign endpoints only. WhileMori can use open-weight models created in the PRC (e.g. DeepSeek, Kimi, GLM,Qwen), inference runs entirely outside the PRC via US-based provider infrastructure:
| Model | Origin | Provider Route |
|---|---|---|
| Kimi K2.6 | Moonshot AI | DeepInfra / Novita / Parasail (US) |
| DeepSeek V4 | DeepSeek | DeepInfra / Novita (US) |
| GLM-5 | Zhipu AI | DeepInfra / Novita / Parasail / Vertex (US) |
| Qwen | Alibaba | Nebius / DeepInfra (US/EU) |
| Gemma 4 31B it | Vertex AI (NAM) | |
| Gemini 3 Flash Preview | Vertex AI (NAM) |
This is explicitly documented in the README because the model names alone couldmislead colleagues into thinking direct Moonshot/DeepSeek API usage is involved.It is not. All inference goes through US-based providers that happen to hostopen-weight models.
For teams
Each team member runs their own Claude Code connected to the same Mori.Memories are shared. Trusted dreamers approve writes.
- Run Mori on a shared server or as a cloud container
- Each member points
mcpServersat the shared URL - Each member installs the skills and hooks
- Run
/dreamperiodically on one instance to consolidate
Building
git clone https://github.com/fjwood69/mori.git
cd mori
podman build -t localhost/mori-advisor:latest .
# Or with Docker: docker build -t mori-advisor:latest .
License
MIT