Skip to main content

Developer memory CLI. Remember what matters.

Project description

poppy: remember what matters

Poppy is a developer memory CLI and MCP server. Your AI coding agent stores decisions, preferences, and lessons in a local SQLite store, then recalls them when they're relevant. No cloud, no API keys, no per-query cost.

curl -fsSL https://trags.ai/install/poppy | sh

Then wire up your agent:

poppy setup claude-code   # or: claude-desktop, cursor, windsurf, codex,
                          #     copilot-cli, pi, goose, hermes-agent

What it does

  • remember stores a fact, decision, preference, or lesson.
  • recall pulls the few memories most relevant to a query.
  • MCP server: agents call remember/recall/forget/consolidate directly. Works with Claude Code, Claude Desktop, Cursor, Windsurf, Codex, Copilot CLI, Pi, Goose, and Hermes Agent.
  • Hooks: Claude Code's SessionStart, UserPromptSubmit, and PreToolUse hooks surface relevant memories, while SessionEnd and PostCompact provide extraction backstops.
  • Zero-touch capture: with your one-time consent, Poppy extracts durable memories from your sessions automatically, locally, using the coding-agent CLI you already have. See Zero-touch capture.
  • Interchangeable engines: retrieval runs on a swappable local engine; the default, bloom, tracks the current best by benchmark. See Engines.
poppy remember "we always use uv for python deps" --type preference
poppy recall "python package manager"
#   we always use uv for python deps
#     preference | 2026-05-25 | score: 0.91

Zero-touch capture

With the Claude Code hooks installed, Poppy can also extract durable memories from your sessions automatically: decisions, preferences, and lessons that would help a future session, written one sentence each.

Nothing is captured without your consent. Capture ships enabled by default but stays inert until you record a one-time consent:

poppy consent --enable     # turn automatic capture on
poppy consent --disable    # opt out (persists across upgrades)
poppy consent --status     # show the current consent and capture state

poppy setup claude-code asks once on a TTY (pass --yes for non-interactive installs). Until you decide, every session start shows a short notice and nothing runs. Opting out persists permanently; a later default change can never silently re-enable capture you turned off.

What fires when, once consent is granted:

  • Every 3rd prompt, a detached background worker reads only the transcript turns added since the last capture and extracts memories from that window. It never blocks your prompt, captures no turn twice, and stops at a per-session soft cap.
  • At session end, a backstop pass flushes whatever the mid-session loop has not captured yet.
  • After context compaction, the compact summary is consolidated the same way.

Extraction runs locally through the same coding-agent CLI you already use (claude, codex, or gemini), on your existing login. Your conversation never leaves your machine, and Poppy never auto-spends on a paid remote model: if only an API-key backend is configured, capture stays off and tells you why.

Before anything is written, a reconciler dedups each candidate against the store: clear duplicates are skipped, clear updates supersede the old memory (tombstoned and restorable for 7 days), and everything uncertain is added rather than merged.

Turning capture off for one project

Consent is global and one-time, but you can keep a single sensitive repo (employer or client code) out of memory without turning capture off everywhere:

poppy capture --off        # in the repo: stop capturing this project
poppy capture --on         # capture this project again
poppy capture --status     # show the capture state for this project
poppy capture --off --project NAME   # target a project by name instead

The project is auto-detected from the current directory (a repo with a .git, CLAUDE.md, pyproject.toml, package.json, ...); pass --project if you're outside it. Disabled projects are stored as a deny-list in ~/.poppy/config.json (disabled_projects), and the switch sits above consent: a disabled project stays out of capture no matter what the global consent says. Only automatic capture stops. Recall keeps working in that project, and poppy remember still writes there, so you stay in control of what gets stored. Session start shows a one-line reminder that capture is off for the project.

Secret redaction

An extraction model can copy a credential out of your transcript into a memory ("the staging DB is postgres://admin:hunter2@db"). Before a captured candidate becomes a memory, Poppy masks secret-shaped tokens in place with [REDACTED]. This runs at the earliest seam shared by all three capture paths (mid-session, session end, post-compact), so the raw value never reaches the store, the capture journal, or Trags sync.

Masked today: private-key blocks (-----BEGIN ... PRIVATE KEY-----), passwords in connection-string URLs, Bearer tokens, AWS access key ids (AKIA... / ASIA...), OpenAI-style sk- keys, and GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_). The memory itself is always kept, with only the secret masked, so a lesson does not get thrown away over one token.

This is a deliberately narrow deny-list of high-signal shapes, not a general secret scanner: it will not catch every credential your session touches, so treat it as a safety net rather than a guarantee. If a repo is sensitive enough that this matters, turn capture off for that project (above). Redaction applies to automatic capture only; a memory you write yourself with poppy remember is stored exactly as you typed it.

What is stored, all locally under ~/.poppy/:

  • the extracted memories themselves, in memories.db, each tagged with the source app and session id;
  • a capture journal (capture_journal.jsonl) recording what each capture stored, so the session banner and poppy doctor can show it;
  • per-session progress state (capture_state.json). The journal and state files are per-device and are never synced anywhere, not even to Trags.

Every session start prints a one-line status banner (active with counts, a loud INACTIVE when something is broken, or the consent nudge), and poppy doctor reports the full capture state. POPPY_CONSOLIDATE=1/0 remains an explicit environment override in both directions.

Engines

Retrieval runs on one of four interchangeable engines. Switch any time with poppy engines use <name> (--migrate re-embeds your existing memories for the new engine):

Name What it is
bloom Default. Hybrid (FTS5 + bge-small embeddings + RRF) into a cross-encoder rerank, with per-speaker content expansion. Needs the poppy-memory[torch] extra (~600 MB ML deps).
petal Same architecture as bloom on ONNX (fastembed), no torch. This is what a slim base install runs, and the automatic fallback whenever the torch engines are unavailable.
sprout Mid tier. Same two-stage architecture, a lighter bi-encoder, no expansion. Needs the [torch] extra.
seed FTS5 only. No ML deps, no model downloads. The universal floor.

How the engines compare on retrieval quality, latency, and footprint, and how Poppy compares end to end against other memory systems (mem0, a full-context baseline) on the public suites (LongMemEval-S, LoCoMo, ConvoMem): see benchmarks/BENCHMARKS.md.

bloom is the evolving default champion: a future release can promote a better local engine into bloom, chosen by benchmark rather than by hand, so your default retrieval improves over time. petal tracks the same champion architecture without torch, so it evolves alongside bloom. sprout and seed are stable anchors that don't change under you. No engine's architecture ever changes silently. Every change ships in a release with notes, and poppy stats always shows the exact architecture you're running as engine vX.Y.Z.

Because a new champion can use a different embedding model, your stored vectors may need refreshing: run poppy migrate-engine after switching engines, or if recall quality dips after a bloom upgrade. If an upgrade ever changes behavior you depend on, switch to sprout or seed for a setup that stays put.

Commands

Command Flags What it does
poppy remember CONTENT --type, --project, --ttl, --expires-at, --supersedes, --check-conflicts, --auto-supersede Store a memory.
poppy recall QUERY --project, --type, --since, --limit, --json, --include-expired Search memories, ranked by relevance to the query.
poppy list --project, --type, --since, --limit, --json, --include-expired List all memories, newest first.
poppy edit MEMORY_ID --content, --type, --project, --no-project, --ttl, --expires-at, --no-expiry Edit a memory in place.
poppy forget MEMORY_ID --yes Delete a memory by ID.
poppy expire --yes List memories whose TTL has passed; --yes purges them.
poppy stats Show memory stats.
poppy engines List the engine catalog with the active engine starred.
poppy engines use NAME --migrate Switch the active retrieval engine.
poppy migrate-engine --project, --memory-type, --since, --all, --dry-run Re-embed memories so their vectors match the active engine.
poppy config set KEY VALUE Set a config value.
poppy consent --enable, --disable, --status Manage consent for automatic capture. See Zero-touch capture.
poppy capture --off, --on, --status, --project Turn automatic capture off or on for one project. See Turning capture off for one project.
poppy telemetry status, on, off Show or change anonymous usage telemetry. See Telemetry.
poppy encrypt status, enable, disable Turn on encryption at rest for the local store. See Encryption at rest.
poppy serve Start the Poppy MCP server (stdio).
poppy ui --host, --port, --no-open Browse and manage memories in a local web UI.
poppy setup claude-code --hooks/--no-hooks, --claude-md/--no-claude-md, --yes Install Poppy into Claude Code (MCP + hooks + CLAUDE.md primer).
poppy setup claude-desktop --print-instructions, --print-import-prompt Register the Poppy MCP server in the Claude desktop app.
poppy setup cursor Install Poppy into Cursor (MCP only).
poppy setup windsurf Install Poppy into Windsurf (MCP only).
poppy setup codex Install Poppy into Codex (MCP via ~/.codex/config.toml + AGENTS.md primer).
poppy setup copilot-cli Install Poppy into GitHub Copilot CLI (MCP via $COPILOT_HOME/mcp-config.json + $COPILOT_HOME/copilot-instructions.md primer; defaults to ~/.copilot).
poppy setup pi Install Poppy into Pi (MCP via pi-mcp-adapter + AGENTS.md primer in $PI_CODING_AGENT_DIR; defaults to ~/.pi/agent).
poppy setup goose Install Poppy into Goose (MCP extension + .goosehints primer).
poppy setup hermes-agent Install Poppy as a Hermes Agent memory provider plugin with SOUL.md guidance.
poppy setup trags --api-url One-command device-code onboarding for Trags cloud sync.
poppy sync push --dry-run Send local memories + tombstones to Trags.
poppy sync pull --dry-run Apply Trags rows newer than the last pull watermark.
poppy sync run --dry-run Pull then push, full bidirectional sync.
poppy sync status Show watermarks and last-sync time.
poppy import claude-memories --dry-run, --projects-dir Import auto-memory files from ~/.claude/projects/<slug>/memory/.
poppy import hermes-memories --dry-run, --memories-dir Import paragraphs from ~/.hermes/memories/{MEMORY,USER}.md.
poppy build mcpb --output-dir Build a Claude Desktop Extension bundle (.mcpb).
poppy hook ... Claude Code hook entrypoints (session-start, user-prompt-submit, pre-tool-use, post-compact, session-end, stop, replay-compact, replay-session-end). Invoked by the hooks that poppy setup claude-code installs.
poppy doctor Verify the installation: engine, storage, MCP config, hooks.

Run poppy COMMAND --help for full flag descriptions.

Storage

Memories live at ~/.poppy/memories.db (set POPPY_DIR to override). The schema is a single SQLite file with FTS5 plus an embeddings table; back it up the same way you'd back up any SQLite DB.

Encryption at rest (optional)

By default the local store is a plaintext SQLite file, protected only by file permissions (~/.poppy is 0700). You can encrypt it at rest with a key held in your operating system's keychain:

# Poppy is installed with pipx (see install.sh), so inject the extra deps there:
pipx inject poppy-memory sqlcipher3 keyring
# (plain pip installs instead: pip install 'poppy-memory[encryption]')
poppy encrypt enable                      # migrates the existing store in place
poppy encrypt status                      # show state
poppy encrypt disable                     # decrypt back to plaintext
poppy encrypt repair                      # reconcile an inconsistent state, if ever needed

enable generates a random 256-bit key, stores it in the OS keychain (macOS Keychain, Linux Secret Service, or Windows Credential Locker), and rewrites the store as a SQLCipher database. Every page is encrypted, including the FTS5 search index and the embedding vectors, and the -wal sidecar's frame contents are encrypted too (the -shm sidecar is a plaintext wal-index that holds no database content). Search and recall keep working locally with no change to the CLI, MCP server, or web UI. Existing plaintext stores are migrated the first time you run enable; new writes go straight to the encrypted store. Stop poppy serve, poppy ui, and any capture or sync workers before migrating; enable/disable refuse if they detect a live writer rather than risk losing a write.

What this protects against: theft of the disk or of an unencrypted backup, and casual inspection of the store file. What it does not do: this is local encryption at rest, not zero-knowledge or end-to-end encryption. The key lives in your keychain, so anything running as your user account that can read the keychain can decrypt the store. Cloud sync is unaffected and is a separate concern (memories synced to Trags are covered by that service's own encryption).

If the keychain entry is lost, the store cannot be recovered, so keep a backup strategy that accounts for the key. On a headless host with no keychain, set the POPPY_DB_KEY environment variable to a 64-hex-character key instead; note that a key in an environment variable is weaker than one in the keychain, because it is visible to anything that can read the process environment.

Sync to Trags (optional)

If you want your memories searchable across machines and inside the Trags web app, run poppy setup trags to onboard. Sync is opt-in and additive; your local store remains the source of truth.

When the keyring package is available (it ships with the encryption extra above), the Trags API key is stored in your operating system's keychain rather than in plaintext ~/.poppy/config.json. With no keychain backend, a base install or a headless host, Poppy keeps the key in config.json (which stays 0600); on such hosts you can instead set the POPPY_TRAGS_API_KEY environment variable, which always takes precedence over both the keychain and the file. An existing plaintext key is moved into the keychain automatically the first time Poppy runs after a keychain becomes available, and scrubbed from the file. This is local secret hygiene, not zero-knowledge: anything running as your user that can read the keychain can read the key.

Browse memories

poppy ui    # opens a local web UI at http://127.0.0.1:7800

Telemetry

Poppy sends a small number of anonymous usage events to PostHog (EU region) so we can see which features get used. The distinct ID is a random UUID generated on your machine and stored in ~/.poppy/analytics.json; it is not derived from hardware, accounts, or anything identifying.

This is the complete list of events and the properties they carry. Every event Poppy can emit is in this table; there are no others.

Event When Properties
cli_install once, before the first event from a machine version (Poppy version), python_version, platform (darwin, linux, win32)
memory_write every stored memory (poppy remember, the MCP remember tool, and automatic capture) memory_type (fact, decision, preference, lesson, summary, context), has_project (true/false, never the project name), source (source app: manual, claude-code, cursor, ...)
recall_call poppy recall query_length (character count, never the query text), result_count, engine (engine name)
agent_setup every poppy setup <client> agent (client name, for example claude-code)
setup_completed once per machine, on the first poppy setup <client> agent (client name)
consent_granted once per machine, when you turn automatic capture on via (consent_cmd, setup_yes, or setup_prompt)
consent_pending_shown once per machine, when the consent nudge is first shown channel (session_start or setup)
first_autocapture_stored once per machine, the first time automatic capture stores a memory trigger (mid_session, post_compact, or session_end)
closed_loop once per machine, the first time a memory captured in an earlier session is recalled back into a session channel (session_start, recall_prompt, or recall_file), injected_count, loop_count (counts only)

The last five are one-time funnel milestones: each fires at most once per machine, ever, and then never again. The first four can fire repeatedly.

Never sent: memory content, recall query text, project names, file paths, session ids, config values, API keys, or anything else you type into Poppy. Properties are counts, lengths, version strings, and fixed enum values only. If you onboard to Trags with poppy setup trags, the random device UUID is shared with the Trags server so it can link this machine's telemetry to your account.

The first time the CLI runs with telemetry on, it prints a one-line notice to stderr. Turn telemetry off any time; the choice persists in ~/.poppy/config.json:

poppy telemetry off       # or: poppy telemetry status | on

Setting POPPY_TELEMETRY_OFF=1 in the environment also turns telemetry off and overrides everything else. When telemetry is off, Poppy makes no telemetry network calls at all.

Hacking on it

git clone https://github.com/trags-garden/poppy
cd poppy
uv sync
uv run pytest -v
uv run poppy --help

License

Apache-2.0. See LICENSE. Poppy's source is open, but the project does not accept external contributions; see CONTRIBUTING. Bug reports are welcome via GitHub issues; for security vulnerabilities use SECURITY instead.

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

poppy_memory-0.2.2.tar.gz (715.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

poppy_memory-0.2.2-py3-none-any.whl (609.2 kB view details)

Uploaded Python 3

File details

Details for the file poppy_memory-0.2.2.tar.gz.

File metadata

  • Download URL: poppy_memory-0.2.2.tar.gz
  • Upload date:
  • Size: 715.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for poppy_memory-0.2.2.tar.gz
Algorithm Hash digest
SHA256 6898d2ba5ba001a340d1beb11e208d199357bb91a6239e8f9dfa80c04c64d841
MD5 611f194a2082a211a307a3e3fa0c188c
BLAKE2b-256 8dbff2c1f22a6c5ccbf40fcca6477c23f9d3fd235905d3d2826f6904717728b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for poppy_memory-0.2.2.tar.gz:

Publisher: publish.yml on trags-garden/poppy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file poppy_memory-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: poppy_memory-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 609.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for poppy_memory-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 28918f7b01becd500b001f60f9ed29c94a2448dd86a7b6a3d8165817935cf4ab
MD5 a5b73b2df5705e62e27930e096d86ea0
BLAKE2b-256 99041eef7224a0b1bd5f5fd006c2b4c79ba99af49e0a9876d914e7331d8492c0

See more details on using hashes here.

Provenance

The following attestation bundles were made for poppy_memory-0.2.2-py3-none-any.whl:

Publisher: publish.yml on trags-garden/poppy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page