Mori

Shared memory layer for AI coding agents with dream pipeline distillation, session grounding, and multi-instance coherence.

Mori — A shared memory layer for AI coding agents

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 Anthropic account, no LLM Gateway required — though those all work too.


Multi-Instance Coherence

One Forest, Many Agents

If you run Claude Code across multiple machines or profiles — one focused on the API layer, another on the frontend, a third on infrastructure — you already know the problem: each instance is brilliant in isolation, but none of them know what the others decided.

Instance B doesn't know that Instance A just changed the auth contract. Instance C doesn't know that Instance B's deployment assumptions shifted. They find out the hard 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 all instances into a unified memory store. At the start of any session, /brief surfaces what the other instances have been doing: the cross-cutting decisions, the architectural tensions, the gotchas one instance hit that another is about to 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 that can'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 days so 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 B should update its session handling assumptions"
  • Cross-cutting gotchas — "This provider 429s under load; all instances should use 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 who contributed what. Attribution is preserved — you can always trace a memory back to the session and device that produced it.

Recommended dream cadence for multi-instance setups

InstancesRecommended interval
1–21 hour
3–530 minutes
5+30 minutes + manual /dream after significant decisions

The PreCompact hook triggers an immediate dream run before any instance's context 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 to POST /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-only event log. This is the raw material everything else builds on.

What it captures:

  • PostToolUse — tool name, input, output, errors
  • PostToolUseFailure — tool call errors (high-value for dream distillation)
  • PreCompact — session snapshot before context compression (triggers synchronous dream)
  • UserPromptSubmit — the prompt text
  • Stop / 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 via Tailscale 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.

The Forest Remembers

Components required: Mori server only. Memories persist in SQLite — no external dependencies.


3. Dream Phase

Session events are captured via Claude Code lifecycle hooks (PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, PreCompact). The dream pipeline reads events since the last watermark, sends them to a configurable LLM, and writes 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

Dream Pipeline

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:

TierScopeEviction
EphemeralAuto-generated session summariesAuto-expire at session end unless explicitly saved
WorkingPatterns, decisions, project contextFlagged for review after 30 days of no retrievals. Not deleted — surfaced via pensieve --since 30d for weekly triage
CanonicalExplicitly promoted by a trusted dreamerIndefinite, but freshness-checked before injection via /brief

The freshness check runs during /brief (session bootstrap). For each canonical memory about dependencies, infrastructure, or tooling, a lightweight validation prompt asks: "Based on current project state, is this still accurate? Answer YES, NO, or STALE." STALE responses suppress injection and queue the memory for review.

Orphan scandream_run also tracks retrieval recency. Memories not retrieved 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 patched cluster'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 loads shared memories, team standards, and dream pipeline state into context at session 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 separate Mori 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: standard and 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 and returns 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 standards are 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 commands across devices. It knows each device's profile layout and produces the right shell commands — no manual copy-paste across machines:

DeviceProfiles
Linux.claude, .claude-sr, .claude-sub, .claude-api
WindowsSame 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, or accumulate noise. Mori has several mechanisms to maintain quality:

Trusted Dreamers

Certain client hostnames are designated as trusted dreamers. Only they can directly modify protected memories. Writes from other instances are queued as pending 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_writes for review
  • mori-memory_pending_list, mori-memory_approve, mori-memory_reject manage 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 themselves versioned, so they can be reversed

Attribution

Every memory tracks its origin:

  • origin_session_ids — which sessions contributed
  • origin_clients — which hostnames contributed
  • mori-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. This serves as both backup and review — you can inspect the full corpus as flat files, edit them, and re-import.


Quickstart

1. Pick your platform

PlatformSectionRecommended pathComplexity
Linux1aDocker Compose or Podman ComposeLow
macOS1bDocker DesktopLow
macOS (dev)1cNative PythonLow
Windows1dDocker DesktopLow
Windows (advanced)1eWSL2 + Podman ComposeMedium
Cloud (any)1fGCP TerraformMedium

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 dream pipeline 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. Docker Desktop (1d) is the easier path for most users.

1f. Cloud — GCP Terraform

See deploy/gcp/ for Terraform configs. Creates a GCE e2-small VM with 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 machine on 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 enables the 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-key with the value of MORI_ADVISOR_API_KEY
  • Replace localhost:8968 with your Mori server address if running remotely
  • $(hostname) is evaluated at hook fire time — override with a fixed name if your hostname is long or changes (e.g. ?client=twiggy)
  • PreCompact posts to /api/precompact — this triggers an immediate synchronous dream run before context compression. Expect 10–30s delay. This is intentional — it preserves session knowledge before it is lost.
  • A full example settings.json is 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:

CommandWhat it does
/briefLoad shared memories + standards + dream state into context
/dreamDistil undreamed events into memories
/dream --statusShow dream pipeline state
/dream --dry-runPreview without writing
/consult "question"Strategic guidance from the advisor model
/pensieve <query>Search memories by keyword
/pensieve read <name>Read a specific memory
/reqRequirements dashboard
/nats pingCheck cross-device messaging
/wrapSession close — summary, dream flush, NATS publish

4. Session grounding with CLAUDE.md

Add a line to ~/.claude/CLAUDE.md to ensure every session starts with shared 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 protected memories 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 review via 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 memories load into context, everything is working.

Dream cadence

Configure how often the dream pipeline runs in .env:

MORI_DREAM_INTERVAL=60  # minutes
Team sizeRecommended
Solo240 min (4 hours)
1–4 people60 min (1 hour)
5–10 people30 min

The PreCompact hook fires an immediate dream regardless of cadence — so no session knowledge is lost at context compression time.


What you get

ToolWhat it does
mori-memory_search/write/read/list/deleteFull CRUD on shared memories
mori-memory_export/import/export_allPortability between instances
mori-memory_history/diff/rollbackVersioning — track changes over time
mori-memory_session_summaryAttribution — see what a session produced
mori-memory_pending_list/approve/reject/protectGovernance — trusted dreamer workflow
mori-consult_advisorStrategic guidance mid-task (configurable model + focus)
mori-dream_run / dream_statusBatch distills session events → memories
mori-standards_reloadRe-import team standards from disk
mori-briefSession bootstrap — loads memories + standards + dream state
mori-pensieveSearch/browse the shared memory store
mori-updateDeploy slash command skills to devices
mori-nats_pub/sub/pingCross-device message bus (NATS optional)
mori-memory_reqRequirements tracking dashboard with status workflow
mori-event_logHTTP event capture endpoint for dream pipeline

Slash commands: /brief, /wrap, /consult, /dream, /pensieve, /update, /nats, /req

Quick Reference

CommandUsageWhat it does
/brief/briefLoad shared memories + standards + dream state into context
/wrap/wrapSession 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/dreamDistill undreamed events into memories
/dream --statusShow dream pipeline state (watermark, event counts)
/dream --dry-runPreview 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 30dFilter by type and recency
/pensieve --tag securityFilter by tag
/req/reqShow requirements dashboard grouped by project
/req --project bifrostFilter by project
/req --project bifrost --status pendingFilter by project and status
/req add "Title" --project bifrost --pri highCreate a new requirement
/req done req-bifrost-<name>Mark a requirement complete
/nats/nats pingCheck NATS connection status
/nats subShow recent cross-device messages
/nats pub "message"Publish a message to other devices
/update/update --device twiggy --skill natsGenerate install commands for a skill on a device

Architecture

Mori Architecture


Configuration

Environment variables

VariableDefaultDescription
MORI_PROVIDER_MODEbifrostdirect or bifrost. New users without a custom gateway should set direct.
MORI_API_KEYProvider key (required in direct mode)
MORI_BASE_URLdependsOpenAI-compatible base URL
MORI_MODELmoonshotai/kimi-k2.6Advisor model
MORI_DREAM_MODELfalls backDream pipeline model
MORI_MCP_SERVER_NAMEmoriMCP tool prefix
MORI_ADVISOR_DATA/data/mori-advisorSQLite DB location
MORI_ADVISOR_API_KEYEvent capture auth (empty = no auth)
MORI_TRUSTED_DREAMERSComma-separated hostnames for write approval bypass
MORI_STANDARDS_DIRPath to team standards .md directory
MORI_SKILLS_DIRPath to slash command skill files (for /update)
MORI_DREAM_INTERVAL60Dream pipeline interval in minutes
MORI_BIFROST_TIMEOUT300API timeout in seconds

Dream interval

How often to run the dream phase depends on session density. The PreCompact hook fires on context compression regardless of schedule, so the cron is just a safety net for sessions that never compact.

Set via MORI_DREAM_INTERVAL in your .env file (used by the Docker Compose dream-cron sidecar). For Podman/systemd deployments, set via the dream timer.

Team sizeSuggested intervalRationale
Solo240 (4 hours)Few events per session, low risk of losing context
1–4 people60 (1 hour)More events, catches cold restarts and short sessions
5–10 people30 minutesHigh event density, any session could be the last before the server goes down

Ports

PortService
8968MCP server (streamable HTTP) + event capture API

Deployment

Deployment matrix

PlatformRecommended pathComplexity
LinuxDocker Compose or Podman ComposeLow
macOSDocker Desktop or native PythonLow
WindowsDocker DesktopLow
Windows (advanced)WSL2 + Podman ComposeMedium
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 a dream-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 pointing at separate databases. Claude Code points at either one via .mcp.json.

To copy memories from an existing instance:

  1. On the old instance: mori-memory_export_all → flat .md files
  2. On the new instance: mori-memory_import → loads into new DB
  3. Verify with mori-memory_list

No downtime — both instances serve during the cutover.

Observability endpoints

EndpointPurposeResponse
/healthLiveness probe200 if process is alive
/readyReadiness probe (HTTP endpoint, not the /ready slash command)200 if DB accessible, 503 otherwise
/metricsPrometheus exposition formatCounts for memories, events, pending writes, eviction queue
/api/events/healthLegacy event endpointEvent count

Provider Policy

Mori routes all LLM inference through US and EU sovereign endpoints only. While Mori 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:

ModelOriginProvider Route
Kimi K2.6Moonshot AIDeepInfra / Novita / Parasail (US)
DeepSeek V4DeepSeekDeepInfra / Novita (US)
GLM-5Zhipu AIDeepInfra / Novita / Parasail / Vertex (US)
QwenAlibabaNebius / DeepInfra (US/EU)
Gemma 4 31B itGoogleVertex AI (NAM)
Gemini 3 Flash PreviewGoogleVertex AI (NAM)

This is explicitly documented in the README because the model names alone could mislead colleagues into thinking direct Moonshot/DeepSeek API usage is involved. It is not. All inference goes through US-based providers that happen to host open-weight models.

For teams

Each team member runs their own Claude Code connected to the same Mori. Memories are shared. Trusted dreamers approve writes.

  1. Run Mori on a shared server or as a cloud container
  2. Each member points mcpServers at the shared URL
  3. Each member installs the skills and hooks
  4. Run /dream periodically 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


Support me on Ko-fi

Related Servers