p-layer
Governed memory for AI agents. A stdlib-only Python memory layer — SQLite + FTS5 + pluggable embeddings — with P0-P6 layer governance enforced in code, not prose.
7 layers. 1 memory. Every write audited.
Why this exists
The P0-P6 "brain layer" memory idea (drewgent, p-layer) is sound, but the reference implementations carry the same core defects:
| Defect | drewgent / p-layer | p-layers |
|---|---|---|
| Schema management | CREATE TABLE IF NOT EXISTS everywhere, no versioning |
Forward-only, checksummed migrations (schema_migrations) |
| Search index | External-content FTS5 + triggers (fragile; p-layer's forget breaks it) |
Standalone FTS5, no trigger coupling |
| Dual backends | Two parallel implementations that drift (TS + Python; SQLite + Pg with silent feature loss) | One implementation, one schema |
| Governance | A table in the README ("P0 overrides everything") | Enforced in code: layer ACLs raise WriteDenied |
| "Remember" tool | Hardcodes layer=P6, bypassing its own governance | Layer is a first-class write parameter, ACL-checked |
This repo is the production-grade rebuild: the governance ideas ported from p-layer, the schema discipline the originals lacked, and an eval harness that proves governance improves retrieval.
What it does
| Feature | What |
|---|---|
| P0-P6 layer ACLs | Who may write to each layer is enforced in code (P0 system-only … P6 agent+manual). Denied writes are audited. |
| Hybrid recall | FTS5 + semantic (Ollama or pluggable), RRF fusion, ranked by confidence × freshness, type-diversified, superseded excluded. |
| Supersede-not-delete | forget/update supersede entries; history is preserved and recall stops surfacing them. |
| Snapshots | Freeze active entries under a version label; rollback supersedes everything after the snapshot. |
| Audit log | Every write and every denied write is recorded — the compliance evidence. |
| Contradiction scan | Heuristic scan (no LLM): conflicting rule priorities, cross-layer duplicates. |
| P5 wiki compile | Offline compile of active memory into per-layer markdown with provenance + INDEX. |
| MCP server | 12 tools (remember, recall, forget, update, snapshot_*, memory_stats, memory_audit, assemble, graph_explore, graph_trace, graph_rca) — zero-dependency stdio implementation, any client. |
| Import tool | import-drewgent migrates an existing drewgent knowledge.db (schema re-validated, re-embedded, sessions carried into episodes). |
| Graph & inference | graph_explore / graph_trace / graph_rca (caused/fixed_by chains) / transitive_closure — drewgent graph_query.py parity, cycle-safe traversal. |
| Vault ingest | import-rules (rules.md → rules) and import-incidents (P6 incidents → episodes) — the vault stays files, p-layers references it. |
| p-layers compat | This package is published on PyPI as p-layers (GitHub repo: p-layer). p_layer/ keeps the 0.1.x KnowledgeDB API and knowledge_* MCP tools over this engine — existing p-layers integrations upgrade without code changes. |
| Re-embed job | reembed backfills embeddings after a model switch; vectors are versioned (old versions stay queryable), recall only reads the current version. Idempotent. |
| Consolidation | consolidate compresses unconsolidated episodes into insight digests — deterministic offline summarizer, pluggable LLM hook, idempotent, audited. |
| PostgreSQL backend | PgStore — the same interface, governance, and parity-tested behavior on Postgres (pg_trgm ILIKE for CJK, pgvector semantic). Ops jobs stay SQLite-only, loudly. |
Proof: governance improves retrieval
Same data, two engines, one command (p-layer eval suite.json):
recall@k (same data, two engines):
drewgent baseline : 0.667 (2/3) ← naive FTS OR-join, insertion order
p-layer : 1.000 (3/3) ← confidence/freshness-ranked
delta : +0.333
ACL compliance: 100.0% (30/30) enforcement cases correct
The baseline can't move — it has no metadata. p-layers turns governance metadata (confidence, layer, supersession) into retrieval quality, and the ACL suite proves the governance is real: every (layer, who) combination is allowed or denied exactly as specified.
Quick start
# no dependencies — stdlib only (Python >= 3.9)
export P_LAYER_EMBED=hash # offline fallback; ollama is the default
export P_LAYER_DB=~/.p_layer/memory.db
python3 -m p_layer init
python3 -m p_layer remember "switched to portone v2 for payments" --type decision --layer P5
python3 -m p_layer recall "portone"
python3 -m p_layer assemble --budget 12000 # rules first, then recent knowledge
Python API:
from p_layer.store import Store, WriteDenied
db = Store()
db.add_knowledge("client prefers weekly sync", type="preference", layer="P6", who="agent")
print(db.recall("weekly sync", limit=5))
try:
db.add_knowledge("secret", layer="P0", who="agent") # P0 is system-only
except WriteDenied:
pass
print(db.audit_log(denied_only=True)) # the denial is on record
MCP (any client — opencode, Claude Desktop, Cursor):
{
"mcp": {
"p-layers": {
"type": "local",
"command": ["python3", "-m", "p-layers", "serve"],
"env": { "P_LAYER_DB": "~/.p_layer/memory.db" }
}
}
}
Architecture
┌─────────────────────────────────────────────┐
rules (P0-P1) │ knowledge (P2-P6) episodes entities/ │
precedence- │ FTS5 + embeddings append-only relations │
ordered │ + confidence/TTL (sessions, (typed, │
│ + superseded_by incidents) validated) │
└─────────────────────────────────────────────┘
SQLite (WAL, FK on) — one schema, migrations v1→v3
│
┌───────────────┬───────────┼──────────────┬────────────┐
recall (hybrid) assemble (budget) audit_log contradictions compile_wiki
RRF+conf+fresh rules→recent every write heuristic P5 wiki
Tables: knowledge · knowledge_fts · embeddings (versioned) · episodes · entities · relations (constraint-validated) · rules · snapshots · audit_log · schema_migrations.
Governance model
| Layer | Purpose | Who may write |
|---|---|---|
| P0 | Immutable rules | system only |
| P1 | Identity & persona | system only |
| P2 | Raw session archive | system, gateway, cron |
| P3 | Tool integrations | system, gateway, cron |
| P4 | Skills & growth | system, cron, agent, manual |
| P5 | Compiled knowledge | system, cron, agent, manual, tool |
| P6 | Incidents & RCA | system, cron, agent, manual, tool |
Precedence is data, not prose: lower priority/higher authority wins, and assemble() emits rules in precedence order under a token budget.
Development
python3 -m unittest discover -s tests -v # 133 tests (22 PG tests skip without a DSN)
Examples
examples/quickstart.py— API walkthroughexamples/demo_import_eval.sh— the full migration story: drewgent fixture (knowledge + sessions + ontology + vault files) → import → vault ingest → eval before/after governance → audit → graph → contradictions → wikiexamples/suite.example.json— eval suite formatexamples/opencode-p-layer.jsonc— ready-to-paste MCP config that replaces drewgent's remember/recall tooling
Replace drewgent's memory with p-layers
The vault (identity, persona, skills as files) stays as files — it is a different storage class and should not be a database. p-layers replaces the knowledge layer:
# 1. migrate the data (knowledge + entities + relations + sessions)
python3 -m p_layer import-drewgent ~/.drewgent/.opencode/knowledge.db --embed ollama
# 2. ingest what the vault holds that belongs in the store (optional)
python3 -m p_layer import-rules ~/.drewgent/@identity/brain/rules.md
python3 -m p_layer import-incidents ~/.drewgent/P6-prefrontal/incidents
# 3. point the agent at the MCP server (examples/opencode-p-layer.jsonc),
# and update AGENTS.md so it uses the p-layers tools
Then p-layer eval suite.json proves the swap: same data, recall@k 0.667 → 1.000 with governance metadata, ACL 30/30.
PostgreSQL backend (multi-agent / SMB phase)
PgStore mirrors the SQLite Store interface — same methods, same governance, verified by a shared parity suite that runs every behavioral assertion against both backends.
from p_layer.pgstore import PgStore
db = PgStore("dbname=memory host=localhost user=me") # or P_LAYER_PG_DSN
db.add_knowledge("switched to portone v2", type="decision", layer="P5")
print(db.recall("portone"))
- FTS:
to_tsvector('simple')+ ts_rank, complemented by a pg_trgm ILIKE search (CJK-friendly). - Semantic: pgvector (
vector(768)), optional — without it the store is FTS-only and reportssemantic_available: false. - Safety:
statement_timeout+connect_timeout— lock waits become clean errors, never hangs. - Ops boundary: single-writer maintenance jobs (
reembed,consolidate,compile-wiki) run on the SQLite store; on Pg they raiseNotImplementedErrorloudly instead of silently degrading. - CI: a postgres service container runs the full suite against a real database in CI.
Credits
Built as a production-grade rebuild of ideas from:
- opencode-drewgent — P0-P6 vault concept, provenance convention
- p-layer — layer authority/ACL design, supersede-not-delete, confidence/TTL ranking, snapshots
- Gajae-Code — agent orchestration conventions
The critique that motivated this repo is documented in the README above; the credits are where the good ideas came from.
License
MIT
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 p_layers-0.6.0.tar.gz.
File metadata
- Download URL: p_layers-0.6.0.tar.gz
- Upload date:
- Size: 59.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7995bcc4e16b0e0b640a80e705cc52d4abeff3185eb3617fe01735abfeea9bf0
|
|
| MD5 |
aa30132d01c1a10282ec49f00470dab4
|
|
| BLAKE2b-256 |
800ec03d88e97751de88ad2158d050c7f535b42b72268919f2c6efa4a7b794b7
|
File details
Details for the file p_layers-0.6.0-py3-none-any.whl.
File metadata
- Download URL: p_layers-0.6.0-py3-none-any.whl
- Upload date:
- Size: 47.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b0391d25011c58a74143c4650d9161f387f3de13aa1931c69efe993869a87d0
|
|
| MD5 |
f80a127c8c13ef067d5b4957dfb01447
|
|
| BLAKE2b-256 |
e45b64b1b68ea6be0921909f60a5c9b1029d82e590c6a840d46d64d0a5f3618f
|