Skip to content

5.3 Producer kinds

kind fetch keyed by
remote (alias api) a gateway op — realm handler or learned REST API the anchor’s id/email/login (list, string-template, or path-param mode)
sql a relational TABLE joined by a column, SCANNED (scan:) ordered and bounded, or a stored PROCEDURE’s result set (procedure:) — the SELECT is generated, never authored, so the governance: grammar (§5.15) is enforceable by construction: governed exposure compiles into the column list, row-level where: predicates (:userId binds the acting user) inject into every statement. A procedure is read as a read — no readOnly: false — and rolled back to keep it one (§5.15.1) the anchor key batch, bound into WHERE <keyColumn> IN (…); rows echo the key column. A scan: producer takes no keys: the pinned value chooses the ordering column and is echoed under echoKeyAs. A procedure: producer is called once per key, and echoes it
compute an in-process function over the keys (scores, rollups, synthesis) the anchor key; no external I/O
vector top-k semantic relevance to the anchor’s text — a fused semantic + lexical retrieval, ranked as one list (§6.6) nothing — relevance is the join (§6)
keyword top-k lexical (fulltext, exact-token) match to the anchor’s text — the honest fit for “MENTIONS <term>” nothing — same relevance contract as vector, only the mode differs (§6.6)
agentic-rag a bounded LLM retrieval loop over the same index: reformulates, runs both modes, reads further into inconclusive candidates, returns only documents it judges fit the edge’s intent brief nothing — relevance as a judgment (§6.6); EXPENSIVE, select explicitly
remote-search top-k lexical match via the REMOTE store’s OWN search API (a gateway op with {query} substituted per anchor wherever it sits in args, nested maps and lists included — e.g. Drive fullText contains, or a stored query’s params: {q: "{query}"}); live, nothing ingested nothing — same relevance contract as keyword, but the source searches itself; per-match mode:'keyword'/rank on the edge, score is a neutral 1.0 (matched, not similarity) unless recordScoreField names the record field carrying the store’s OWN score, which then rides the edge as score unrescaled — a record without it keeps 1.0. compute: maps the record onto the type’s properties (an expression over r), as on every kind that returns a source’s records
generative an LLM invents plausible records (“suggest things like X”), each resolved onto the spine via resolveVia; demand-driven (re-probes with a growing exclusion until enough survive) the anchor’s name/text, batched into ONE prompt
aggregate gathers the anchor’s connected neighborhood and LLM-reduces it to ONE record (a taste summary, a digest) the anchor’s identity; one record per anchor
extract gathers the anchor’s neighborhood and extracts typed records from it — lazy ENTITIES, committed with real containment on first traversal (§5.6). cardinality: chooses fan-out (many records) or reshape (ONE record per anchor — §5.6.1) the anchor key (per-anchor collect); many records per anchor, or exactly one
tabular a published CSV / TSV / XLSX / XML file (optionally a zip of XML parts), downloaded lazily, cached deployment-wide, and joined on one of its COLUMNS (§5.8) the value of keyColumn, compared to the anchor key under keyMatch
feed an RSS/Atom search feed — one search per anchor key, each item a record; optionally FOLLOWING each item’s page for phrase-anchored excerpts (§5.10) the anchor key, substituted into the feed URL
sparql a SPARQL 1.1 endpoint (Wikidata, UniProt, an enterprise triplestore) queried with an authored SELECT template; the whole key batch lands in one VALUES clause (§5.14) the anchor key, serialized into the query’s {{keys}} token; rows echo it under keyVar
cypher a remote openCypher graph (Neo4j, Memgraph, Neptune — any engine speaking Bolt or openCypher-over-HTTP) queried with an authored read query; structurally read-only, governed by the shared governance: grammar (§5.15) the anchor key batch, bound to $keys; rows echo it via the query’s own RETURN
jar a Java jar’s classes, read from the jar when a query reaches it — packages, types, fields, methods, field accesses and calls, with source lines; nothing ingested or stored (§5.18) a jar path, or <jar path>!<value> for a property the producer selects by
source-tree a project’s source in fourteen languages, parsed when a query reaches it — files, declarations with their annotations, imports, name-matched calls and the type names each file uses, in the standard model’s code columns, and any syntax a realm’s own query captures (§5.21) a project’s location: an https git URL (@ref optional) or a folder
stacktrace the Java stack trace in a piece of text — its throwables and frames, each frame naming the jar that compiled its class with that jar’s keys (§5.20) the text: a pasted trace, or a log event’s content
files the world’s local files, walked live under data/ roots — one row per file, per line (unit: line) or per log event (unit: event, §5.20.1) the owner key; free-text terms with emit: search
elasticsearch (alias elastic) an Elasticsearch index or alias retrieved by relevance — lexical, semantic, or the two fused cluster-side in one search — with index / document id / score / mode on the edge, so every hit is citable (§5.16) nothing — relevance is the join (§6); the anchor’s text is the query
captured handler (no kind) an approved handler in the realm’s own captured installation, declared as a version-1 producers/ file. It may walk its own cursor (page:) and receive =/IN filters as list arguments (pushdown:). No cache (captured handler producers) the anchor key batch, as a list under keyArgument; each record carries its key under recordKeyField

All producers honour the batch contract (all keys at once, never N+1) and an orthogonal cache: policy: none (alias fresh, the default — read live every query), ttl (a window in seconds, with an opt-in negativeTtlSeconds for misses), immutable (reference data, held until the process is restarted or maintenance clears it), and graph for aggregates and extraction (§5.5/§5.6, where the committed graph itself is the cache tier). WHO a kept record is kept for — the principal, the deployment, or the deployment’s organisation, according to the label the fetch targets — is §15.4, and it is what decides whether a second asker pays.

negativeTtlSeconds is opt-in because an empty is not always durable. Under ttl alone only RECORDS are kept: a key that answered empty is asked again on the next query, for ever. That is the right default for a source over data the owner is still filling, where empty means “nothing yet” and keeping it would hide everything they add until the window closed. It is the wrong default for a published register, where a point with no value does not acquire one this afternoon — so declaring negativeTtlSeconds says “an empty answer from this source is a real, durable answer”, and the empties are kept for that window, on their own clock rather than the record window’s. The engine says when the declaration is missing and the re-buy is real: a fetch whose keys mostly answered empty carries an EMPTY_NOT_KEPT note naming the count and printing the declaration. It is a note about COST and never about the rows, and it is not raised after a fetch that was capped, refused or throttled — an empty there may be a key the source never reached, which no window should pin.

kind: session parses and is reported by the catalog, but the per-AgentProcess lifetime it names is not wired — it behaves as none, and validating a realm that declares it raises a warning saying so. Use ttl for a window you can state, or immutable for data that does not change.

A remote producer with page-number paging starts at page 1 unless its declaration sets a non-negative startPage. This lets a realm match a zero-based source without changing existing one-based realms:

paging:
style: page
param: page
sizeParam: size
startPage: 0
size: 200
maxPages: 2

This example fetches pages 0 and 1. maxPages always counts pages fetched, not the numeric value of the final page. A short page ends the walk normally under either convention; reaching maxPages on a full page produces the same truncation diagnostic. Omitting startPage fetches pages 1, 2, and so on. A negative value is rejected. Cursor paging does not send or interpret startPage.

total names where a page says how many records the source holds in all (a JSONPath, e.g. data.meta.all_count). With it, a page-number or offset walk reads page one, learns how many pages remain, and fetches them concurrently rather than one after another — a 29-page sweep costs about one page’s latency instead of 29. Everything a serial walk promises still holds: records come back in page order, a pushed-down LIMIT stops at the same page, a page that fails ends the walk there with the pages before it and a warning, maxPages still caps and still warns when it cuts, and the declared cost.rate still paces every call. Without total (or when the page does not carry it) the walk is serial, because it cannot know how many pages there are until a short one arrives. Cursor paging is always serial.

paging: { style: page, param: page, size: 25, maxPages: 200, total: data.meta.all_count }

The LLM-backed kinds (generative’s generator:, aggregate’s reduce:, extract’s extract:) take optional per-edge tuning — role:, a portable, world-defined model role id such as chat_cheap; never a concrete model name, which stays an ops concern. A query can override it for one fetch with the ai.model edge directive (§7.2). These blocks take no sampling temperature: current models ignore or reject one, so the host sets none, and a temperature: a realm still declares is ignored with a warning.

5.3.1 Cost discipline for LLM-backed producers — defaultWant and tools:

Section titled “5.3.1 Cost discipline for LLM-backed producers — defaultWant and tools:”

Two producer knobs dominate the wall-clock of a generative fetch, and both default in the expensive direction if authored carelessly:

  • Size defaultWant to the edge’s real consumers, not to abundance. The demand loop runs a FULL LLM generation round per iteration, sequentially, re-probing with a growing exclusion set until want survivors exist — and steering hints, confidence floors, and spine-resolution misses all shrink each round’s survivor yield. A defaultWant: 25 behind queries that LIMIT 5 can burn ten-plus sequential LLM calls chasing survivors nobody will see (observed: 12 rounds / 43 seconds from 2 anchors under a steering hint). Set it just above the largest LIMIT the edge’s saved views and skill queries actually use; a filtered/steered run then converges in a round or two.

  • Scope tools: to the narrowest search surface that can answer. A generator’s tools: list is a capability grant, and every granted tool is an invitation the model will eventually accept: the broad web group attaches every web-ish tool in the world (brave AND wikipedia AND generic search), and a producer hunting one known site will wander into the irrelevant ones (observed: encyclopedia summary lookups with another site’s page titles — guaranteed misses — plus retry churn). Name the concrete tool that serves the producer’s source (tools: [brave]); reach for the broad group only when the producer genuinely cannot know where its answer lives.