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
- Core Memory (5 blocks, always loaded):
user,projects,patterns,learnings,context - Archival Memory (searchable): Long-term storage with Qwen3-Embedding-8B semantic vector search (1024 dims via OpenRouter)
- Recall Memory (pre-loaded): Recent conversation history injected at startup
Quick Start
Prerequisites
- Python 3.11+
- uv
- Modal account (free tier works)
- OpenRouter API key (for embeddings)
- GitHub OAuth App (for MCP auth)
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:
- Modal account — checks you're logged in (run
modal setupif not) - Server URL — computed from your Modal username
- GitHub OAuth App — tells you exactly what to fill in at https://github.com/settings/developers (Homepage URL, Callback URL)
- Configuration — creates
~/.config/yaucca/.envwith your auth token pre-generated and placeholders for the keys you need to paste in - Deploy — pushes secrets to Modal and deploys (only after
.envis 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:
- User profile → interactively asks your name, role, etc. and seeds
the
usermemory block on the server (skips if already seeded; use--user-block "..."to skip the interactive prompt) - Hooks → added to
~/.claude/settings.json(SessionStart, Stop, SessionEnd — see Hook lifecycle below) - 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. - MCP server → runs
claude mcp addto 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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11b607109e2ed6c900d293e86ba00f1748332809d67fb772339c91587e9395d3
|
|
| MD5 |
4266bfff2b78e56abc7feaf814dcb854
|
|
| BLAKE2b-256 |
d8dd0de9109ff2df04c8c09e6f35c3becc7d10105a2b384e0e84ec26b56e068f
|
Provenance
The following attestation bundles were made for yaucca-0.10.0.tar.gz:
Publisher:
publish.yml on jakemannix/yaucca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yaucca-0.10.0.tar.gz -
Subject digest:
11b607109e2ed6c900d293e86ba00f1748332809d67fb772339c91587e9395d3 - Sigstore transparency entry: 1280112374
- Sigstore integration time:
-
Permalink:
jakemannix/yaucca@02e9604dc7854efa43b95c9076b8649071fd9461 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/jakemannix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@02e9604dc7854efa43b95c9076b8649071fd9461 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8e2c645c2d33c1d917af589105587125d8659ed3e04428644c4d1c33b6b990f
|
|
| MD5 |
3d9ee9cba67108a0b7df58dfbc8fbc39
|
|
| BLAKE2b-256 |
6f8b1c57092c88a3b5268c24dc0b3a88b8e659603b031900b304981b1d563b51
|
Provenance
The following attestation bundles were made for yaucca-0.10.0-py3-none-any.whl:
Publisher:
publish.yml on jakemannix/yaucca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yaucca-0.10.0-py3-none-any.whl -
Subject digest:
c8e2c645c2d33c1d917af589105587125d8659ed3e04428644c4d1c33b6b990f - Sigstore transparency entry: 1280112427
- Sigstore integration time:
-
Permalink:
jakemannix/yaucca@02e9604dc7854efa43b95c9076b8649071fd9461 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/jakemannix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@02e9604dc7854efa43b95c9076b8649071fd9461 -
Trigger Event:
release
-
Statement type: