Skip to main content
Glama

English | 中文

folio

A personal memory hub that unifies memories across AI coding harnesses (Claude Code, Codex, Cursor, Kimi Code, ZCode).

Every harness keeps its own memory/rules store, in incompatible formats that never talk to each other. folio one-way imports those scattered memories into a local file-based memory bank (~/.folio) with a unified format and unified search, then serves the bank back over an MCP server so any MCP-capable harness can read all of it.

Three design principles

  1. One-way import: only reads from each harness's native store and syncs incrementally into the memory bank; it never writes back to any harness's native directory. The bank is the single source of truth — a memory stays in the bank even after its source file is deleted.

  2. Unified format: every memory is Markdown + frontmatter, in one of four types — user (user preferences), feedback (feedback and lessons learned), project (project state and decisions), reference (reference material).

  3. Serve back over MCP: the bank is exposed to harnesses via folio serve (a stdio MCP server); harnesses without MCP support are not integrated.

Related MCP server: memory-mcp

Screenshots

Architecture

┌──────────────────── Harness native stores (read-only) ────────────────────┐
│ ~/.claude  ~/.codex  ~/.cursor  ~/.kimi-code  ~/.zcode  <project>/.*      │
└───────────────────────────────────┬───────────────────────────────────────┘
                                    │ ① import (adapters, incremental sync / watch)
                                    ▼
                        ┌───────────────────────┐
                        │ organize               │  LLM or local dedup rules:
                        │ dedup / merge / retype │  dry-run preview,
                        │ / retag / flag         │  --apply to execute
                        │ conflicts              │
                        └───────────┬───────────┘
                                    ▼
              ┌────────────────────────────────────────┐
              │ local memory bank ~/.folio           │
              │ (single source of truth)                │
              │ memory/<type>/*.md + MEMORY.md index    │
              │ state/ sync state   cache/ search index │
              └─────────┬───────────────────┬──────────┘
                        │ ③ serve           │ browse/manage
                        ▼                   ▼
              folio serve          folio CLI
              (stdio MCP server)      list/search/show/stats/
              5 tools + 1 resource    organize/conflicts/doctor

Requirements

  • Node.js ≥ 18 (also for development builds)

  • pnpm 9 (the repo pins pnpm@9.15.4; corepack enable prepares it automatically)

Quick start

# Install from npm (self-contained bundle, zero runtime deps)
npm i -g folio-memory        # gives you the `folio` command

# Or build from source (monorepo: core / cli / mcp-server)
pnpm install && pnpm -r build
npm i -g packages/cli        # or: cd packages/cli && pnpm link --global

# Four steps
folio init                # ① initialize ~/.folio (directory layout + default config.toml)
folio sync                # ② incrementally import memories from detected harnesses
folio organize            # ③ organize: dry-run preview (falls back to local dedup rules without an API key)
folio install --all       # ④ register the folio MCP server into each harness's config

install targets only detected harnesses by default; --all targets all 5. The original config is backed up before every write (<file>.bak-<timestamp>), and folio uninstall removes the registration.

Without a global install you can also run node packages/cli/dist/cli.js <command> directly.

Command reference

Global options: --home <dir> (takes precedence over FOLIO_HOME, default ~/.folio), --json (machine-readable output), -V/--version. Exit code 2 for usage errors, 1 for runtime errors.

Command

Purpose

Common options

init

Initialize the home directory (idempotent)

—

sync

Collect memories from harnesses and sync incrementally

--adapter <id> (repeatable), --project-dir <dir> (repeatable), --classify (call the LLM to classify new memories during sync)

watch

Watch memory source directories; auto-sync after debounce

--debounce <ms> (default 2000)

list

List memories

--type, --scope, --limit, --archived

search <query>

Full-text search (Chinese supported)

--type, --scope, --limit

show <id>

Show a memory's full frontmatter and body (accepts a unique id prefix)

—

organize

Organize: merge duplicates, retype/retag, flag conflicts (dry-run by default)

--apply, --batch-size <n>

conflicts

List conflicting memory pairs flagged with conflictsWith

—

serve

Start the MCP server over stdio (stdout is the protocol channel)

—

install

Register the MCP server into harness configs (backup before write)

--adapter <id>, --all, --command "<cmd>"

uninstall

Remove the registration from harness configs (backup before write)

--adapter <id>, --all

doctor

Health check: home/config/adapters/memory bank/search index

--check-llm (additionally probes real LLM connectivity)

stats

Grouped stats by type/scope/source harness

—

Config file ~/.folio/config.toml

Default config generated by init; environment variables take precedence over the file.

[llm]
enabled = true                          # master switch for LLM-assisted features (classify, organize)
# apiKey = "sk-..."                     # prefer the FOLIO_LLM_API_KEY environment variable
baseURL = "https://api.openai.com/v1"   # OpenAI-compatible endpoint
model = "gpt-4o-mini"
classifyOnSync = false                  # whether sync auto-classifies new memories via the LLM

[sync]
autoOrganize = false                    # whether to auto-organize after sync completes

# Per-adapter switches; treated as enabled when omitted
# [adapters.claude-code]
# enabled = false

Environment variables:

Variable

Purpose

FOLIO_HOME

Memory bank home directory (default ~/.folio)

FOLIO_LLM_API_KEY / FOLIO_LLM_BASE_URL / FOLIO_LLM_MODEL

Override the corresponding [llm] fields

CLAUDE_CONFIG_DIR / CODEX_HOME / FOLIO_CURSOR_HOME / KIMI_CODE_HOME / FOLIO_ZCODE_HOME

Override each harness's home directory (defaults ~/.claude, etc.)

The LLM is used for exactly two things: classifying new memories in sync --classify, and generating organize plans in organize. Everything works without an API key; organize automatically degrades to local dedup rules.

Adapter support matrix

Harness

What gets imported

Where the MCP server is registered

Notes

Claude Code

~/.claude/CLAUDE.md (global instructions); ~/.claude/rules/**/*.md; ~/.claude/projects/<proj>/memory/**/*.md

top-level mcpServers in ~/.claude.json

skips the MEMORY.md index; project directory names are decoded -→/ into paths; frontmatter type is honored

Codex

~/.codex/AGENTS.md; ~/.codex/memories/**/*.md

top-level mcp_servers (TOML) in $CODEX_HOME/config.toml

memories_extensions/ (screen context, sensitive) is never collected; frontmatter project decides project-level scope

Cursor

~/.cursor/rules/**/*.{md,mdc}; <project>/.cursor/rules/**/*.mdc

top-level mcpServers in ~/.cursor/mcp.json

project rules are .mdc only; frontmatter description/globs/alwaysApply is stored in metadata

Kimi Code

~/.kimi-code/AGENTS.md; ~/.kimi-code/memories/**/*.md

top-level mcpServers in ~/.kimi-code/mcp.json

skips MEMORY.md; entries under memories/<proj>/ are treated as project-level

ZCode

~/.zcode/AGENTS.md; ~/.zcode/cli/memories/projects/<proj>/memory/**/*.md; ~/.zcode/agent-memory/** and <project>/.zcode/agent-memory/**

nested mcp.servers in ~/.zcode/cli/config.json

skips MEMORY.md; MCP servers live under a nested key, not top-level

All adapters are read-only against harness directories; session records (sessions/history, etc.) are never touched. sync automatically includes the current working directory (and any --project-dir) in the project-level rules scan.

Memory file format

Each memory is a Markdown file under ~/.folio/memory/<type>/:

---
id: m_abc123def456
type: feedback                # user | feedback | project | reference
scope: project:/path/to/proj  # global or project:<project identifier>
title: Build before testing
tags: [testing, build]
source:
  harness: claude-code        # source harness ("mcp" when written via MCP)
  path: /original/source.md   # original file path (optional)
  importedAt: 2026-09-29T12:00:00.000Z
hash: <body sha256>           # basis for incremental sync and dedup
created: 2026-09-29T12:00:00.000Z
updated: 2026-09-29T12:00:00.000Z
supersedes: [m_xxx]           # optional: ids this memory supersedes
conflictsWith: [m_yyy]        # optional: ids this memory conflicts with
archived: false               # optional: archived into archive/ after a merge
---

Body (Markdown).

MEMORY.md (the bank index) is rebuilt automatically by folio — do not edit it by hand.

MCP server

folio serve starts over stdio and provides 5 tools + 1 resource:

  • Tools: memory_search, memory_list, memory_read, memory_write, memory_update

  • Resource: memory://index (current content of the bank index MEMORY.md)

The default launch command registered by folio install is folio serve; customize it with --command "node /abs/path/cli.js".

Privacy

  • All data stays on the local filesystem; there is no telemetry of any kind.

  • The only network egress is the LLM API you configure yourself (requests are made only by sync --classify / organize / doctor --check-llm).

Docker end-to-end tests

The repo ships a full-pipeline verification image (it never installs any harness on your host):

docker build -f docker/Dockerfile -t folio-e2e .   # build stage runs pnpm install / build / full unit tests
docker run --rm folio-e2e                          # in-container: init → sync → search → install → MCP smoke

The image globally installs real harness CLIs (@anthropic-ai/claude-code, @openai/codex, @moonshot-ai/kimi-code — installability check only, no login, no runs); Cursor and ZCode have no headless install path, so docker/e2e.sh fakes their home directories per the documented layout. Any failed assertion exits non-zero.

Note: @moonshot-ai/kimi-code requires Node ≥ 22.5 at runtime (it uses createZstdDecompress from node:zlib), so on the node:20 base only the install check passes and --version fails; the build report marks it installed-but-run-failed as expected, without affecting the rest of the verification.

Real-harness lab (optional)

docker/Dockerfile.harness-lab + docker/harness-lab.sh spin up a lab with real models: the container installs Claude Code / Codex / Kimi Code, configures a third-party API key per each vendor's docs (all three can share one Kimi Code subscription key — api.kimi.com/coding exposes both an Anthropic-compatible and an OpenAI/Responses-compatible endpoint), runs one real session per harness that induces a long-term memory write, then verifies folio imports it and serves it over MCP:

docker build -f docker/Dockerfile.harness-lab -t folio-harness-lab .
echo "KIMI_API_KEY=sk-..." > /tmp/folio-lab.env   # key never enters the image or the repo
docker run --rm --env-file /tmp/folio-lab.env folio-harness-lab

Third-party key configuration per harness (verified in the lab):

Harness

Configuration

Claude Code

ANTHROPIC_BASE_URL=https://api.kimi.com/coding/ + ANTHROPIC_API_KEY, plus the ~/.claude.json onboarding-skip flags (see the lab script)

Codex

[model_providers.kimi] in ~/.codex/config.toml: base_url + env_key (key read from env) + wire_api = "responses"; [features] memories = true enables memory

Kimi Code

[providers.kimi] in ~/.kimi-code/config.toml: type = "kimi" + base_url + api_key_env

Cursor and ZCode are GUI desktops with no headless mode; they are out of lab scope (their adapters are covered by fixtures + e2e).

Field notes: ① Claude Code's headless mode (claude -p) has a known flaky MCP tool-registration race — the server shows Connected and its resource is visible, but the tool list occasionally misses the session; retrying or using a fresh project directory recovers. ② Current Kimi Code has no long-term memory files (the official data-locations doc lists no memories/ directory); its adapter is future-proofing for when the feature lands. ③ Whether a model calls MCP tools is inherently stochastic — the lab's hard assertion is "at least one harness completes a real write".

Desktop app (in development)

packages/desktop (@folio/desktop) is the Electron desktop app for folio. The renderer is a 1:1 port of the gui-mock/ design prototype (Vite + React 18 + Tailwind 3 + shadcn); all data is produced for real by @folio/core over IPC.

Features: memory-bank file-tree browsing / search (⌘K) / reading & editing / create / archive / reveal in Finder, MEMORY.md index page, sources page (adapter detection status + MCP register/unregister), organize page (confirm each LLM plan item before applying), activity page (sync log), settings page (LLM endpoint/model/key, adapter toggles), sidebar manual sync + watch auto-sync toggle.

pnpm --filter @folio/desktop dev     # development mode (electron-vite dev, HMR)
pnpm --filter @folio/desktop build   # output to out/{main,preload,renderer}
pnpm --filter @folio/desktop start   # preview the built output
pnpm --filter @folio/desktop test    # services unit tests (vitest, no display needed)

During development, point at a temporary bank with FOLIO_HOME=/tmp/xxx pnpm --filter @folio/desktop dev to avoid touching the real ~/.folio. Without an Electron environment, out/renderer is a plain static site (with mock-data fallback) — host it on any static server to preview the UI.

Note for Node 18 hosts: electron 44's install.js (the postinstall that downloads the binary) goes through @electron/get v5, which requires Node ≥ 22, so pnpm install under Node 18 skips the binary download. To actually run Electron, patch it in manually (one-time):

curl -sL https://github.com/electron/electron/releases/download/v44.4.5/electron-v44.4.5-darwin-arm64.zip -o /tmp/e.zip
unzip -q -o /tmp/e.zip -d packages/desktop/node_modules/electron/dist
echo "Electron.app" > packages/desktop/node_modules/electron/path.txt

Security baseline (enforced item by item — read packages/desktop/src/main/ before changing any of this):

  1. webPreferences explicitly sets contextIsolation: true / sandbox: true / nodeIntegration: false / webSecurity: true; the preload is bundled as a single-file CJS (a hard sandbox requirement).

  2. Strict CSP injected per mode (transformIndexHtml in electron.vite.config.ts): prod default-src 'self', connect-src 'none'; dev only additionally relaxes what HMR needs — connect-src 'self' ws: http://localhost:* and script-src 'unsafe-inline'.

  3. setPermissionRequestHandler / setPermissionCheckHandler deny everything by default.

  4. will-navigate is always prevented; setWindowOpenHandler always denies.

  5. Every ipcMain.handle first validates event.senderFrame.origin (dev only accepts the vite dev-server origin, prod only file://).

  6. All IPC payloads pass zod schemas at the main-process entry (src/shared/ipc.ts, shared by all three sides).

  7. contextBridge exposes only narrow functions + plain data, never the raw ipcRenderer; subscription APIs return an unsubscribe function.

  8. Zero telemetry: no crashReporter.start(), no analytics of any kind.

  9. The LLM API key only ever lands in state/secrets.json encrypted via safeStorage; settings:get returns only hasApiKey + a mask — the plaintext never enters config.toml and is never sent to the renderer.

  10. No remote module; the window reference is nulled on close, and nothing is sent after webContents is destroyed.

Roadmap

  • Second batch of adapters: Gemini CLI, Qwen Code, Trae, CodeBuddy, OpenCode

  • Legacy tool imports: Roo, Continue, iFlow

  • Local REST API (bound to 127.0.0.1 + token auth)

  • Go backend (single-binary distribution)

  • Electron desktop app: first version has landed (see "Desktop app (in development)"); packaging/distribution and auto-update are next

Repository structure

packages/
  core/        # adapters, memory store, sync, search, organize, LLM (frozen public API in src/index.ts)
  cli/         # folio CLI (commander)
  mcp-server/  # stdio MCP server (5 tools + 1 resource)
  desktop/     # Electron desktop app (in development; renderer ported from gui-mock/)
docker/        # Dockerfile + e2e.sh (containerized end-to-end verification)
assets/        # logo and icons (README header, desktop window icon)
gui-mock/      # design prototype (Vite + React static mock, not product code)

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that reads all your Claude Code project memory files and exposes them as tools. Lets any Claude instance — in any project, or via Claude.ai — query your full project history and preferences.
    5
    10 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A local stdio MCP server that exposes a shared brain (read/search/write) over ~/.claude/memory, allowing memories written in any agent to be readable and searchable across all three (Claude Code, Cursor, Codex). It provides tools like memory_search, memory_read, memory_write, etc., with no external services.
    23 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides a file-first personal memory layer for AI agents, enabling them to store and retrieve memories as markdown files with an SQLite index. The MCP server offers read-only search by default, with optional write tools for manual memory addition and conflict resolution.
    1 npm
    MIT