Built-in State Docs

State docs are YAML documents stored as .state/* notes that drive keep's processing flows. Six ship by default. 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:

RuleConditionAction
summaryContent exceeds max summary length and no summary existssummarize
describedItem has a URI, media content, and a media provider configureddescribe

Builtin fragments:

FragmentRuleConditionAction
ocrextractedItem has _ocr_pages tag and a URIocr
analyzeanalyzedNon-system itemanalyze (decompose into parts)
tagtaggedNon-system item with contenttag (classify against .tag/* specs)
linkslinkedNon-system markdown item with contentextract_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-context

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:

RuleActionPurpose
similarfind (by similarity)Semantically related items
partsfind (by prefix)Structural parts from analyze
metaresolve_metaMeta-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.

  1. Run semantic search with the query
  2. If no results, return immediately
  3. Traverse edges from search hits to find connected items
  4. 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:

ConditionAction
High margin (clear winner)Return done
Strong lineage signalRe-search with dominant lineage tags, loop back
Low margin or high entropyTransition 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:

RulePurpose
pivot1Facet-narrowed search using top tag facets
bridgeCross-facet bridging search

After both complete:


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

  1. Broad search with expanded limit
  2. If high margin → return done
  3. If budget remains → even wider search, then transition back to query-resolve
  4. 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:

ValueEffect
after (default)Appended after all base rules
beforePrepended 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-enable

Listing fragments

keep list --prefix .state/after-write/ --all

Shows 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 defaults

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

See also