Built-in State Docs
State docs are YAML documents stored as .state/* notes that drive keep's processing flows. Eleven ship by default: five simple operation wrappers (put, tag, delete, move, stats) and six processing/query flows. Each is loaded from disk on first use and can be edited in the store.
To view the current state docs: keep list .state --all
To reset to defaults: keep config --reset-system-docs
To view the state diagram: keep config --state-diagram
.state/after-write
Trigger: Every put() call.
Mode: match: all — all matching rules fire in parallel.
Path: Background (returns immediately, work runs async).
Runs post-write processing on new or updated items. The base doc defines core rules; additional rules are loaded from builtin fragments at .state/after-write/*.
Base rules:
| Rule | Condition | Action |
|---|---|---|
summary | Content exceeds max summary length and no summary exists | summarize |
described | Item has a URI, media content, and a media provider configured | describe |
Builtin fragments:
| Fragment | Rule | Condition | Action |
|---|---|---|---|
ocr | extracted | Item has _ocr_pages tag and a URI | ocr |
analyze | analyzed | Non-system item | analyze (decompose into parts) |
tag | tagged | Non-system item with content | tag (classify against .tag/* specs) |
links | linked | Non-system markdown item with content | extract_links (wiki/markdown links → references edges) |
System notes (IDs starting with .) skip analysis, tagging, and link extraction to avoid recursive processing. Fragments can be disabled individually (see Extending state docs below).
.state/get
Trigger: get() and now() calls.
Mode: match: all — all queries run in parallel.
Path: Synchronous (completes before returning to caller).
Assembles the display context shown when you retrieve a note. Three parallel queries:
| Rule | Action | Purpose |
|---|---|---|
similar | find (by similarity) | Semantically related items |
parts | find (by prefix) | Structural parts from analyze |
meta | resolve_meta | Meta-doc sections (learnings, todos, etc.) |
.state/find-deep
Trigger: find() with --deep flag.
Mode: match: sequence — rules evaluate top-to-bottom.
Path: Synchronous.
Searches, then follows edges from results to discover related items.
- Run semantic search with the query
- If no results, return immediately
- Traverse edges from search hits to find connected items
- Return combined results
.state/query-resolve
Thresholds for query resolution are configurable but not yet tuned
against real query patterns. Results are functional but may route
suboptimally in edge cases.
Trigger: Internal query resolution (multi-step search).
Mode: match: sequence — first matching rule wins.
Path: Synchronous, with tick budget.
The entry point for iterative query refinement. Searches, evaluates result quality, and routes:
| Condition | Action |
|---|---|
| High margin (clear winner) | Return done |
| Strong lineage signal | Re-search with dominant lineage tags, loop back |
| Low margin or high entropy | Transition to query-branch |
| Low entropy (tight cluster) | Widen search, loop back |
| No strong signal (fall-through) | Transition to query-explore |
Signals used: search.margin, search.entropy, search.lineage_strong, search.dominant_lineage_tags, search.top_facet_tags
.state/query-branch
Trigger: Transition from query-resolve when results are ambiguous.
Mode: match: all — parallel faceted searches.
Path: Synchronous, shares tick budget with caller.
Runs two parallel queries to break ambiguity:
| Rule | Purpose |
|---|---|
pivot1 | Facet-narrowed search using top tag facets |
bridge | Cross-facet bridging search |
After both complete:
- If either has high margin → return done
- If budget remains → transition back to
query-resolve - Otherwise → return
stopped: ambiguous
.state/query-explore
Trigger: Transition from query-resolve as last resort.
Mode: match: sequence.
Path: Synchronous, shares tick budget with caller.
Wider exploratory search when resolve and branch haven't produced high-confidence results.
- Broad search with expanded limit
- If high margin → return done
- If budget remains → even wider search, then transition back to
query-resolve - Otherwise → return
stopped: budget
Extending state docs
You can add processing steps to any state doc without editing the original. Create a child note under the state doc's path:
# Add a custom step to after-write
keep put --id .state/after-write/obsidian-links 'rules:
- when: "item.content_type == '\''text/markdown'\''"
id: obsidian-links
do: extract_links
with:
tag: references
create_targets: "true"'Child fragments are discovered automatically and merged into the base doc. Each fragment has a rules: list (same syntax as a full state doc) and an optional order: field.
Ordering
The order field controls where fragment rules are inserted:
| Value | Effect |
|---|---|
after (default) | Appended after all base rules |
before | Prepended before all base rules |
after:{rule_id} | Inserted after the named base rule |
before:{rule_id} | Inserted before the named base rule |
For match: all pipelines (like after-write), order rarely matters — all rules run in parallel. For match: sequence pipelines, order determines execution position.
Enabling and disabling
Fragments are active by default. To disable one without deleting it:
keep tag .state/after-write/obsidian-links active=false # disable
keep tag .state/after-write/obsidian-links -r active # re-enableListing fragments
keep list --prefix .state/after-write/ --allShows all fragments with their tags, so active/inactive status is visible at a glance.
Editing state docs
State docs are regular keep notes. To edit one:
keep get .state/after-write # View current content
keep put ".state/after-write" ... # Replace with new content
keep config --reset-system-docs # Restore all defaultsChanges take effect on the next flow invocation. The built-in versions are compiled into keep as a fallback — if a state doc is missing from the store, the bundled version is used automatically.
Simple operation wrappers
These are thin state docs that wrap a single action, providing named flow access to every store operation.
.state/put
Params: content, uri, id, tags, summary
Action: put — stores content or indexes a URI.
Output: {"id": "..."}
.state/tag
Params: id (single item) or items (list from search results), tags
Action: tag — applies explicit tags to one or more items.
Output: {"count": N, "ids": [...]}
.state/delete
Params: id
Action: delete — permanently removes an item.
Output: {"deleted": "id"}
.state/move
Params: name (target ID), source (default: "now"), tags (filter), only_current
Action: move — extracts matching versions from source into target.
Output: {"id": "...", "summary": "..."}
.state/stats
Params: top_k (default: 10)
Action: stats — computes store profile for query planning.
Output: {"total": N, "tags": {...}, "all_tags": [...], "dates": {...}, "structure": {...}}
See FLOW-ACTIONS.md for detailed output shapes.
See also
- FLOWS.md — How flows work, with narrative and diagram
- KEEP-FLOW.md — Running, resuming, and steering flows
- FLOW-ACTIONS.md — Available actions reference