Skip to main content

Persistent long-term memory for Claude Code — cloud-native with SQLite + Modal

Project description

yaucca

Yet Another Useless Claude Code Agent — persistent long-term memory for Claude Code, deployed as a self-hosted FastAPI + SQLite + sqlite-vec backend on Modal.com.

Inspired by the MemGPT tiered memory architecture (now Letta), but built as a lightweight single-container stack with scale-to-zero billing (~$0-1/month).

Every Claude Code session starts with full memory context and ends by persisting what happened. Memory survives across sessions, projects, and context compactions — accessible from Claude Code (laptop), Claude.ai (web), and Claude mobile (phone).

Architecture

┌──────────────────────────────────────────────────────┐
│  Modal.com (scale-to-zero, ~$0-1/month)              │
│                                                      │
│  FastAPI + SQLite + sqlite-vec + Qwen3 embeddings    │
│  Remote MCP (OAuth 2.1 + GitHub login)               │
│  Persistent volume: /data/yaucca.db                  │
└───────────────┬──────────────────────────────────────┘
                │ HTTPS
    ┌───────────┼───────────┐
    │           │           │
┌───┴────┐ ┌────┴───┐ ┌─────┴────┐
│ Claude │ │ Claude │ │  Claude  │
│ Code   │ │  .ai   │ │  mobile  │
│(laptop)│ │ (web)  │ │ (phone)  │
│        │ │        │ │          │
│ hooks  │ │ remote │ │ remote   │
│+remote │ │ MCP    │ │ MCP      │
│  MCP   │ │        │ │          │
└────────┘ └────────┘ └──────────┘

How it works

  • Hooks (Claude Code only): SessionStart injects memory, Stop persists raw exchanges, SessionEnd generates a summary via claude -p
  • Remote MCP (all surfaces): 7 tools for reading/writing memory blocks, semantic search over archival passages, and progressive disclosure drill-down
  • OAuth 2.1: GitHub login gates access — only allowed users can connect

Memory tiers

  1. Core Memory (5 blocks, always loaded): user, projects, patterns, learnings, context
  2. Archival Memory (searchable): Long-term storage with Qwen3-Embedding-8B semantic vector search (1024 dims via OpenRouter)
  3. Recall Memory (pre-loaded): Recent conversation history injected at startup

Quick Start

Prerequisites

Step 1: Deploy your backend (once, from your laptop)

uv pip install yaucca[deploy]

# Guided setup: checks Modal, shows GitHub OAuth instructions,
# creates ~/.config/yaucca/.env, deploys to Modal
yaucca-deploy

yaucca-deploy walks you through each step:

  1. Modal account — checks you're logged in (run modal setup if not)
  2. Server URL — computed from your Modal username
  3. GitHub OAuth App — tells you exactly what to fill in at https://github.com/settings/developers (Homepage URL, Callback URL)
  4. Configuration — creates ~/.config/yaucca/.env with your auth token pre-generated and placeholders for the keys you need to paste in
  5. Deploy — pushes secrets to Modal and deploys (only after .env is complete)

First run will pause at step 4 and ask you to edit ~/.config/yaucca/.env with your OpenRouter API key and GitHub OAuth credentials. Fill those in, then re-run yaucca-deploy to finish.

Step 2: Use it everywhere

On every machine or cloud environment where you use Claude Code:

uv pip install yaucca

# Interactive setup: seeds your user profile, installs hooks + memory
# rules, adds the remote MCP server
yaucca-install

yaucca-install does four things:

  1. User profile → interactively asks your name, role, etc. and seeds the user memory block on the server (skips if already seeded; use --user-block "..." to skip the interactive prompt)
  2. Hooks → added to ~/.claude/settings.json (SessionStart, Stop, SessionEnd — see Hook lifecycle below)
  3. Memory rules → installed at ~/.claude/rules/yaucca-memory.md — teaches Claude how to use the memory blocks (read-modify-write, hygiene, when to update each block). Edit this file to customize.
  4. MCP server → runs claude mcp add to register the remote MCP server

First-time MCP auth: after install, start Claude Code and type /mcp → select yaucca → browser opens for GitHub login → authorize → connected. Token auto-refreshes after that.

Claude.ai web / mobile: Settings → Integrations → Add custom integration → paste your server URL /mcp → GitHub OAuth.

Claude Code cloud environments: Set YAUCCA_URL + YAUCCA_AUTH_TOKEN as environment variables, then add to your setup script:

uv pip install yaucca && yaucca-install

Hook lifecycle

Hook When What Cost
SessionStart Session opens Injects core blocks + recent exchanges 1 HTTP GET
Stop Every turn Persists raw exchanges 1 HTTP POST
SessionEnd Session closes claude -p generates summary + updates context block 1 LLM call

Rollback

yaucca-install --uninstall              # remove hooks
claude mcp remove -s user yaucca        # remove MCP
cp ~/.claude/settings.json.bak ~/.claude/settings.json  # restore backup

Your data on Modal is never touched by rollback.

Configuration

Client-side (set in .env or environment)

Variable Default Description
YAUCCA_URL (required) Your Modal deployment URL
YAUCCA_AUTH_TOKEN (none) Bearer token for the REST API (hooks use this)
YAUCCA_REQUIRED false If true, hooks fail hard when cloud is unreachable

Server-side (set in Modal secrets via yaucca-deploy-secrets)

Variable Description
YAUCCA_AUTH_TOKEN Same token as client-side — authenticates hook REST calls
OPENROUTER_API_KEY For Qwen3-Embedding-8B embeddings
YAUCCA_ISSUER_URL Public URL of your deployment (OAuth issuer)
GITHUB_CLIENT_ID From your GitHub OAuth App
GITHUB_CLIENT_SECRET From your GitHub OAuth App
GITHUB_ALLOWED_USERS Comma-separated GitHub usernames allowed to authorize

Embedding Profiles

yaucca supports multiple embedding profiles for A/B testing retrieval quality. Each profile creates a separate passages_vec_{name} table. Search targets a profile via ?profile= query param.

from yaucca.cloud.db import Database, EmbeddingProfile

db = Database(
    db_path="/data/yaucca.db",
    embedding_profiles=[
        EmbeddingProfile("d1024", 1024),  # full Qwen3-Embedding-8B
        EmbeddingProfile("d512", 512),    # Matryoshka half
    ],
)

Backfill existing passages into new profiles:

curl -X POST "$YAUCCA_URL/api/admin/backfill?profile=d512" \
  -H "Authorization: Bearer $YAUCCA_AUTH_TOKEN"

Development

git clone https://github.com/jakemannix/yaucca.git
cd yaucca
uv sync --extra dev                # Install all deps (client + server + test)
uv run pytest                      # Unit tests (125 tests)
uv run ruff check . && ruff format .  # Lint + format
uv run mypy src/yaucca             # Type check

Testing the install flow

All commands support --app-name to create an isolated instance that doesn't touch your production server, database, or config:

# Deploy a test instance (separate Modal app, volume, and secrets)
yaucca-deploy --app-name yaucca-test

# Install against the test instance (separate .env, hooks, MCP entry)
yaucca-install --app-name yaucca-test

# Or use the wrapper scripts (backup + deploy + install in one step):
./scripts/setup.sh --app-name yaucca-test

# Tear down (restores your production config from backups):
./scripts/teardown.sh --app-name yaucca-test

# Fully destroy the test Modal resources (app, volume, secrets):
./scripts/teardown.sh --app-name yaucca-test --destroy-backend

Note: test instances need their own GitHub OAuth App with the test callback URL (https://<username>--yaucca-test-serve.modal.run/oauth/github/callback).

Releasing to PyPI

Releases use GitHub Actions with trusted publishing (no API tokens needed).

# 1. Bump the version in pyproject.toml
#    version = "0.3.0"

# 2. Commit, tag, and push
git add pyproject.toml
git commit -m "Bump version to 0.3.0"
git tag v0.3.0
git push && git push --tags

# 3. Create a GitHub release from the tag
gh release create v0.3.0 --generate-notes

The publish.yml workflow triggers on the release, builds with uv build, and publishes via OIDC to PyPI.

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yaucca-0.10.0.tar.gz (205.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yaucca-0.10.0-py3-none-any.whl (56.9 kB view details)

Uploaded Python 3

File details

Details for the file yaucca-0.10.0.tar.gz.

File metadata

  • Download URL: yaucca-0.10.0.tar.gz
  • Upload date:
  • Size: 205.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for yaucca-0.10.0.tar.gz
Algorithm Hash digest
SHA256 11b607109e2ed6c900d293e86ba00f1748332809d67fb772339c91587e9395d3
MD5 4266bfff2b78e56abc7feaf814dcb854
BLAKE2b-256 d8dd0de9109ff2df04c8c09e6f35c3becc7d10105a2b384e0e84ec26b56e068f

See more details on using hashes here.

Provenance

The following attestation bundles were made for yaucca-0.10.0.tar.gz:

Publisher: publish.yml on jakemannix/yaucca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file yaucca-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: yaucca-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 56.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for yaucca-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c8e2c645c2d33c1d917af589105587125d8659ed3e04428644c4d1c33b6b990f
MD5 3d9ee9cba67108a0b7df58dfbc8fbc39
BLAKE2b-256 6f8b1c57092c88a3b5268c24dc0b3a88b8e659603b031900b304981b1d563b51

See more details on using hashes here.

Provenance

The following attestation bundles were made for yaucca-0.10.0-py3-none-any.whl:

Publisher: publish.yml on jakemannix/yaucca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page