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.8.0.tar.gz (191.1 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.8.0-py3-none-any.whl (53.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for yaucca-0.8.0.tar.gz
Algorithm Hash digest
SHA256 204e70321ba7e9503ffc67f2e1af38d3c611bd362777a4b4ef061cec2c6266a4
MD5 e7e96180040f5fc143983c822e520494
BLAKE2b-256 04a1e9c60f89aeb5a72f389112c1ea16e8aeec85aa6504d4867cd9d707092c59

See more details on using hashes here.

Provenance

The following attestation bundles were made for yaucca-0.8.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.8.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for yaucca-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d95966f09c5b334680e8c5caee5eeed51c27e48d8ad9c331f973085c787f795
MD5 4d69f0446630babacbe3a71e610991f4
BLAKE2b-256 c21aaeba01079a584b622e5a924baf9b5fb38083d00b74e8ae849cfa41cdc012

See more details on using hashes here.

Provenance

The following attestation bundles were made for yaucca-0.8.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