pi-qdrant-memory
Semantic, cross-session retrieval over durable conversation knowledge for pi.dev, backed by Qdrant.
Package details
Install pi-qdrant-memory from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-qdrant-memory- Package
pi-qdrant-memory- Version
0.4.1- Published
- Sep 20, 2026
- Downloads
- 1,457/mo · 411/wk
- Author
- jh0nnyrosa
- License
- MIT
- Types
- extension
- Size
- 218.9 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-qdrant-memory
Semantic, cross-session (single project) retrieval over durable conversation knowledge for pi.dev, backed by Qdrant.
Complements a running ecosystem:
codegraph= current code structure (exact/graph). Optional.pi-blackhole= durable decision capture before compaction (lexical recall). Optional.- pi-qdrant-memory = semantic retrieval over durable knowledge, indexed code-structure retrieval (optional).
Tools available:
memory_save(agent tool) — persist a durable decision/constraint/preference.memory_search(agent tool) — semantic search of prior durable knowledge.code_memory(agent tool, opt-in) — semantic search of indexed code-structure summaries (enabled viacodeKnowledge: "on"; see Code memory).memory_forget(agent tool, opt-in) — retract a previously saved memory by exact verbatim text (enabled viamemoryForget: "on").
Commands:
/qdrant-*command set —qdrant-status,qdrant-settings,qdrant-remember,qdrant-search,qdrant-forget,qdrant-clear,qdrant-help, plusqdrant-index-codewhen code memory is on. Each is a unique pi command (no subcommand parsing).- Mode-aware: detects when pi-blackhole is installed and ingests blackhole's durable artifacts; without it, it captures pi's own compaction summary as a
session_summary.
All writes are idempotent (deterministic content-hash point ids). Unreachable Qdrant/embeddings degrade gracefully — tools report the problem and never crash the session.
Install
pi install npm:pi-qdrant-memory # or: pi install git:github.com/lordekeen/pi-qdrant-memory
Quickstart (no package publish): drop src/index.ts into .pi/extensions/.
Runtime is in-process; no background resources are started by the factory (all lifecycle work happens on session_start / compaction events / session_shutdown).
Prerequisites
- Node >= 22.19 (native TypeScript type-stripping).
- A running Qdrant server — default
http://localhost:6333(REST). - An OpenAI-compatible
/embeddingsendpoint — default a local llama.cpp OpenAI-format server running nomic-embed-text at 768 dimmension. Ollama or any hosted OpenAI-compatible API also work (setembeddingApiKey).
Config
The global config file lives at ~/.pi/agent/pi-qdrant-memory/pi-qdrant-memory-config.json (honors PI_CODING_AGENT_DIR). It holds all twelve keys and is the layer that applies where no project override exists. A copyable template of the shipped defaults ships at pi-qdrant-memory-config.example.json (repo root, listed in package.json files) — copy it onto the path above to materialize the file. The template equals the built-in DEFAULTS in src/config.ts, which remains the real layer-4 default: a missing global file is not an error, the built-in defaults apply (zero-config run).
Storage layout — ~/.pi/agent/pi-qdrant-memory/ holds the global config file and a projects/ directory with one <projectId>.json override per project (~100 B each).
Precedence, per field, highest first: env → project override → global file → DEFAULTS (see Project overrides).
The ten keys below always live in the global file and are edited with /qdrant-settings; the two allowlisted keys' defaults are edited by hand using the template.
| Key | Default | Meaning |
|---|---|---|
qdrantUrl |
http://localhost:6333 |
Qdrant REST base URL |
qdrantApiKey |
null |
Optional Qdrant API key |
embeddingBaseURL |
http://localhost:8080/v1 |
OpenAI-compatible /embeddings endpoint |
embeddingModel |
nomic-embed-text |
Embedding model id |
embeddingApiKey |
null |
Optional key for hosted embedding APIs |
expectedDimension |
768 |
Embedding dimension (positive integer); dimension drift recreates on write (after successful embed) and fails safely on read |
scoreThreshold |
0.18 |
Search score threshold, 0–1 (per-model; nomic ≈ 0.15–0.2) |
maxResults |
10 |
Default memory_search limit |
mode |
auto |
auto detect | blackhole force Mode 1 | own force Mode 2 |
codeKnowledge |
off |
on enables structural code summaries + the code_memory tool (next session) |
codeScoreThreshold |
0.55 |
Score threshold for code_memory searches, 0–1 (per-model; see Code memory) |
memoryForget |
off |
on enables model exact-match memory retraction via memory_forget tool (next session) |
Env overrides (highest precedence, above the project and file layers):
PI_QDRANT_URL, PI_QDRANT_API_KEY, PI_QDRANT_EMBEDDING_BASE_URL, PI_QDRANT_EMBEDDING_MODEL,
PI_QDRANT_EMBEDDING_API_KEY, PI_QDRANT_EXPECTED_DIMENSION, PI_QDRANT_SCORE_THRESHOLD,
PI_QDRANT_MAX_RESULTS, PI_QDRANT_MODE, PI_QDRANT_CODE_KNOWLEDGE, PI_QDRANT_CODE_SCORE_THRESHOLD,
PI_QDRANT_MEMORY_FORGET.
The global file stores API keys and is written with owner-only permissions (0600);
a previously loosened file is tightened on the next save.
Project overrides
Two config keys are repo-shaped and can be overridden per project:
codeKnowledge (should this repo be indexed at all) and codeScoreThreshold (how strict
code retrieval is in this repo). An override lives at
<agentDir>/pi-qdrant-memory/projects/<projectId>.json — a partial config holding only
those keys, keyed by the same projectId as the collection.
- Env still wins:
PI_QDRANT_CODE_KNOWLEDGE/PI_QDRANT_CODE_SCORE_THRESHOLDmask a project override. /qdrant-settings codeKnowledge onand/qdrant-settings codeScoreThreshold 0.6write this project's store;/qdrant-settings <key> defaultclears the override.- A moved or renamed repo orphans its override exactly as it orphans its collection (both are keyed by the git-root realpath hash).
scoreThresholdandmaxResultsare not project-overridable on purpose:scoreThresholdis purely model-calibrated and has no repo-shaped half, andmaxResultsis a user preference about result volume, not a repo property.
Commands
/qdrant-status— connection health + active mode + collection point count./qdrant-settings <key> <value>— persist a config field.codeKnowledgeandcodeScoreThresholdapply to this project (an override); every other key (mode,embeddingBaseURL,embeddingModel,expectedDimension,scoreThreshold,maxResults,memoryForget, …) writes the global config file.<key> defaultclears an allowlisted override; for the other keysdefaultis an ordinary value. Bare/qdrant-settingsprints usage./qdrant-remember <text>— manual durable save./qdrant-search <query>— manual semantic search./qdrant-forget <query>— search matching memories and delete them interactively with confirmation. The dialog lists the matches (type, score, 60-char preview) and one confirmation deletes at most the 5 closest matches./qdrant-index-code— re-index code summaries now (only whencodeKnowledge: on)./qdrant-clear all | code— reset the current project's entire memory collection (all) or purge indexed code summaries (code). Bare/qdrant-cleardisplays usage guidance./qdrant-help— list all commands available.
Statusline
While a session is active the extension shows a footer status entry: 🧠 Memory (N): <mode> (<collection>) — Where N is the number of memories stored in the project collection (code + project knowledge), refreshed at session start and after every successful save/clear/forget (and code sync). When Qdrant is unreachable the count is omitted.
Agent tools
memory_save(text, type?)— persist a durable decision/constraint/preference. Type defaults todecision.memory_search(query, type?, limit?)— semantic search of prior durable knowledge (limit capped bymaxResults).code_memory(query, limit?)(opt-in) — semantic search of indexed code-structure summaries.memory_forget(text)(opt-in) — retract a previously saved memory by exact verbatim text (gated onmemoryForget: "on").
The tools carry always-on prompt guidance (via promptSnippet/promptGuidelines): the model is nudged to call memory_save when a decision/constraint/preference settles (with concise, self-contained statements, without re-recording what auto-capture covers) and to call memory_search when resuming prior work or before re-deciding. When code memory is on, code_memory likewise carries guidance to reach for it on "how/where does X work" questions and to open the returned file:line pointers.
No companion skill is needed for the core loop — the guidance ships with the tools descriptions. You can optionally instruct the model in the System Prompt (APPEND_SYSTEM.md) to nudge its decision on when to save/read memories.
Code memory (optional)
With codeKnowledge: "on", the extension scans the repo at session_start (fire-and-forget) and indexes structural summaries of top-level definitions — exported functions/classes/types, Python defs, per-file anchors — as code points in the same collection. Zero dependencies: the extractor is built in; no external indexer is used. The status row counts {symbols} as per-definition summaries only; the one anchor point per file is represented by the files count, not as a symbol.
Freshness: payloads carry
file_path+file_sha; unchanged files are skipped, changed files are deleted-and-replaced, vanished files are cleaned up./qdrant-index-codeforces a resync.Retrieval: the
code_memorytool (registered only while enabled) searches code summaries atcodeScoreThreshold;memory_searchnever returns code hits.Thresholds are per-model, and this one is load-bearing. The shipped
0.55was calibrated againstnomic-embed-textso that relevant code queries (≈0.6) clear it while unrelated ones (≈0.5) do not — a narrow band, because embedding models compress cosine similarity. On a model that scores lower overall, code search can silently return nothing. To find where your model actually sits, temporarily lowercodeScoreThreshold(e.g. to0.3) and read thescore=values incode_memoryoutput, then set it just above the band where unrelated matches stop appearing. Note this only applies to code search:memory_searchusesscoreThresholdand never returns code hits.Mid-session flips take effect at the next session start (the settings output reminds you); only indexing can be run immediately via
/qdrant-index-code.
Modes
- Mode 1 (pi-blackhole present, autodetects): reads pi-blackhole's pending durable artifacts (
<agent-dir>/pi-blackhole/*-pending.json) and ingests them atsession_start(catch-up) andsession_shutdown. It never claims thesession_before_compacthook, pi-blackhole owns it. - Mode 2 (pi-blackhole absent): claims
session_before_compactand captures pi's own compaction summary (from thesession_compactevent) as asession_summarypoint — fire-and-forget so capture can never stall compaction./qdrant-rememberis the manual safety net.
Forcing mode: own while pi-blackhole is operational is an explicitly contradictory configuration: both extensions claim session_before_compact, and if pi-blackhole cancels compaction (its live-message guard), no mode-2 capture happens. The settings write and /qdrant-status both warn; auto avoids the conflict.
An early-session auto snapshot is not yet wired: it needs mid-session content distillation access this extension does not currently have, so Mode 2's safety nets are the compaction capture and /qdrant-remember.
Data model (summary)
One Qdrant collection per project, named pi-mem-<16 hex of sha256(git root)>; single unnamed vector, Cosine, on_disk, HNSW. Points carry { type, text, project_id, session_id?, source_entry_id?, ts, source_kind } plus code-provenance fields on code points (file_path, file_sha, symbol, start_line, end_line); keyword payload indexes on source_kind and file_path. Deterministic point ids (sha256(normalized text | source_kind | context)) make every write idempotent.
Development
Zero runtime npm dependencies (Node global fetch + crypto only). TypeScript is erasable-syntax only and runs directly via Node's type stripping — no build step.
npm test # node --test over test/**/*.test.ts
npm run typecheck # tsc --noEmit (needs devDependencies installed)
Opt-in end-to-end smoke test (requires real Qdrant on :6333 and an OpenAI-compatible embeddings server):
QDRANT_MEMORY_SMOKE=1 npm run test:smoke. Server endpoints are overridable via QDRANT_MEMORY_URL,
QDRANT_MEMORY_EMBED_URL, QDRANT_MEMORY_EMBED_MODEL, and QDRANT_MEMORY_EMBED_DIM.