Yet another lessons-learned/core-memories autonomous database backed MCP for Opencode
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-06 11:36:29 +02:00
docs/superpowers chore(hygiene): replace internal hostnames/paths with placeholders; add public repo hygiene notes 2026-09-06 11:36:29 +02:00
reflect chore(hygiene): replace internal hostnames/paths with placeholders; add public repo hygiene notes 2026-09-06 11:36:29 +02:00
systemd docs: clarify README with separate local/hub/client setup sections 2026-08-10 16:19:23 +02:00
tests chore(hygiene): replace internal hostnames/paths with placeholders; add public repo hygiene notes 2026-09-06 11:36:29 +02:00
.gitignore docs: finalize self-exclusion design + add gitignore 2026-08-09 09:52:24 +02:00
.python-version chore: commit uv-generated project files 2026-08-09 09:52:28 +02:00
AGENTS.md chore(hygiene): replace internal hostnames/paths with placeholders; add public repo hygiene notes 2026-09-06 11:36:29 +02:00
icon.png chore: add logo assets 2026-08-09 13:48:28 +02:00
icon.svg chore: add logo assets 2026-08-09 13:48:28 +02:00
LICENSE docs: add CC0 1.0 Universal license 2026-08-09 15:01:51 +02:00
pyproject.toml chore: bump version to 1.0.0 2026-08-09 21:16:39 +02:00
README.md fix: search recall, judgement loop, and dedup 2026-09-05 09:48:58 +02:00
reflect.toml chore(hygiene): replace internal hostnames/paths with placeholders; add public repo hygiene notes 2026-09-06 11:36:29 +02:00
uv.lock docs: clarify README with separate local/hub/client setup sections 2026-08-10 16:19:23 +02:00

Reflect logo

Reflect

Autonomous nightly extraction of "lessons learned" from Opencode session history, served to future sessions via an MCP server. Sessions are required to judge every lesson they read as useful or not useful, and that judgement — not mere retrieval — is what raises or lowers a lesson's strength. Lessons climb to core memories when repeatedly judged useful and decay when never needed again.

Cost note: Reflect deliberately spends tokens to build and maintain memory — the nightly worker reads session transcripts and an LLM summarizes them, and future sessions can consume lessons. Only run it where token cost is low, e.g. with local LLMs (like llama.cpp) rather than a metered hosted model.

Prerequisites

  • uv (for dependency management)
  • opencode (source of session history)

Setup

Pick the section that matches your deployment. All three share the first two steps (clone + config); they diverge at step 3.

1. Single machine (local)

Everything runs on one computer. The nightly worker reads that machine's opencode.db and writes to a local lessons.db. Future opencode sessions on the same machine read lessons back via a local MCP server.

# 1. Clone and install
git clone <repo-url> reflect
cd reflect
uv sync                            # installs dependencies into .venv

# 2. Configure
cp reflect.toml ~/.config/reflect/reflect.toml
# (edit: nothing mode-specific needed; defaults are local)

# 3. Register the read-only MCP server so opencode sessions can query lessons
opencode mcp add reflect -- uv run --project <path-to-reflect> reflect mcp

# 4. Enable the nightly timer
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.service
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.timer
systemctl --user enable --now reflect-nightly.timer

# 5. Verify a run end-to-end before trusting it
uv run reflect run --dry-run
uv run reflect run

2. Hub

The hub owns the shared lessons.db and serves two MCP servers over HTTP so client machines can connect. The hub also runs the nightly extraction for its own sessions.

# 1. Clone and install (same as local)
git clone <repo-url> reflect
cd reflect
uv sync

# 2. Configure — set mode = "hub"
cp reflect.toml ~/.config/reflect/reflect.toml
# Edit ~/.config/reflect/reflect.toml:
#   mode = "hub"
#   (optionally set mcp_host to a non-loopback address if clients are remote)

# 3. Register the hub's own MCP server for this machine's opencode sessions
opencode mcp add reflect --url http://127.0.0.1:4010/mcp

# 4. Enable the nightly timer (extracts lessons from the hub's own sessions)
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.service
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.timer
systemctl --user enable --now reflect-nightly.timer

# 5. Start the two MCP servers that clients connect to
#    (edit the %h paths in the .service files to match your layout first)
systemctl --user link <path-to-reflect>/systemd/reflect-mcp-read.service
systemctl --user link <path-to-reflect>/systemd/reflect-mcp-worker.service
systemctl --user enable --now reflect-mcp-read.service reflect-mcp-worker.service

The hub's read server listens on mcp_host:mcp_port (default 0.0.0.0:4010). The worker server listens on mcp_host:mcp_worker_port (default 0.0.0.0:4011). Clients connect to the worker URL for writes and the read URL for queries.

The hub must be on a trusted network — there is no authentication.

3. Client

A client (spoke) has no local lessons.db. It reads its own opencode.db, runs the nightly worker against the hub's worker MCP server, and registers the hub's read server so its own opencode sessions can query the shared lessons.

# 1. Clone and install (same as local)
git clone <repo-url> reflect
cd reflect
uv sync

# 2. Configure — set mode = "client" and point at the hub
cp reflect.toml ~/.config/reflect/reflect.toml
# Edit ~/.config/reflect/reflect.toml:
#   mode = "client"
#   hub_url = "http://<hub-hostname>:4011/mcp"

# 3. Register the hub's read server for this machine's opencode sessions
opencode mcp add reflect --url http://<hub-hostname>:4010/mcp

# 4. Enable the nightly timer (runs the extraction driver against the hub)
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.service
systemctl --user link <path-to-reflect>/systemd/reflect-nightly.timer
systemctl --user enable --now reflect-nightly.timer

# 5. Verify
uv run reflect run --dry-run
uv run reflect run

Clients do not need the MCP systemd services — the hub serves those. Clients only need the nightly timer (which drives the extraction) and the opencode mcp add entry (so sessions can read lessons).

Configuration

Config is a TOML file. Resolution order (first hit wins):

  1. --config <path> passed to reflect run|mcp|lifecycle|status
  2. REFLECT_CONFIG environment variable (path to a file)
  3. ~/.config/reflect/reflect.toml if it exists
  4. Built-in defaults

Every option lives under a [core] section — flat top-level keys are silently ignored. To create your config, copy the shipped sample and uncomment what you need:

cp reflect.toml ~/.config/reflect/reflect.toml   # then edit

Invalid or unknown keys are ignored and fall back to defaults.

Key Default Purpose
opencode_db ~/.local/share/opencode/opencode.db session history (read-only)
lessons_db ~/.local/share/reflect/lessons.db lesson store (created on first run; clients ignore this)
worker_model "" model used by the nightly agent (empty = opencode's default)
worker_agent reflect-worker reserved agent name (self-excluded from extraction)
exclude_agents ["reflect-worker"] agents never processed
disabled_projects [] project names hidden from search
core_threshold 80.0 strength at which a lesson becomes a core memory
archive_threshold 15.0 strength below which a lesson is archived
base_strength 30.0 strength of a newly created lesson
decay_per_day 1.0 daily strength decay
reoccurrence_bonus 10.0 strength gain when a lesson re-occurs
retrieval_bump 1.0 strength gain on each search retrieval (stopgap so retrieved lessons survive until judged)
useful_bonus 10.0 strength gain when an agent judges a lesson useful (rate_lessons)
not_useful_penalty 15.0 strength loss when an agent judges a lesson not useful (rate_lessons)
rescan_interval_days 30 re-review older sessions this often
rescan_quota 20 max old sessions re-reviewed per night
max_sessions_per_run 0 (0 = unlimited) per-night cap on sessions processed; the shipped sample sets 50 to drain the first-run backlog gradually
batch_size 15 sessions processed per LLM run (each run starts with a fresh context)
consolidate_interval_nights 7 run the consolidation pass (merge duplicates, supersede outdated lessons) every N nights
purge_ttl_days 90 hard-delete archived lessons after this long
run_timeout_s 3600 nightly agent timeout
transcript_max_chars 12000 per-session transcript budget fed to the agent
mode "local" "local" (single machine), "hub" (local + serve the MCP over HTTP), or "client" (spoke that writes to a remote hub)
hub_url "" hub worker MCP URL for mode = "client", e.g. http://<hub-hostname>:4011/mcp
mcp_host 0.0.0.0 bind host for the reflect mcp streamable-http transport
mcp_port 4010 default HTTP port for the read MCP server
mcp_worker_port 4011 default HTTP port for the worker MCP server (reflect mcp --worker)

workdir, opencode_bin, uv_bin, and path_extra default to this machine's paths; adjust them if you run reflect on a different machine.

Sizing the token budget (rule of thumb). Sessions are processed in batches of batch_size, and every batch runs in a fresh LLM context (the driver launches one short agent run per batch, so context never accumulates across a night — no compaction, no overflow). To pick a batch_size that fits your model's window, roughly one batch can hold

batch_size ≈ (context tokens × 0.85) ÷ (transcript_max_chars ÷ 4 + ~750)

tokens per session before overflowing — a token is about 4 characters, the 0.85 reserves ~15% of the window for the prompt, tool definitions, and output, and the ~750 accounts for the agent's own reasoning per session. For a 140K-token model with transcript_max_chars = 12000 that is (140000 × 0.85) ÷ (12000 ÷ 4 + 750) ≈ 32 sessions per batch; the shipped default of 15 is a safe choice. If a batch aborts with "request exceeds the available context size", lower transcript_max_chars (each session costs transcript_max_chars ÷ 4 tokens) or lower batch_size, rather than raising max_sessions_per_run — smaller batches keep each run within the window. run_timeout_s applies to each batch run.

Commands

  • uv run reflect run — run the nightly pipeline once (worklist → LLM agent → verify → lifecycle). --config <path> is optional and only needed to point at a config other than the default (~/.config/reflect/reflect.toml). In mode = "client" this drives the spoke against the hub instead.
  • uv run reflect run --dry-run — print the session worklist without running the agent
  • uv run reflect mcp — MCP server (read-only tools; --worker adds extraction tools). --transport stdio|streamable-http, --host, and --port select the transport; the HTTP defaults come from mcp_host/mcp_port/mcp_worker_port.
  • uv run reflect lifecycle — apply decay/archive/purge only
  • uv run reflect status — recent run history
  • uv run reflect stats [--days N] — usefulness dashboard (retrievals, judgements, reoccurrences, coverage, per-project)

status, stats, and lifecycle operate on the hub's lessons.db; running them on a client returns an error.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        HUB machine                          │
│                                                             │
│  nightly timer ──> reflect run (extracts lessons)           │
│                  ──> writes to local lessons.db              │
│                                                             │
│  reflect mcp (port 4010) ──> read-only lessons for anyone   │
│  reflect mcp --worker (port 4011) ──> write tools for clients│
└─────────────────────────────────────────────────────────────┘
           ▲                          ▲
           │ hub_url (worker)         │ opencode mcp add (read)
           │                          │ (localhost on hub)
┌──────────┴──────────┐  ┌───────────┴──────────────┐
│   Client machine 1  │  │   Client machine 2       │
│                     │  │                          │
│ nightly timer ──>   │  │ nightly timer ──>        │
│   reflect run       │  │   reflect run            │
│   (writes via hub)  │  │   (writes via hub)       │
│                     │  │                          │
│ opencode mcp add ───┘  │ opencode mcp add           │
│   (reads from hub)     │   (reads from hub)         │
└───────────────────────┘  └──────────────────────────┘

On each machine the lesson decay/purge lifecycle is claimed at most once per UTC day across all machines, gated by the hub's shared lessons.db: whichever reflect run or finish_run call runs first that day applies it, and the rest skip — so spokes and the hub's own night never double-run it.

Notes

  • Reflect sessions are excluded from extraction (agent reflect-worker and descendants).
  • opencode.db is read-only; all writes go to lessons.db.
  • Search is FTS5 keyword search; no embeddings.
  • The nightly worker is confined to reflect_* MCP tools only; all other tools (bash, edit, webfetch, …) are denied via a deny-by-default agent permission allowlist.
  • max_sessions_per_run caps how many sessions a single night processes (built-in default 0 = no cap). Sessions not processed stay in the worklist and are picked up the next night. The shipped reflect.toml overrides this to 50 as a safety valve: the first run faces the entire history of past sessions as one huge backlog, so the cap drains it gradually instead of in a single enormous run.

License

This project is released to the public domain under the CC0 1.0 Universal license. See the LICENSE file for the full legal text.