pi-qdrant-memory

Semantic, cross-session retrieval over durable conversation knowledge for pi.dev, backed by Qdrant.

Packages

Package details

extension

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 via codeKnowledge: "on"; see Code memory).
  • memory_forget (agent tool, opt-in) — retract a previously saved memory by exact verbatim text (enabled via memoryForget: "on").

Commands:

  • /qdrant-* command set — qdrant-status, qdrant-settings, qdrant-remember, qdrant-search, qdrant-forget, qdrant-clear, qdrant-help, plus qdrant-index-code when 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 /embeddings endpoint — default a local llama.cpp OpenAI-format server running nomic-embed-text at 768 dimmension. Ollama or any hosted OpenAI-compatible API also work (set embeddingApiKey).

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_THRESHOLD mask a project override.
  • /qdrant-settings codeKnowledge on and /qdrant-settings codeScoreThreshold 0.6 write this project's store; /qdrant-settings <key> default clears 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).
  • scoreThreshold and maxResults are not project-overridable on purpose: scoreThreshold is purely model-calibrated and has no repo-shaped half, and maxResults is 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. codeKnowledge and codeScoreThreshold apply to this project (an override); every other key (mode, embeddingBaseURL, embeddingModel, expectedDimension, scoreThreshold, maxResults, memoryForget, …) writes the global config file. <key> default clears an allowlisted override; for the other keys default is an ordinary value. Bare /qdrant-settings prints 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 when codeKnowledge: on).
  • /qdrant-clear all | code — reset the current project's entire memory collection (all) or purge indexed code summaries (code). Bare /qdrant-clear displays 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 to decision.
  • memory_search(query, type?, limit?) — semantic search of prior durable knowledge (limit capped by maxResults).
  • 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 on memoryForget: "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-code forces a resync.

  • Retrieval: the code_memory tool (registered only while enabled) searches code summaries at codeScoreThreshold; memory_search never returns code hits.

    Thresholds are per-model, and this one is load-bearing. The shipped 0.55 was calibrated against nomic-embed-text so 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 lower codeScoreThreshold (e.g. to 0.3) and read the score= values in code_memory output, then set it just above the band where unrelated matches stop appearing. Note this only applies to code search: memory_search uses scoreThreshold and 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 at session_start (catch-up) and session_shutdown. It never claims the session_before_compact hook, pi-blackhole owns it.
  • Mode 2 (pi-blackhole absent): claims session_before_compact and captures pi's own compaction summary (from the session_compact event) as a session_summary point — fire-and-forget so capture can never stall compaction. /qdrant-remember is 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.