Flow Actions

Actions are the operations available inside state doc flows. Each action searches, reads, or enriches items in the store.

State docs invoke actions via do: <name>. Parameters come from the rule's with: block. The runtime calls the action, receives an output dict, and makes the output available to subsequent rules via {rule_id.field} template references.

Search actions

find

Search the store by semantic query, or list items by tags/prefix.

- id: search
  do: find
  with:
    query: "search terms"
    tags: { topic: "auth" }
    bias: { "now": 0, "preferred-doc": 2.0 }
    limit: 10
    since: "P7D"
    offset: 0
ParamTypeDefaultDescription
querystrSearch query text (semantic + full-text)
similar_tostrItem ID to find similar items to
tagsdictTag filter — only items matching all specified tags
biasdictScore multiplier per item ID: 0=exclude, 1=neutral, >1=boost
prefixstrID prefix filter (e.g. "doc@P" for parts)
limitint10Maximum results
sincestrISO duration (P7D) or date — items updated since
untilstrISO duration or date — items updated before
offsetint0Skip first N results (pagination)
include_hiddenboolfalseInclude system notes (dot-prefix IDs)
order_bystrupdatedSort order for list mode: updated, accessed, created

At least one of query, similar_to, tags, prefix, or since is required. query and similar_to are mutually exclusive.

Output:

{
    "results": [
        {"id": "...", "summary": "...", "tags": {...}, "score": 0.87},
        ...
    ],
    "count": 5,
    # Statistics (added by runtime for search-mode queries):
    "margin": 0.15,       # score gap between #1 and #2 (0=tied, 1=dominant)
    "entropy": 0.7,       # score distribution spread (0=peaked, 1=uniform)
    "lineage_strong": 0.0,
    "dominant_lineage_tags": null,
    "top_facet_tags": [{"topic": "auth"}]
}

Access via {search.results}, {search.margin}, {search.entropy}, etc.

traverse

Follow edges from items to discover related items.

- id: related
  do: traverse
  with:
    items: "{search.results}"
    limit: 5
ParamTypeDefaultDescription
itemslistrequiredItems to follow edges from
limitint5Max related items per source

Output: {"groups": {"source-id": [{id, summary, tags}, ...], ...}, "count": N}

Context actions

get

Retrieve a specific item by ID.

- id: target
  do: get
  with:
    id: "item-id"
ParamTypeDefaultDescription
idstrrequiredItem identifier

Output: {"id": "...", "summary": "...", "tags": {...}} or {} if not found.

list_parts

List decomposed parts (structural children) of an item.

- id: parts
  do: list_parts
  with:
    id: "{params.item_id}"
    limit: 5
ParamTypeDefaultDescription
idstrrequiredParent item ID
limitint5Maximum parts to return

Output: {"results": [{id, summary, tags, score}, ...], "count": N}

list_versions

List version history for an item.

- id: versions
  do: list_versions
  with:
    id: "{params.item_id}"
    limit: 3
ParamTypeDefaultDescription
idstrrequiredItem identifier
limitint3Maximum versions to return

Output: {"versions": [{"offset": 1, "summary": "...", "date": "2026-03-15"}, ...], "count": N}

resolve_meta

Resolve meta-document definitions against a target item. Meta-docs (.meta/*) define tag-based queries; this evaluates them against the target item's tags.

- id: meta
  do: resolve_meta
  with:
    item_id: "{params.item_id}"
    limit: 3
ParamTypeDefaultDescription
item_idstrrequiredItem to resolve meta-docs for
limitint3Max items per meta-doc section

Output: {"sections": {"learnings": [{id, summary, tags}, ...], "todo": [...]}, "count": N}

resolve_edges

Resolve forward and inverse edges for an item.

- id: edges
  do: resolve_edges
  with:
    id: "{params.item_id}"
    limit: 5
ParamTypeDefaultDescription
idstrrequiredItem identifier
limitint5Max edges per predicate

Output: {"edges": {"references": [{id, summary, predicate, date}, ...], ...}, "count": N}

Mutation actions

move

Move versions from a source item into a named target.

- id: moved
  do: move
  with:
    name: "project-notes"
    source: "now"
    tags: { project: "myapp" }
    only_current: false
ParamTypeDefaultDescription
namestrrequiredTarget item ID (created if new)
sourcestrnowSource item to extract from
tagsdictOnly extract versions matching these tags
only_currentboolfalseOnly move the current version, not history

Output: {"id": "project-notes", "summary": "..."}

delete

Permanently delete an item and its version history.

- do: delete
  with:
    id: "old-item"
ParamTypeDefaultDescription
idstrrequiredItem to delete

Output: {"deleted": "old-item"}

Processing actions

These run during after-write flows to enrich newly stored items.

summarize

Generate a summary for a target item using the configured LLM provider.

- do: summarize
  with:
    item_id: "{params.item_id}"
ParamTypeDefaultDescription
item_idstrrequiredItem to summarize

Output: {"summary": "..."} Mutations: [{"op": "set_summary", "summary": "..."}]

tag

Set explicit tags on one or more items. Accepts a single item ID or a list of search results.

# Single item
- do: tag
  with:
    id: "my-item"
    tags: { project: "security-audit" }

# Bulk: tag all search results
- do: tag
  with:
    items: "{search.results}"
    tags: { reviewed: "true" }
ParamTypeDefaultDescription
idstrSingle item ID to tag
itemslistList of items (result dicts or IDs) to tag
tagsdictrequiredTags to apply

One of id or items is required.

Output: {"count": N, "ids": ["..."], "mutations": [...]} Mutations: [{"op": "set_tags", "target": "id", "tags": {...}}, ...]

auto_tag

Classify a target item against tag specs (.tag/* docs) in the store using an LLM. Used by the after-write flow for automatic classification.

- do: auto_tag
  with:
    item_id: "{params.item_id}"

What gets tagged depends on which .tag/* specs exist. Users add new tagging vocabularies by creating spec docs.

Output: {"tags": {"act": "commitment", "status": "open", ...}} Mutations: [{"op": "set_tags", "tags": {...}}]

analyze

Decompose a target item into structural parts.

- do: analyze
  with:
    item_id: "{params.item_id}"

Output: {"parts": [{summary, content, tags, part_num}, ...]} Mutations: [{"op": "put_item", "id": "item@p1", ...}, ...]

describe

Extract text description from images or media content.

- do: describe
  with:
    item_id: "{params.item_id}"

resolve_duplicates

Detect items with identical content and link them via edge tags.

- id: resolve-duplicates
  do: resolve_duplicates
  with:
    tag: duplicates

Output: {"duplicates": ["id1", "id2", ...]} or {"skipped": true, "reason": "..."} Mutations: [{"op": "set_tags", "tags": {"duplicates": [...]}}]

Extract markdown links from content and create edge relationships.

- do: extract_links
  with:
    tag: references
    create_targets: "true"

generate

Raw LLM prompt — the escape hatch for custom processing.

- do: generate
  with:
    system: "You are a classifier."
    user: "{item.content}"
    max_tokens: 4096
    format: json
ParamTypeDefaultDescription
systemstr""System prompt
userstr""User prompt
max_tokensint4096Maximum tokens in response
formatstrIf "json", parse response as structured data

Output: {"text": "..."} — with parsed fields merged in when format: json.

ocr

Extract text from images or scanned PDF pages.

- do: ocr
  with:
    item_id: "{params.item_id}"
ParamTypeDefaultDescription
item_idstrrequiredItem to OCR
pageslistZero-indexed page numbers (PDF only)

Output: {"text": "extracted...", "pages_processed": N} Mutations: [{"op": "upsert_item", "content": "..."}]

resolve_stubs

Resolve auto-vivified stub items by fetching content from their URI.

- do: resolve_stubs

Runs during after-write for URI-backed items that were auto-created as stubs.

Store profiling

stats

Compute store profile statistics for query planning. Returns tag distributions, date histograms, structural counts, and edge fan-in/fan-out.

- id: profile
  do: stats
  with:
    top_k: 10
ParamTypeDefaultDescription
top_kint10Number of top tags to detail, and top values per tag

Output:

{
    "total": 2672,
    "tags": {                           # Detailed stats for top_k tag keys
        "project": {
            "count": 248, "distinct": 9, "unique": 4,
            "coverage": 0.09, "categorical": true, "edge": false,
            "top": {"keep": 226, "superpowers": 6}
        },
        "references": {
            "count": 161, "distinct": 1017, "unique": 947,
            "coverage": 0.06, "categorical": false, "edge": true,
            "fan_out": {"avg": 7.4, "max": 35},
            "fan_in": {"avg": 1.2, "max": 17},
            "top": {"REFERENCE.md": 17, "TAGGING.md": 11}
        },
    },
    "all_tags": ["topic", "type", "project", "act", ...],  # All tag key names
    "dates": {
        "created": {"min": "2024-05-06", "max": "2026-03-15",
                     "annual": {"2026": 2099}, "monthly": {"2026-03": 1796},
                     "daily": {"2026-03-12": 1284}},
        "updated": {...},
        "accessed": {...},
    },
    "structure": {
        "sources": {"inline": 1579, "uri": 824},
        "with_versions": 65,
        "versions_len": {"1": 44, "2": 9, "3": 2},
        "with_parts": 0,
        "parts_len": {},
        "edges": {
            "forward": {"references": 1184, "informs": 12},
            "inverse": {"referenced_by": 1184, "informed_by": 12},
            "distinct_sources": 173, "distinct_targets": 1020
        }
    }
}

Key per-tag-key fields:

Result statistics

When the find action runs in search mode, the runtime computes statistics from the result set:

StatisticDescription
marginScore gap between #1 and #2 (0=tied, 1=clear winner)
entropyScore distribution spread (0=peaked at one result, 1=uniform)
lineage_strongVersion/part lineage concentration
dominant_lineage_tagsTags from the dominant lineage group
top_facet_tagsMost common non-system tag constraints

These are available as {rule_id.margin}, {rule_id.entropy}, etc. on the rule that invoked find. List-mode queries (no search scores) produce null for score-based statistics.

Custom actions

Actions live in keep/actions/. To add a new action:

# keep/actions/my_action.py
from keep.actions import action

@action(id="my_action")
class MyAction:
    def run(self, params, context):
        item = context.get(params["id"])
        return {"result": "..."}

Actions are auto-discovered on startup. Use them in state docs with do: my_action.

See also