Skip to main content
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, vscode, windsurf, codex,
                          #     gemini, 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, VS Code, Windsurf, Codex, Gemini CLI, Copilot CLI, Pi, Goose, and Hermes Agent.
  • Hooks: Claude Code, Cursor, and Codex hooks surface relevant memories and capture new ones. Claude Code and Cursor use end-of-session and compaction backstops; Codex uses cadence-only capture.
  • 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, is a hybrid two-stage engine, with a zero-dependency full-text fallback (seed). 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, Cursor, or Codex 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. The unified command reports the resolved effect plus the project and global layers:

poppy autocapture                 # show resolved, project, and global status
poppy autocapture on --global     # turn automatic capture on everywhere
poppy autocapture off --global    # opt out everywhere (persists across upgrades)

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 automatic capture is enabled globally:

  • 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 in Claude Code and Cursor. Codex has no soft cap because it has no end-of-session flush.
  • At session end in Claude Code and Cursor, a backstop pass flushes whatever the mid-session loop has not captured yet. Codex Stop fires after each turn, so Poppy uses it only as a liveness signal and never as a flush.
  • Around context compaction, Claude Code consolidates the compact summary; Cursor flushes the live transcript at preCompact before compaction begins.

Extraction runs locally through the same coding-agent CLI you already use (claude, cursor-agent, 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 autocapture off        # in the repo: choose this project or everywhere
poppy autocapture on         # capture this project again
poppy autocapture status     # show the resolved, project, and global state
poppy autocapture 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.

Customizing redaction

Add exact values or environment-variable names when your project uses secrets that do not match the built-in shapes:

poppy redaction add "company-specific-secret"
poppy redaction add --env INTERNAL_API_TOKEN
poppy redaction list
poppy redaction remove "company-specific-secret"
poppy redaction remove --env INTERNAL_API_TOKEN

Literal entries are case-sensitive and match anywhere in captured content. Environment-variable entries look up the variable's current value when Poppy redacts a capture; poppy redaction list shows only the variable name, never its value. Configured literals are secrets themselves, so list hides their values too unless you pass --show. Literals and resolved environment values must be at least four characters long. The six built-in pattern families always stay on, and custom matches are masked in place with the same [REDACTED] marker.

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 an autocapture 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 two 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. Runs on local ONNX models, so a plain pip install poppy-memory has everything it needs.
seed FTS5 only. No ML deps, no model downloads. The floor, and the automatic fallback when bloom cannot start.

bloom is where retrieval work lands, so its architecture can change between releases; seed is a stable anchor that doesn't change under you. No engine changes silently: every change ships in a release with notes, and poppy stats shows the exact architecture you're running as engine vX.Y.Z.

Because a release can change the embedding model, your stored vectors may need refreshing: run poppy migrate-engine after switching engines, or if recall quality dips after a bloom upgrade. Memories whose vectors don't match the active engine stay searchable by keyword in the meantime. Nothing is lost, and poppy doctor tells you how many are waiting.

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 autocapture status, on, off, --global, --project, --yes Show or change automatic capture at project or global scope. See Zero-touch capture.
poppy consent --enable, --disable, --status Legacy visible consent command; still works while callers migrate to poppy autocapture.
poppy capture --off, --on, --status, --project Deprecated hidden alias for the per-project controls; still works while callers migrate.
poppy telemetry status, on, off Show or change anonymous usage telemetry. See Telemetry.
poppy encrypt status, enable, disable, repair 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, --daemon 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 --hooks/--no-hooks, --daemon Install Poppy into Cursor (MCP + zero-touch capture hooks). Honors $CURSOR_HOME.
poppy setup vscode --daemon Install Poppy into VS Code (MCP only).
poppy setup windsurf Install Poppy into Windsurf (MCP only).
poppy setup codex --hooks/--no-hooks, --daemon Install Poppy into Codex (MCP + cadence capture hooks + AGENTS.md primer). Honors $CODEX_HOME.
poppy setup gemini --daemon Install Poppy into Gemini CLI (MCP only).
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, Cursor, and Codex hook entrypoints (session-start, user-prompt-submit, pre-tool-use, post-compact, session-end, stop, replay-compact, replay-session-end). Invoked by Poppy-installed hooks.
poppy doctor Verify the installation: engine, storage, MCP config, hooks.

Cursor hooks are installed globally at ~/.cursor/hooks.json by poppy setup cursor. Capture starts immediately once automatic capture is enabled; Cursor has no hook trust step. Hook execution also depends on Cursor's server-side enable_execute_hook_exec account flag. If hooks are installed but capture stays silent, poppy doctor reports that flag as a possible cause. Cursor resumes append to the same transcript and keep the same session id. A pure Q&A resume that runs no tool can emit no hooks, including sessionEnd; a future idle-session sweep will cover that accepted tail-loss edge case. Cursor transcripts omit tool outputs, so automatic capture sees the user and assistant text but cannot use command or tool results as evidence. In headless cursor-agent -p runs, beforeSubmitPrompt never fires, so capture is backstop-only: there is no every-third-prompt cadence or prompt-time behavior, but sessionEnd still flushes the remaining transcript.

Codex capture has a one-time trust gate. After poppy setup codex, run Codex interactively in the TUI once and approve the Poppy hooks. Headless codex exec silently skips untrusted hooks. Hook trust is tied to the command hash, so editing a hook command later requires approval again. Poppy never adds a trust-bypass flag to Codex commands.

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.

Set POPPY_MODEL_IDLE_S to change the 600-second model idle-unload timeout; zero or an invalid value disables unloading.

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:

# The installer (curl -fsSL https://trags.ai/install/poppy | sh) uses pipx, so inject the extra dep there:
pipx inject poppy-memory sqlcipher3
# (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.

On a standard install the Trags API key is stored in your operating system's keychain (the keyring package is a base dependency), not in plaintext ~/.poppy/config.json. When no keychain backend is usable, on a headless Linux host or in CI, Poppy falls back to keeping 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.

Update check

poppy doctor may send one anonymous HTTP request to PyPI at most once every 24 hours to check the latest poppy-memory version. The request contains only the installed version in its User-Agent and no identifiers. Normal commands never make this request; they can only show a once-per-version hint already stored in the local cache. Disable the check with POPPY_UPDATE_CHECK_OFF=1, poppy config set update_check off, or the umbrella no-phone-home switch POPPY_TELEMETRY_OFF=1.

Hacking on it

The full source ships in every release on PyPI:

pip download poppy-memory --no-binary :all: --no-deps -d .
tar xzf poppy_memory-*.tar.gz && cd poppy_memory-*/
uv sync
uv run pytest -v
uv run poppy --help

License

Apache-2.0; the LICENSE file ships with the package. Poppy's source is open, and you are free to read, run, self-host, fork, and modify it under those terms. The project is developed in-house and does not accept external pull requests. Bug reports and feedback are welcome by email at feedback@trags.ai. For security vulnerabilities, email security@trags.ai instead of filing publicly.

Metadata

Release files for poppy-memory 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for poppy-memory 0.3.0
File Size Uploaded Provenance
poppy_memory-0.3.0.tar.gz 980.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for poppy-memory 0.3.0
File Interpreter ABI Platform
poppy_memory-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.7 MB

Release files / poppy_memory-0.3.0.tar.gz

Download URL poppy_memory-0.3.0.tar.gz
Size 980.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c1e2e0c1df4c14a9ef49438e3a40902b3116e6617fa02ec7e829a173dba9e19f
BLAKE2b-256 checksum
How to use checksums
29cad33e66430c20b4d715ec4cbc458b20f0c3c0aacb743035281417362458f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release files / poppy_memory-0.3.0-py3-none-any.whl

Download URL poppy_memory-0.3.0-py3-none-any.whl
Size 746.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7e664e44a040a005c6102d99093c48a8bbbade26597bd4c548750b72cf8122d9
BLAKE2b-256 checksum
How to use checksums
f54e393aabb200d1b815edbf718233e983495e712be69eec9c94b9982bc15311
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Security for poppy-memory 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for poppy_memory-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page