Guides

Quick Start

Installation

Use uv (recommended) or pip.

To use local MLX models on macOS Apple Silicon (no API keys needed):

uv tool install 'keep-skill[local]'

For all others:

uv tool install keep-skill

That's it! API providers for Voyage, OpenAI, Anthropic, and Gemini are included.

Provider Configuration

Hosted Service

Sign up at keepnotes.ai to get an API key — no local models, no database setup:

export KEEPNOTES_API_KEY=kn_...
keep put "test"                    # That's it — storage, search, and summarization handled

Works across all your tools (Claude Code, Kiro, Codex) with the same API key. Project isolation, media pipelines, and backups are managed for you.

API Providers

Set environment variables for your preferred providers:

ProviderEnv VariableGet API KeyEmbeddingsSummarization
Voyage AIVOYAGE_API_KEYdash.voyageai.com-
AnthropicANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN*console.anthropic.com-
OpenAIOPENAI_API_KEYplatform.openai.com
Google GeminiGEMINI_API_KEYaistudio.google.com

\* Anthropic Authentication Methods:

Simplest setup (single API key):

export OPENAI_API_KEY=...      # Does both embeddings + summarization
# Or: GEMINI_API_KEY=...       # Also does both
keep put "test"             # Store auto-initializes on first use

Best quality (two API keys for optimal embeddings):

export VOYAGE_API_KEY=...      # Embeddings (Anthropic's partner)
export ANTHROPIC_API_KEY=...   # Summarization (cost-effective: claude-3-haiku)
# Or: CLAUDE_CODE_OAUTH_TOKEN  # OAuth token alternative
keep put "test"

Ollama (Local LLM Server)

If Ollama is running locally with models pulled, keep auto-detects it — no configuration needed:

ollama pull llama3.2:3b             # Any model works
keep put "test"                     # Auto-detected on first run

Keep picks the best available model: dedicated embedding models (e.g. nomic-embed-text) for embeddings, generative models (e.g. llama3.2) for summarization. Respects OLLAMA_HOST if set.

Local Providers (Apple Silicon)

For offline operation on macOS Apple Silicon without Ollama:

uv tool install 'keep-skill[local]'
keep put "test"             # No API key needed

Claude Desktop Setup

For use in Claude Desktop, API-based providers can be used.

For OpenAI (handles both embeddings and summarization):

  1. Get an OpenAI API key at platform.openai.com
  2. Add to network allowlist: api.openai.com
  3. Set OPENAI_API_KEY and use normally

Alternatively, for best quality embeddings with Anthropic summarization:

  1. Get API keys at dash.voyageai.com and console.anthropic.com
  2. Add to network allowlist: api.voyageai.com, api.anthropic.com
  3. Set both VOYAGE_API_KEY and ANTHROPIC_API_KEY

Basic Usage

# Index content (files, URLs, or inline text)
keep put "file://$(keep config tool)/docs/library/ancrenewisse.pdf"
keep put https://inguz.substack.com/p/keep -t topic=practice
keep put "Meeting notes from today" -t type=meeting
keep put "some content" --suggest-tags  # Show tag suggestions from similar items

# Search (returns: id date summary)
keep find "authentication" --limit 5
keep find "auth" --since P7D           # Last 7 days

# Retrieve (shows similar items by default)
keep get "file://$(keep config tool)/docs/library/ancrenewisse.pdf"
keep get https://inguz.substack.com/p/keep
keep get ID --meta                   # List meta items only
keep get ID --similar                # List similar items only

# Tags
keep list --tag project=myapp          # Find by tag
keep list --tags=                      # List all tag keys
keep tag-update ID --tag status=done   # Update tags

Reading the Output

Commands produce output in a distinctive format. Here's what to expect.

Search results (keep find) show one line per result — id date summary:

now 2026-02-07 Finished reading MN61. The mirror teaching: ...
file:///.../library/mn61.html 2026-02-07 The Exhortation to Rāhula...
https://inguz.substack.com/p/keep 2026-02-07 Keep: A Reflective Memory...
file:///.../library/han_verse.txt 2026-02-07 Han Verse: Great is the matter...

Full output (keep get, keep now) uses YAML frontmatter with the document body below:

---
id: file:///.../library/mn61.html
tags: {_source: uri, _updated: 2026-02-07T15:14:28+00:00, topic: reflection, type: teaching}
similar:
  - https://inguz.substack.com/p/keep (0.47) 2026-02-07 Keep: A Reflective Memory...
  - now (0.45) 2026-02-07 Finished reading MN61. The mirror teachi...
  - file:///.../library/han_verse.txt (0.44) 2026-02-07 Han Verse: Great is the matter...
meta/todo:
  - %a1b2c3d4 Update auth docs for new flow
meta/learnings:
  - %e5f6g7h8 JSON validation before deploy
prev:
  - @V{1} 2026-02-07 Previous version summary...
---
The Exhortation to Rāhula at Mango Stone is a Buddhist sutra that teaches...

Key fields:

Other output formats: --json for machine-readable JSON, --ids for bare IDs only.

Current Intentions

Track what you're working on:

keep now                               # Show current intentions
keep now "Working on auth bug"         # Update intentions
keep now -V 1                          # Previous intentions
keep now --history                     # All versions
keep reflect                           # Deep structured reflection

Version History

All documents retain history on update:

keep get ID                  # Current version (shows prev nav)
keep get ID -V 1             # Previous version
keep get ID --history        # List all versions

Text updates use content-addressed IDs:

keep put "my note"              # Creates ID from content hash
keep put "my note" -t done      # Same ID, new version (tag change)
keep put "different note"       # Different ID (new document)

Python API

For embedding keep into applications, see PYTHON-API.md.

Model Configuration

Customize models in ~/.keep/keep.toml:

[embedding]
name = "voyage"
model = "voyage-3.5-lite"

[summarization]
name = "anthropic"
model = "claude-3-haiku-20240307"

Media Description (optional)

When configured, images and audio files get model-generated descriptions alongside their extracted metadata, making them semantically searchable. Without this, media files are indexed with metadata only (EXIF, ID3 tags).

[media]
name = "mlx"
vision_model = "mlx-community/Qwen2-VL-2B-Instruct-4bit"
whisper_model = "mlx-community/whisper-large-v3-turbo"

Install media dependencies (Apple Silicon): pip install keep-skill[media]

Auto-detected if mlx-vlm or mlx-whisper is installed, or if Ollama has a vision model (e.g. llava).

Available Models

ProviderTypeModels
VoyageEmbeddingsvoyage-3.5-lite (default), voyage-3-large, voyage-code-3
AnthropicSummarizationclaude-3-haiku-20240307 (default, $0.25/MTok), claude-3-5-haiku-20241022
OpenAIEmbeddingstext-embedding-3-small (default), text-embedding-3-large
OpenAISummarizationgpt-4o-mini (default), gpt-4o
GeminiEmbeddingstext-embedding-004 (default)
GeminiSummarizationgemini-3-flash-preview (default), gemini-3-pro-preview
OllamaEmbeddingsAny model; prefer nomic-embed-text, mxbai-embed-large
OllamaSummarizationAny generative model (e.g. llama3.2, mistral, phi3)
OllamaMediaVision models: llava, moondream, bakllava (images only)
LocalEmbeddingsall-MiniLM-L6-v2 (sentence-transformers)
LocalSummarizationMLX models (Apple Silicon only)
LocalMediamlx-vlm for images, mlx-whisper for audio (Apple Silicon only)

Tool Integrations

On first use, keep detects coding tools and installs a protocol block and hooks into their global configuration. This happens once and is tracked in keep.toml.

ToolProtocol BlockHooks
Claude Code (~/.claude/)CLAUDE.md — reflective practice promptsettings.json — session start, prompt submit, subagent, session end
Kiro (~/.kiro/)steering/keep.md — reflective practice prompthooks/*.kiro.hook — agent spawn, prompt submit, agent stop
OpenAI Codex (~/.codex/)AGENTS.md — reflective practice prompt
OpenClaw (cwd)AGENTS.md — reflective practice prompt (if found in cwd)Plugin — agent start, agent stop

Hooks inject keep now context at key moments (session start, prompt submit) so the agent always has current intentions and relevant context. The protocol block teaches the reflective practice itself.

Run keep config to see integration status. Set KEEP_NO_SETUP=1 to skip auto-install.

Environment Variables

KEEP_STORE_PATH=/path/to/store       # Override store location
KEEP_TAG_PROJECT=myapp               # Auto-apply tags
KEEP_NO_SETUP=1                      # Skip auto-install of tool integrations
OLLAMA_HOST=http://localhost:11434   # Ollama server URL (auto-detected)
OPENAI_API_KEY=sk-...                # For OpenAI (embeddings + summarization)
GEMINI_API_KEY=...                   # For Gemini (embeddings + summarization)
VOYAGE_API_KEY=pa-...                # For Voyage embeddings only
ANTHROPIC_API_KEY=sk-ant-...         # For Anthropic summarization only
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...  # OAuth token alternative

Data Security

Encryption at Rest

Keep stores data in SQLite databases and ChromaDB files on disk. These are not encrypted by default.

If you store sensitive content (plans, credentials, reasoning traces), enable disk encryption:

OSSolutionHow
macOSFileVaultSystem Settings > Privacy & Security > FileVault
LinuxLUKSEncrypt home directory or the partition containing ~/.keep/
WindowsBitLockerSettings > Privacy & security > Device encryption

This is the recommended approach because it transparently covers both SQLite and ChromaDB's internal storage without application-level changes.

Troubleshooting

No embedding provider configured: Set an API key (e.g., VOYAGE_API_KEY) or install keep-skill[local].

Model download hangs: First use of local models downloads weights (~minutes). Cached in ~/.cache/.

ChromaDB errors: Delete ~/.keep/chroma/ to reset.

Slow local summarization: Large content is summarized in the background automatically.

Claude Code hooks need jq: The prompt-submit hook uses jq to extract context. Install with your package manager (e.g., brew install jq). Hooks are fail-safe without it, but prompt context won't be captured.

Next Steps

Configuration

Show configuration and resolve paths.

Usage

keep config                           # Show all config
keep config file                      # Config file location
keep config tool                      # Package directory (SKILL.md location)
keep config docs                      # Documentation directory
keep config store                     # Store path
keep config openclaw-plugin           # OpenClaw plugin directory
keep config providers                 # All provider config
keep config providers.embedding       # Embedding provider name

Options

OptionDescription
--reset-system-docsForce reload system documents from bundled content
-s, --store PATHOverride store directory

Config file location

The config file is keep.toml inside the config directory. The config directory is resolved in this order:

  1. KEEP_CONFIG environment variable — explicit path to config directory
  2. Tree-walk — search from current directory up to ~ for .keep/keep.toml
  3. Default~/.keep/

The tree-walk enables project-local stores: place a .keep/keep.toml in your project root and keep will use it when you're in that directory tree.

Store path resolution

The store (where data lives) is resolved separately from config:

  1. --store CLI option — per-command override
  2. KEEP_STORE_PATH environment variable
  3. store.path in config file[store] section of keep.toml
  4. Config directory itself — backwards compatibility default

Config file format

[store]
version = 2
max_summary_length = 1000

[embedding]
name = "mlx"                           # or "voyage", "openai", "ollama", "sentence_transformers"
model = "all-MiniLM-L6-v2"

[summarization]
name = "mlx"                           # or "anthropic", "openai", "ollama"
model = "mlx-community/Llama-3.2-3B-Instruct-4bit"

[media]
name = "mlx"                           # or "ollama" (auto-detected)
vision_model = "mlx-community/Qwen2-VL-2B-Instruct-4bit"
whisper_model = "mlx-community/whisper-large-v3-turbo"

[document]
name = "composite"

[tags]
project = "my-project"                 # Default tags applied to all new items
owner = "alice"

Environment variables

KEEP_STORE_PATH=/path/to/store        # Override store location
KEEP_CONFIG=/path/to/.keep            # Override config directory
KEEP_TAG_PROJECT=myapp                # Auto-apply tags (any KEEP_TAG_* variable)
KEEP_VERBOSE=1                        # Debug logging to stderr
KEEP_NO_SETUP=1                       # Skip auto-install of tool integrations

Config subpaths

PathReturns
fileConfig file path (~/.keep/keep.toml)
toolPackage directory (where SKILL.md lives)
docsDocumentation directory
storeStore data path
openclaw-pluginOpenClaw plugin directory
providersAll provider configuration
providers.embeddingEmbedding provider name
providers.summarizationSummarization provider name
providers.mediaMedia description provider name

Subpath output is raw (unquoted) for shell scripting:

cat "$(keep config tool)/SKILL.md"    # Read the practice guide
ls "$(keep config store)"             # List store contents

Resetting system documents

System documents (.conversations, .domains, .tag/*, etc.) are bundled with keep and loaded on first use. If they've been modified or corrupted:

keep config --reset-system-docs       # Reload all from bundled content

See Also

keep put

Add or update a document in the store.

Usage

Three input modes, auto-detected:

keep put "my note"                    # Text mode (inline content)
keep put file:///path/to/doc.pdf      # URI mode (fetch and index)
keep put https://example.com/page     # URI mode (web content)
keep put -                            # Stdin mode (explicit)
echo "piped content" | keep put       # Stdin mode (detected)

Options

OptionDescription
-t, --tag KEY=VALUETag as key=value (repeatable)
-i, --id IDCustom document ID (auto-generated for text/stdin)
--summary TEXTUser-provided summary (skips auto-summarization)
--suggest-tagsShow tag suggestions from similar items
-s, --store PATHOverride store directory

Text mode and content-addressed IDs

Text mode uses content-addressed IDs for automatic versioning:

keep put "my note"              # Creates %a1b2c3d4e5f6
keep put "my note" -t done      # Same ID, new version (tag change)
keep put "different note"       # Different ID (new document)

Same content = same ID = enables versioning through tag changes.

Smart summary behavior

Tag suggestions

The --suggest-tags flag shows tags from similar existing items, helping maintain consistent tagging:

keep put "OAuth2 token handling" --suggest-tags
# Suggests: project=myapp (3 similar), topic=auth (5 similar)

Update behavior

When updating an existing document (same ID):

Contextual summarization

When you provide tags during indexing, the summarizer uses context from related items to produce more relevant summaries.

  1. System finds similar items sharing your tags
  2. Items with more matching tags rank higher (+20% score boost per tag)
  3. Top related summaries are passed as context to the LLM
  4. Summary highlights relevance to that context

Tag changes trigger re-summarization:

keep put doc.pdf                       # Generic summary
keep put doc.pdf -t topic=auth         # Re-queued for contextual summary

Supported formats

FormatExtensionsContent extractedAuto-tags
Text.md, .txt, .py, .js, .json, .yaml, ...Full text
PDF.pdfText from all pages
HTML.html, .htmText (scripts/styles removed)
DOCX.docxParagraphs + tablesauthor, title
PPTX.pptxSlides + notesauthor, title
Audio.mp3, .flac, .ogg, .wav, .aiff, .m4a, .wmaStructured metadata (+ transcription\*)artist, album, genre, year, title
Images.jpg, .png, .tiff, .webpEXIF metadata (+ description\*)dimensions, camera, date

\* When a media description provider is configured ([media] in keep.toml), images get vision-model descriptions and audio files get speech-to-text transcription, appended to the extracted metadata. See QUICKSTART.md for setup.

Auto-extracted tags merge with user-provided tags. User tags win on collision:

keep put file:///path/to/song.mp3                    # Auto-tags: artist, album, genre, year
keep put file:///path/to/song.mp3 -t genre="Nu Jazz" # Overrides auto-extracted genre
keep put file:///path/to/photo.jpg -t topic=vacation  # Adds topic alongside auto camera/date

Indexing documents

Index important documents encountered during work:

keep put "https://docs.example.com/auth" -t topic=auth -t project=myapp
keep put "file:///path/to/design.pdf" -t type=reference -t topic=architecture

See Also

keep get

Retrieve item(s) by ID.

Usage

keep get ID                           # Current version with similar items
keep get ID1 ID2 ID3                  # Multiple items (separated by ---)
keep get ID -V 1                      # Previous version
keep get "ID@V{1}"                    # Same as -V 1 (version identifier syntax)

Options

OptionDescription
-V, --version NGet specific version (0=current, 1=previous)
-H, --historyList all versions (default 10, use -n to override)
-S, --similarList similar items (default 10)
-M, --metaList meta items
-t, --tag KEY=VALUERequire tag (error if item doesn't match)
-n, --limit NMax items for --history, --similar, --meta (default 10)
-s, --store PATHOverride store directory

Default output

Single-item commands (get, now) default to full YAML frontmatter format:

---
id: %a1b2c3d4
tags: {project: myapp, topic: auth, type: learning}
similar:
  - %e5f6a7b8 (0.89) 2026-01-14 Related authentication...
  - %c9d0e1f2 (0.85) 2026-01-13 Token handling notes...
meta/learnings:
  - %d3e4f5a6 Token refresh needs clock sync
prev:
  - @V{1} 2026-01-14 Previous summary text...
---
Document summary here...

Multiple IDs

keep get doc:1 doc:2 doc:3            # Items separated by ---
keep --ids list -n 5 | xargs keep get # Pipe from list

Display modes

keep get ID --similar                 # Show similar items
keep get ID --similar -n 20           # Show 20 similar items
keep get ID --meta                    # Show meta items
keep get ID --meta -n 5              # Show 5 meta items per section
keep get ID --history                # List all versions

Tag filtering

keep get ID -t project=myapp          # Error if item doesn't have this tag

See Also

keep find

Find items by semantic similarity or full-text search.

Usage

keep find "authentication"            # Semantic similarity search
keep find "auth" --text               # Full-text search on summaries
keep find --id ID                     # Find items similar to an existing item

Options

OptionDescription
--textUse full-text search instead of semantic similarity
--id IDFind items similar to this ID (instead of text query)
--include-selfInclude the queried item (only with --id)
-t, --tag KEY=VALUEFilter by tag (repeatable, AND logic)
-n, --limit NMaximum results (default 10)
--since DURATIONOnly items updated since (see time filtering below)
--historyInclude archived versions of matching items
-s, --store PATHOverride store directory

Semantic search (default) finds items by meaning using embeddings. "authentication" matches items about "login", "OAuth", "credentials" even if they don't contain the word "authentication".

Full-text search (--text) matches exact words in summaries. Faster but literal.

Find items similar to an existing document:

keep find --id file:///path/to/doc.md           # Similar to this document
keep find --id %a1b2c3d4                        # Similar to this item
keep find --id %a1b2c3d4 --since P30D           # Similar items from last 30 days

Tag filtering

Combine semantic search with tag filters (AND logic):

keep find "auth" -t project=myapp               # Search within a project
keep find "auth" -t project -t topic=security    # Multiple tags (AND)

Time filtering

The --since option accepts ISO 8601 durations or dates:

keep find "auth" --since P7D           # Last 7 days
keep find "auth" --since P1W           # Last week
keep find "auth" --since PT1H          # Last hour
keep find "auth" --since P1DT12H       # 1 day 12 hours
keep find "auth" --since 2026-01-15    # Since specific date

Including archived versions

keep find "auth" --history             # Also search old versions of items

See Also

keep list

List recent items, filter by tags, or list tag keys and values.

Usage

keep list                             # Recent items (by update time)
keep list -n 20                       # Show 20 most recent
keep list --sort accessed             # Sort by last access time

Options

OptionDescription
-n, --limit NMaximum results (default 10)
-t, --tag KEY=VALUEFilter by tag (repeatable, AND logic)
-T, --tags=List all tag keys
-T, --tags=KEYList values for a specific tag key
--sort ORDERSort by updated (default) or accessed
--since DURATIONOnly items updated since (ISO duration or date)
--historyInclude archived versions
-s, --store PATHOverride store directory

Tag filtering

keep list --tag project=myapp         # Items with project=myapp
keep list --tag project               # Items with any 'project' tag
keep list --tag foo --tag bar         # Items with both tags (AND)
keep list --tag project --since P7D   # Combine tag filter with recency

Listing tags

The --tags option (note: different from --tag) lists tag metadata:

keep list --tags=                     # List all distinct tag keys
keep list --tags=project              # List all values for 'project' tag

Time filtering

keep list --since P3D                 # Last 3 days
keep list --since P1W                 # Last week
keep list --since PT1H               # Last hour
keep list --since 2026-01-15         # Since specific date

Including versions

keep list --history                   # Include archived versions

Pipe composition

keep --ids list -n 5 | xargs keep get              # Get details for recent items
keep --ids list --tag project=foo | xargs keep del  # Bulk operations

See Also

keep now

Get or set the current working intentions.

The nowdoc is a singleton document that tracks what you're working on right now. It has full version history, so previous intentions are always accessible.

Usage

keep now                              # Show current intentions
keep now "Working on auth flow"       # Update intentions
keep now "Auth work" -t project=myapp # Update with tag
echo "piped content" | keep now       # Set from stdin

Options

OptionDescription
--resetReset to default from system
-V, --version NGet specific version (0=current, 1=previous)
-H, --historyList all versions
-t, --tag KEY=VALUESet tag (with content) or filter (without content)
-n, --limit NMax similar/meta items to show (default 3)
-s, --store PATHOverride store directory

Tag behavior

Tags behave differently depending on mode:

keep now "Auth work" -t project=myapp   # Sets project=myapp tag
keep now -t project=myapp               # Finds recent version with that tag

Version navigation

The nowdoc retains full history. Each update creates a new version.

keep now -V 1              # Previous intentions
keep now -V 2              # Two versions ago
keep now --history         # List all versions

Output includes prev: navigation for browsing version history. See VERSIONING.md for details.

Similar items and meta sections

When updating (keep now "..."), the output surfaces similar items and meta sections as occasions for reflection:

---
id: now
tags: {project: myapp, topic: auth}
similar:
  - %a1b2c3d4 OAuth2 token refresh pattern...
meta/todo:
  - %e5f6a7b8 validate redirect URIs
meta/learnings:
  - %c9d0e1f2 Token refresh needs clock sync
---
Working on auth flow

keep move

When a string of work is complete, move the now history into a named note. Requires either -t (tag filter) or --only (cherry-pick the tip).

keep move "auth-string" -t project=myapp   # Move matching versions
keep move "quick-note" --only              # Move just the current version

Moving to an existing name appends incrementally. Use --from to reorganize between items. See KEEP-MOVE.md for details.

keep reflect

Deep structured reflection practice. Guides you through gathering context, examining actions, and updating intentions.

keep reflect

See Also

keep move

Move versions from now (or another note) into a named note.

As you update keep now throughout a session, a string of versions accumulates. keep move extracts selected versions into a named note. Requires either -t (tag filter) or --only (cherry-pick the tip).

Usage

keep move "auth-string" -t project=myapp       # Move matching versions from now
keep move "auth-string" -t project=myapp       # Incremental: appends more
keep move "quick-note" --only                   # Move just the current version
keep move "target" --from "source" -t topic=X  # Reorganize between items

Options

OptionDescription
-t, --tag KEY=VALUEOnly extract versions matching these tags (repeatable)
--onlyMove only the current (tip) version
--from ITEM_IDSource item to extract from (default: now)
-s, --store PATHOverride store directory

Required: at least one of -t or --only must be specified.

How it works

  1. Versions matching the filter are moved from the source to the named target
  2. Non-matching versions remain in the source, with gaps tolerated
  3. If the source is fully emptied and is now, it resets to default content
  4. The moved item gets _saved_from and _saved_at system tags

Note on URI-shaped target names: The target name is just a string ID — it can be anything, including a URI like https://example.com/doc or file:///path/to/file. However, if the target name looks like a URI, a subsequent keep put will re-fetch content from the URL and overwrite what was moved there. This is by design (the ID is its fetch source), but be aware that move can effectively create an item whose ID points to a different origin than its content. This is analogous to a redirect — the stored content came from now, but the ID says https://....

Cherry-picking with --only

--only moves just the current (tip) version, one at a time. This is the cherry-picker for reorganizing untagged items:

keep move "string-a" --only          # Move tip to string-a
keep move "string-b" --only          # Move next tip to string-b
keep move "string-a" --only          # Append another to string-a

Combine with -t to only move the tip if it matches:

keep move "auth-log" --only -t topic=auth   # Move tip only if tagged auth

Reorganizing with --from

Use --from to extract versions from any item, not just now:

# Over-grabbed? Pull specific versions out
keep move "auth-string" --from "big-dump" -t project=auth
keep move "docs-string" --from "big-dump" -t project=docs

# Cherry-pick one version from an existing item
keep move "highlights" --from "session-log" --only

Incremental move

Moving to an existing name appends the new versions on top of the existing history:

# Session 1
keep now "design discussion" -t project=alpha
keep now "decided on approach B" -t project=alpha
keep move "alpha-log" -t project=alpha

# Session 2
keep now "implemented approach B" -t project=alpha
keep now "tests passing" -t project=alpha
keep move "alpha-log" -t project=alpha          # Appends to existing

keep get alpha-log --history                     # Shows all 4 versions

Tag-filtered move

When you work on multiple projects in one session, tag filtering lets you move each string separately:

keep now "auth: token refresh" -t project=auth
keep now "docs: update API guide" -t project=docs
keep now "auth: added tests" -t project=auth

keep move "auth-string" -t project=auth    # Extracts 2 auth versions
keep now                                    # Still has docs version

Version history

The moved item has full version history, navigable like any other item:

keep get string-name                 # Current (newest moved)
keep get string-name -V 1            # Previous version
keep get string-name --history       # List all versions

See Also

Tagging

Tags are key-value pairs attached to every item. They enable filtering, organization, and commitment tracking.

One value per key. Setting a tag overwrites any existing value for that key.

Setting tags

keep put "note" -t project=myapp -t topic=auth    # On create
keep now "working on auth" -t project=myapp        # On now update
keep tag-update ID --tag key=value                 # Add/update tag on existing item
keep tag-update ID --remove key                    # Remove a tag
keep tag-update ID1 ID2 --tag status=done          # Tag multiple items

Tag merge order

When indexing documents, tags are merged in this order (later wins):

  1. Existing tags — preserved from previous version
  2. Config tags — from [tags] section in keep.toml
  3. Environment tags — from KEEP_TAG_* variables
  4. User tags — passed via -t on the command line

Environment variable tags

Set tags via environment variables with the KEEP_TAG_ prefix:

export KEEP_TAG_PROJECT=myapp
export KEEP_TAG_OWNER=alice
keep put "deployment note"    # auto-tagged with project=myapp, owner=alice

Config-based default tags

Add a [tags] section to keep.toml:

[tags]
project = "my-project"
owner = "alice"

Tag filtering

The -t flag filters results on find, list, get, and now:

keep find "auth" -t project=myapp          # Semantic search + tag filter
keep find "auth" -t project -t topic=auth  # Multiple tags (AND logic)
keep list --tag project=myapp              # List items with tag
keep list --tag project                    # Any item with 'project' tag
keep get ID -t project=myapp              # Error if item doesn't match
keep now -t project=myapp                 # Find now version with tag

Listing tags

keep list --tags=                    # List all distinct tag keys
keep list --tags=project             # List all values for 'project' tag

Organizing by project and topic

Two tags help organize work across boundaries:

TagScopeExamples
projectBounded work contextmyapp, api-v2, migration
topicCross-project subject areaauth, testing, performance
# Project-specific knowledge
keep put "OAuth2 with PKCE chosen" -t project=myapp -t topic=auth

# Cross-project knowledge (topic only)
keep put "Token refresh needs clock sync" -t topic=auth

# Search within a project
keep find "authentication" -t project=myapp

# Search across projects by topic
keep find "authentication" -t topic=auth

For more on these conventions: keep get .tag/project and keep get .tag/topic.

For domain-specific organization patterns: keep get .domains.

Speech-act tags

Two tags make the commitment structure of work visible:

act — speech-act category

ValueWhat it marksExample
commitmentA promise to act"I'll fix auth by Friday"
requestAsking someone to act"Please review the PR"
offerProposing to act"I could refactor the cache"
assertionA claim of fact"The tests pass on main"
assessmentA judgment"This approach is risky"
declarationChanging reality"Released v2.0"

status — lifecycle state

ValueMeaning
openActive, unfulfilled
fulfilledCompleted and satisfied
declinedNot accepted
withdrawnCancelled by originator
renegotiatedTerms changed

Usage

# Track a commitment
keep put "I'll fix the auth bug" -t act=commitment -t status=open -t project=myapp

# Query open commitments and requests
keep list -t act=commitment -t status=open
keep list -t act=request -t status=open

# Mark fulfilled
keep tag-update ID --tag status=fulfilled

# Record an assertion or assessment (no lifecycle)
keep put "The tests pass" -t act=assertion
keep put "This approach is risky" -t act=assessment -t topic=architecture

For full details: keep get .tag/act and keep get .tag/status.

System tags

Tags prefixed with _ are protected and auto-managed. Users cannot set them directly.

Implemented: _created, _updated, _updated_date, _accessed, _accessed_date, _content_type, _source

See SYSTEM-TAGS.md for complete reference.

Python API

kp.tag("doc:1", {"status": "reviewed"})      # Add/update tag
kp.tag("doc:1", {"obsolete": ""})            # Delete tag (empty string)
kp.query_tag("project", "myapp")             # Exact key=value match
kp.query_tag("project")                      # Any doc with 'project' tag
kp.list_tags()                               # All distinct tag keys
kp.list_tags("project")                      # All values for 'project'

See PYTHON-API.md for complete Python API reference.

See Also

System Tags

System tags are automatically managed metadata prefixed with underscore (_). Users cannot set or modify these tags directly - they are protected by the filter_non_system_tags() function in keep/types.py.

Implemented Tags

These tags are actively set and maintained by the system.

_created

Purpose: ISO 8601 timestamp of when the item was first indexed.

Set by: DocumentStore.upsert() in document_store.py, ChromaStore.upsert() in store.py

Behavior: Set once on first insert, preserved on updates. Both stores maintain this independently.

Example: "2026-01-15T10:30:00.123456+00:00"

Access: Read via item.created property or item.tags["_created"]


_updated

Purpose: ISO 8601 timestamp of the last modification.

Set by: DocumentStore.upsert() in document_store.py, ChromaStore.upsert() in store.py

Behavior: Updated on every modification (content, summary, or tags). Both stores maintain this independently.

Example: "2026-02-02T14:45:00.789012+00:00"

Access: Read via item.updated property or item.tags["_updated"]


_updated_date

Purpose: Date portion of _updated for efficient date-based queries.

Set by: DocumentStore.upsert() in document_store.py, ChromaStore.upsert() in store.py

Behavior: Always set alongside _updated. Format: YYYY-MM-DD. Both stores maintain this independently.

Example: "2026-02-02"

Usage: Used by --since filtering in CLI commands.


_accessed

Purpose: ISO 8601 timestamp of when the item was last retrieved (read).

Set by: _record_to_item() in api.py, from accessed_at column in DocumentStore

Behavior: Updated whenever the item is retrieved via get(), find(), find_similar(), or query_fulltext(). Distinct from _updated — reading an item updates _accessed but not _updated.

Example: "2026-02-07T09:15:00.123456+00:00"

Access: Read via item.accessed property or item.tags["_accessed"]


_accessed_date

Purpose: Date portion of _accessed for efficient date-based queries.

Set by: _record_to_item() in api.py

Behavior: Always set alongside _accessed. Format: YYYY-MM-DD.

Example: "2026-02-07"


_content_type

Purpose: MIME type of the document content.

Set by: Keeper.update() in api.py (only for URI-based documents)

Behavior: Set if the document provider returns a content type.

Example: "text/markdown", "text/html", "application/pdf", "audio/mpeg", "image/jpeg"

Note: Not set for inline content (CLI: keep put "text", API: kp.remember()).


_source

Purpose: How the content was obtained.

Set by: Keeper.update() and Keeper.remember() in api.py

Values:

Usage: Query with kp.query_tag("_source", "inline") to find remembered content.


Protection Mechanism

System tags are protected from user modification:

# In keep/types.py
SYSTEM_TAG_PREFIX = "_"

def filter_non_system_tags(tags: dict[str, str]) -> dict[str, str]:
    """Filter out any system tags (those starting with '_')."""
    return {k: v for k, v in tags.items() if not k.startswith(SYSTEM_TAG_PREFIX)}

This function is called before merging user-provided tags in update() (API), remember() (API), and tag() methods.

Tag Merge Order

When indexing documents, tags are merged in this order (later wins on collision):

  1. Existing tags - Preserved from previous version
  2. Config tags - From [tags] section in keep.toml
  3. Environment tags - From KEEP_TAG_* variables
  4. User tags - Passed to update(), remember(), or tag()
  5. System tags - Added/updated by system (cannot be overridden)

Querying by System Tags

# Find items by source
inline_items = kp.query_tag("_source", "inline")
uri_items = kp.query_tag("_source", "uri")

# Find items by date
today = kp.query_tag("_updated_date", "2026-02-02")

# Find system documents
system_docs = kp.query_tag("_system", "true")

Versioning and System Tags

When a document is updated, the previous version (including all its system tags) is archived in the document_versions table. This preserves the complete tag state at each point in history.

# Current version has current timestamps
current = kp.get("doc:1")
print(current.tags["_updated"])  # "2026-02-02T14:45:00..."

# Previous version has its own timestamps
prev = kp.get_version("doc:1", offset=1)
print(prev.tags["_updated"])  # "2026-02-01T10:30:00..."

See Also

Meta-Docs

Meta-docs are system documents that surface contextually relevant items when you view your current context (keep now) or retrieve any item (keep get). They answer the question: what else should I be aware of right now?

How It Works

A meta-doc is a .meta/* system document containing tag queries. When you run keep now or keep get, each meta-doc's queries run against the store, and matching items appear in the output as named sections.

For example, when you run keep now while working on a project tagged project=myapp:

---
id: now
tags: {project: myapp, topic: auth}
similar:
  - %a1b2 OAuth2 token refresh pattern
meta/todo:
  - %c3d4 validate redirect URIs
  - %e5f6 update auth docs for new flow
meta/learnings:
  - %g7h8 JSON validation before deploy saves hours
---
Working on auth flow refactor

The meta/todo: section appeared because you previously captured commitments tagged with project=myapp:

keep put "validate redirect URIs" -t act=commitment -t status=open -t project=myapp

The meta/learnings: section appeared because you previously captured a learning tagged with the same project:

keep put "JSON validation before deploy saves hours" -t type=learning -t project=myapp

Bundled Meta-Docs

keep ships with five meta-docs:

.meta/todo` — Open Loops

Surfaces unresolved commitments, requests, offers, and blocked work. These are things someone said they'd do, or asked for, that aren't resolved yet.

Queries:

  • act=commitment status=open
  • act=request status=open
  • act=offer status=open
  • status=blocked

Context keys: project=, topic=

.meta/learnings` — Experiential Priming

Surfaces past learnings, breakdowns, and gotchas. Before starting work, check what went wrong or was hard-won last time.

Queries:

  • type=learning
  • type=breakdown
  • type=gotcha

Context keys: project=, topic=

.meta/genre` — Same Genre

Groups media items by genre. Only activates for items that have a genre tag.

Prerequisites: genre=*

Context keys: genre=

.meta/artist` — Same Artist

Groups media items by artist/creator. Only activates for items that have an artist tag.

Prerequisites: artist=*

Context keys: artist=

.meta/album` — Same Album

Groups tracks from the same release. Only activates for items that have an album tag.

Prerequisites: album=*

Context keys: album=

Query Structure

A meta-doc file contains prose (for humans and LLMs) plus structured lines:

Context matching is what makes meta-docs contextual. If the current item has project=myapp, then the query act=commitment status=open combined with context key project= becomes act=commitment status=open project=myapp. This scopes results to the current project.

If the current item has no matching tag for a context key, the query runs without that filter.

Prerequisites act as gates. A meta-doc with genre=* only activates for items that have a genre tag — items without one skip the meta-doc entirely. This lets you create meta-docs that only apply to certain kinds of items (e.g., media files tagged with genre, artist, album) without polluting results for unrelated items.

A meta-doc with only prerequisites and context keys (no query lines) becomes a pure group-by: it finds items sharing the same tag value as the current item. For example, genre=* + genre= means "find other items with the same genre as this one."

Ranking

Results are ranked by a combination of:

  1. Embedding similarity to the current item — so semantically related items rank higher
  2. Recency decay — recent items get a boost

Each meta-doc returns up to 3 items. Meta sections with no matches are omitted from the output.

Viewing Meta-Docs

You can read the meta-doc definitions themselves:

keep get .meta/todo        # See the todo meta-doc's queries and description
keep get .meta/learnings   # See the learnings meta-doc's queries

Feeding the Loop

Meta-docs only surface what you put in. The tags that matter:

# Commitments and requests (surface in meta/todo)
keep put "I'll fix the login bug" -t act=commitment -t status=open -t project=myapp
keep put "Can you review the PR?" -t act=request -t status=open -t project=myapp

# Resolve when done
keep put "Login bug fixed" -t act=commitment -t status=fulfilled -t project=myapp

# Learnings and breakdowns (surface in meta/learnings)
keep put "Always check token expiry before refresh" -t type=learning -t topic=auth
keep put "Assumed UTC, server was local time" -t type=breakdown -t project=myapp

# Gotchas (surface in meta/learnings)
keep put "CI cache invalidation needs manual clear after dep change" -t type=gotcha -t topic=ci

Media Library

The media meta-docs (genre, artist, album) surface related media automatically. Tag your audio files when ingesting them:

keep put ~/Music/OK_Computer/01_Airbag.flac -t artist=Radiohead -t album="OK Computer" -t genre=rock
keep put ~/Music/OK_Computer/02_Paranoid_Android.flac -t artist=Radiohead -t album="OK Computer" -t genre=rock
keep put ~/Music/Kid_A/01_Everything.flac -t artist=Radiohead -t album="Kid A" -t genre=electronic

Now when you keep get any of these items, you'll see:

Items without media tags won't see these sections at all — the genre=* prerequisite ensures they're skipped.

The tag taxonomy is documented in system docs:

keep get .tag/act       # Speech-act categories
keep get .tag/status    # Lifecycle status values
keep get .tag/type      # Content type values
keep get .tag/project   # Project tag conventions
keep get .tag/topic     # Topic tag conventions

See Also

Versioning

All documents retain history on update. Previous versions are archived automatically.

Strings

Every note is a string — a linear chain of versions that grows each time you update it.

Your working context (now) accumulates a string of intentions as you work.

When a string of work is complete, keep move extracts matching versions by tag

and strings them into a named note.

This isn't version control (no branches, no merges) and it isn't an append-only log

(you can reorganize). It's the temporal dimension of how knowledge evolved — and

unlike other memory stores, you can re-string it by meaning.

Version identifiers

Append @V{N} to any ID to specify a version by offset:

keep get "%a1b2c3d4@V{1}"        # Previous version
keep get "now@V{2}"              # Two versions ago of nowdoc

Accessing versions

keep get ID -V 1                 # Previous version (equivalent to @V{1})
keep get ID --history            # List all versions
keep now -V 1                   # Previous intentions
keep now --history              # List all nowdoc versions

History output

--history shows versions as summary lines with version identifiers:

now           2026-01-16 Current summary...
now@V{1}      2026-01-15 Previous summary...
now@V{2}      2026-01-14 Older summary...

With --ids, outputs version identifiers for piping:

keep --ids now --history
# now@V{0}
# now@V{1}
# now@V{2}

Version navigation in output

When viewing a single item (keep get, keep now), the output includes prev: and next: navigation:

---
id: %a1b2c3d4
prev:
  - @V{1} 2026-01-14 Previous summary text...
---
Current summary here...

Content-addressed IDs

Text-mode updates use content-addressed IDs for versioning:

keep put "my note"              # Creates %a1b2c3d4e5f6
keep put "my note" -t done      # Same ID, new version (tag change)
keep put "different note"       # Different ID (new document)

Same content = same ID = enables versioning via tag changes.

Deleting and reverting

keep del has revert semantics when versions exist:

keep del ID                     # If versions exist: reverts to previous version
                                # If no versions: removes item entirely

Pipe composition

keep --ids now --history | xargs -I{} keep get "{}"   # Get all versions
diff <(keep get doc:1) <(keep get "doc:1@V{1}")       # Diff current vs previous

Python API

kp.get_version(id, offset=1)    # Previous version
kp.get_version(id, offset=2)    # Two versions ago
kp.list_versions(id)            # All archived versions (newest first)

See PYTHON-API.md for complete Python API reference.

See Also

Output Format

When you run keep get or keep now, items are displayed in YAML frontmatter format. Each section carries meaning.

Annotated Example

---
id: %a1b2c3d4                                            # 1. Identity
tags: {project: myapp, topic: auth, act: commitment, status: open}  # 2. Tags
score: 0.823                                              # 3. Relevance
similar:                                                  # 4. Similar items
  - %e5f6a7b8         (0.89) 2026-01-14 OAuth2 token refresh pattern...
  - %c9d0e1f2         (0.85) 2026-01-13 Token handling notes...
  - .tag/act           (0.45) 2026-01-10 Speech-act categories...
meta/todo:                                                # 5. Meta sections
  - %d3e4f5a6 validate redirect URIs
  - %b7c8d9e0 update auth docs for new flow
meta/learnings:
  - %f1a2b3c4 Token refresh needs clock sync
prev:                                                     # 6. Version navigation
  - @V{1} 2026-01-14 Previous version of this item...
  - @V{2} 2026-01-13 Older version...
---
I'll fix the auth bug by Friday                           # 7. Content

Sections

1. id: — Identity

The document's unique identifier.

FormatMeaning
%a1b2c3d4Content-addressed (text mode, hash of content)
file:///path/to/docLocal file URI
https://example.comWeb URL
nowThe nowdoc (current intentions)
.conversationsSystem document (dotted prefix)
.tag/actTag description document

When viewing an old version, a suffix appears: id: %a1b2c3d4@V{1}

2. tags: — Metadata

Key-value pairs in YAML flow format. One value per key.

tags: {project: myapp, topic: auth, act: commitment, status: open}

Tags you set are shown here. System tags (prefixed _) are hidden from display but accessible via --json.

Key tag patterns:

3. score: — Relevance

Appears on search results (keep find). A similarity score between 0 and 1, with recency decay applied.

Higher = more relevant. Scores above 0.7 are strong matches. This field is absent when viewing items directly (not via search).

4. similar: — Semantic neighbors

Items semantically close to this one, ranked by similarity. Each line:

  - ID               (score) date summary...

Similar items are occasions for reflection: what else do I know about this? Control with -n (limit) or --no-similar to suppress.

5. meta/*: — Contextual sections

Meta-docs surface items matching tag-based queries relevant to what you're viewing. Each section name maps to a meta-doc:

SectionSurfacesSource
meta/todo:Open requests and commitments.meta/todo
meta/learnings:Relevant learnings.meta/learnings
meta/decisions:Related decisions.meta/decisions

These are dynamically resolved — the same item shows different meta sections depending on its tags. For example, an item tagged project=myapp surfaces todos and learnings also tagged with that project.

See META-DOCS.md for how meta-docs work and how to create custom ones.

6. prev: / next: — Version navigation

Navigate through the item's version history.

prev:
  - @V{1} 2026-01-14 Previous version summary...
  - @V{2} 2026-01-13 Older version summary...

See VERSIONING.md for full versioning details.

7. Content (after ---)

The item's summary, below the closing ---. For short content this is the full text; for long documents it's a generated summary.

Output Formats

The frontmatter format is one of four output modes:

FlagFormatUse case
(default)Summary line or frontmatterFrontmatter for single items, summary lines for lists
--idsVersioned IDs onlyPiping to other commands
--fullYAML frontmatter (forced)When you want full details on list/find results
--jsonJSONProgrammatic access

See REFERENCE.md for examples of each format.

See Also

CLI Reference

Purpose: Persistent memory for documents with semantic search.

Default store: ~/.keep/ in user home (auto-created)

Commands

CommandDescriptionDocs
keep nowGet or set current working intentionsKEEP-NOW.md
keep putAdd or update a documentKEEP-PUT.md
keep getRetrieve item(s) by IDKEEP-GET.md
keep findSearch by meaning or textKEEP-FIND.md
keep listList recent items, filter by tagsKEEP-LIST.md
keep configShow configuration and pathsKEEP-CONFIG.md
keep moveMove versions into a named item (-t or --only required)KEEP-MOVE.md
keep reflectStructured reflection practiceKEEP-NOW.md
keep delRemove item or revert to previous version
keep tag-updateAdd, update, or remove tagsTAGGING.md
keep reindexRebuild search index

Global Flags

keep --json <cmd>   # Output as JSON
keep --ids <cmd>    # Output only versioned IDs (for piping)
keep --full <cmd>   # Output full YAML frontmatter
keep -v <cmd>       # Enable debug logging to stderr

Output Formats

Three output formats, consistent across all commands:

Default: Summary Lines

One line per item: id date summary

%a1b2c3d4         2026-01-14 URI detection should use proper scheme validation...
file:///path/doc  2026-01-15 Document about authentication patterns...

With --ids: Versioned IDs Only

%a1b2c3d4@V{0}
file:///path/doc@V{0}

With --full: YAML Frontmatter

Full details with tags, similar items, and version navigation:

---
id: %a1b2c3d4
tags: {project: myapp, status: reviewed}
similar:
  - %e5f6a7b8@V{0} (0.89) 2026-01-14 Related authentication...
meta/todo:
  - %c9d0e1f2 Update auth docs for new flow
score: 0.823
prev:
  - @V{1} 2026-01-14 Previous summary text...
---
Document summary here...

Note: keep get and keep now default to full format since they display a single item.

With --json: JSON Output

{"id": "...", "summary": "...", "tags": {...}, "score": 0.823}

Version numbers are offsets: @V{0} = current, @V{1} = previous, @V{2} = two versions ago.

Output width: Summaries are truncated to fit the terminal. When stdout is not a TTY (e.g., piped through hooks), output uses 200 columns for wider summaries.

Pipe Composition

keep --ids find "auth" | xargs keep get              # Get full details for all matches
keep --ids list -n 5 | xargs keep get                # Get details for recent items
keep --ids list --tag project=foo | xargs keep tag-update --tag status=done
keep --json --ids find "query"                       # JSON array: ["id@V{0}", ...]

# Version history composition
keep --ids now --history | xargs -I{} keep get "{}"  # Get all versions
diff <(keep get doc:1) <(keep get "doc:1@V{1}")      # Diff current vs previous

Quick CLI

# Current intentions
keep now                              # Show current intentions
keep now "What's important now"       # Update intentions
keep reflect                          # Structured reflection practice
keep move "name" -t project=foo       # Move matching versions from now
keep move "name" --only               # Move just the current version
keep move "name" --from "source" -t X # Reorganize between items

# Add or update
keep put "inline text" -t topic=auth  # Text mode
keep put file:///path/to/doc.pdf      # URI mode
keep put "note" --suggest-tags        # Show tag suggestions

# Retrieve
keep get ID                           # Current version
keep get ID -V 1                      # Previous version
keep get ID --history                 # List all versions

# Search
keep find "query"                     # Semantic search
keep find "query" --text              # Full-text search
keep find "query" --since P7D         # Last 7 days

# List and filter
keep list                            # Recent items
keep list --tag project=myapp        # Filter by tag
keep list --tags=                    # List all tag keys

# Modify
keep tag-update ID --tag key=value   # Add/update tag
keep tag-update ID --remove key      # Remove tag
keep del ID                          # Remove item or revert to previous version

# Maintenance
keep reindex                         # Rebuild search index
keep reindex -y                      # Skip confirmation

Python API

See PYTHON-API.md for complete Python API reference.

from keep import Keeper
kp = Keeper()
kp.remember("note", tags={"project": "myapp"})
results = kp.find("authentication", limit=5)

When to Use

Topics

More

Python API

The CLI (keep put, keep find, etc.) is the primary interface. The Python API is for embedding keep into applications.

Installation

pip install keep-skill

See QUICKSTART.md for provider configuration (API keys or local models).

Quick Start

from keep import Keeper

kp = Keeper()  # Uses ~/.keep/ by default

# Index documents
kp.update("file:///path/to/doc.md", tags={"project": "myapp"})
kp.update("https://example.com/page", tags={"topic": "reference"})
kp.remember("Important insight about auth patterns")

# Search
results = kp.find("authentication", limit=5)
for r in results:
    print(f"[{r.score:.2f}] {r.id}: {r.summary}")

# Retrieve
item = kp.get("file:///path/to/doc.md")
print(item.summary)
print(item.tags)

API Reference

Imports

from keep import Keeper, Item
from keep.document_store import VersionInfo  # for version history

Initialization

# Default store (~/.keep/)
kp = Keeper()

# Custom store location
kp = Keeper(store_path="/path/to/store")

Core Operations

Indexing
# Index from URI (file:// or https://)
kp.update(uri, tags={}, summary=None) → Item

# Index inline content
kp.remember(content, summary=None, tags={}) → Item

# Notes:
# - If summary provided, skips auto-summarization
# - remember() uses content verbatim if short (≤max_summary_length)
# - User tags (domain, topic, etc.) provide context for summarization
# Semantic search
kp.find(query, limit=10, since=None) → list[Item]

# Find similar to a document
kp.find_similar(uri, limit=10, since=None) → list[Item]

# Similar items using stored embedding
kp.get_similar_for_display(id, limit=3) → list[Item]

# Tag-based query
kp.query_tag(key, value=None, since=None) → list[Item]

# Full-text search
kp.query_fulltext(query, since=None) → list[Item]

# Time filtering (all search methods support since)
kp.find("auth", since="P7D")      # Last 7 days
kp.find("auth", since="P1W")      # Last week  
kp.find("auth", since="PT1H")     # Last hour
kp.find("auth", since="2026-01-15")  # Since date
Item Access
# Get by ID
kp.get(id) → Item | None

# Check existence
kp.exists(id) → bool

# List recent items
kp.list_recent(limit=10, sort="updated") → list[Item]
# sort options: "updated" (default), "created", "accessed"

# List collections
kp.list_collections() → list[str]
Tags
# Update tags only (no re-processing)
kp.tag(id, tags={}) → Item | None

# Delete tag by setting empty value
kp.tag("doc:1", {"obsolete": ""})  # Removes 'obsolete' tag

# List tag keys or values
kp.list_tags(key=None) → list[str]
kp.list_tags()          # All tag keys
kp.list_tags("project") # All values for 'project' key
Version History
# Get previous version
kp.get_version(id, offset=1) → Item | None
# offset: 0=current, 1=previous, 2=two versions ago

# List all versions
kp.list_versions(id, limit=10) → list[VersionInfo]

# Get navigation metadata
kp.get_version_nav(id) → dict
# Returns: {"prev": [...], "next": [...]}
Current Intentions (Now)
# Get current intentions (auto-creates if missing)
kp.get_now() → Item

# Set current intentions
kp.set_now(content, tags={}) → Item
Deletion
# Delete item (reverts to previous version if history exists)
kp.delete(id) → Item | None

# Complete removal (if no history)
# If history exists, archives current version and restores previous

Data Types

Item

Fields:

Properties (from tags):

VersionInfo

Fields for version history listings:

Tags

Tag Merge Order

When indexing, tags merge in priority order (later wins):

  1. Existing tags (from previous version)
  2. Config tags (from [tags] in keep.toml)
  3. Environment tags (from KEEP_TAG_* variables)
  4. User tags (passed to update(), remember(), or tag())

Tag Rules

Environment Tags

import os
os.environ["KEEP_TAG_PROJECT"] = "myapp"
os.environ["KEEP_TAG_OWNER"] = "alice"

kp.remember("note")  # Auto-tagged with project=myapp, owner=alice

Config Tags

Add to keep.toml:

[tags]
project = "my-project"
owner = "alice"

See TAGGING.md for details on:

System Tags (Read-Only)

Protected tags (prefix _) managed automatically:

Query system tags:

kp.query_tag("_updated_date", "2026-01-30")
kp.query_tag("_source", "inline")

See SYSTEM-TAGS.md for complete reference.

Version Identifiers

Append @V{N} to specify version by offset:

item = kp.get("doc:1@V{1}")  # Get previous version
versions = kp.list_versions("doc:1")

Collections

Separate stores for different contexts:

import os

# Work context
os.environ["KEEP_COLLECTION"] = "work"
kp_work = Keeper()
kp_work.set_now("Working on auth")

# Personal context
os.environ["KEEP_COLLECTION"] = "personal"
kp_personal = Keeper()
kp_personal.set_now("Personal notes")

Collections are completely separate. For overlays within a single store, use tags instead.

Error Handling

from keep import Keeper

try:
    kp = Keeper()
    item = kp.get("nonexistent")
    if item is None:
        print("Item not found")
except Exception as e:
    print(f"Error: {e}")

Common errors:

See Also

Architecture

What is keep?

keep is a reflective memory system providing persistent storage with vector similarity search. It's designed as an agent skill for OpenClaw and other agentic environments, enabling agents to remember information across sessions over time.

Think of it as: ChromaDB + embeddings + summarization + tagging wrapped in a simple API.

Published by Hugh Pyle, "inguz ᛜ outcomes", under the MIT license.

Contributions are welcome; code is conversation, "right speech" is encouraged.


Core Concept

Every stored item has:

The original document content is not stored — only the summary and embedding.


Architecture

┌─────────────────────────────────────────────────────────────┐
│  API Layer (api.py)                                         │
│  - Keeper class                                             │
│  - High-level operations: update(), remember(), find()      │
│  - Version management: get_version(), list_versions()       │
└──────────────────┬──────────────────────────────────────────┘
                   │
        ┌──────────┼──────────┬──────────┬──────────┬───────────┐
        │          │          │          │          │           │
        ▼          ▼          ▼          ▼          ▼           ▼
   ┌────────┐ ┌─────────┐ ┌────────┐ ┌────────┐ ┌─────────┐ ┌─────────┐
   │Document│ │Embedding│ │Summary │ │Media   │ │Vector   │ │Document │
   │Provider│ │Provider │ │Provider│ │Descr.* │ │Store    │ │Store    │
   └────────┘ └─────────┘ └────────┘ └────────┘ └─────────┘ └─────────┘
       │          │           │          │             │           │
   fetch()    embed()    summarize()  describe()  vectors/    summaries/
   from URI   text→vec  text→summary  media→text  search      versions

Components

api.py — Main facade

store.py — Vector persistence

document_store.py — Document persistence

providers/ — Pluggable services

config.py — Configuration

types.py — Data model


Data Flow

Indexing: update(uri) or remember(content)

URI or content
    │
    ▼
┌─────────────────┐
│ Fetch/Use input │ ← DocumentProvider (for URIs only)
└────────┬────────┘
         │ raw bytes
         ▼
┌─────────────────┐
│ Content Regular-│ ← Extract text from HTML/PDF
│ ization         │   (scripts/styles removed)
└────────┬────────┘
         │ clean text
         ▼
┌─────────────────┐
│ Media Enrichment│ ← Optional: vision description (images)
│ (if configured) │   or transcription (audio) appended
└────────┬────────┘
         │ enriched text
    ┌────┴────┬─────────────┐
    │         │             │
    ▼         ▼             ▼
  embed()  summarize()   tags (from args)
    │         │             │
    └────┬────┴─────────────┘
         │
    ┌────┴────────────────┐
    │                     │
    ▼                     ▼
┌─────────────────┐  ┌─────────────────┐
│ DocumentStore   │  │ ChromaStore     │
│ upsert()        │  │ upsert()        │
│ - summary       │  │ - embedding     │
│ - tags          │  │ - summary       │
│ - timestamps    │  │ - tags          │
│ - archive prev  │  │ - version embed │
└─────────────────┘  └─────────────────┘

Versioning on update:

Retrieval: find(query)

query text
    │
    ▼
  embed()  ← EmbeddingProvider
    │
    │ query vector
    ▼
┌───────────────────┐
│ ChromaStore       │
│ query_embedding() │ ← L2 distance search
└─────────┬─────────┘
          │
          ▼ results with distance scores
    ┌──────────────┐
    │ Apply decay  │ ← Recency weighting (ACT-R style)
    │ score × 0.5^(days/half_life)
    └──────┬───────┘
           │
           ▼
    list[Item] (sorted by effective score)

Delete / Revert: delete(id) or revert(id)

delete(id)
    │
    ▼
  version_count(id)
    │
    ├── 0 versions → full delete from both stores
    │
    └── N versions → revert to previous
            │
            ├─ get archived embedding from ChromaDB (id@vN)
            ├─ restore_latest_version() in DocumentStore
            │    (promote latest version row to current, delete version row)
            ├─ upsert restored embedding as current in ChromaDB
            └─ delete versioned entry (id@vN) from ChromaDB

Key Design Decisions

1. Schema as Data

2. Lazy Provider Loading

3. Separation of Concerns

4. No Original Content Storage

5. Immutable Items

6. System Tag Protection

7. Document Versioning

8. Version-Based Addressing


Storage Layout

store_path/
├── keep.toml               # Provider configuration
├── chroma/                 # ChromaDB persistence (vectors + metadata)
│   └── [collection]/       # One collection = one namespace
│       ├── embeddings
│       ├── metadata
│       └── documents
├── document_store.db       # SQLite store (summaries, tags, versions)
│   ├── documents           # Current version of each document
│   └── document_versions   # Archived previous versions
└── embedding_cache.db      # SQLite cache for embeddings

Provider Types

Embedding Providers

Generate vector representations for semantic search.

Dimension determined by model. Must be consistent across indexing and queries.

Summarization Providers

Generate human-readable summaries from content.

Contextual Summarization:

When documents have user tags (domain, topic, project, etc.), the summarizer

receives context from related items. This produces summaries that highlight

relevance to the tagged context rather than generic descriptions.

How it works:

  1. When processing pending summaries, the system checks for user tags
  2. Finds similar items that share any of those tags (OR-union)
  3. Boosts scores for items sharing multiple tags (+20% per additional match)
  4. Top 5 related summaries are passed as context to the LLM
  5. The summary reflects what's relevant to that context

Example: Indexing a medieval text with domain=practice produces a summary

highlighting its relevance to contemplative practice, not just "a 13th-century

guide for anchoresses."

Tag changes trigger re-summarization: When user tags are added, removed, or

changed on an existing document, it's re-queued for contextual summarization

even if content is unchanged. The existing summary is preserved until the new

one is ready.

Non-LLM providers (truncate, first_paragraph, passthrough) ignore context.

Document Providers

Fetch content from URIs with content regularization.

Content Regularization:

Provider-extracted tags merge with user tags (user wins on collision). This ensures both embedding and summarization receive clean text.

Media Description Providers (optional)

Generate text descriptions from media files, enriching metadata-only content.

Media description runs in Keeper.update() between fetch and upsert. Descriptions are appended to the metadata content before embedding/summarization, making media files semantically searchable by their visual or audio content.

Design points:


Extension Points

New Provider

  1. Implement Protocol from providers/base.py
  2. Register with get_registry().register_X("name", YourClass)
  3. Reference by name in config

New Store Backend

New Query Types


Performance Characteristics

Indexing

Querying

Caching

Scaling


Failure Modes

Missing Dependencies

URI Fetch Failures

Invalid Config

Store Inconsistency

Store Corruption


Testing Strategy

Unit Tests: tests/test_core.py

Document Store Tests: tests/test_document_store.py

Integration Tests: tests/test_integration.py

Provider Tests: (TODO)


Future Work

Planned (in later/)

Under Consideration

Agent Guide

Patterns for using the reflective memory store effectively in working sessions.

For the practice (why and when), see ../SKILL.md.

For CLI reference, see REFERENCE.md. Be sure you understand the output format — every item surfaces similar items and meta sections you can navigate with keep get.


The Practice

This guide assumes familiarity with the reflective practice in SKILL.md. The key points:

Reflect before acting: Check your current work context and intentions.

keep now                    # Current intentions
keep find "this situation"  # Prior knowledge

While acting: Is this leading to harm? If yes: give it up.

Reflect after acting: What happened? What did I learn?

keep put "what I learned" -t type=learning

Periodically: Run a full structured reflection:

keep reflect

This cycle — reflect, act, reflect — is the mirror teaching. Memory isn't storage; it's how you develop skillful judgment.


Working Session Pattern

Use the nowdoc as a scratchpad to track where you are in the work. This isn't enforced structure — it's a convention that helps you (and future agents) maintain perspective.

# 1. Starting work — check context and intentions
keep now                                    # What am I working on?

# 2. Update context as work evolves (tag by project and topic)
keep now "Diagnosing flaky test in auth module" -t project=myapp -t topic=testing
keep now "Found timing issue" -t project=myapp

# 3. Check previous context if needed
keep now -V 1                               # Previous version
keep now --history                          # List all versions
keep now -t project=myapp                   # Find recent now with project tag

# 4. Record learnings (cross-project knowledge uses topic only)
keep put "Flaky timing fix: mock time instead of real assertions" -t topic=testing -t type=learning

Key insight: The store remembers across sessions; working memory doesn't. When you resume, read context first. All updates create version history automatically.


Agent Handoff

Starting a session:

keep now                              # Current intentions with version history
keep now --history                    # How intentions evolved
keep find "recent work" --since P1D   # Last 24 hours

Ending a session:

keep now "Completed OAuth2 flow. Token refresh working. Next: add tests." -t topic=auth
keep move "auth-string" -t project=myapp  # Archive this string of work

Strings

As you work, keep now accumulates a string of versions — a trace of how intentions evolved. keep move lets you name and archive that string, making room for what's next. It requires -t (tag filter) or --only (tip only) to prevent accidental grab-all moves.

Snapshot before pivoting. When the conversation shifts topic, move what you have so far before moving on:

keep move "auth-string" -t project=myapp     # Archive the auth string
keep now "Starting on database migration"    # Fresh context for new work

Incremental archival. Move to the same name repeatedly — versions append, building a running log across sessions:

# Session 1
keep move "design-log" -t project=myapp
# Session 2 (more work on same project)
keep move "design-log" -t project=myapp      # Appends new versions

End-of-session archive. When a string of work is complete:

keep move "auth-string" -t project=myapp

Tag-filtered extraction. When a session mixes multiple projects, extract just the string you want:

keep move "frontend-work" -t project=frontend   # Leaves backend versions in now

The moved item is a full versioned document — browse with keep get name --history, navigate with -V 1, -V 2, etc.


Index Important Documents

Whenever you encounter documents important to the task, index them:

keep put "https://docs.example.com/auth" -t topic=auth -t project=myapp
keep put "file:///path/to/design.pdf" -t type=reference -t topic=architecture

Ask: what is this? Why is it important? Tag appropriately. Documents indexed during work become navigable knowledge.


Breakdowns as Learning

When the normal flow is interrupted — expected response doesn't come, ambiguity surfaces — an assumption has been revealed. First: complete the immediate conversation. Then record:

keep put "Assumed user wanted full rewrite. Actually: minimal patch." -t type=breakdown

Breakdowns are how agents learn.


Tracking Commitments

Use speech-act tags to make the commitment structure of work visible:

# Track promises
keep put "I'll fix the auth bug" -t act=commitment -t status=open -t project=myapp

# Track requests
keep put "Please review the PR" -t act=request -t status=open

# Query open work
keep list -t act=commitment -t status=open

# Close the loop
keep tag-update ID --tag status=fulfilled

See TAGGING.md for the full speech-act framework.


Data Model

An item has:

The full original document is not stored. Summaries are contextual — tags shape how new items are understood. See KEEP-PUT.md.


System Documents

Bundled system docs provide patterns and conventions, accessible via keep get:

IDWhat it provides
.domainsDomain-specific organization patterns
.conversationsConversation framework (action, possibility, clarification)
.tag/actSpeech-act categories
.tag/statusLifecycle states
.tag/projectProject tag conventions
.tag/topicTopic tag conventions

See Also

OpenClaw Integration

How to install and configure keep as an OpenClaw plugin.


Install keep

uv tool install keep-skill                    # API providers included
# Or: uv tool install 'keep-skill[local]'    # Local models, no API keys needed

Install the Plugin

openclaw plugins install -l $(keep config openclaw-plugin)
openclaw plugins enable keep
openclaw gateway restart

This installs the lightweight plugin from keep's package data directory.

What Gets Installed

Protocol blockAGENTS.md in your OpenClaw workspace gets the keep protocol block appended automatically (on any keep command, if AGENTS.md exists in the current directory).

Plugin hooks:

HookEventWhat it does
before_agent_startAgent turn beginsRuns keep now -n 10, injects output as prepended context
after_agent_stopAgent turn endsRuns keep now 'Session ended' to update intentions

The agent starts each turn knowing its current intentions, similar items, open commitments, and recent learnings.

Reinstall / Upgrade

After upgrading keep, reinstall the plugin:

openclaw plugins install -l $(keep config openclaw-plugin)
openclaw gateway restart

The plugin source lives at $(keep config openclaw-plugin) — this resolves to the openclaw-plugin/ directory inside the installed keep package.

Optional: Daily Reflection Cron

For automatic deep reflection, create a cron job:

openclaw cron add \
  --name keep-reflect \
  --cron "0 21 * * *" \
  --session isolated \
  --system-event "Reflect on this day with \`keep reflect\`. Follow the practice."

This runs in an isolated session at 9pm daily. Delivery is silent — the value is in what gets written to the store.

Provider Configuration

keep auto-detects AI providers from environment variables. Set one and go:

export OPENAI_API_KEY=...      # Simplest (handles both embeddings + summarization)
# Or: GEMINI_API_KEY=...       # Also does both
# Or: VOYAGE_API_KEY=... and ANTHROPIC_API_KEY=...  # Separate services

If Ollama is running locally, it's auto-detected with no configuration needed.

For local-only operation (no API keys): uv tool install 'keep-skill[local]'

See QUICKSTART.md for full provider options, model configuration, and troubleshooting.