Skip to content

5.5 Graph-cached aggregates — cache: {kind: graph}

For an aggregate producer, cache: {kind: graph} makes the committed graph itself the cache tier: the first traversal collects the anchor’s neighborhood, LLM-reduces it, and persists the result as a real, committed, scope-stamped node; while the inputs are unchanged, a repeat traversal is a plain graph hit with zero model calls. The canonical use is the on-demand document summary:

producers/summaries.yml
- name: docSummary
kind: aggregate
edgeType: HAS_SUMMARY
collect:
targetLabel: Chunk
via: PART_OF
incoming: true # the neighbors point AT the anchor: (Chunk)-[:PART_OF]->(Document)
anchorLabel: Document
anchorKeyProperty: uri # PER-ANCHOR mode: each key is one document, owner-guarded
text: "{{ text }}"
reduce: { using: summarize, into: summary, args: ["Summarize this document faithfully."] }
identityField: id
anchorKeyField: docUri
cache: { kind: graph }

MATCH (d:Document …)-[:HAS_SUMMARY]->(s:Summary) RETURN s.summary then generates once per document and serves from the graph thereafter.

The rules, all engine-enforced:

  • Per-anchor collection. collect.anchorKeyProperty switches the aggregate from its default per-principal mode (the anchor is the acting principal’s own node when it has one; one reduction for the whole batch) to per-ANCHOR: each producer key names one anchor (matched on that property, guarded to the acting user’s own nodes), and each anchor’s neighborhood reduces independently. incoming: true flips the traversal for containment shapes like (Chunk)-[:PART_OF]->(Document).
  • Freshness is an INPUT HASH, not a clock. The persisted node carries a hash of the collected items plus the full reduce fingerprint (function, effective instruction, role, band). Edit the document — or the realm’s prompt, or the role — and exactly that node regenerates, in place. The re-check costs one graph read; the model runs only when something actually changed.
  • Wordcount BANDS. A query’s {ai: {wordcount: N}} quantizes to the nearest persisted band — gist (~40 words), standard (~200), long (~600) — and each band is its own committed node. The directive-free canonical is the standard band; a nearby request (250) is a hit on the same node. The band’s own target (never the raw request) folds into the instruction, so one band has one stable fingerprint.
  • Semantic steering stays transient. A hint, voice, realm.* parameter, or a model override makes the result a non-canonical artifact: it is reduced fresh for that query, returned with its own transient identity, and never persisted, never served from, and never overwrites the canonical node. {ai: {fresh: true}} bypasses the hash check but writes the regenerated canonical through.
  • An honest miss never earns a node. A reduction that answers with the no-answer sentinel stays transient and is retried on the next traversal — fabrication can never become durable.
  • Regeneration is progressive — for reductions whose results compose. When content changes, only the changed portions re-reduce: the reduction proceeds over content windows, unchanged windows reuse their previous partial results, and the partials combine into the answer — a re-reduction for prose (summarize, synthesize), a pure truth combination for a verdict (holds: any supporting window ⇒ true; a grounded no with no true ⇒ false; all silent ⇒ no answer), a union for a semantic filter (relevant). Appending to a large document costs a reduction proportional to the appended content, not the whole document; the same content always yields the same windows. A reduction that must read the whole group at once (classify, score, themes, cluster, extract, argmax) always re-reduces whole. {ai: {fresh: true}} regenerates everything. A window whose reduction honestly found nothing keeps that answer until its own content changes.
  • Node-only, scope-stamped writes. The engine commits only the node, stamped with immutable worldId, contextId, and access-policy revision. Two contexts summarizing an identical URI do not share a node unless policy explicitly promotes the result to WORLD visibility; two worlds never share one. The anchor edge is re-linked transiently per query. The node’s label is stamped by the engine from the join’s own target type — a realm never declares it, so it can never drift.

Cost intuition: the expensive thing (the reduction) runs once per (worldId, contextId, access-policy revision, anchor, band) and again only on change — and on change, only over the windows that changed; everything else — the freshness probe, the hit path, the re-link — is indexed graph reads.