MemQL Vocabulary
Every named thing in the MemQL language -- construct, annotation, keyword, operator, field type, function and builtin -- with how it is written and what it means. It is generated from the tables that own those names, so a description here cannot disagree with the engine that reads them.
It is the companion to the grammar: the grammar says what may be written, this says what each written thing does. Together they are what a model is given before it is asked to write MemQL.
Edition 2026, grammar version 2026.09-dsl-v1-followups-9f344ecf.
| Section | Entries |
|---|---|
| Constructs | 18 |
| Annotations | 76 |
| Keywords | 37 |
| Operators | 22 |
| Field types | 12 |
| Functions and methods | 46 |
| Builtins | 10 |
| Total | 221 |
Constructs
The words a top-level declaration opens with. One section of the grammar per entry.
| Name | Written | What it means |
|---|---|---|
action | action <name> { ... } | Authored external-side-effect primitive (behavioral-constructs ADR §2.3, construct-invocation ADR Decision 3): performs exactly ONE external capability (shell.* / fs.* / http.* / integration.* / mcp.*) on a surface and never touches the graph. Body is an args { } schema plus a SINGLE capability <verb>(...) call -- no body{}, no return; the @sideEffect class lives on the capability, not the action. Invoked from an automation as action <name>(args...); replays token-free (fingerprint-verified) on identical input. |
automation | automation <name> { ... } | Event-, schedule-, or before-write-triggered body (via @trigger). A before-write body adjusts declared fields with row.<field> = <expression> before persistence and may call only transitively read-only logic or queries. Its statements run in the order written and call every construct kind; the triggering event's payload is bound into its args { } block. |
builtin | builtin <name> { ... } | Go-backed executor exposed as a DSL function. Body is the input schema; @executor names the integration.X.Y implementation. |
capability | capability <dotted-name> { ... } | Surface-backed external capability verb (construct-invocation ADR Decision 4): declared like a typed, side-effect-classified builtin with NO body. Namespaced/dotted name (fs.* / shell.* / http.* / integration.* / mcp.*). @sideEffect("read"|"write"|"exec") -- the UNSPOOFABLE risk class -- lives HERE, not on the action that invokes it (ADR §7). Imported at the verb level (use capabilities.<ns>.{ verb }) and called via capability verb(args). Body: an optional args{} input schema. |
concept | concept <name> { ... } | Define a node schema (the base of the dependency tree). Body is a field list; cross-concept links via @relationship. |
logic | logic <name> { ... } | A procedure that decides. args { } declares its inputs; its statements follow in the order they run (x := <kind> name(...), if, for, switch, parallel) and end with return <expr>. It calls queries, mutations, logic and builtins; publishing, calling an automation and dispatching an action are an automation's (D14). |
mutation | mutation <concept-name> <name> { ... } | Write function on a bound concept: declared mutation <Concept> <name> with exactly one insert{} OR update{} block, and called mutation <name>(...) -- the one word declares and calls (D13). |
policy | policy <name> { ... } | AI provider-selection record (empty body): an ordered chain of @primary / @fallback entries -- a provider name, a fleet: / app: / federation: selector, or policy:<name> -- consumed by the AI Router. (Caller-context checks use specs, not policies.) |
prompt | prompt <name> { ... } | AI prompt template with an input schema and a default provider. Body is a bare input-schema field list; @templateFile points at the .tmpl. |
provider | provider <name> { ... } | AI provider configuration (vendor + model + auth). @base providers carry auth+type; children @extends a base. Body: params{} / auth{}. |
query | query <concept-name> <name> { ... } | Read function: stitch a bound concept + filter (specs) + projection (shape) + args into a typed read. Struct form query <Concept> <name>. |
rule | rule <name> { ... } | Routing rule (empty body): maps a call's declared metadata to a policy. @when(...) states the closed condition set, @policy names the chain, @level overrides the call's level, @precedence orders the set (highest first) and @onUnavailable says whether an exhausted chain degrades or parks. |
seed | seed <concept-name> <name> { ... } | Seed an initial row for a bound concept. Struct form seed <Concept> <name>. |
shape | shape [ <concept-name> ] <name> { ... } | Reusable field projection. @row projects a concept payload/intrinsics (signature shape <Concept> <name>); @actor projects the auth envelope; both = mixed. Body is a path list; there is no composition verb. |
spec | spec <bound-name> <name> = <lambda> | Atomic boolean predicate over one bound concept or shape: spec <bound> <name> = row => <predicate>, applied as name(row). Over an @actor shape the parameter is actor and the predicate evaluates in process; over a row it pushes down to SQL. |
tool | tool <name> { ... } | AI-callable tool definition. Body is the input-schema field list; @handler wires it to a query/function. |
trait | trait <name> = <lambda> | Concept-agnostic boolean predicate (same runtime contract as spec): trait <name> = row => <predicate>, applied as name(row) to a row of any concept. |
use | use <dotted-path> "{" <import-name> { "," <import-name> } "}" | File-top cross-file import: use <domain>.<construct>.{ a, b } pulls named constructs (concepts/shapes/specs/...) into local scope. |
Annotations
The @name directives a construct or a field carries. WHERE each is legal, and in which argument form, is the attribute matrix.
| Name | Written | What it means |
|---|---|---|
@actor | @actor | Declares that the body reads the authenticated actor (actor.userId, actor.role, actor.identityId, actor.isClusterOwner, actor.primaryEmail, actor.now). The actor-binding load rule refuses a body that reads actor.* without it (memql#2621). Written as no arguments; legal on automation, logic, mutation, query and shape. |
@addToSet | @addToSet("disabledDeployables") | On an update mutation: treat the named array-typed payload fields as SETS and UNION the written elements into the stored array -- deduped, existing order kept, new members appended in the order given. The membership half @appendFields is not: append is not deduped and has no counterpart that removes, so a toggle built on it duplicates on a double click. Pairs with @removeFromSet. Format: @addToSet("disabledDeployables"). See memql#4951. Both follow the same rules: - Only an update mutation may carry it; on an insert it is refused at load, because an insert has no stored set to change. - A field absent from the write is untouched: the stored array survives the read-merge. - Members compare by their rendered form, so 1 and "1" are one member. A stored number comes back from JSON as a float and its string spelling as a string, and keeping both would be a set that quietly grew. - A stored value that is not an array reads as empty, and a scalar written where an array is expected is one member, as with @appendFields. - A field named by two of @appendFields, @addToSet and @removeFromSet is refused at load: each rewrites the same key, so which one won would depend on the executor's order rather than on anything the mutation says. Written as one or more strings; legal on mutation. |
@alias | @alias("memqlVersion") | Additional name the builtin is registered under. Repeatable. Written as one string; legal on builtin. |
@allowedRoles | @allowedRoles("assistant", "specialist") | DEPRECATED (rule deprecated_allowed_roles, memql#5438): write @requiresAgentRole for an agent kind and @requiresRank for a person's role -- memqlmigrate --rewrite=allowed-roles rewrites it. It compares ONE role string whose meaning depends on who is calling: the acting agent's role (assistant / specialist) in an agent's tool loop, and the person's cluster role over MCP. Two axes in one list is why it is leaving: neither list can be read without knowing which path the call arrives by. Until its window closes it keeps that behaviour, on every path a tool is listed or called by -- read by component/memql/tool_gate.go through Tool.IsAllowedForRole (tool_types.go). Written as one or more strings; legal on tool. |
@appendFields | @appendFields("attachmentIds") | On an update mutation: append the named array-typed payload fields' elements to the stored array instead of replacing it wholesale, so a single-writer mutation can accumulate list items (e.g. attach one id). Format: @appendFields("attachmentIds"). Written as one or more strings; legal on mutation. |
@args | @args(profile="object") | Parse-time argument contract for builtin functions. Written as keyword arguments; legal on builtin. |
@autoInjected | @autoInjected | The field is filled by the runtime rather than the model: it is kept out of the input schema the model sees and stamped server-side (the calling agent's id, for example). Written as no arguments; legal on tool field. |
@base | @base | Mark a vendor-level base provider (auth + type only). Written as no arguments; legal on provider. |
@cache | @cache(300) | Override the result-cache TTL for the query. Preferred form (#2618): @cache(300) -- the single ttl arg makes position unambiguous. The keyword form @cache(ttl="300") keeps parsing. Pure reads cache BY DEFAULT (a 60s backstop) without this annotation; @cache sets a different TTL, longer or shorter. @cache(0) is the explicit "never cache" opt-out -- @nocache was retired with it (epic memql#5375), and one annotation for the TTL is the whole reason. The engine keys the cache on the plan signature (query/sort/limit/depth/shape + the keyset cursor) and evicts on any write to the read concept via the cache.invalidate.* broadcast channel -- cross-node eviction needs no per-concept routing rule. Written as one number or keyword arguments; legal on query. |
@composable | @composable(as="ticket", fields="title,status", list="openTickets") | The Materializer's mark (epic memql#4977, D2): this concept's rows are worth composing a file FROM. Bare, it takes the defaults; as= names the row kind, fields= lists the fields to compose from and list= names the query that lists the rows. Read by clients/os (src/apps/materializer/useCompose.ts) through integration.compose.composableConcepts. Written as no arguments or keyword arguments; legal on concept. |
@createOnly | @createOnly("status", "attempts") | On an insert (create-or-upsert) mutation: write the named payload fields ONLY when creating the row. If the target id already exists, the fields are dropped from the delta before the engine read-merge, so the stored value is preserved rather than clobbered -- making a deterministic-id re-stage idempotent for lifecycle fields another writer owns after creation (e.g. stageOutboundRequest seeds status but must not reset a row the outbound worker moved to sent). The inverse of @mergeFields/@appendFields: only valid on insert-kind mutations. Format: @createOnly("status", "attempts"). See fylo#63. Written as one or more strings; legal on mutation. |
@default | @default | Mark this provider as the default for its modality. Written as no arguments; legal on prompt field, provider and tool field. |
@defaultProvider | @defaultProvider("fleet") | Default AI provider for prompt execution: an explicit pin that wins over every routing rule. Written as one string; legal on prompt. |
@description | @description("List the caller's open tickets, newest first.") | Human-readable description of this definition. PREFER the /// doc-comment form (#2601): a /// block immediately above the declaration IS the description and wins over this annotation; @description remains the valid compatibility fallback -- the tree gate rejects the redundant long form (including a bare @description shadowed by a /// block). Aim for ~500 characters (editorial target). Written as one string; legal on 20 receivers (see the attribute matrix). |
@destructive | @destructive | Mark a tool as destructive (mutates/deletes); the tool loop gates it behind a confirmation. Written as no arguments; legal on tool. |
@disabled | @disabled | Takes the construct out of service without deleting it. Every construct is enabled by default and @enabled is an accepted no-op, so @disabled is the only off switch. A disabled construct stays in the tree, is still maintained, and must stay valid, because re-enabling it is only removing the annotation. What it does depends on the construct: - A disabled query, mutation, logic or builtin is hidden from functions() and from the MCP surface, its @mcp tool included, and a direct call is refused with function "name" is disabled; help() still describes it, reporting "enabled": false. A disabled builtin also skips the check that its Go executor exists, so a builtin whose executor is gone can be disabled without refusing boot. - A disabled automation is neither scheduled nor subscribed to its trigger, is dropped from the MCP surface, and is refused on every manual run path: the MCP dry run and live run, the run relay and the HTTP trigger. - A disabled tool, seed, prompt, provider, action, spec or trait is skipped at load. A tool is not registered, and its name stays reserved so no ungoverned tool is generated from the function behind it. A seed is never materialised and a prompt never registers. A provider is not registered and no auth is resolved for it, and on a @base provider it skips every provider that @extends it. An action is parsed but not registered. A spec or trait is still validated but never binds, and its name stays reserved, so a call to it says it is disabled rather than not found. - A disabled rule is loaded but never evaluated. - A disabled capability is still reconciled against its Go capability and checked for duplicates -- @disabled is not a validation bypass -- but it is left out of the catalog: use capabilities.... imports stop resolving it, and an action that names it is refused at load with capability "name" is @disabled. To park DSL ahead of the Go it needs, put the file under a _disabled/ directory instead, which the loaders skip. An authored spec or trait keeps the same state through the authoring lifecycle. Staging or promoting a disabled one is refused (construct is @disabled; enable it (remove @disabled from the source) before promoting). A promoted row that is disabled is skipped when the engine re-hydrates authored constructs and counted as skippedDisabled rather than quarantined; it reserves the name only when nothing live or already reserved holds it, and promoting the corrected source or demoting the name releases it. A staged one is skipped without reserving anything. A bundle that authors a disabled capability is refused (capability "name" is @disabled), because a disabled capability compiles to nothing. Written as no arguments; legal on 13 receivers (see the attribute matrix). |
@displayCard | @displayCard(primary="title", secondary="status") | Rendering hints for concept-agnostic clients (memql#160): the field shown as a row's title (primary=, required) and the fields for the secondary, tertiary and status slots. Each must be a displayable field the concept declares, checked once the property set is known. Read by clients/os (src/apps/concepts/displayCard.ts, consumed by RowsPanel.tsx); test/dslconformance's displaycard_inventory_test.go requires every concept to declare or decline one. Written as keyword arguments; legal on concept. |
@enum | @enum("open", "closed") | The closed set of string values the field accepts: @enum("a", "b"). The enum("a", "b") type is the same constraint in one statement. Written as one or more strings; legal on args field, builtin field, prompt field and tool field. |
@eventField | @eventField("id", "status") | On an event-triggered logic: declare the allowed top-level event payload fields (e.g. @eventField("partitionId", "siParticipantId")). Opt-in field-level validation -- every event.payload.<field> reference in the body is checked against this set at load time, rejecting typos / fields the (possibly synthetic, handler-assembled) triggering event cannot carry (memql#1743). Bare names or payload.-prefixed paths both normalize to the head segment. Written as one or more strings; legal on logic. |
@exclude | @exclude("fleet:qwen3.5:7b") | On a rule: remove one concrete entry from the chain's resolution, e.g. @exclude("fleet:qwen3.5:7b"). Repeatable. Written as one or more strings; legal on rule. |
@executionTime | @executionTime("fast") | Expected execution time hint: "fast", "medium", or "slow". Written as one string; legal on tool. |
@executor | @executor("integration.campaigns.startSend") | Go executor name for builtin functions (integration.X.Y). Written as one string; legal on builtin. |
@extends | @extends("openai") | Inherit configuration from a base provider. Written as one string; legal on provider. |
@fallback | @fallback("app:*") | On a policy: an entry tried when the ones before it cannot serve the call. Repeatable (order preserved). Same closed grammar as @primary. Written as one string; legal on policy. |
@filter | @filter(row => row.status == "open") | Filter for automation triggers: a lambda of one parameter over the triggering row, as in @filter(row => row.status == "open"). Written as an expression; legal on automation. |
@handler | @handler(type="function", name="createTodo") | Tool handler configuration. Format: @handler(type="query", query="...") / @handler(type="function", name="..."). Written as keyword arguments; legal on tool. |
@internal | @internal | On a concept field: server-only (memql#2035) -- never projected by a shape's default projection and never accepted from a mutation's caller args; emitted as x-internal. (On a construct, @internal is retired, #2708.) Written as no arguments; legal on concept field. |
@level | @level("fast") | How much intelligence the call needs: fast, strong, reasoning or embeddings. The router's rules branch on it, so a prompt never names a model (epic memql#5127). Required on every prompt: one without it is refused at load (prompt_level_missing), a @disabled prompt included. Written as one string; legal on prompt and rule. |
@locked | @locked | On a rule: evaluate before every unlocked rule regardless of precedence. Accepted only in the embedded tree -- the loader refuses it elsewhere. Written as no arguments; legal on rule. |
@loop | @loop(maxDepth=4, until=row => row.status == "done") | On an automation that closes a deliberate cycle: permits the cycle the load would otherwise refuse, bounds it to maxDepth runs of this automation per causal chain, and names the predicate that ends it. The @filter must exclude the rows where until holds. Written as keyword arguments; legal on automation. |
@maxLength | @maxLength(120) | The most characters a string value may carry. Written as one number; legal on args field and concept field. |
@maximum | @maximum(100) | The INCLUSIVE upper bound on a numeric value. Written as one number; legal on args field and concept field. |
@mcp | @mcp | Expose this construct on the MCP connector surface. On a query/mutation/automation it promotes the construct into its own first-class MCP tool (otherwise it stays reachable via the generic run_query / run_mutation / run_automation dispatchers). On a tool it opts the tool into the curated connector allowlist: once ANY tool carries @mcp, tools/list reflects only @mcp tools (otherwise -- zero tagged -- the full tool surface is reflected, so the annotation is inert until the curated set is tagged). Written as no arguments; legal on automation, mutation, query and tool. |
@mergeFields | @mergeFields("preferences") | On an update mutation: deep-merge the named object-typed payload fields into the stored object instead of replacing them wholesale, so sibling keys survive a single-key write. Format: @mergeFields("preferences"). Written as one or more strings; legal on mutation. |
@minLength | @minLength(1) | The fewest characters a string value may carry. Written as one number; legal on concept field. |
@minimum | @minimum(0) | The INCLUSIVE lower bound on a numeric value. Written as one number; legal on args field and concept field. |
@mirroredTo | @mirroredTo("shopify") | Declares WHO ELSE HOLDS A COPY of this MemQL-origin concept: @mirroredTo("shopify"), or several names in one annotation. Every write to the concept appends one v1:platform:outboxEntry per target, in the write's own transaction, which a per-connector drain worker delivers with an idempotency key, backoff, dead-lettering and audit. Only valid on a MemQL-origin concept -- @mirroredTo beside an external @origin is REFUSED at load, because re-mirroring somebody else's data onward is the origin's job and a mirror that also publishes is a second origin wearing the first one's badge. Each named connector must be registered or the engine refuses boot: a mirror target nobody drains is a silent drop. See docs/public/concepts/data-origins.md. Written as one or more strings; legal on concept. |
@modality | @modality("embedding") | Provider modality (e.g., "chat", "audio", "image", "embedding"). Written as one string; legal on provider. |
@mode | @mode(queued, max=10) | How concurrent fires of this automation behave in one process: single, queued, restart or parallel, with max for queued and parallel. Without it an automation runs every fire in parallel. Written as keyword arguments; legal on automation. |
@model | @model("gpt-5.4-mini") | Model identifier (e.g., "gpt-5.4-mini", "claude-sonnet-4-6"). Written as one string; legal on provider. |
@noUnset | @noUnset("bootstrappedAt") | On any mutation: declare the named payload fields ONE-WAY -- a write may set them or change one non-empty value to another, but may never take a stored non-empty value back to empty. On the read-merge path a named field arriving empty is dropped from the delta when the stored row holds a non-empty value. Closes the gap read-merge cannot (it only inherits fields ABSENT from a delta, so a body writing f: args.f ?? "" blanks the stored value with an explicit empty string). Distinct from @createOnly, which forbids any post-create write; @noUnset forbids only set -> unset, so a legitimately-later stamp still lands. Format: @noUnset("bootstrappedAt"). See memql#3415. Empty means nil, a blank or whitespace-only string, or an empty array or object. A numeric or boolean zero is a value, not an unset: 0 and false are written like any other value, and treating them as empty would make @noUnset unwritable for those types. Written as one or more strings; legal on mutation. |
@onUnavailable | @onUnavailable("degrade") | On a rule: what happens when the chain is exhausted at the level -- "degrade" walks down to the next level and records that it did, "park" returns the refusal with the door report. Empty reads as degrade. Written as one string; legal on rule. |
@open | @open | On a nested object block: the block accepts keys it does not declare, suppressing the closed-by-default additionalProperties: false (memql#3641). For a block whose keys are data rather than schema. Written as no arguments; legal on concept field. |
@origin | @origin("memql") | Declares WHERE CHANGES TO THIS CONCEPT ARE MADE -- the system that owns the data. @origin("memql") (the default when the annotation is absent) means MemQL originates it; @origin("<connector>") names an external system, which makes the concept a MIRROR. A mirror is READ-ONLY BY CONSTRUCTION: component/memql refuses every write to it -- mutation, tool handler, raw insert or staged write -- that does not come from the connector the origin names, so what the badge says is what a reader may assume. The name must be a registered connector or the engine REFUSES BOOT naming the concept: a mirror nobody fills is a lie. Pairs with @mirroredTo to derive dataState (mirror | origin | native), which the registry, both SDKs and client data-origin displays read. See docs/public/concepts/data-origins.md. Written as one string; legal on concept. |
@pattern | @pattern("^[a-z][a-z0-9-]*$") | A regular expression a string value must match, in Go's RE2 syntax. It is compiled at load on a concept field and an args field alike, so an expression that does not compile is refused there (pattern_invalid) rather than at the first write or call. Written as one string; legal on args field and concept field. |
@pii | @pii | The field is personally identifying: emitted as x-pii, and the hard-delete scrub (@scrubPii, memql#1711) zeroes every such field generically. Written as no arguments; legal on concept field. |
@policy | @policy("localFirst") | Required on a rule: the policy this rule resolves the call through. Written as one string; legal on rule. |
@precedence | @precedence(60) | On a rule: evaluation order, highest first. A tie between two rules of the same locked-ness is a load error. Written as one number; legal on rule. |
@primary | @primary("fleet:strongest") | Required on a policy: the first entry the AI Router resolves. A provider name, a fleet: / app: / federation: selector, or policy:<name>. Written as one string; legal on policy. |
@public | @public | Per-row-authz marker: this query/mutation is intentionally callable without a caller-scope filter (concept catalogs, pre-auth login paths). See docs/public/operate/auth/per-row-authz-audit.md. Written as no arguments; legal on mutation and query. |
@relationship | @relationship(type="parent", field="ownerUserId", target=user, direction="outgoing") | Foreign-key relationship metadata. Format: @relationship(type="parent", field="x", target=concept, direction="outgoing"), plus an optional as="domainVerb" label. Written as keyword arguments; legal on concept. |
@removeFromSet | @removeFromSet("disabledDeployables") | On an update mutation: treat the named array-typed payload fields as SETS and REMOVE the written elements from the stored array, keeping the order of what remains. Removing something absent is a no-op rather than an error, so the mutation is idempotent and two callers removing the same member both succeed. Pairs with @addToSet, and the pair is what lets a set be edited one member at a time instead of read-modify-written whole. Format: @removeFromSet("disabledDeployables"). See memql#4951. Both follow the same rules: - Only an update mutation may carry it; on an insert it is refused at load, because an insert has no stored set to change. - A field absent from the write is untouched: the stored array survives the read-merge. - Members compare by their rendered form, so 1 and "1" are one member. A stored number comes back from JSON as a float and its string spelling as a string, and keeping both would be a set that quietly grew. - A stored value that is not an array reads as empty, and a scalar written where an array is expected is one member, as with @appendFields. - A field named by two of @appendFields, @addToSet and @removeFromSet is refused at load: each rewrites the same key, so which one won would depend on the executor's order rather than on anything the mutation says. Written as one or more strings; legal on mutation. |
@required | @required | The field is required: a concept write without it fails the schema, and a caller must pass an args / tool / prompt / builtin field that carries it. The ! sigil after the type is the same thing. Written as no arguments; legal on args field, builtin field, concept field, prompt field and tool field. |
@requiresAgentRole | @requiresAgentRole("assistant") | The AGENT-KIND gate on a tool: only an agent whose role is one of the values may be offered the tool or call it -- @requiresAgentRole("assistant") (memql#5438). The values are the v1:agents:agent concept's own role enum (assistant, specialist), and the load VALIDATES them against that declaration, so a value no agent can hold refuses boot rather than admitting nobody. It is the axis @requiresRank and @requiresCapability cannot express: they judge the PERSON a call is for, this judges WHICH AGENT is calling. A person calling over MCP is not an agent, so a tool carrying it is neither listed nor callable for them. It replaces the agent half of the deprecated @allowedRoles; a tool with neither gate is callable by every agent and by an authenticated person over MCP. Written as one or more strings; legal on tool. |
@requiresCapability | @requiresCapability("read", "principal") | The CAPABILITY a caller must hold to invoke the construct -- @requiresCapability("read", "principal") (epic memql#5166, D11). The sibling of @requiresRank, and the difference is the point: a RANK is a floor on the cluster's ladder, a CAPABILITY is a (verb x resource) grant a role was given, and a cluster can hold one without the other -- developer ranks above admin and holds strictly fewer principal verbs. Declared together, BOTH must pass. VALIDATED AT LOAD against the five verbs and the resource kinds any role in this cluster holds a grant on, so a misspelling refuses boot rather than gating a surface into silence; ENFORCED at execution through the runtime capability catalog, on the direct call and on every plan that expands the construct. It replaces the slug-comparing specs (requiresAdmin, requiresOwnerOrAdmin, requiresDeveloperOrAbove), which could not see a custom role at all: role == "admin" is false for a rank-250 role holding every principal verb. It gates WHO MAY CALL; @rowAuthz still decides WHICH ROWS come back. Written as one or more strings; legal on builtin, logic, mutation and query. |
@requiresConfirmation | @requiresConfirmation | Require explicit user confirmation before the tool executes. Written as no arguments; legal on tool. |
@requiresRank | @requiresRank("developer") | The actor-rank FLOOR: only a caller holding this role, or one ranked above it, may invoke the construct -- @requiresRank("developer") (epic memql#4832, D6). ENFORCED at execution and VALIDATED AT LOAD against the role ladder in dsl/rbac, so a typo refuses boot rather than gating on rank 0 and admitting everyone. This is the server-side counterpart to MemQL OS's per-surface role requirement: the shell keeps hiding what a caller cannot reach (hiding an action beats letting them click it and reading a refusal) and this makes the hidden surface a REFUSED one. Declared on the CONSTRUCT because a surface is a set of constructs and an app id from a browser is a claim, not a fact. It gates WHO MAY CALL; @rowAuthz still decides WHICH ROWS come back. Written as one string; legal on logic, mutation, query and tool. |
@row | @row | Shape kind: projects a concept's payload + row intrinsics (concept bound via the shape <Concept> <name> signature). Written as no arguments; legal on shape. |
@rowAuthz | @rowAuthz(owner="ownerUserId", clusterOwner) | Declares WHO MAY SEE this concept's rows, once on the concept, instead of as an actor.* term every filter over it must remember to carry. Four tiers, one spelling each: @rowAuthz(public) (globally readable by intent -- spelled explicitly, because "no annotation" and "declared public" are different states), @rowAuthz(clusterOwner) (administrative), @rowAuthz(owner="<field>") (the field is compared against actor.userId; it must be a field the concept declares, OR the literal "id" for a SELF-OWNED concept whose owner is the row itself -- memql#3029; id and only id, since createdBy means who WROTE the row, not whose row it is), and @rowAuthz(via="<spec>") (a relationship spec grants visibility). A fifth FORM, not a fifth tier: @rowAuthz(owner="<field>", clusterOwner) is the composite -- the owner, OR a cluster owner (memql#4312), in either order -- and the form an operator console needs over per-user rows, since a plain owner= tier has no cluster-owner bypass. The other keys (account=, rankVisible, rankStrict, unowned=, requiresIdentity, rankFloor=) qualify a tier rather than naming one. ENFORCED ON THE READ PATH since Phase 3 (memql#3172): declaring a tier CHANGES WHAT READS RETURN. Two mechanisms, and neither consults the filter to decide whether to engage -- the tier's predicate is ANDed into the plan before the read runs (resolved from the construct's declared binding, so the narrowing pushes down into SQL), and every row leaving the engine is separately admitted against the tier ITS OWN concept declares, which is the only mechanism available to a raw client-supplied query string, to graph expansion, or to a TOP-LEVEL BUILTIN CALL whose rows come out of a Go handler (memql#3982) -- none of which has a filter to AND anything into. SUBSCRIPTIONS are gated by the same row admission (memql#4309): a graph.node.* event reaches a stream only if the tier admits the row for that stream's actor, a granted row arrives id-only with payload_omitted set for the client to re-read, and an UNDECLARED concept is delivered to everyone exactly as its reads already return to everyone -- the live feed mirrors the read path rather than running a second rulebook. The write side is enforced too: update/delete refuse when the target row's declared owner is not the actor (memql#3174). Implementation: component/memql/rowauthz_enforce.go, called from parser.go. MEASURED BY TestClusterOwnerTierInjectsTheAdminGate, TestFilteredReadPathAppliesTheRowGate, TestGraphExpansionAppliesTheTraversalGateBeforeItEmitsTheRow, TestTopLevelBuiltinAppliesTheRowGate and TestSubscriptionFanOutAppliesTheRowGate -- named so a reader can check whether this is still true rather than trust the sentence. Trusting it would have been wrong before: this doc described the tier as parsed-but-unread for as long as Phase 3 had been live, which is false in the one direction that costs a reader a wrong authorization assumption, and because the doc also feeds editor hover, the error reached every author who hovered the annotation (memql#3727). See docs/public/operate/auth/per-row-authz-audit.md and memql#2803. Written as keyword arguments; legal on concept. |
@scope | @scope("perUser") | Seed scope: "global" seeds once for the cluster, "perUser" once for every user. Written as one string; legal on seed. |
@scrubPii | @scrubPii | On an update mutation (the hard-delete / data-deletion path): after the partial payload merges, zero EVERY field the bound concept marks @pii. The field set is derived from the schema, so a newly-annotated PII field is scrubbed automatically with no change to the mutation. Bare flag, no arguments. See memql#1711. Written as no arguments; legal on mutation. |
@sdk | @sdk | Generator marker (sdk/gen reads from source); no engine effect. Written as no arguments; legal on builtin. |
@secret | @secret | The field holds a secret. It is emitted as x-secret, and every validation surface that quotes a rejected value replaces the value with <redacted> while the argument name and the declared constraint (enum members, bounds, pattern) survive, so the diagnostic stays usable. The covered surfaces come from an exhaustive enumeration of validators -- every jsonschema .Validate( call site and every value-quoting rejection message in the engine -- not from adding one entry at a time. Three incremental passes each shipped a scope statement that walked past a surface the next pass found, so extend the list by re-running the enumeration, never by appending to it. - The function-args validator (component/memql/function_validator.go): enum, minimum, maximum, pattern and date-time (memql#3036). - The tool-args validator (MemQLEngine.validateToolArgs, component/memql/tool_execution.go), compiled from the same args schema and running before the function-args validator on the agent path, so it is the surface a rejected secret reaches first. Both of its exits redact (memql#3182): the message returned to the model, and the WARN, which redacts per key from the args schema instead of serializing the whole args map, and falls back to a values-free argKeys list when the tool's arguments cannot be classified. - The automation args binder (component/automations/args_binding.go), which applies the same rules to event payloads: a graph.node.created event carries the concept row's fields flattened into its payload. The loader stamps the secret flag from the trigger topic's concept, so the enum and pattern refusals, and the WARN log they are written to, all print <redacted>, closing the one path by which a row value could reach a structured log (memql#3183). - Concept payload validation (Concept.validate, so Create and Delete), where @minimum, @maximum and @format declared on the concept are enforced. The six jsonschema keywords that interpolate the instance value (minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, format) all redact, and every other message stays byte-identical to an unannotated field's (memql#3184). - The DSL-callable validate and preflight builtins (component/memql/executor_builtin.go), which compile the same concept schema and return every leaf message to the caller in a result payload. The last two resolve secrecy through the schema at the failing instance location, so they cover @secret at any nesting depth and inherit it onto array elements, unlike Concept.SecretFields(), which is top-level only and is not the accessor those paths use. On the two args-validator surfaces, matching is by argument NAME, not by write target: an args field is redacted when its name appears among the bound concept's @secret fields. A mutation writing apiKey: args.credential into a @secret apiKey leaves credential unredacted, and renaming between argument and field is common, so do not rely on the write target. The concept-payload and builtin surfaces resolve through the schema and are not subject to this rule. A @secret value is not redacted from query results: any query that projects it returns it in full. That is an authorization decision, which needs a definition of "elevated" and depends on the per-row authorization model (memql#2803). Length is never redacted anywhere, deliberately and uniformly: value too long (N runes, max M) and the jsonschema minLength and maxLength messages report a rune count for a secret field too. They quote no value, but a length is a disclosure; redacting it on one surface would make that surface the only one that differs while withholding nothing the others already print. Prompt input-schema validation (PromptTemplate.ValidateData, component/memql/ai_prompts.go) is unclassified: it runs a jsonschema over caller data and interpolates the instance the same way, but its schema is built from the prompt's own field list rather than from a concept, so there is no x-secret in it to read. Nothing is redacted there; treat it as uncovered, not as safe. So @secret stops a credential leaking through a validation diagnostic. It is not a general secrecy guarantee, and not a reason to treat a credential in the graph as protected. Written as no arguments; legal on concept field. |
@serverOnly | @serverOnly | Bars the construct from client-originated calls while leaving server-side Go free to call it (memql#2800). ENFORCED at execution against auth.CallOrigin -- unlike the retired @internal, which only hid a construct from discovery. Use only when caller-scoping is impossible: the auth path resolving sub -> user before an actor exists, or an automation acting on a user other than the actor. Callers must stamp auth.ContextWithInternalOrigin. Written as no arguments; legal on mutation and query. |
@serverSet | @serverSet | The field is stamped server-side (createdAt, createdBy, status, ...): never accepted from a mutation's caller args, but projected like any other field. Emitted as x-serverSet (memql#2035). Written as no arguments; legal on concept field. |
@shopperFormExtension | @shopperFormExtension(pack="wholesale", form="application") | On a mutation: this construct is ONE CLIENT DOMAIN'S ADDITION to a shopper form a PACK declares -- the fields that client collects which the pack does not know about, and the concept they are stored on. It ADDS FIELDS TO AN EXISTING PUBLIC ROUTE and never opens one: putting an endpoint on a hosted site's origin stays the privilege of Go compiled into the engine. The field list is this mutation's own args block MINUS the server-stamped names (storeId, siteId, submissionId), which the body declares so it can write them. The bff validates the pack's fields and these together BEFORE anything is written, runs the pack's construct first so the pack's own gate decides, then this mutation with the same stamped submission id. Legal on a MUTATION alone -- a logic may call builtins, and a public form pointed at one would reach them under the site owner's borrowed authority. At most one extension per form. Format: @shopperFormExtension(pack="wholesale", form="application"). Written as keyword arguments; legal on mutation. |
@sideEffect | @sideEffect("write") | On a capability: the coarse risk class @sideEffect("read"|"write"|"exec"). It is the authoritative side-effect class: it lives on the capability, not on the action that calls it, and must equal the class of the Go capability the declaration names, so an authored or generated action cannot claim a lower one. Written as one string; legal on capability. |
@template | @template | On an automation: this is a work-spine TEMPLATE, invoked by a v1:work:run that named it rather than fired by the graph (memql#5048). It is the third way an automation can be reachable, alongside an event trigger and a schedule. A @template automation must carry no @trigger -- the load-time gate refuses the combination, so "called" and "triggered" stay distinct. Written as no arguments; legal on automation. |
@templateFile | @templateFile("prompts/consolidateMemory.tmpl") | The prompt's template: a .tmpl file beside the prompt, rendered with the input fields. Written as one string; legal on prompt and seed. |
@trigger | @trigger(event="node.created", concept="v1:cluster:node") | Event trigger for automations. Format: @trigger(event="graph.node.created.*.v1:ns:concept") or @trigger(schedule="0 0 * * * *"). Written as keyword arguments; legal on automation. |
@type | @type("collection") | The concept's row kind: "object" (the default), "collection" or "reference". Written as one string; legal on concept. |
@unbounded | @unbounded("a small catalog of fixed size") | On a list-returning query: opt out of the pagination authoring rule and the implicit 50-row runtime cap. Format: @unbounded("reason"). The reason string is REQUIRED -- it documents why this query is a legitimate full-set read (small bounded catalog, sweep job, etc.) and is enumerated by the pagination audit report. A query that paginates/sorts is already bounded and must NOT carry @unbounded; the engine clamps the realized window to MEMQL_MEMORY_ENGINE_MAX_WINDOW regardless. See docs/public/language/authoring-rules.md. Written as one string; legal on query. |
@variant | @variant(discriminator="kind") | A discriminated union: @variant(discriminator="kind") on an object field, followed by one block per branch; the discriminator field's value picks the branch. Written as keyword arguments; legal on concept field. |
@vendor | @vendor("OpenAI") | The provider's type, which picks the client that serves it: OpenAI (or OpenAIChat), OpenAITTS or OpenAIEmbedding for OpenAI, and Anthropic (or AnthropicChat) for Anthropic, matched without regard to case. Fleet and SubscriptionApp are accepted on a @base provider only: their models are named from a policy (fleet:<model>, app:<id>) rather than declared as children. Any other type leaves the provider registered but unavailable (unsupported provider type). Streaming is a parameter (streaming true in params), not a type, and a child that @extends a base takes the base's type. Written as one string; legal on provider. |
@version | @version("1.0.0") | Version tag for the seed. Written as one string; legal on concept and seed. |
@when | @when(level="fast") | On a rule: the condition set, as keyword arguments. Closed keys, every one optional, all present keys ANDed: level, modality, prompt, role, actorRole, tag, touches. @when() with no arguments matches every call. Written as empty parentheses or keyword arguments; legal on rule. |
Keywords
The reserved words that are not constructs: the statements of a body, the clauses of a construct, and the engine identifiers a body may read.
| Name | Written | What it means |
|---|---|---|
accept | "accept" "{" ( <name> [ "," ] )* "}" | Write-block sugar: accept { name, ... } lists the public fields the mutation accepts -- each auto-binds to its same-named arg (name -> name: args.name). Every name must be a declared arg. Nested inside insert{}/update{} (or top-level, which means insert). Never mixed with loose fields. |
actor | actor | Reserved: the auth envelope. Closed member set (#2623): userId, role, identityId, isClusterOwner, primaryEmail, now, plus the legacy isOwner alias. Reading it requires @actor in the construct preamble (#2621). Members: actor.userId, actor.role, actor.identityId, actor.isClusterOwner, actor.primaryEmail, actor.now, actor.isOwner. |
args | "args" "{" <field>* "}" / args | Input-schema block: declares caller-passed args read as args.X in the body. Reserved namespace for caller-passed inputs (args.X). |
asOf | `"asOf" ( "latest" | <expression> )` |
auth | "auth" "{" <auth-entry>* "}" | Provider block: vendor auth (e.g. apiKey env("...")). |
branch | branch | One list of a parallel statement: branch <label> { ... }. |
case | case | A switch branch: case <literal>[, <literal>] { ... }. A label is a literal, written once per switch. |
config | config | Reserved namespace for the allow-listed configuration values a body may read, as config.<key>. The allow-list is component/config/policy_exposable.go; a key outside it is not readable from the DSL at all, so configuration cannot leak into a construct by accident. |
count | "count" | Query clause: aggregate the matching set to {count: N} -- a bare count. Mutually exclusive with shape, sort and paginate. |
default | default | The switch branch that runs when no case matches. |
else | else | Alternative branch of an if statement, on the closing brace's line: } else {. |
filter | "filter" <lambda> | Query clause: filter row => <predicate>, the boolean predicate over the row, pushed down to SQL. A line clause -- it takes the rest of its line and the lines after it that open with an operator; no block. |
for | "for" <name> "in" <expression> [ "if" <expression> ] "{" <statement>* "}" [ <trailing-clause> ] | Loop statement: for item in <source> [if <cond>] { ... } -- the loop variable and every name bound in the body exist in each iteration only. A return inside ends the body the loop is in. Trailing clause: on error continue. |
if | "if" <expression> "{" <statement>* "}" { "else" "if" <expression> "{" <statement>* "}" } [ "else" "{" <statement>* "}" ] | Conditional statement: if <cond> { ... } else if <cond> { ... } else { ... }. A name bound in a branch is readable after the chain -- absent if the branch that binds it did not run -- and the branches of one chain may bind the same name. For a conditional VALUE write the expression p ? a : b. |
in | in | Membership test: args.tag in row.tags, or row.kind in ["a", "b"]. The single membership operator (has and the .contains(v) collection method are retired). |
insert | "insert" "{" ( <write-entry> [ "," ] )* "}" | Mutation block: the row to create. Exactly one insert OR update per mutation. |
now | now | Reserved: RFC3339 timestamp captured at eval start. |
on | `"on" "surface" "(" <string> ")" | "on" "error" ( "continue" |
paginate | "paginate" <number> | Query clause: bound the result to a window -- paginate 25. A list-returning query carries paginate, sort, count or @unbounded("reason") (memql#1965). |
parallel | "parallel" "{" { "branch" <name> "{" <statement>* "}" } "}" [ "wait" "any" ] [ <trailing-clause> ] | Parallel statement: parallel { branch <label> { ... } ... } [wait any] -- the branches run at once, each a list of its own whose names stay inside it; a failed branch stops the others. A branch cannot return. Trailing clause: on error continue. |
params | "params" "{" <param-entry>* "}" | Provider block: the model parameters this provider is called with -- context window, completion cap and the per-million input and output costs the router bills against. Written params { contextWindow 128000 ... }, one key value pair per line. |
partition | partition | Reserved: the active partition for this call. |
payload | payload | Reserved: bound-concept row payload (payload.X) -- valid in query filter/shape only (SQL pushdown). |
precondition | "precondition" <name> "{" <precondition-entry>* "}" | Automation block: precondition <name> { check: <expr> }, a deterministic check that must hold before the statements run (Epic 4, memql#2139). Its body is key: value entries, NOT statements: three keys -- check: the boolean expression, required; literal: the machine-specific literal it asserts, for the repair loop; description: the context the miss signal carries. A block with no check: is refused at load. |
publish | "publish" <string> "{" <map-entry> { "," <map-entry> } "}" | Publish statement, in an automation: publish "<topic>" { key: value, ... } puts an event on the bus. A logic may not publish (D14): publish from the automation that calls it. |
refine | "refine" <lambda> | Query clause: refine row => <predicate>, a predicate the database cannot run, applied in process to each page paginate reads, so a page may come back short. Requires paginate; never with count. |
retry | "retry" "(" <number> ")" | Trailing clause of a construct call: <call> retry(n) runs a failed call up to n more times. Written after on surface(...) and before on error continue. |
return | `"return" [ <construct-call> | <expression> ]` |
shape | "shape" <name> | Query clause: names the projection shape for the result -- shape <name>. (Also the shape construct keyword and the <expr> with shape(...) expression.) |
sort | "sort" <string> { "," <string> } | Query clause: order the result -- sort "row.createdAt", "desc". Payload keys are bare; row intrinsics take the row. namespace. |
stamp | "stamp" "{" ( <map-entry> [ "," ] )* "}" | Write-block sugar: stamp { key: value, ... } carries the server-set fields beside an accept{} list. Nested inside insert{}/update{} (or top-level with accept, which means insert). |
startsWith | startsWith | String-prefix test: row.name startsWith "lit", a list of prefixes (ANY of), or an arg. An empty list and a blank prefix match nothing (memql#4208). |
switch | "switch" <expression> "{" { "case" <literal> { "," <literal> } "{" <statement>* "}" } [ "default" "{" <statement>* "}" ] "}" | Switch statement: switch <value> { case <literal>[, <literal>] { ... } default { ... } } -- the first case whose label equals the value runs, else the default. Its names share the switch's scope, as an if chain's do. |
trace | trace | Reserved engine identifier for the current call's trace context. It is reserved so an args field or a local cannot take the name and shadow it; no construct in the tree reads it today. |
update | "update" "{" ( <write-entry> [ "," ] )* "}" | Mutation block: partial read-merge-write of an existing row (keyed by id). |
use | use | File-top import: use <domain>.<construct>.{ names }. |
wait | `"wait" ( "any" | "all" )` |
Operators
Every operator of the expression grammar, with what it does to an ABSENT operand -- the half an author gets wrong -- and where it binds.
| Name | Written | What it means |
|---|---|---|
add | a + b | Adds two numbers, or joins two strings: "si-" + args.id. A number joined to a string contributes its text. Absence: In a join, an absent operand contributes the empty string. Written +, precedence level 4. |
and | a && b | Is true when both sides are true, and skips the right side when the left is false. Both sides must be boolean: nothing else counts as true. Written &&, precedence level 7. |
coalesce | a ?? b | Returns a, or b when a is missing: args.stage ?? "active". It binds tighter than comparison, so a ?? "" == "x" compares the coalesced value. Absence: Falls through to b when a is absent or a blank or whitespace-only string; false, 0 and an empty list are kept. Written ??, precedence level 5. |
divide | a / b | Divides a by b; dividing by zero is an error. Written /, precedence level 3. |
equal | a == b | Is true when two values are equal. Numbers compare numerically, and 1 == "1" is false. Absence: A missing field, JSON null, nil and "" are one unset value: each equals the others and nothing else. Written ==, precedence level 6. |
greater | a > b | Is true when a orders after b. Absence: Any ordering against an absent field is false. Written >, precedence level 6. |
greaterOrEqual | a >= b | Is true when a orders after b or equals it. Absence: Any ordering against an absent field is false. Written >=, precedence level 6. |
in | v in list | Is true when v equals an element of the list: row.status in ["open", "held"], args.tag in row.tags. Absence: Membership is == against each element, so an unset v is in a list only when the list holds "" or nil. Nothing is in an absent list. Written in, precedence level 6. |
lambda | row => row.status == "open" | Names the parameter a predicate or a projection reads: row => ... over a row, t => t == "x" over an element. Two parameters are written (acc, x) => .... Written =>, precedence level 10. |
less | a < b | Is true when a orders before b. Strings order by byte, so RFC 3339 timestamps order by time. Absence: Any ordering against an absent field is false. Written <, precedence level 6. |
lessOrEqual | a <= b | Is true when a orders before b or equals it. Absence: Any ordering against an absent field is false. Written <=, precedence level 6. |
member | row.status | Reads a field of an object: row.status is the row's status field. Absence: An absent object or field reads as absent. Where the object itself may be absent, the load requires .? instead. Written ., precedence level 1. |
multiply | a * b | Multiplies two numbers. Written *, precedence level 3. |
negate | -x | Negates a number. Written -, precedence level 2. |
not | !(x in list) | Negates a boolean: !(row.status in ["open", "held"]) is true when the status is neither. Absence: Every predicate answers true or false, absent included, so !(x == v) is exactly x != v. Written !, precedence level 2. |
notEqual | a != b | Is true when two values differ. Absence: The exact negation of ==, so an unset field is not equal to "x", and row.f != nil and row.f != "" are false for it. Written !=, precedence level 6. |
optionalMember | row.?lineage.planId | Reads a field of an object that may be absent: row.?lineage.planId is the plan id when lineage is present. Absence: An absent object reads as absent instead of refusing, and the rest of the chain stays absent. The load requires .? wherever the object may be absent: an optional object field, an optional arg, an untyped value. Written .?, precedence level 1. |
or | `a | |
remainder | a % b | Returns the remainder of dividing a by b; a zero divisor is an error. Written %, precedence level 3. |
startsWith | s startsWith p | Is true when the string s begins with the prefix p, or with any prefix in a list of them. Absence: Is false when either side is absent; a blank prefix and an empty list match nothing. Written startsWith, precedence level 6. |
subtract | a - b | Subtracts b from a. Write it with spaces, because a-b is one hyphenated name. Written -, precedence level 4. |
ternary | p ? a : b | Returns a when the boolean p is true, and b when it is false. It binds loosest of the value operators, so x == 1 ? a : b tests x == 1. Written ? :, precedence level 9. |
Field types
The type words a concept field, an args field or a schema-body field is declared with.
| Name | Written | What it means |
|---|---|---|
any | <name> any | No declared type: the field takes whatever the caller sends, and nothing checks it. It is what an automation's args block writes for the fields of a trigger payload, where the concept the event came from has already declared their types. Write it only there: any is the one spelling that buys no validation, and a named type is what makes a wrong value a refusal instead of a surprise further down. |
array | <name> array | Deprecated list spelling. DEPRECATED: write []T instead. |
bool | <name> bool | True or false, and nothing else. There is no truthiness in the language, so a bool field is the only thing a condition may read directly; false is a value and is never coalesced away by ??. |
boolean | <name> boolean | True or false: the second spelling of bool, and the same type everywhere. The args validator, the schema a tool advertises and the expression lowering all answer to either word, so which to write is house style -- and bool is the one the rest of this table is written in. |
datetime | <name> datetime | An RFC 3339 timestamp, carried as a string. Strings order by byte, so an RFC 3339 field orders by time under < and >, and addDuration and daysBetween take and return this type. |
enum | <name> enum | Restricted value set. First-class parameterized form (#2618): status enum("open", "closed") -- self-contained, same representation as the legacy string @enum(...) pair (which keeps parsing). |
float | <name> float | A number with a fractional part, carried as a JSON number. @minimum and @maximum bound it inclusively, and a value that arrives as an integer is accepted. |
int | <name> int | A whole number, positive or negative. Bound it with @minimum and @maximum, which are inclusive; a discrete numeric set has no annotation, because @enum takes string literals only. |
integer | <name> integer | A whole number: the second spelling of int, and the same type everywhere. The args validator accepts a JSON number under either word only when it is whole, and the tool schema emits "integer" for both. |
number | <name> number | A JSON number, whole or fractional. Broader than int, which refuses a fraction, and the word a tool's or a prompt's schema emits for float -- so a field that must take both is number or float, never int. |
object | <name> object | A nested object -- a JSON map of further fields, declared as a block. Read a leaf through the optional-member operator (row.?lineage.planId) wherever the object itself may be absent, which the load requires. |
string | <name> string | UTF-8 text of any length. It is the type an enum, a pattern and a length bound narrow, and the one an absent value reads as the empty string in. |
Functions and methods
Every call an expression may make, one spelling each. P lowers to SQL; M runs in process, and in a pushdown position only on an input that does not read the row.
| Name | Written | Tier | What it means |
|---|---|---|---|
addDuration | addDuration(ts datetime, dur duration) datetime | M | Returns ts moved by the ISO 8601 duration dur, as an RFC 3339 timestamp; a leading minus sign moves it back. An absent or unparseable argument is an error. |
aliasOf | aliasOf(label? string, match lambda) rows | P | Returns the rows sharing an alias group with the rows selected by match; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
canonicalId | canonicalId(value string, concept string) string | M | Returns value as a canonical id of the named concept, whether it arrives as a bare short id or already canonical. An absent or blank value yields the empty string, and a value canonical for a different concept is an error. |
childOf | childOf(label? string, match lambda) rows | P | Returns the children of the rows selected by match, the rows whose parent edge points at one of them; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
contains | contains(label? string, match lambda) rows | P | Returns the members of the collection rows selected by match, following their contains edges (the graph traversal: the substring test is string.includes); a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
createdBy | createdBy(label? string, match lambda) rows | P | Returns the creators of the rows selected by match, following their createdBy edges; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
daysBetween | daysBetween(a datetime, b datetime) number | M | Returns the number of whole days from a to b, truncated toward zero and negative when b is earlier. An absent or unparseable argument is an error. |
equals | equals(label? string, match lambda) rows | P | Returns the rows joined to the rows selected by match by an equals edge; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
error | error(message string) any | M | Raises an error carrying message and ends the evaluation, so it never returns a value. |
hash | hash(value string) string | M | Returns the SHA-256 digest of value as 64 lowercase hexadecimal characters. An absent value hashes as the empty string, so the result is always 64 characters wide. |
ids | ids(match lambda) rows | P | Returns the rows selected by match as id-only rows, without payload or schema. It follows no edge, so it takes no label. |
list.all | list.all(pred lambda) bool | P | Reports whether pred holds for every element of the list. An empty or absent list answers true. |
list.any | list.any(pred lambda) bool | P | Reports whether pred holds for at least one element of the list. An empty or absent list answers false. |
list.avg | list.avg(fn lambda) number | M | Returns the mean of fn over the elements of the list. An empty or absent list yields an absent value, and fn returning a non-number is an error. |
list.count | list.count() number | P | Returns the number of elements in the list. An absent list counts as zero. Over a row field it counts what is stored: an array's elements, a string's characters, and zero for anything else. |
list.distinct | list.distinct(key? lambda) list | M | Returns the list with repeated elements dropped, keeping the first of each; with key, two elements repeat when key gives both the same value. An absent list yields an empty list. |
list.empty | list.empty() bool | M | Reports whether the list has no elements. An absent list is empty. |
list.first | list.first() any | M | Returns the first element of the list. An empty or absent list yields an absent value; to take the first match, filter first: xs.where(x => p).first(). |
list.groupBy | list.groupBy(key lambda) list | M | Returns one group per distinct value of key, in first-seen order, each a map holding key and items. An absent list yields an empty list. |
list.last | list.last() any | M | Returns the last element of the list. An empty or absent list yields an absent value. |
list.max | list.max(fn lambda) number | M | Returns the largest value of fn over the elements of the list. An empty or absent list yields an absent value, and fn returning a non-number is an error. |
list.min | list.min(fn lambda) number | M | Returns the smallest value of fn over the elements of the list. An empty or absent list yields an absent value, and fn returning a non-number is an error. |
list.nodes | list.nodes() list | M | Returns the rows of a query result as a list; a list comes back unchanged. An absent value yields an empty list. |
list.orderBy | list.orderBy(key lambda) list | M | Returns the list sorted ascending by key, elements with equal keys keeping their order. An absent list yields an empty list. |
list.orderByDesc | list.orderByDesc(key lambda) list | M | Returns the list sorted descending by key, elements with equal keys keeping their order. An absent list yields an empty list. |
list.reduce | list.reduce(seed any, fn lambda) any | M | Returns seed folded through the list, calling fn with the accumulator and each element in turn. An empty or absent list returns seed unchanged. |
list.select | list.select(fn lambda) list | M | Returns fn applied to each element of the list, in order. An absent list yields an empty list. |
list.single | list.single() any | M | Returns the one element of the list. Any other number of elements, none included, is an error; to take the one match, filter first: xs.where(x => p).single(). |
list.skip | list.skip(n number) list | M | Returns the list without its first n elements; a negative n skips none. An absent list yields an empty list. |
list.sum | list.sum(fn lambda) number | M | Returns the sum of fn over the elements of the list. An empty or absent list sums to zero, and fn returning a non-number is an error. |
list.take | list.take(n number) list | M | Returns the first n elements of the list, or all of them when there are fewer; a negative n takes none. An absent list yields an empty list. |
list.where | list.where(pred lambda) list | M | Returns the elements of the list that pred holds for, in order. An absent list yields an empty list. |
lower | lower(value string) string | M | Returns value with its letters lowercased. An absent value yields the empty string. |
owns | owns(label? string, match lambda) rows | P | Returns the rows joined to the rows selected by match by an owns edge, in either direction; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
parentOf | parentOf(label? string, match lambda) rows | P | Returns the parents of the rows selected by match, following their parent edges; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
references | references(label? string, match lambda) rows | P | Returns the rows joined to the rows selected by match by a references edge; a leading label follows only the edges whose as label it names. A traversal that finds nothing returns no rows, and a pointer that is absent or blank is skipped. |
secret | secret(name string) string | M | Returns the decrypted value of the secret named name, looked up among the partition secrets and then the global ones. A name that matches no secret is an error, and the value must never be logged. |
shortId | shortId(value string) string | M | Returns the bare short id of value by stripping one canonical concept prefix; a value that is already bare comes back unchanged. An absent or blank value yields the empty string. |
string.count | string.count() number | M | Returns the number of characters in the string, counting Unicode code points rather than bytes. An absent string counts as zero. |
string.includes | string.includes(sub string) bool | P | Reports whether sub occurs in the string. A blank sub matches nothing, as a blank prefix does for startsWith, and an absent string or sub answers false. |
systemSecret | systemSecret(name string) string | M | Returns the decrypted value of the global secret named name, with no partition lookup. A name that matches no secret is an error, and the value must never be logged. |
systemVar | systemVar(name string) string | M | Returns the plaintext value of the global configuration variable named name, with no partition lookup. A name that matches no variable is an error. |
toString | toString(value any) string | M | Returns value as text; a string comes back unchanged. An absent value yields the empty string. |
trim | trim(value string) string | M | Returns value without its leading and trailing whitespace. An absent value yields the empty string. |
upper | upper(value string) string | M | Returns value with its letters uppercased. An absent value yields the empty string. |
var | var(name string) string | M | Returns the plaintext value of the configuration variable named name, looked up among the partition variables and then the global ones. A name that matches no variable is an error. |
Builtins
The context accessors the parser recognises and the builtins resolved from the integration registry -- the callables the function catalog does not describe.
| Name | Written | What it means |
|---|---|---|
actor | actor() | Access the resolved auth context (userId, role, identityId, isClusterOwner, partitions). |
ai | ai(templateId string, data object) | Call a named prompt with a data object and return {prompt, reply}. Written as a builtin call, builtin ai(templateId: ..., data: ...), in a logic or automation body. The prompt's @level and the routing rules choose the model, so the call never names one. |
children | children(concept string) | Retrieve child nodes of the current node for a given concept. |
embed | embed(text string, model? string) | Generate an embedding vector for text using the configured provider. |
event | event() | Access the trigger event data in an automation. |
field | field(name string) | Reference a field accessor in the current evaluation context. |
node | node(field string) | Access a field from the current node in a shape template. |
parent | parent(concept string) | Retrieve the parent node for a given concept. |
payload | payload(field string) | Access a field from the event payload. |
similar | similar(text string, concept string, field string, topK? int, minScore? float) | Find semantically similar nodes using vector search (pgvector). |