Tagging
Tags are key-value pairs attached to every item. They enable filtering, organization, and commitment tracking.
Multiple values per key are allowed. Setting an additional value for the same key adds it (deduplicated), rather than overwriting. Each key supports up to 512 distinct values.
Some tags are singular — new values replace old ones instead of accumulating. The act and status tags are singular: keep tag ID --tag status=fulfilled on a doc with status=open results in only fulfilled, not both.
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 ID --tag key=value # Add/update tag on existing item
keep tag ID --remove key # Remove a tag
keep tag ID1 ID2 --tag status=done # Tag multiple itemsTag merge order
When indexing documents, tags are merged in this order (later wins):
- Existing tags — preserved from previous version
- Config tags — from
[tags]section inkeep.toml - Environment tags — from
KEEP_TAG_*variables - User tags — passed via
-ton 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=aliceConfig-based default tags
Add a [tags] section to keep.toml:
[tags]
project = "my-project"
owner = "alice"
required = ["user"] # Enforce required tags on put()
namespace_keys = ["category", "user"] # LangGraph namespace mappingThe required list enforces that specified tag keys must be present on every put() call. The namespace_keys list configures how LangGraph namespace components map to tag names — see LANGCHAIN-INTEGRATION.md.
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 tagListing tags
keep list --tags= # List all distinct tag keys
keep list --tags=project # List all values for 'project' tagOrganizing by project and topic
Two tags help organize work across boundaries:
| Tag | Scope | Examples |
|---|---|---|
project | Bounded work context | myapp, api-v2, migration |
topic | Cross-project subject area | auth, 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=authFor more on these conventions: keep get .tag/project and keep get .tag/topic.
For domain-specific organization patterns: keep get .domains.
Tag-based isolation
Tags on find are pre-filters on the vector search, not post-filters. When you search with -t user=alice, the similarity search only considers notes tagged user=alice — you get the best matches within that scope, not global results filtered afterward. This makes tags suitable for data isolation.
Pattern: scoped search with required_tags
# keep.toml
[tags]
required = ["user"]With this config, every put() must include a user tag (or it raises ValueError). Pair it with tag filters on every search to get per-user isolation:
kp.put("my note", tags={"user": "alice"}) # enforced by required_tags
kp.find("auth", tags={"user": "alice"}) # only searches alice's noteskeep put "my note" -t user=alice
keep find "auth" -t user=alice # scoped to aliceNote: required_tags enforces tags on writes only. The caller is responsible for passing the same tag filter on reads. Without the filter, find searches across all notes.
This pattern works for any isolation key — user, project, tenant, session, etc. The LangChain integration automates this: namespace components become tags on both writes and searches.
Speech-act tags
Two tags — act and status — make the commitment structure of work visible. These are constrained tags: only pre-defined values are accepted.
# 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 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=architectureFor inline reference: keep get .tag/act and keep get .tag/status.
Tag descriptions (.tag/*)
Every tag key can have a description document at .tag/KEY. These are installed automatically on first use and serve as living documentation:
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 conventionsTag descriptions have their own summary and embedding, so they participate in semantic search. They also contain structured information that the analyzer uses when auto-tagging parts during keep analyze.
Constrained values
Some tags are constrained — only pre-defined values are accepted. When a .tag/KEY document has _constrained: true in its tags, keep validates that every value you assign has a corresponding sub-document at .tag/KEY/VALUE.
keep put "note" -t act=commitment # ✓ .tag/act/commitment exists
keep put "note" -t act=blurb # ✗ ValueError: no .tag/act/blurbThe error message lists valid values:
Invalid value for constrained tag 'act': 'blurb'. Valid values: assertion, assessment, commitment, declaration, offer, requestYou can extend constrained tags by creating new sub-documents:
keep put "Active work in progress." --id .tag/status/working
# Now status=working is acceptedSingular values
Some tags are singular — at most one value is allowed per key. When a .tag/KEY document has _singular: true in its tags, new values replace old ones instead of accumulating via set-union.
keep put "fix auth" -t status=open -t act=commitment
keep tag ID --tag status=fulfilled # replaces open → fulfilled
keep get ID # status: fulfilled (not [open, fulfilled])Providing multiple values for a singular key in one call is an error:
keep tag ID --tag status=open,fulfilled # ✗ ValueError: singular tagA tag can be both _constrained and _singular. The act and status tags are both — values are validated against sub-documents and only one value is kept.
To make a custom tag singular, set _singular: true on its tagdoc:
keep put "$(cat <<'EOF'
---
tags:
_singular: "true"
---
# Tag: priority
Priority level. Only one value at a time.
EOF
)" --id .tag/priorityBundled tag descriptions
keep ships with these tag descriptions:
| Tag | Constrained | Singular | Values | Purpose |
|---|---|---|---|---|
act | Yes | Yes | commitment, request, offer, assertion, assessment, declaration | Speech-act category (what the speaker is doing) |
status | Yes | Yes | open, blocked, fulfilled, declined, withdrawn, renegotiated | Lifecycle state of commitments/requests/offers |
type | No | No | learning, breakdown, gotcha, reference, teaching, meeting, pattern, possibility, decision | Content classification |
project | No | No | (user-defined) | Bounded work context |
topic | No | No | (user-defined) | Cross-cutting subject area |
Constrained tags (act, status) also have individual sub-documents (e.g., .tag/act/commitment, .tag/status/open) that describe each value in detail.
Unconstrained tags (type, project, topic) accept any value. Their descriptions document conventions but don't enforce them.
Some tags also define edges — navigable relationships between documents. See EDGE-TAGS.md for details.
How tag docs are injected into LLM prompts
Tag descriptions feed into analysis through two independent paths:
1. Guide context (all tags)
When you pass -t to keep analyze, the full content of each .tag/KEY document is prepended to the analysis prompt as context. This guides the LLM's decomposition — how it splits content into parts and what boundaries it recognizes.
keep analyze doc:1 -t topic -t projectThis fetches .tag/topic and .tag/project descriptions and includes them in the analysis prompt, producing better part boundaries and more consistent tagging. Any tag doc participates in guide context, whether constrained or not.
2. Classification (constrained tags only)
After decomposition, a second LLM pass classifies each part. The TagClassifier loads all constrained tag descriptions (those with _constrained: true) and assembles a classification prompt from their ## Prompt sections:
- The parent doc's
## Promptsection (e.g., from.tag/act) provides overall guidance for the tag key - Each value sub-doc's
## Promptsection (e.g., from.tag/act/commitment) describes when to assign that specific value
The classifier assigns tags only when confidence exceeds the threshold (default 0.7). Tags without a ## Prompt section use their full content as a fallback description.
To customize classification behavior, edit the ## Prompt section in a tag doc — the classifier only sees ## Prompt content, not the surrounding documentation.
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.list_items(tags={"project": "myapp"}) # Exact key=value match
kp.list_items(tag_keys=["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
- META-TAGS.md — Contextual queries (
.meta/*) - PROMPTS.md — Prompts for summarization, analysis, and agent workflows
- SYSTEM-TAGS.md — Auto-managed system tags
- KEEP-LIST.md — List and filter by tags
- KEEP-FIND.md — Search with tag filters
- LANGCHAIN-INTEGRATION.md — LangChain/LangGraph namespace-to-tag mapping
- REFERENCE.md — Quick reference index