Poppy

ai.trags由 trags.ai 發布v0.3.6更新於 Nov 3, 2026

Developer memory CLI and MCP server. Remember decisions, preferences, and lessons locally.

已驗證STDIO僅桌面知識與記憶開發者工具

概覽

AI 產生的概覽

本機開發者記憶伺服器,讓程式助理把專案決策、偏好與經驗教訓存進本機 SQLite,並可搜尋、編輯與刪除。

可以做什麼
  • 以事實、決策、偏好、教訓、摘要或情境類型儲存記憶,可依專案限定範圍,支援到期時間與關聯記憶
  • 用自然語言查詢搜尋記憶,可一次回傳全文,或先取索引片段再批次取全文,並依專案、類型與時間篩選
  • 就地編輯記憶並保留其 ID、建立時間與來源,或依 ID 刪除記憶
  • 取得某專案最近的記憶作為工作階段開場,並在工作階段結束時儲存摘要與一組事實
適用情境
適合希望程式助理在同一台機器上跨工作階段記住決策、慣例與經驗教訓,且不想依賴雲端服務或 API 金鑰的開發者。專案較多、需要依專案隔離記憶時尤其有用。
執行需求
以 stdio 本機程序執行;PyPI 套件名為 poppy-memory,可用 pipx 或 uv 安裝,需要 Python 3.11 或更新版本。poppy 指令必須在 PATH 中,設定後需重新啟動助理。核心儲存不需雲端帳號或 API 金鑰。
安裝前請注意
助理可以寫入、編輯與刪除已存記憶,刪除為軟刪除,約 7 天內可還原。選用的自動擷取會讀取工作階段逐字稿,並透過你現有的程式助理 CLI 把片段送給其模型供應商,敏感儲存庫應關閉此功能。選用的 Trags 同步與無驗證的本機網頁介面會擴大暴露範圍;守護程序權杖會寫入用戶端設定與備份檔。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Poppy,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

[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.

bash
pipx install poppy-memory

Then wire up your agent, and restart it:

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

The package on PyPI is poppy-memory; the command it installs is poppy. No pipx? See Install for the other ways in, and for the one PATH check that saves most first-day trouble.

First ten minutes

The order a new user walks, each step linking to its section:

  1. Install Poppy with pipx, uv, or the one-line installer. See Install.
  2. Check that poppy is on your PATH: command -v poppy should print a path. See Check your PATH.
  3. Wire up your agent with poppy setup <client>. See Supported clients.
  4. Restart the agent so it loads the MCP server and hooks. See Restart the agent after setup.
  5. Store and recall once with poppy remember and poppy recall. See What it does.
  6. Pair with Trags (optional) with poppy setup trags, and approve it in the browser within five minutes. See Sync to Trags.
  7. Install the daemon (optional) with poppy daemon install. See MCP daemon.
  8. Run poppy doctor to check engine, storage, MCP config, and hooks. See Commands.

Install

Use the first option your machine already has:

  • pipx: pipx install poppy-memory. Needs pipx and Python 3.11 or newer.
  • uv: uv tool install poppy-memory. Needs only uv, which fetches its own Python. A fresh Mac has neither pipx nor uv.
  • One-line installer: curl -fsSL https://trags.ai/install/poppy | sh. Needs Python 3.11 or newer. It uses pipx or uv when it finds one and otherwise installs into its own virtualenv.

Check your PATH

pipx and uv put the poppy command in ~/.local/bin. A fresh Mac often does not have that folder on its PATH, and the install still succeeds: uv only prints a warning. Check:

bash
command -v poppy    # prints a path such as /Users/you/.local/bin/poppy

If it prints nothing, add the folder to your shell profile and open a new terminal (pipx ensurepath or uv tool update-shell do the same):

bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc

This matters for your agent too. The hooks call poppy by name, so an agent that cannot find it fails every hook with poppy: command not found. An agent keeps the PATH it started with, so restart it after you fix PATH. poppy doctor checks only the PATH of the terminal you run it in, so it can report the hooks as fine while your agent still cannot find poppy.

Restart the agent after setup

poppy setup <client> ends with Restart <client> to activate. Do that. Agents read their MCP servers and hooks only when they start, so a session that was already open does not see Poppy until you quit and reopen it.

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 the tools in MCP tools directly. Works with Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Codex, Gemini CLI, Copilot CLI, Pi, and Goose. Hermes Agent gets a memory provider plugin instead.
  • 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.
bash
poppy remember "we always use uv for python deps" --type preferencepoppy recall "python package manager"#   we always use uv for python deps#     preference | 2026-05-25 | score: 0.91 | mem_3f9a1c2b7d4e

Supported clients

poppy setup <client> wires Poppy into a coding agent. Eleven clients have an integration today:

  • MCP server plus zero-touch capture hooks: Claude Code, Cursor, Codex.
  • MCP server: Claude Desktop, VS Code, Windsurf, Gemini CLI, GitHub Copilot CLI, Pi, Goose. Most also get a short primer file so the agent knows the memory tools are there.
  • Memory provider plugin that calls the Poppy CLI: Hermes Agent.

What supported means here: each integration is verified against its client at release time, and maintained best-effort between releases. These clients change on their own schedule, so when one of them breaks Poppy, that is a bug worth reporting as a GitHub issue rather than something that holds the next release back.

poppy doctor checks the integrations installed on your machine.

Setup treats any hook command of the form <path>/poppy hook <event> (including a versioned executable name such as poppy-0.3.3) as Poppy's own. It rewrites these commands to the current executable, so a deliberate pin to a development install is overwritten on the next setup and removed by --remove. Before downgrading, run poppy setup <client> --remove with the current version, then set up again with the older version. Older versions do not recognise absolute-path hook entries and would add duplicates.

Claude Code plugin

Claude Code can also load Poppy as a plugin, in place of poppy setup claude-code. Install poppy-memory first, as above. The plugin only connects Claude Code to the poppy command you already have.

bash
claude plugin marketplace add trags-garden/poppyclaude plugin install poppy@trags-garden

Restart Claude Code afterwards.

The plugin gives Claude Code the same MCP server and the same five hooks that setup writes. It looks for poppy on the PATH and then in the folders pipx, uv and pip install --user install into, so it works when ~/.local/bin is not on Claude Code's PATH. In place of the CLAUDE.md block it ships a memory skill with the same text. Claude loads the skill when memory is relevant, and you can run it yourself with /poppy:memory. The plugin needs macOS or Linux. On Windows, use poppy setup claude-code.

Use the plugin or setup, not both. With the plugin installed, poppy setup claude-code does not add the MCP server or hooks and says so. Pass --claude-md to add the CLAUDE.md block next to the skill, so the primer loads in every session. If you ran setup before installing the plugin, run poppy setup claude-code --remove to remove setup's entries, or every hook runs twice. poppy doctor warns when it finds both, and fails when the plugin cannot find poppy.

The plugin does not ask for capture consent the way setup does. Answer the notice at session start, or run poppy autocapture on --global.

Uninstall

Remove client integrations before uninstalling the CLI, so leftover hooks do not fail with poppy: command not found on every session.

  1. Run the following for each client you set up:

    bash
    poppy setup claude-code --removepoppy setup cursor --removepoppy setup codex --remove

    This removes Poppy's MCP entry, hooks and managed primer blocks while preserving your other settings. If you installed the Claude Code plugin, also run claude plugin uninstall poppy@trags-garden. Other clients still need their Poppy entry and primer removed by hand: Claude Desktop's claude_desktop_config.json, VS Code's user-profile mcp.json, Windsurf, Gemini CLI, GitHub Copilot CLI ($COPILOT_HOME/mcp-config.json and copilot-instructions.md, default ~/.copilot), Pi ($PI_CODING_AGENT_DIR/mcp.json and AGENTS.md, default ~/.pi/agent), Goose, and Hermes Agent (plugin and SOUL.md guidance).

  2. If you installed the daemon service, run poppy daemon uninstall.

  3. Run pipx uninstall poppy-memory, or uv tool uninstall poppy-memory if you installed it with uv.

  4. Optionally run rm -rf ~/.poppy. This permanently deletes your memories and local Poppy settings. If you use a custom POPPY_DIR, your data lives there instead.

--remove leaves your memory store, daemon service, Trags key and capture consent untouched. It also keeps the .pre-poppy.bak backups that setup and removal write next to each client file; delete those too if you are removing Poppy for good, since a backup can hold the daemon token. Use it on its own, without installation flags such as --daemon, --hooks, --claude-md or --yes.

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. If you already had consolidate-enabled true set from an earlier version, that setting counts as your recorded consent and capture stays active with no new prompt. Setting POPPY_CONSOLIDATE=1 overrides the consent check entirely and turns capture on regardless. The unified command reports the resolved effect plus the project and global layers:

bash
poppy autocapture                 # show resolved, project, and global statuspoppy autocapture on --global     # turn automatic capture on everywherepoppy 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 through the coding-agent CLI you already use (claude, cursor-agent, codex, or gemini), on your existing login. That CLI sends the transcript excerpt to the model provider you already use, so no new third party sees your conversation. The store, your memories, and retrieval stay on your machine; retrieval runs on local ONNX models. Poppy sends nothing to Trags unless you turn sync on. It 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:

bash
poppy autocapture off        # in the repo: choose this project or everywherepoppy autocapture on         # capture this project againpoppy autocapture status     # show the resolved, project, and global statepoppy 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:

bash
poppy redaction add "company-specific-secret"poppy redaction add --env INTERNAL_API_TOKENpoppy redaction listpoppy 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, owner-only mode 0600) recording types and counts for the session banner and poppy doctor, without memory text. Rewriting the journal removes older previews. Forgetting a memory also attempts this cleanup after deletion; a journal error logs a warning and does not prevent the deletion;
  • 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; setting it to 1 bypasses the consent check entirely.

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):

NameWhat it is
bloomDefault. Hybrid (FTS5 + bge-small embeddings + RRF) into a cross-encoder rerank. Runs on local ONNX models, so a plain pip install poppy-memory has everything it needs.
seedFTS5 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

CommandFlagsWhat it does
poppy remember CONTENT--type, --project, --ttl, --expires-at, --supersedes, --check-conflicts, --auto-supersedeStore a memory; rejects empty or whitespace-only content.
poppy recall QUERY--project, --type, --since, --limit, --json, --include-expiredSearch memories, ranked by relevance to the query.
poppy list--project, --type, --since, --limit, --json, --include-expiredList all memories, newest first.
poppy edit MEMORY_ID--content, --type, --project, --no-project, --ttl, --expires-at, --no-expiryEdit a memory in place.
poppy forget MEMORY_ID--yesSoft delete a memory by ID, restorable for 7 days.
poppy restore MEMORY_IDRestore a forgotten memory from Trash. Trash keeps it for at least 7 days; a memory whose own expiry has passed is not restored.
poppy expire--yesList memories whose TTL has passed; --yes purges them.
poppy statsShow memory stats.
poppy enginesList the engine catalog with the active engine starred.
poppy engines use NAME--migrateSwitch the active retrieval engine.
poppy migrate-engine--project, --memory-type, --since, --all, --dry-runRe-embed memories so their vectors match the active engine.
poppy config set KEY VALUESet a config value.
poppy autocapturestatus, on, off, --global, --project, --yesShow or change automatic capture at project or global scope. See Zero-touch capture.
poppy consent--enable, --disable, --statusLegacy visible consent command; still works while callers migrate to poppy autocapture.
poppy capture--off, --on, --status, --projectDeprecated hidden alias for the per-project controls; still works while callers migrate.
poppy telemetrystatus, on, offShow or change anonymous usage telemetry. See Telemetry.
poppy encryptstatus, enable, disable, repairTurn on encryption at rest for the local store. See Encryption at rest.
poppy serveStart the Poppy MCP server (stdio).
poppy daemonstatus, install, uninstall, start, stop, restart, runManage the shared MCP daemon that poppy setup <client> --daemon connects clients to. See MCP daemon.
poppy ui--host, --port, --no-open, --allow-remoteBrowse and manage memories in a local web UI. --allow-remote permits a non-loopback --host; the UI has no authentication, so this exposes its delete and edit API to your network.
poppy setup claude-code--hooks/--no-hooks, --claude-md/--no-claude-md, --yes, --daemon, --removeInstall Poppy into Claude Code (MCP + hooks + CLAUDE.md primer), or remove setup's entries with --remove. Adds only the CLAUDE.md block, and only with an explicit --claude-md, when the Claude Code plugin is installed.
poppy setup claude-desktop--print-instructions, --print-import-promptRegister the Poppy MCP server in the Claude desktop app.
poppy setup cursor--hooks/--no-hooks, --daemon, --removeInstall Poppy into Cursor (MCP + zero-touch capture hooks), or remove setup's entries with --remove. Honors $CURSOR_HOME.
poppy setup vscode--daemonInstall Poppy into VS Code (MCP only).
poppy setup windsurfInstall Poppy into Windsurf (MCP only).
poppy setup codex--hooks/--no-hooks, --daemon, --removeInstall Poppy into Codex (MCP + cadence capture hooks + AGENTS.md primer), or remove setup's entries with --remove. Honors $CODEX_HOME.
poppy setup gemini--daemonInstall Poppy into Gemini CLI (MCP only).
poppy setup copilot-cliInstall Poppy into GitHub Copilot CLI (MCP via $COPILOT_HOME/mcp-config.json + $COPILOT_HOME/copilot-instructions.md primer; defaults to ~/.copilot).
poppy setup piInstall Poppy into Pi (MCP via pi-mcp-adapter + AGENTS.md primer in $PI_CODING_AGENT_DIR; defaults to ~/.pi/agent).
poppy setup gooseInstall Poppy into Goose (MCP extension + .goosehints primer).
poppy setup hermes-agentInstall Poppy as a Hermes Agent memory provider plugin with SOUL.md guidance.
poppy setup trags--api-urlOne-command device-code onboarding for Trags cloud sync.
poppy sync push--dry-runSend local memories + tombstones to Trags.
poppy sync pull--dry-runApply Trags rows newer than the last pull watermark.
poppy sync run--dry-runPull then push, full bidirectional sync.
poppy sync statusShow watermarks and last-sync time.
poppy import claude-memories--dry-run, --projects-dirImport auto-memory files from ~/.claude/projects/<slug>/memory/.
poppy import hermes-memories--dry-run, --memories-dirImport paragraphs from ~/.hermes/memories/{MEMORY,USER}.md.
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 doctorVerify the installation: engine, storage, MCP config, hooks, and the Claude Code plugin.

MCP remember also rejects blank content. MCP remember and edit accept only fact, decision, preference, lesson, summary, or context as memory_type; invalid inputs return tool errors. Unknown memory types received from cloud sync become fact. SessionStart renders each stored note on one line, collapsing whitespace in its content and type.

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.

MCP tools

The MCP server (poppy serve) exposes these tools:

ToolWhat it does
rememberStore a memory (optional memory_type, project, related_to, ttl/expires_at, supersedes, check_conflicts, auto_supersede).
recallSearch memories by natural-language query, with optional project, memory_type, since, and limit (default 10). Returns full content.
recall_indexFirst pass of a two-stage recall: returns only IDs and snippets for a query (limit default 20).
recall_fullSecond pass: fetch full content for a batch of IDs from recall_index. Batch the IDs in one call.
forgetDelete a memory by ID.
editChange a memory in place by ID, keeping its ID, creation time, and source. Pass only the fields to change.
contextReturn the most recent memories for a project (limit default 20), for priming a session.
consolidateStore a session summary plus a list of facts at the end of a session.

remember, forget, and edit are explicit writes by the agent, so they are not gated by the automatic-capture consent described under Zero-touch capture.

MCP daemon

By default each client starts its own poppy serve process over stdio. poppy setup <client> --daemon instead points the client at one shared HTTP server at http://127.0.0.1:7679/mcp, so every client talks to the same process and the same loaded engine. Setup installs and starts the daemon first, then writes its URL and a bearer token into the client config. A plain stdio poppy serve also forwards to the daemon when one is running or installed; pass --no-daemon to keep it in process.

SubcommandWhat it does
poppy daemon statusShow whether the service is installed, whether the daemon holds its lock, whether it answers over HTTP, and its version.
poppy daemon installInstall and load a per-user service: a launchd agent on macOS, a systemd user unit on Linux. Other platforms are not supported.
poppy daemon uninstallUnload and remove that service.
poppy daemon startStart the daemon through the installed service, or as a detached background process when no service is installed.
poppy daemon stopStop the daemon, through the service when one is installed. On Linux, the next client that starts poppy serve starts the service again; use uninstall to keep it off.
poppy daemon restartStop a running daemon, then start it again. Run it after upgrading Poppy so clients reach the new version.
poppy daemon runRun the daemon in the foreground. This is what the service runs.

The daemon keeps its files in ~/.poppy: daemon.token holds the bearer token and is readable only by you, and daemon.lock holds the PID of the daemon that last held the lock. On macOS, and when no service is installed, output goes to ~/.poppy/logs/daemon.log. On Linux, the service logs to the journal: run journalctl --user -u poppy-daemon.service. If poppy daemon start reports the daemon did not become reachable, check the log.

The installed service saves POPPY_DAEMON_PORT and POPPY_DIR overrides set when you run poppy daemon install. Reinstall and restart the service to change them.

Storage

Client setup saves an explicit POPPY_DIR override in MCP registrations and prefixes installed hook commands with a shell-quoted POPPY_DIR assignment on macOS and Linux so clients launched outside your shell use the same store. Run setup again to change or remove that saved override.

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.

Environment variables

VariableEffect
POPPY_DIRStore directory (default ~/.poppy).
POPPY_DAEMON_PORTPort for the MCP daemon (default 7679).
POPPY_SERVE_NO_DAEMONSet to 1 to make stdio poppy serve run in process instead of forwarding to the daemon, like --no-daemon.
POPPY_FASTEMBED_CACHEDirectory for downloaded embedding and reranker model files. Defaults to $XDG_CACHE_HOME/fastembed, or ~/.cache/fastembed.
POPPY_AUTOSYNC_DISABLESet to 1 to turn off the automatic background sync with Trags after writes.
POPPY_AUTOSYNC_DEBOUNCESeconds to wait before an automatic sync so a burst of writes becomes one (default 1.0).
POPPY_CONSOLIDATE1 or 0 forces automatic capture on or off; see Zero-touch capture.
POPPY_CONSOLIDATE_MODEL, POPPY_CONSOLIDATE_BASE_URL, POPPY_CONSOLIDATE_API_KEYModel, endpoint, and key for the OpenAI-compatible capture backend. They override config.json; the base URL and key fall back to OPENAI_BASE_URL and OPENAI_API_KEY. POPPY_CONSOLIDATE_MODEL does not apply to host-CLI backends.
POPPY_CONSOLIDATE_MAX_ITEMSMost memories one capture may extract (default 5).
POPPY_CLAUDE_DESKTOP_CONFIGPath to claude_desktop_config.json for poppy setup claude-desktop, instead of the platform default.
POPPY_VSCODE_MCP_CONFIGPath to the VS Code MCP config file for poppy setup vscode, instead of the user-profile default.
POPPY_GOOSE_CONFIG_DIRGoose config directory for poppy setup goose, instead of the GOOSE_PATH_ROOT, XDG_CONFIG_HOME, or platform default.

POPPY_MODEL_IDLE_S, POPPY_DB_KEY, POPPY_TRAGS_API_KEY, POPPY_MCP_SOURCE, POPPY_TELEMETRY_OFF, and POPPY_UPDATE_CHECK_OFF are described in their own sections.

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:

bash
# pipx installs poppy in its own virtualenv, so inject the extra dependency there:pipx inject poppy-memory sqlcipher3# (plain pip installs instead: pip install 'poppy-memory[encryption]')poppy encrypt enable                      # migrates the existing store in placepoppy encrypt status                      # show statepoppy encrypt disable                     # decrypt back to plaintextpoppy 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.

poppy setup trags opens a browser page and shows a one-time code, including how long it is valid. If it expires while you are in a terminal, Poppy offers a fresh code, for up to three codes in total. In a non-interactive session, run poppy setup trags again for a fresh code.

Once your Trags key is set up, by default each local change (a new memory, an edit, a forget, a restore) starts a background sync: Poppy pulls changes from Trags, then pushes yours. poppy sync run does the same by hand. A push sends the memories that changed since the last push, and an upload that fails is tried again on the next push. When two devices edit the same memory, the newer edit wins.

On a converged store, poppy sync run and poppy sync pull may still report pull: 1 live on every run. The pull watermark is inclusive, so when the newest row on the server is a live memory, the pull serves it again. A push that sends 0 live and 0 tombstones shows that this machine has nothing left to upload. It still counts the memories it skipped, and it does not prove the server agrees: a deletion the server refused is not sent again.

Forgetting a memory is a soft delete: Trash keeps the record for at least 7 days and poppy restore brings it back, unless the memory's own expiry has passed. When sync is configured, an expired deletion stays in Trash until the current sync server has confirmed the deletion, so it can remain there past 7 days, until a sync succeeds. Two kinds of expired deletion leave Trash without that confirmation: one the current server refused for good, and, when a sync cleans up Trash, one for a memory the current server is not known to hold. Poppy then keeps a small record, without the memory's content, so a pull does not bring the memory back. A deletion goes to Trags only when Trags is known to hold that memory, because this machine uploaded it or a pull saw it there, so a memory that never left your machine does not reach the server just because you deleted it. A deletion carries the whole memory, which lets another device restore it from its own Trash.

Three limits apply:

  • If an upload reaches Trags but the reply is lost, Poppy does not count that memory as on Trags until a later push or pull confirms it. A deletion before then stays local and the copy on Trags remains. Pulls only fetch rows changed since the previous pull, so a later pull may not catch it.
  • A deletion sends this machine's copy of the memory, including edits that were never pushed.
  • If sync was already set up when you upgraded to the release that added this check, every memory that existed then counts as already on Trags, and deleting one sends it even if it never got there. The full guarantee covers memories created after the upgrade, and stores that had no Trags remote at upgrade time.

After pairing, the Trags API key normally lives in your operating system's keychain, and config.json holds no key. On macOS that is the login keychain, under the service poppy-memory, with an account name that starts with trags-api-key:. Check that it is there without printing the key:

bash
security find-generic-password -s poppy-memory    # shows attributes only

The key goes into the keychain only when Poppy can write it and read it back in the same process; otherwise it stays in plaintext in config.json (which stays 0600), and setup tells you which one happened. Keys are resolved in this order: the POPPY_TRAGS_API_KEY environment variable, then a plaintext key in config.json, then the OS keychain. An existing plaintext key is moved into the keychain automatically and scrubbed from the file the first time a Poppy process can write it and read it back from the keychain.

On macOS, the daemon that poppy daemon install sets up is a LaunchAgent in ~/Library/LaunchAgents that runs in your login session. It reads the key from the keychain, also after you log out and back in, so do not set POPPY_TRAGS_API_KEY for it. Processes started outside a login session, such as cron jobs, SSH sessions, and editor background jobs, can often write the login keychain but not read it. Set POPPY_TRAGS_API_KEY for those; captures they make are still pushed by the next sync a login-session process triggers, such as the daemon. This is local secret hygiene, not zero-knowledge: anything running as your user that can read the keychain can read the key.

Browse memories

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

Telemetry

Telemetry is off until you turn it on. The first time you run Poppy in a terminal it asks once whether to send anonymous usage events; the default is no, so pressing Enter declines. Until you answer yes, Poppy sends nothing.

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

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.

EventWhenProperties
cli_installonce, before the first event from a machineversion (Poppy version), python_version, platform (darwin, linux, win32)
memory_writeeach memory stored by poppy remember or the MCP remember and consolidate tools; automatic capture, imports, and memories edited or superseded in poppy ui do not send itmemory_type (fact, decision, preference, lesson, summary, context), has_project (true/false, never the project name), source (manual for poppy remember, agent for an MCP client that does not name itself, a known client such as claude-code or cursor, or other for any client Poppy does not know; the connecting client's own name is slugified before comparison, so Claude Memory becomes claude-memory; ui, claude-memory and hermes-memory appear only when the MCP server's --source setting or the connecting client's own name is one of them; a --source or POPPY_MCP_SOURCE value is only trimmed and lowercased, so Claude Memory there becomes claude memory and is reported as other; POPPY_MCP_SOURCE sets the same value as --source, and both apply only when the MCP server runs in process, so a poppy serve that forwards to the daemon ignores them)
recall_callpoppy recallquery_length (character count, never the query text), result_count, engine (engine name)
agent_setupevery poppy setup <client>agent (client name, for example claude-code), and via (plugin) when the Claude Code plugin was already installed
setup_completedonce per machine, on the first poppy setup <client>agent (client name), and via (plugin) when the Claude Code plugin was already installed
consent_grantedonce per machine, when you turn automatic capture onvia (consent_cmd, setup_yes, or setup_prompt)
consent_pending_shownonce per machine, when the consent nudge is first shownchannel (session_start or setup)
first_autocapture_storedonce per machine, the first time automatic capture stores a memorytrigger (mid_session, post_compact, or session_end)
closed_looponce per machine, the first time a memory captured in an earlier session is recalled back into a sessionchannel (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 question is asked on stderr, and only when someone can actually answer it: stdin, stdout and stderr all have to be a terminal, and Poppy must see no sign of a coding agent or a CI runner driving that terminal. Hooks, the MCP server (poppy serve), daemon commands, detached workers, --json output, --help and any piped or redirected run are never interrupted by it, and nothing is sent from any of them while the question is unanswered.

So if you only ever run Poppy through a coding agent, you never see the question: the agent's terminal counts as a program, not a person. Telemetry then stays off, and poppy telemetry status shows off (not answered yet). Run a command such as poppy stats in a plain terminal to be asked, or answer directly with poppy telemetry on or poppy telemetry off.

Your answer persists in ~/.poppy/config.json, and either answer counts, so you are asked once. Change your mind at any time, or answer without waiting to be asked:

bash
poppy telemetry status    # on, off, or not answered yetpoppy telemetry onpoppy telemetry off

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 or poppy config set update_check off. Turning telemetry off, with poppy telemetry off or POPPY_TELEMETRY_OFF=1, also turns it off.

So does leaving the telemetry question unanswered. A fresh install makes no network request of any kind, to PyPI or to PostHog, until you have said yes.

Hacking on it

Clone the repo:

bash
git clone https://github.com/trags-garden/poppycd poppyuv syncuv run pytest -vuv run poppy --help

For a map of the code, see docs/architecture.md.

For the exact source of a release, download it from PyPI instead:

bash
pip download poppy-memory --no-binary :all: --no-deps -d .tar xzf poppy_memory-*.tar.gz && cd poppy_memory-*/

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. Poppy is maintained by the Trags team. We are not accepting pull requests right now. Bug reports and feature requests are welcome as GitHub issues, or by email at [email protected]. For security vulnerabilities, email [email protected] instead of filing publicly.

來源:README.md,提交 0e91af1

工具

8

remember

Store a memory. Use for facts, decisions, preferences, or lessons learned during coding. Args: content: The memory to store memory_type: One of: fact, decision, preference, lesson, summary, context project: Project name (optional, for scoping) related_to: IDs of related memories (optional) ttl: Expire after duration (e.g. "30d", "12h", "1w3d") for time-sensitive facts expires_at: ISO-8601 datetime to expire at (mutually exclusive with ttl) supersedes: ID of a memory this one replaces; the old memory is tombstoned (restorable for 7 days) check_conflicts: Dry-run LLM conflict detection only; returns candidates, writes nothing auto_supersede: If a high-confidence conflict (>=0.85) is found, supersede that memory

輸入參數ttlcontent必填projectexpires_atrelated_tosupersedesmemory_typeauto_supersedecheck_conflicts

edit

Edit a memory in place. Preserves id, created_at, and source. Bumps updated_at. Pass only the fields you want to change. clear_project / clear_expiry explicitly null those fields. Args: id: Memory ID to edit content: New content (optional) memory_type: New type: fact, decision, preference, lesson, summary, context (optional) project: New project name (optional) clear_project: Set project to null ttl: Reset expiry to a duration (e.g. "30d") expires_at: Reset expiry to an ISO-8601 datetime clear_expiry: Remove expiry: make memory permanent

輸入參數id必填ttlcontentprojectexpires_atmemory_typeclear_expiryclear_project

recall

Search memories by natural language query. Args: query: What to search for project: Scope to a specific project (optional) memory_type: Filter by type: fact, decision, preference, lesson (optional) since: Only memories created at or after this point: ISO date (2026-06-01) or duration (7d, 1w3d) limit: Max results (default 10)

輸入參數limitquery必填sinceprojectmemory_type

recall_index

Cheap first pass: returns IDs and snippets only. Use recall_full to fetch full content for relevant IDs. Args: query: What to search for project: Scope to a specific project (optional) memory_type: Filter by type (optional) limit: Max results (default 20)

輸入參數limitquery必填projectmemory_type

recall_full

Fetch full content for a batch of memory IDs returned by recall_index. Always batch. Args: ids: Memory IDs from a prior recall_index call

輸入參數ids必填

forget

Delete a memory by ID. Args: id: The memory ID to delete

輸入參數id必填

consolidate

Store learnings from a coding session. Call at session end to persist what you learned. Args: session_summary: Brief summary of what was done in the session (stored as a 'summary' memory) facts: List of facts, decisions, preferences, or lessons learned project: Project name (optional, for scoping)

輸入參數facts必填projectsession_summary必填

context

Get the top-N most recent memories for a project. Useful for session-start priming. Args: project: Scope to a specific project (optional) limit: Max memories to return (default 20)

輸入參數limitproject

版本歷史

1
  1. v0.3.6最新Oct 26, 2026

評分

已滿足適用檢查項的 90%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分
  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分