Metadata-Version: 2.5
Name: persistent-memory-core
Version: 1.1.0
Summary: Reusable persistent memory platform for AI agents — PostgreSQL + pgvector
Project-URL: Homepage, https://github.com/aress2525/persistent-memory-core
Project-URL: Documentation, https://pmc-mcp-318538827786.us-central1.run.app/dashboard
Project-URL: Repository, https://github.com/aress2525/persistent-memory-core
Project-URL: Changelog, https://github.com/aress2525/persistent-memory-core/blob/main/CHANGELOG.md
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ai,llm,mcp,memory,pgvector
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Requires-Dist: anthropic>=1.0
Requires-Dist: anyio>=4
Requires-Dist: bcrypt>=4.0
Requires-Dist: click>=8.0
Requires-Dist: gitpython>=3.1
Requires-Dist: openai>=1.0
Requires-Dist: pgvector>=0.3
Requires-Dist: psycopg-pool>=3.1
Requires-Dist: psycopg[binary]>=3.1
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: pydantic>=2.0
Provides-Extra: all
Requires-Dist: alembic>=1.13; extra == 'all'
Requires-Dist: google-api-python-client>=2.0; extra == 'all'
Requires-Dist: google-auth>=2.0; extra == 'all'
Requires-Dist: httpx>=0.27; extra == 'all'
Requires-Dist: markdownify>=0.13; extra == 'all'
Requires-Dist: mcp[cli]<2,>=1.10; extra == 'all'
Requires-Dist: notion-client>=2.0; extra == 'all'
Requires-Dist: paddleocr>=2.7; (python_version < '3.13') and extra == 'all'
Requires-Dist: paddlepaddle>=2.6; (python_version < '3.13') and extra == 'all'
Requires-Dist: pillow>=10.0; extra == 'all'
Requires-Dist: reportlab>=4.0; extra == 'all'
Requires-Dist: sentence-transformers>=2.7; extra == 'all'
Requires-Dist: slack-bolt>=1.18; extra == 'all'
Requires-Dist: sqlalchemy>=2.0; extra == 'all'
Requires-Dist: starlette>=0.37; extra == 'all'
Requires-Dist: uvicorn>=0.29; extra == 'all'
Requires-Dist: watchdog>=4.0; extra == 'all'
Provides-Extra: audit
Requires-Dist: reportlab>=4.0; extra == 'audit'
Provides-Extra: clickup
Requires-Dist: httpx>=0.27; extra == 'clickup'
Provides-Extra: connectors
Requires-Dist: google-api-python-client>=2.0; extra == 'connectors'
Requires-Dist: google-auth>=2.0; extra == 'connectors'
Requires-Dist: notion-client>=2.0; extra == 'connectors'
Provides-Extra: local
Requires-Dist: sentence-transformers>=2.7; extra == 'local'
Provides-Extra: mcp
Requires-Dist: httpx>=0.27; extra == 'mcp'
Requires-Dist: mcp[cli]<2,>=1.10; extra == 'mcp'
Requires-Dist: starlette>=0.37; extra == 'mcp'
Requires-Dist: uvicorn>=0.29; extra == 'mcp'
Provides-Extra: migrate
Requires-Dist: httpx>=0.27; extra == 'migrate'
Provides-Extra: migrations
Requires-Dist: alembic>=1.13; extra == 'migrations'
Requires-Dist: sqlalchemy>=2.0; extra == 'migrations'
Provides-Extra: research
Requires-Dist: httpx>=0.27; extra == 'research'
Requires-Dist: markdownify>=0.13; extra == 'research'
Provides-Extra: slack
Requires-Dist: slack-bolt>=1.18; extra == 'slack'
Provides-Extra: vision
Requires-Dist: paddleocr>=2.7; (python_version < '3.13') and extra == 'vision'
Requires-Dist: paddlepaddle>=2.6; (python_version < '3.13') and extra == 'vision'
Requires-Dist: pillow>=10.0; extra == 'vision'
Provides-Extra: watch
Requires-Dist: watchdog>=4.0; extra == 'watch'
Description-Content-Type: text/markdown

# persistent-memory-core

Persistent Memory Core (PMC) is a memory layer for AI coding agents. It indexes
your repositories into PostgreSQL + pgvector, records decisions, tasks, agent
runs and lessons, and hands the right slice of that memory back to Claude
Code, claude.ai, or any MCP client at the moment it is useful. GitHub stays
the canonical source of truth; the database holds rebuildable derived memory.

## Capabilities

- **Hybrid retrieval** — vector (HNSW) and full-text lanes fused with
  Reciprocal Rank Fusion, so exact identifiers and conceptual questions both
  land (ADR-030).
- **Project memory** — mission, milestones, prioritised tasks, decisions with
  rationale, issues, temporal facts, salience decay.
- **Episodic lessons** — lessons extracted from logged agent runs, embedded,
  deduplicated, reinforced when they help, injected before code.
- **Ambient context** — a compact context block for a prompt: lessons,
  relevant chunks, active tasks, recent decisions.
- **Claude Code plugin** — briefing on session start, ambient context on every
  prompt, automatic flush before compaction and at session end, search /
  brief / lessons / flush skills (`plugins/pmc-memory/`).
- **MCP server** — 33 tools over Streamable HTTP (`/mcp`) and SSE, bearer or
  OAuth 2.0 + PKCE for claude.ai, deployed on Cloud Run.
- **Operations** — Alembic migrations, `pmc doctor` (connectivity, schema
  invariants, embedding dimensions, Cloud Run reachability), production
  runbook, CI on four Python versions against pgvector.

## Quick start (local)

Prerequisites: Docker Desktop, [uv](https://docs.astral.sh/uv/), git.

```bash
git clone https://github.com/aress2525/persistent-memory-core.git
cd persistent-memory-core
docker compose up -d                 # PostgreSQL 16 + pgvector on port 5434
uv sync --all-extras                 # runtime extras + dev group (tests need them)
cp .env.example .env                 # add OPENAI_API_KEY / ANTHROPIC_API_KEY
uv run alembic upgrade head          # schema
uv run pmc doctor                    # everything green?
uv run pmc add-project --key myproj --name "My project"
uv run pmc reindex --project myproj --repo-path ../my-repo
uv run pmc search --project myproj --query "connection pool"
```

`scripts/bootstrap_local.{ps1,sh}` does the same in one step. Without
`OPENAI_API_KEY` the mock embedding provider is used (deterministic vectors,
fine for tests, useless for real search). Full instructions, including
macOS/Windows notes and production bootstrap: [docs/INSTALL.md](docs/INSTALL.md).

## Using it with Claude Code

Install the plugin once, then every checkout that has a `.pmc.json` (or a
registered git remote) gets memory automatically:

```
/plugin marketplace add aress2525/persistent-memory-core
/plugin install pmc-memory@pmc
```

Set `PMC_API_KEY` as a user-level environment variable (never in a settings
file). In a checkout of this repository, `claude --plugin-dir plugins/pmc-memory`
loads the plugin directly. What the hooks and skills do, and how to disable the
per-prompt context: [plugins/pmc-memory/README.md](plugins/pmc-memory/README.md)
and the [user manual](docs/USER_MANUAL.md).

For claude.ai, add the server as a custom connector at
`https://pmc-mcp-318538827786.us-central1.run.app/mcp` (OAuth is discovered
automatically): [docs/CLAUDE_AI_SETUP.md](docs/CLAUDE_AI_SETUP.md).

## CLI

The most used commands (`uv run pmc --help` lists all 60):

| Command | Purpose |
|---|---|
| `pmc doctor` | Health: database, schema invariants, embedding dims, Cloud Run |
| `pmc add-project` / `pmc projects` | Register and list projects |
| `pmc reindex --project K --repo-path P` | Index a repository (`--changed-only` for incremental) |
| `pmc search --project K --query Q` | Hybrid search with per-lane ranks |
| `pmc brief` / `pmc resume` / `pmc priorities` | Where you are and what is next |
| `pmc add-decision` / `pmc add-task` | Record memory by hand |
| `pmc flush --project K --text "…"` | Extract decisions and tasks from session text |
| `pmc lessons` / `pmc extract-lessons` | Episodic lessons |
| `pmc benchmark --project K --compare` | Recall@5, vector-only vs hybrid |
| `pmc set-remote` | Map a git remote to a project (used by the plugin) |
| `pmc export-md` | Obsidian-compatible Markdown export |
| `pmc oauth register` | Create an OAuth client for claude.ai |

## Architecture

- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — the full picture.
- Five schemas: `pmc_entity` (projects, repositories, documents, chunks,
  decisions, tasks, issues, strategic memory, lessons), `pmc_vector`
  (embeddings), `pmc_graph` (edges), `pmc_event` (ingestion, retrieval,
  agent runs, mutations, sessions), `pmc_policy` (index policies).
- Packages: `cli/` (Click commands by topic), `mcp_server/` (`tools/` and
  `routes/`, tools run off the event loop behind a bounded limiter),
  `retrieval/` (vector, full-text, fusion, hydration, salience), plus single
  modules for stores, chunking, embedding providers, extraction, lessons.
- Configuration is one typed `Settings` object (`config.py`); `os.environ` is
  banned elsewhere. Alembic owns the schema; `sql/cloudsql_bootstrap.sql` only
  creates the extension and schemas.

Decisions are recorded as ADRs in `docs/` (ADR-004 and ADR-005 were never
written; numbering continues at 006). ADR-028 covers the uv toolchain and the
async tool model, ADR-029 the Claude Code plugin, ADR-030 hybrid retrieval.

## Python API

```python
from persistent_memory_core.db import get_connection
from persistent_memory_core.reindex import reindex
from persistent_memory_core.retrieval import hybrid_search

with get_connection() as conn:
    reindex(conn, project_key="myproj", repo_path="../my-repo")
    for r in hybrid_search(conn, "myproj", "get_embedding_provider", top_k=5):
        print(r.file_path, r.metadata["vector_rank"], r.metadata["fts_rank"], round(r.score, 4))
```

## Environment variables

Generated from `Settings` with `uv run python scripts/dev/env_table.py`. A
`.env` in the working directory beats the process environment (local
development is authoritative); production reads the injected environment.
Set `PMC_DOTENV_OVERRIDE=0` to reverse that for one process.

| Variable | Default | Description |
|---|---|---|
| `DATABASE_URL` | `postgresql+psycopg://pmc_user:…@localhost:5434/pmc_dev` | PostgreSQL connection string (local Docker by default) |
| `PMC_DB_READY_TIMEOUT` | `15.0` | Seconds the server waits for the database at startup |
| `PMC_TENANT_ID` | `default` | Tenant scope for every query (single-tenant today) |
| `OPENAI_API_KEY` | *(none)* | Embeddings via OpenAI; omit for the mock provider |
| `EMBEDDING_MODEL` | `text-embedding-3-small` | Embedding model name |
| `EMBEDDING_DIMS` | `1536` | Must equal the vector column width (`pmc doctor` checks) |
| `EMBEDDING_PROVIDER` | *(none)* | `openai`, `local`, `mock`, or blank for auto-selection |
| `EMBEDDING_BASE_URL` | *(none)* | OpenAI-compatible local embedding server (TEI, Ollama, ...) |
| `EMBEDDING_DEVICE` | `cpu` | Device for the local provider |
| `EMBEDDING_BATCH_SIZE` | `32` | Batch size for the local provider |
| `ANTHROPIC_API_KEY` | *(none)* | Extraction and lesson models (Claude) |
| `PMC_EXTRACTION_MODEL` | `claude-haiku-4-5-20251001` | Model for `pmc extract` / session flush |
| `PMC_LESSON_MODEL` | `claude-haiku-4-5-20251001` | Model for lesson extraction |
| `PMC_API_KEY` | *(none)* | Operator bearer key for the MCP server (fail-closed) |
| `PMC_ALLOW_UNAUTHENTICATED` | `False` | Local development only: run without a key |
| `PORT` / `MCP_PORT` | `8080` | Server port (`PORT` from Cloud Run wins over `MCP_PORT`) |
| `MCP_HOST` | `0.0.0.0` | Bind address |
| `MCP_ALLOWED_HOSTS` | *(none)* | Extra hosts for DNS-rebinding protection (comma-separated) |
| `PMC_CLOUD_RUN_URL` | `https://pmc-mcp-318538827786.us-central1.run.app/health` | Health URL `pmc doctor` probes |
| `LOG_LEVEL` | `INFO` | Python logging level |
| `PMC_SESSION_FILE` | `~/.pmc/session.json` | Where the CLI keeps the active project |
| `SLACK_BOT_TOKEN` | *(none)* | Slack bot token (digest, `/pmc` command) |
| `SLACK_SIGNING_SECRET` | *(none)* | Slack request signing secret |
| `SLACK_DEFAULT_CHANNEL` | `#pmc-updates` | Channel for the morning digest |
| `CLICKUP_API_TOKEN` | *(none)* | ClickUp two-way sync |
| `CLICKUP_WEBHOOK_SECRET` | *(none)* | HMAC secret for `/webhooks/clickup`; webhooks are refused until set |
| `NOTION_API_KEY` | *(none)* | Notion connector |
| `GOOGLE_SERVICE_ACCOUNT_KEY` | *(none)* | Google Drive connector (service-account JSON path) |
| `PMC_RRF_K` | `60` | RRF constant; lower = sharper rank decay |
| `PMC_VECTOR_WEIGHT` | `1.0` | Vector lane weight |
| `PMC_BM25_WEIGHT` | `1.0` | Full-text lane weight for identifier-like queries |
| `PMC_BM25_PROSE_WEIGHT` | `0.15` | Full-text lane weight for natural-language queries |
| `PMC_TOP_N_PER_SOURCE` | `50` | Candidates fetched per lane before fusion |
| `PMC_HNSW_EF_SEARCH` | `100` | Minimum `hnsw.ef_search` for vector queries |

## Deploying

Production is Cloud Run (`pmc-mcp`) + Cloud SQL (`pmc-postgres`, database
`pmc_prod`) with secrets in Secret Manager. Migrations and rollouts go through
`scripts/migrate_prod.ps1` (proxy → `alembic upgrade head` → `pmc doctor` →
optional `-Deploy -ImageTag <sha>`). The GitHub `deploy.yml` workflow only builds,
pushes and deploys the image behind a `production` approval gate (once Workload
Identity Federation is set up); it does **not** run migrations, so run the
runbook's migrate step first for any release that adds a schema revision. Details, rotation, backups and troubleshooting:
[docs/ADMIN_MANUAL.md](docs/ADMIN_MANUAL.md).

## Project structure

```
persistent-memory-core/
  .claude-plugin/marketplace.json     # plugin marketplace (pmc-memory)
  .pmc.json                           # this repo's project key (pmc-self)
  alembic/versions/                   # 0001 … 0015, the schema source of truth
  plugins/pmc-memory/                 # Claude Code plugin: .mcp.json, hooks/, skills/, templates/
  scripts/                            # bootstrap, proxy, secrets, migrate_prod.ps1, dev/ probes
  sql/cloudsql_bootstrap.sql          # extension + schemas only
  src/persistent_memory_core/
    config.py                         # Settings (pydantic-settings), the only env reader
    cli/                              # pmc command groups
    mcp_server/                       # server.py, guard.py, readiness.py, tools/, routes/
    retrieval/                        # vector.py, fts.py, fusion.py, hydrate.py, hybrid.py, salience.py
    llm.py                            # structured-output LLM calls (Anthropic)
    embedding_provider.py             # OpenAI / local / mock providers
    entity_store.py, vector_store.py, graph_store.py, event_store.py
    reindex.py, chunking.py, github_ingest.py, policies.py
    extraction.py, episodic_rl.py, ambient.py, executive_cues.py, benchmarks.py, doctor.py
    integrations/, connectors/
  tests/                              # 300+ tests; `db` marker needs Docker pgvector
  docs/                               # manuals, architecture, ADRs, modernization plan
```

## Documentation

- [Installation](docs/INSTALL.md) · [User manual](docs/USER_MANUAL.md) ·
  [Admin manual](docs/ADMIN_MANUAL.md)
- [Architecture](docs/ARCHITECTURE.md) · [Multi-project](docs/MULTI_PROJECT.md) ·
  [Multi-machine](docs/MULTI_MACHINE_SETUP.md) · [Security](docs/SECURITY.md) ·
  [HIPAA](docs/HIPAA_COMPLIANCE.md) · [Data retention](docs/DATA_RETENTION.md)
- [Modernization plan and status](docs/MODERNIZATION_PLAN.md) · [Changelog](CHANGELOG.md)

## License

MIT
