docsLanguage

MemQL Attribute Matrix

This page lists every annotation the language accepts, the constructs and fields that accept it, and the form its arguments take there. It is generated from the annotation registry in component/language/annotations, which the construct parsers and the concept loader check every annotation against, and make docs-matrix regenerates it.

How to read it

Under At a glance, a row is an annotation and a column is a construct or a kind of field: a filled cell says how the annotation is written there, and an empty cell means it is refused there, with one of the codes under Refusals. The words in a cell are argument forms: flag means no arguments, empty empty parentheses, string one string, strings one or more strings, number one number, keywords keyword arguments and expression an expression. Each annotation links to its entry under Annotations, which shows it written on every construct that accepts it, lists its keys and says what it does.

At a glance

One table per family of constructs, and one for the fields a construct declares.

Functions

Annotationquerymutationlogicautomation
@actorflagflagflagflag
@addToSetstrings
@appendFieldsstrings
@cachenumber or keywords
@createOnlystrings
@descriptionstringstringstringstring
@disabledflagflagflagflag
@eventFieldstrings
@filterexpression
@loopkeywords
@mcpflagflagflag
@mergeFieldsstrings
@modekeywords
@noUnsetstrings
@publicflagflag
@removeFromSetstrings
@requiresCapabilitystringsstringsstrings
@requiresRankstringstringstring
@scrubPiiflag
@serverOnlyflagflag
@shopperFormExtensionkeywords
@templateflag
@triggerkeywords
@unboundedstring

Data

Annotationconceptshapespec and traitseed
@actorflag
@composableflag or keywords
@descriptionstringstringstringstring
@disabledflagflag
@displayCardkeywords
@mirroredTostrings
@originstring
@relationshipkeywords, in the body
@rowflag
@rowAuthzkeywords
@scopestring
@templateFilestring
@typestring
@versionstringstring

Model access

Annotationpromptproviderpolicyruletool
@allowedRolesstrings
@baseflag
@defaultflag
@defaultProviderstring
@descriptionstringstringstringstringstring
@destructiveflag
@disabledflagflagflagflag
@excludestrings
@executionTimestring
@extendsstring
@fallbackstring
@handlerkeywords
@levelstringstring
@lockedflag
@mcpflag
@modalitystring
@modelstring
@onUnavailablestring
@policystring
@precedencenumber
@primarystring
@requiresAgentRolestrings
@requiresConfirmationflag
@requiresRankstring
@templateFilestring
@vendorstring
@whenempty or keywords

Capabilities

Annotationbuiltinactioncapability
@aliasstring
@argskeywords
@descriptionstringstringstring
@disabledflagflagflag
@executorstring
@requiresCapabilitystrings
@sdkflag
@sideEffectstring

Fields

Annotationconcept fieldargs fieldtool fieldprompt fieldbuiltin field
@autoInjectedflag
@defaultstringstring or number
@descriptionstringstringstringstring
@enumstringsstringsstringsstrings
@internalflag
@maximumnumbernumber
@maxLengthnumbernumber
@minimumnumbernumber
@minLengthnumber
@openflag
@patternstringstring
@piiflag
@requiredflagflagflagflagflag
@secretflag
@serverSetflag
@variantkeywords

By construct

Every construct and every kind of field, with each annotation it accepts, how the annotation is written there, and an example.

query

AnnotationWritten asExample
@actorno arguments@actor
@cacheone number or keyword arguments@cache(300)
@descriptionone string@description("List the caller's open tickets, newest first.")
@disabledno arguments@disabled
@mcpno arguments@mcp
@publicno arguments@public
@requiresCapabilityone or more strings@requiresCapability("read", "principal")
@requiresRankone string@requiresRank("developer")
@serverOnlyno arguments@serverOnly
@unboundedone string@unbounded("a small catalog of fixed size")

The fields of its args block take the annotations under args field.

mutation

AnnotationWritten asExample
@actorno arguments@actor
@addToSetone or more strings@addToSet("disabledDeployables")
@appendFieldsone or more strings@appendFields("attachmentIds")
@createOnlyone or more strings@createOnly("status", "attempts")
@descriptionone string@description("Rename one of the caller's tickets.")
@disabledno arguments@disabled
@mcpno arguments@mcp
@mergeFieldsone or more strings@mergeFields("preferences")
@noUnsetone or more strings@noUnset("bootstrappedAt")
@publicno arguments@public
@removeFromSetone or more strings@removeFromSet("disabledDeployables")
@requiresCapabilityone or more strings@requiresCapability("execute", "app:deployables/publish")
@requiresRankone string@requiresRank("admin")
@scrubPiino arguments@scrubPii
@serverOnlyno arguments@serverOnly
@shopperFormExtensionkeyword arguments@shopperFormExtension(pack="wholesale", form="application")

The fields of its args block take the annotations under args field.

logic

AnnotationWritten asExample
@actorno arguments@actor
@descriptionone string@description("Close every ticket that has been idle for a week.")
@disabledno arguments@disabled
@eventFieldone or more strings@eventField("id", "status")
@requiresCapabilityone or more strings@requiresCapability("execute", "app:deployables/deploy")
@requiresRankone string@requiresRank("admin")

The fields of its args block take the annotations under args field.

automation

AnnotationWritten asExample
@actorno arguments@actor
@descriptionone string@description("On a new ticket, notify its owner.")
@disabledno arguments@disabled
@filteran expression@filter(row => row.status == "open")
@loopkeyword arguments@loop(maxDepth=4, until=row => row.status == "done")
@mcpno arguments@mcp
@modekeyword arguments@mode(queued, max=10)
@templateno arguments@template
@triggerkeyword arguments@trigger(event="node.created", concept="v1:cluster:node")

The fields of its args block take the annotations under args field.

concept

AnnotationWritten asExample
@composableno arguments or keyword arguments@composable(as="ticket", fields="title,status", list="openTickets")
@descriptionone string@description("A support ticket raised by a customer.")
@displayCardkeyword arguments@displayCard(primary="title", secondary="status")
@mirroredToone or more strings@mirroredTo("shopify")
@originone string@origin("memql")
@relationshipkeyword arguments, repeatable, in the body@relationship(type="parent", field="ownerUserId", target=user, direction="outgoing")
@rowAuthzkeyword arguments@rowAuthz(owner="ownerUserId", clusterOwner)
@typeone string@type("collection")
@versionone string@version("1.0.0")

The fields in its body take the annotations under concept field.

shape

AnnotationWritten asExample
@actorno arguments@actor
@descriptionone string@description("Ticket summary card.")
@rowno arguments@row

spec and trait

AnnotationWritten asExample
@descriptionone string@description("Matches the rows that are still open.")
@disabledno arguments@disabled

seed

AnnotationWritten asExample
@descriptionone string@description("Knowledge baseline for every professional role.")
@disabledno arguments@disabled
@scopeone string@scope("perUser")
@templateFileone string@templateFile("templates/assistant.tmpl")
@versionone string@version("1.0.0")

prompt

AnnotationWritten asExample
@defaultProviderone string@defaultProvider("fleet")
@descriptionone string@description("Distil a cluster of episodes into one memory.")
@disabledno arguments@disabled
@levelone string@level("fast")
@templateFileone string@templateFile("prompts/consolidateMemory.tmpl")

The fields in its body take the annotations under prompt field.

provider

AnnotationWritten asExample
@baseno arguments@base
@defaultno arguments@default
@descriptionone string@description("OpenAI GPT-5.4 Mini -- balanced cost and latency.")
@disabledno arguments@disabled
@extendsone string@extends("openai")
@modalityone string@modality("embedding")
@modelone string@model("gpt-5.4-mini")
@vendorone string@vendor("OpenAI")

policy

AnnotationWritten asExample
@descriptionone string@description("Local strongest, then an app, then the cheapest federated model.")
@fallbackone string, repeatable@fallback("app:*")
@primaryone string@primary("fleet:strongest")

rule

AnnotationWritten asExample
@descriptionone string@description("Operator turns reason locally first.")
@disabledno arguments@disabled
@excludeone or more strings, repeatable@exclude("fleet:qwen3.5:7b")
@levelone string@level("strong")
@lockedno arguments@locked
@onUnavailableone string@onUnavailable("degrade")
@policyone string@policy("localFirst")
@precedenceone number@precedence(60)
@whenempty parentheses or keyword arguments@when(level="fast")

tool

AnnotationWritten asExample
@allowedRolesone or more strings@allowedRoles("assistant", "specialist")
@descriptionone string@description("Create a to-do for the caller.")
@destructiveno arguments@destructive
@disabledno arguments@disabled
@executionTimeone string@executionTime("fast")
@handlerkeyword arguments@handler(type="function", name="createTodo")
@mcpno arguments@mcp
@requiresAgentRoleone or more strings@requiresAgentRole("assistant")
@requiresConfirmationno arguments@requiresConfirmation
@requiresRankone string@requiresRank("developer")

The fields in its body take the annotations under tool field.

builtin

AnnotationWritten asExample
@aliasone string, repeatable@alias("memqlVersion")
@argskeyword arguments@args(profile="object")
@descriptionone string@description("Preflight and start a campaign send.")
@disabledno arguments@disabled
@executorone string@executor("integration.campaigns.startSend")
@requiresCapabilityone or more strings@requiresCapability("execute", "app:deployables/deploy")
@sdkno arguments@sdk

The fields in its body take the annotations under builtin field.

action

AnnotationWritten asExample
@descriptionone string@description("Tag a release in the repository.")
@disabledno arguments@disabled

The fields of its args block take the annotations under args field.

capability

AnnotationWritten asExample
@descriptionone string@description("Create a git tag and a GitHub release for a version.")
@disabledno arguments@disabled
@sideEffectone string@sideEffect("write")

The fields of its args block take the annotations under args field.

concept field

Written after a field's type in the body of a concept.

AnnotationWritten asExample
@descriptionone string@description("The ticket's one-line title.")
@internalno arguments@internal
@maximumone number@maximum(100)
@maxLengthone number@maxLength(120)
@minimumone number@minimum(0)
@minLengthone number@minLength(1)
@openno arguments@open
@patternone string@pattern("^[a-z][a-z0-9-]*$")
@piino arguments@pii
@requiredno arguments@required
@secretno arguments@secret
@serverSetno arguments@serverSet
@variantkeyword arguments@variant(discriminator="kind")

args field

Written after a field's type in the args block of a query, mutation, logic, automation, action or capability.

AnnotationWritten asExample
@enumone or more strings@enum("open", "closed")
@maximumone number@maximum(60)
@maxLengthone number@maxLength(120)
@minimumone number@minimum(30)
@patternone string@pattern("^[A-Za-z]{2,3}$")
@requiredno arguments@required

tool field

Written after a field's type in the body of a tool.

AnnotationWritten asExample
@autoInjectedno arguments@autoInjected
@defaultone string@default("5")
@descriptionone string@description("Max results to return.")
@enumone or more strings@enum("exec", "fs_read")
@requiredno arguments@required

prompt field

Written after a field's type in the body of a prompt.

AnnotationWritten asExample
@defaultone string or one number@default("en")
@descriptionone string@description("The episodes to distil.")
@enumone or more strings@enum("short", "long")
@requiredno arguments@required

builtin field

Written after a field's type in the body of a builtin.

AnnotationWritten asExample
@descriptionone string@description("The campaign to send.")
@enumone or more strings@enum("patch", "minor")
@requiredno arguments@required

Annotations

One entry per annotation, in alphabetical order: where it is accepted and how it is written there, its keys, and what it does. An annotation marked repeatable may be written more than once on one declaration; any other is refused the second time with annotation_repeated.

@actor

OnWritten asExample
query, mutation, logic, automation, shapeno arguments@actor
  • On a query, a mutation, a logic or an automation: 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).
  • On a shape: Shape kind marker: the shape projects the authenticated actor's envelope (actor.userId / actor.role / ...), and carries no signature concept.

@addToSet

OnWritten asExample
mutationone or more strings@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.

@alias

OnWritten asExample
builtinone string, repeatable@alias("memqlVersion")

Additional name the builtin is registered under. Repeatable.

@allowedRoles

OnWritten asExample
toolone or more strings@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).

@appendFields

OnWritten asExample
mutationone or more strings@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").

@args

OnWritten asExample
builtinkeyword arguments@args(profile="object")

@args takes these keys, each written as key=value.

KeyTypeMeaning
profilestringThe argument profile: none, object, optionalObject, stringOrObject, optionalString or optionalStringOrObject. Inferred from the body when absent.
stringKeystringThe key a bare string argument binds to, for the profiles that accept one.
additionalPropertiesstringWhether an object argument may carry keys the body does not declare (default false).
whenstringAn inert note naming the type of routingRuleActivate's when argument, which its body cannot declare (when is a keyword). No loader reads it.
excludesstringAn inert note naming the type of routingRuleActivate's excludes argument. No loader reads it.

Parse-time argument contract for builtin functions.

@autoInjected

OnWritten asExample
tool fieldno arguments@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).

@base

OnWritten asExample
providerno arguments@base

Mark a vendor-level base provider (auth + type only).

@cache

OnWritten asExample
queryone number or keyword arguments@cache(300)

@cache takes these keys, each written as key=value.

KeyTypeMeaning
ttlstringCache TTL in whole seconds. Positional preferred (#2618): @cache(300); keyword ttl="300" keeps parsing.

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.

@composable

OnWritten asExample
conceptno arguments or keyword arguments@composable(as="ticket", fields="title,status", list="openTickets")

@composable takes these keys, each written as key=value.

KeyTypeMeaning
asstringThe label a composed file names the row kind by, e.g. "invoice".
fieldsstringComma-separated fields to compose from, e.g. "number,issuedAt,total"; each must be a field the concept declares or a row intrinsic.
liststringThe bare name of the query that lists the rows to compose from.

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.

@createOnly

OnWritten asExample
mutationone or more strings@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.

@default

OnWritten asExample
providerno arguments@default
tool fieldone string@default("5")
prompt fieldone string or one number@default("en")
  • On a provider: Mark this provider as the default for its modality.
  • On a tool field: The default the tool's input schema advertises to the model, written quoted: its text converts to the field's type as a call's default always has -- @default("5") on an integer field, @default("true") on a boolean, one of the values of an enum field -- or the load refuses it (tool_default_type, memql#5430).
  • On a prompt field: The default the prompt's input schema declares for the field: its text -- quoted, or an unquoted number as written -- converts to the field's type by the rule a tool field's does, or the load refuses it (prompt_default_type, memql#5430).

@defaultProvider

OnWritten asExample
promptone string@defaultProvider("fleet")

Default AI provider for prompt execution: an explicit pin that wins over every routing rule.

@description

OnWritten asExample
queryone string@description("List the caller's open tickets, newest first.")
mutationone string@description("Rename one of the caller's tickets.")
logicone string@description("Close every ticket that has been idle for a week.")
automationone string@description("On a new ticket, notify its owner.")
conceptone string@description("A support ticket raised by a customer.")
shapeone string@description("Ticket summary card.")
spec and traitone string@description("Matches the rows that are still open.")
seedone string@description("Knowledge baseline for every professional role.")
promptone string@description("Distil a cluster of episodes into one memory.")
providerone string@description("OpenAI GPT-5.4 Mini -- balanced cost and latency.")
policyone string@description("Local strongest, then an app, then the cheapest federated model.")
ruleone string@description("Operator turns reason locally first.")
toolone string@description("Create a to-do for the caller.")
builtinone string@description("Preflight and start a campaign send.")
actionone string@description("Tag a release in the repository.")
capabilityone string@description("Create a git tag and a GitHub release for a version.")
concept fieldone string@description("The ticket's one-line title.")
tool fieldone string@description("Max results to return.")
prompt fieldone string@description("The episodes to distil.")
builtin fieldone string@description("The campaign to send.")
  • On a query, a mutation, a logic, an automation, a concept, a shape, a spec or trait, a seed, a prompt, a provider, a policy, a rule, a tool, a builtin, an action or a capability: 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).
  • On a concept field: The field's description, emitted into the concept schema.
  • On a tool field: The field's description, shown to the model in the tool's input schema.
  • On a prompt field: The field's description, carried into the prompt's input schema.
  • On a builtin field: The field's description, read into the generated SDKs' docs.

@destructive

OnWritten asExample
toolno arguments@destructive

Mark a tool as destructive (mutates/deletes); the tool loop gates it behind a confirmation.

@disabled

OnWritten asExample
query, mutation, logic, automation, spec and trait, seed, prompt, provider, rule, tool, builtin, action, capabilityno arguments@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.

@displayCard

OnWritten asExample
conceptkeyword arguments@displayCard(primary="title", secondary="status")

@displayCard takes these keys, each written as key=value.

KeyTypeMeaning
primarystringThe field shown as the row's title. Required.
secondarystringThe field shown under the title.
tertiarystringA third field shown in the row's detail line.
statusstringThe field shown as the row's 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.

@enum

OnWritten asExample
args fieldone or more strings@enum("open", "closed")
tool fieldone or more strings@enum("exec", "fs_read")
prompt fieldone or more strings@enum("short", "long")
builtin fieldone or more strings@enum("patch", "minor")

The closed set of string values the field accepts: @enum("a", "b"). The enum("a", "b") type is the same constraint in one statement.

@eventField

OnWritten asExample
logicone or more strings@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.

@exclude

OnWritten asExample
ruleone or more strings, repeatable@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.

@executionTime

OnWritten asExample
toolone string@executionTime("fast")

Expected execution time hint: "fast", "medium", or "slow".

@executor

OnWritten asExample
builtinone string@executor("integration.campaigns.startSend")

Go executor name for builtin functions (integration.X.Y).

@extends

OnWritten asExample
providerone string@extends("openai")

Inherit configuration from a base provider.

@fallback

OnWritten asExample
policyone string, repeatable@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.

@filter

OnWritten asExample
automationan expression@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").

@handler

OnWritten asExample
toolkeyword arguments@handler(type="function", name="createTodo")

@handler takes these keys, each written as key=value.

KeyTypeMeaning
typestringHandler type: "function", "query", "webhook" or "delegate". Required.
namestringFunction or builtin name (with type="function").
querystringMemQL query or mutation call (with type="query").
urlstringWebhook URL (with type="webhook").
methodstringHTTP method for a webhook handler, e.g. "POST".

Tool handler configuration. Format: @handler(type="query", query="...") / @handler(type="function", name="...").

@internal

OnWritten asExample
concept fieldno arguments@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.)

@level

OnWritten asExample
promptone string@level("fast")
ruleone string@level("strong")
  • On a prompt: 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.
  • On a rule: The level to resolve the call at: fast, strong, reasoning or embeddings, OVERRIDING what the call declared.

@locked

OnWritten asExample
ruleno arguments@locked

On a rule: evaluate before every unlocked rule regardless of precedence. Accepted only in the embedded tree -- the loader refuses it elsewhere.

@loop

OnWritten asExample
automationkeyword arguments@loop(maxDepth=4, until=row => row.status == "done")

@loop takes these keys, each written as key=value.

KeyTypeMeaning
maxDepthintThe most runs of this automation one causal chain may hold. An integer from 1 to the depth cap (MEMQL_AUTOMATION_MAX_CHAIN_DEPTH, default 16).
untilexpressionThe convergence predicate: a lambda of one parameter over the triggering row, until=row => row.status == "done". The automation's @filter must hold its negation as a top-level conjunct, which is what stops the loop.

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.

@maximum

OnWritten asExample
concept fieldone number@maximum(100)
args fieldone number@maximum(60)
  • On a concept field: The INCLUSIVE upper bound on a numeric value.
  • On an args field: The INCLUSIVE upper bound on a numeric argument (memql#4522).

@maxLength

OnWritten asExample
concept field, args fieldone number@maxLength(120)
  • On a concept field: The most characters a string value may carry.
  • On an args field: The most characters a string argument may carry (a rune count, enforced on strings).

@mcp

OnWritten asExample
query, mutation, automation, toolno arguments@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).

@mergeFields

OnWritten asExample
mutationone or more strings@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").

@minimum

OnWritten asExample
concept fieldone number@minimum(0)
args fieldone number@minimum(30)
  • On a concept field: The INCLUSIVE lower bound on a numeric value.
  • On an args field: The INCLUSIVE lower bound on a numeric argument (memql#4522).

@minLength

OnWritten asExample
concept fieldone number@minLength(1)

The fewest characters a string value may carry.

@mirroredTo

OnWritten asExample
conceptone or more strings@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 data-origins.md.

@modality

OnWritten asExample
providerone string@modality("embedding")

Provider modality (e.g., "chat", "audio", "image", "embedding").

@mode

OnWritten asExample
automationkeyword arguments@mode(queued, max=10)

@mode takes these keys. A key of type flag is written bare, and every other as key=value.

KeyTypeMeaning
singleflagOne run at a time in this process; a fire while one runs is refused with a warning.
queuedflagFires wait their turn in order; more than max waiting are refused.
restartflagA fire cancels the run in flight and starts again.
parallelflagRuns concurrently; more than max at once are refused. The default when no @mode is written, with no max.
maxintWith queued, the most fires that may wait (default 10); with parallel, the most runs at once.

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.

@model

OnWritten asExample
providerone string@model("gpt-5.4-mini")

Model identifier (e.g., "gpt-5.4-mini", "claude-sonnet-4-6").

@noUnset

OnWritten asExample
mutationone or more strings@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.

@onUnavailable

OnWritten asExample
ruleone string@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.

@open

OnWritten asExample
concept fieldno arguments@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.

@origin

OnWritten asExample
conceptone string@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 data-origins.md.

@pattern

OnWritten asExample
concept fieldone string@pattern("^[a-z][a-z0-9-]*$")
args fieldone string@pattern("^[A-Za-z]{2,3}$")
  • On a concept field: 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.
  • On an args field: A regular expression a string argument must match, compiled once at load.

@pii

OnWritten asExample
concept fieldno arguments@pii

The field is personally identifying: emitted as x-pii, and the hard-delete scrub (@scrubPii, memql#1711) zeroes every such field generically.

@policy

OnWritten asExample
ruleone string@policy("localFirst")

Required on a rule: the policy this rule resolves the call through.

@precedence

OnWritten asExample
ruleone number@precedence(60)

On a rule: evaluation order, highest first. A tie between two rules of the same locked-ness is a load error.

@primary

OnWritten asExample
policyone string@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>.

@public

OnWritten asExample
query, mutationno arguments@public

Per-row-authz marker: this query/mutation is intentionally callable without a caller-scope filter (concept catalogs, pre-auth login paths). See per-row-authz-audit.md.

@relationship

OnWritten asExample
conceptkeyword arguments, repeatable, in the body@relationship(type="parent", field="ownerUserId", target=user, direction="outgoing")

@relationship takes these keys, each written as key=value.

KeyTypeMeaning
typestringSTRUCTURAL type -- what the engine does with the edge. Closed set: parent, owns, createdBy, alias, equals, contains, references.
fieldstringLocal field holding the foreign key; a dotted path reaches into a nested object block.
fieldSourcestringWhere the foreign key lives: "payload" (the default) or "table" (the edge table).
targetstringTarget concept: a bare concept name resolved through a file-top use import.
directionstring"outgoing" or "incoming".
asstringDOMAIN label -- what the edge means, e.g. as="assignedTo". Optional. Any lowerCamelCase identifier; validated for form only and never checked against a list, so a new verb never needs an engine release (memql#3652).

Foreign-key relationship metadata. Format: @relationship(type="parent", field="x", target=concept, direction="outgoing"), plus an optional as="domainVerb" label.

@removeFromSet

OnWritten asExample
mutationone or more strings@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.

@required

OnWritten asExample
concept field, args field, tool field, prompt field, builtin fieldno arguments@required
  • On a concept field, a tool field, a prompt field or a builtin field: 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.
  • On an args field: The caller must pass the argument. The ! sigil after the type is the same thing.

@requiresAgentRole

OnWritten asExample
toolone or more strings@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.

@requiresCapability

OnWritten asExample
queryone or more strings@requiresCapability("read", "principal")
mutationone or more strings@requiresCapability("execute", "app:deployables/publish")
logic, builtinone or more strings@requiresCapability("execute", "app:deployables/deploy")

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.

@requiresConfirmation

OnWritten asExample
toolno arguments@requiresConfirmation

Require explicit user confirmation before the tool executes.

@requiresRank

OnWritten asExample
query, toolone string@requiresRank("developer")
mutation, logicone string@requiresRank("admin")
  • On a query, a mutation or a logic: 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.
  • On a tool: The person-rank FLOOR on a tool: a call is admitted only when the person it is for -- the authenticated user over MCP, or the user an agent acts for in its tool loop -- holds this role or one ranked above it, and a tool above the caller's rank is not listed to them either. VALIDATED AT LOAD against the role ladder in dsl/rbac exactly as on a query, mutation or logic, and resolved at call time through the same ladder, so a custom role is admitted by its rank. It judges the PERSON and never the agent -- @requiresAgentRole is that gate -- and it replaces the person half of the deprecated @allowedRoles.

@row

OnWritten asExample
shapeno arguments@row

Shape kind: projects a concept's payload + row intrinsics (concept bound via the shape <Concept> <name> signature).

@rowAuthz

OnWritten asExample
conceptkeyword arguments@rowAuthz(owner="ownerUserId", clusterOwner)

@rowAuthz takes these keys. A key of type flag is written bare, and every other as key=value.

KeyTypeMeaning
publicflagThe public tier: globally readable by intent.
clusterOwnerflagThe administrative tier; beside owner=, the composite -- the owner, or a cluster owner (memql#4312).
ownerstringThe owned tier: the payload field compared against actor.userId, or "id" for a self-owned concept (memql#3029).
viastringThe granted tier: the relationship spec that grants visibility.
accountstringBeside owner=: also admits anyone whose group ties them to the row's account field (epic memql#5165).
rankVisibleflagBeside owner=: reads widen to anyone at or above the owner's rank (epic memql#4832).
rankStrictflagBeside owner= and rankVisible: writes widen to rows owned strictly below the caller's rank, and the cluster-owner write escape is withdrawn.
unownedstringBeside owner= and rankVisible: the actor rank from which a row with an empty owner is readable.
requiresIdentityflagBeside public: readable by authenticated callers only (memql#4809).
rankFloorstringBeside clusterOwner: relaxes the READ to a rank floor while the write stays cluster-owner (memql#5216).

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 per-row-authz-audit.md and memql#2803.

@scope

OnWritten asExample
seedone string@scope("perUser")

Seed scope: "global" seeds once for the cluster, "perUser" once for every user.

@scrubPii

OnWritten asExample
mutationno arguments@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.

@sdk

OnWritten asExample
builtinno arguments@sdk

Generator marker (sdk/gen reads from source); no engine effect.

@secret

OnWritten asExample
concept fieldno arguments@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.

@serverOnly

OnWritten asExample
query, mutationno arguments@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.

@serverSet

OnWritten asExample
concept fieldno arguments@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).

@shopperFormExtension

OnWritten asExample
mutationkeyword arguments@shopperFormExtension(pack="wholesale", form="application")

@shopperFormExtension takes these keys, each written as key=value.

KeyTypeMeaning
packstringThe pack whose declared shopper form this mutation extends, e.g. "wholesale".
formstringThe form's route name on that pack, e.g. "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").

@sideEffect

OnWritten asExample
capabilityone string@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.

@template

OnWritten asExample
automationno arguments@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.

@templateFile

OnWritten asExample
seedone string@templateFile("templates/assistant.tmpl")
promptone string@templateFile("prompts/consolidateMemory.tmpl")
  • On a seed: A template file whose rendered text is the seeded row's content.
  • On a prompt: The prompt's template: a .tmpl file beside the prompt, rendered with the input fields.

@trigger

OnWritten asExample
automationkeyword arguments@trigger(event="node.created", concept="v1:cluster:node")

@trigger takes these keys, each written as key=value.

KeyTypeMeaning
beforestringAdjust the incoming row before create, update, or write; requires concept.
eventstringEvent pattern, e.g. "node.created" (with concept=) or a raw topic such as "system.startup".
conceptstringConcept id the triggering event targets; required by the structured node.* event kinds.
schedulestringCron schedule with a leading seconds field, e.g. "0 0 * * * *".
filterexpressionThe trigger filter as a keyword: a lambda of one parameter over the triggering row, filter=row => <predicate>. The standalone @filter(...) annotation is the usual spelling and sets the same filter.
onstringA synonym for event=: on=<concept>.<created|updated|deleted>, with the concept named through the file's use import, folds to the same graph.node.<action>.<concept> pattern event= names (resolved by the automation loader and the concept resolver). A later epic retires the synonyms (D15/D17).

Event trigger for automations. Format: @trigger(event="graph.node.created.*.v1:ns:concept") or @trigger(schedule="0 0 * * * *").

@type

OnWritten asExample
conceptone string@type("collection")

The concept's row kind: "object" (the default), "collection" or "reference".

@unbounded

OnWritten asExample
queryone string@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 authoring-rules.md.

@variant

OnWritten asExample
concept fieldkeyword arguments@variant(discriminator="kind")

@variant takes these keys, each written as key=value.

KeyTypeMeaning
discriminatorstringThe field whose value picks the branch.

A discriminated union: @variant(discriminator="kind") on an object field, followed by one block per branch; the discriminator field's value picks the branch.

@vendor

OnWritten asExample
providerone string@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.

@version

OnWritten asExample
concept, seedone string@version("1.0.0")
  • On a concept: Version tag for the concept: a semver string, @version("1.0.0"). Metadata only -- canonical ids are not versioned by it (#2613).
  • On a seed: Version tag for the seed.

@when

OnWritten asExample
ruleempty parentheses or keyword arguments@when(level="fast")

@when takes these keys, each written as key=value.

KeyTypeMeaning
levelstringThe level the call declared: fast, strong, reasoning or embeddings.
modalitystringThe modality derived from the call site: chat, streamingChat, tools, streamingTools, structured, vision, embedding, speech, transcribe.
promptstringThe DSL prompt this call renders. Empty matches a Go call site with no prompt.
rolestringThe AGENT's role slug. Distinct from actorRole: this is what is acting.
actorRolestringThe calling human's cluster role. Distinct from role: this is who is watching.
tagstringA call tag, e.g. "background" or "backgroundEscalation".
touchesstringA concept id PREFIX the call's footprint matches (startsWith semantics).

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.

Retired

A retired name is refused where the table says, with annotation_retired and a hint that says what to write instead.

AnnotationWhereWrite instead
@actorspec and traitIt is a shape-only marker since epic #2281 -- to predicate on the caller, bind an @actor shape in the signature (spec <shape> <name>) and read its projected key by bare name.
@asynceverywhereRefused on automations since memql#2712: an automation already runs asynchronously off its event or schedule trigger, and nothing reads the annotation -- delete it.
@auditeverywhereRemoved from the allow-lists in memql#989; nothing reads it -- delete the annotation; run memqlmigrate --rewrite=attributes.
@cacheconceptA concept carries no cache setting -- a read caches, so set the TTL on the query that reads the concept (@cache(300)); a write to the concept evicts every cached read of it.
@callershapeUse @actor (#221); the field accessor was renamed at the same time (caller.X -> actor.X).
@clientExecutiontoolIt dispatched the tool to the connected browser over the client-tool relay, which was removed with the cognition node (epic memql#4988). Every tool now needs a server-side @handler.
@conceptsshapeBind the concept in the signature instead: shape <Concept> <name> { ... }, with the concept imported by a file-top use line.
@defaultconcept fieldMemql#5375: it is never applied on insert -- it was published as the JSON-Schema default keyword, which no validator applies; write args.<field> ?? <default> in the mutation body. It stays live on a tool / prompt / builtin field, where the body IS the schema the model reads; run memqlmigrate --rewrite=attributes.
@defaultargs fieldIt is never applied; write args.<field> ?? <default> in the body (a concept-field @default is not a substitute -- it is never applied on insert either).
@deprecatedeverywhereMemql#5375: it was rendered by help() and editor hover and read by nothing that changes behaviour -- delete it, or say so in the construct's @description; run memqlmigrate --rewrite=attributes.
@descriptionargs fieldIt was never retained (no AST slot); document the field with a /// doc comment on the line above it (memql#3336).
@enabledeverywhereMemql#5375: constructs are enabled by default, so @enabled was an explicit no-op that read like a switch -- delete it, and use @disabled to deactivate; run memqlmigrate --rewrite=attributes.
@idempotenteverywhereRemoved from the mutation allow-list in memql#989; nothing reads it -- delete the annotation; run memqlmigrate --rewrite=attributes.
@immutableeverywhereMemql#5375: declared metadata with no write guard behind it -- delete it; a field that must not change is enforced by the mutation that writes it; run memqlmigrate --rewrite=attributes.
@internaleverywhere except concept fieldRetired under the 2026.08 epoch (#2620 ruling / #2708); it only hid the construct from external discovery surfaces (tool listing, MCP promotion, the help()/listFunctions internal flag) while leaving it callable -- delete the annotation.
@kindactionAn action is always one primitive capability call now, and a composite of several is an automation, so there is nothing to mark; remove it.
@latestModeeverywhereMemql#5375: the engine derives time-dependence from asOf latest in the body, so the annotation restated it and could contradict it -- delete it; run memqlmigrate --rewrite=attributes.
@namespaceeverywhereMemql#5375: a concept's namespace is its domain directory, or that directory's one-line namespace.pin -- the annotation could only restate one of those or silently disagree with it; delete it, and pin a deliberate divergence with a namespace.pin file (#2614); run memqlmigrate --rewrite=attributes.
@nocacheeverywhereMemql#5375: write @cache(0) -- one annotation for the cache TTL, with 0 meaning never; run memqlmigrate --rewrite=attributes.
@permissioneverywhereBuried (#2631 ruling close-out / #2713); the @role twin -- documented but never enforced (its one help-payload reader was dead; the load gate rejects it) -- access control lives at the actor layer (RBAC + the @public per-row-authz classification).
@rateLimiteverywhereMemql#5375: Tool.RateLimit was cloned and copied into a Function field nothing reads, so the declared ceiling did not exist -- delete it; the live ceilings are the provider chokepoint in ai_guard.go and the run budget in component/work; run memqlmigrate --rewrite=attributes.
@reliabilityactionReliability is runtime state the engine keeps, not something the source declares; remove it.
@retryeverywhereRemoved from the allow-lists in memql#989; nothing reads it -- delete the annotation; run memqlmigrate --rewrite=attributes.
@roleeverywhereBuried (#2631 ruling / #2709); it was documented but never enforced (nothing ever checked the value at runtime; the load gate rejects it) -- access control lives at the actor layer (RBAC + the @public per-row-authz classification).
@rowspec and traitIt is a shape-only marker since epic #2281 -- to predicate on row metadata, bind a @row shape in the signature (spec <shape> <name>) and read its projected key by bare name.
@scheduleautomationA scheduled automation is written @trigger(schedule="<cron>"), the one spelling (D15, epic memql#5370); memqlmigrate --rewrite=bodies rewrites it.
@scopeconceptRemove the annotation; every concept lives in the default partition post-#56.
@scopeseverywhereMemql#5375: Tool.Scopes was advertised on the gRPC tool descriptor and checked nowhere, so it read as an authorization gate while gating nothing -- delete it; use @requiresCapability for a real one; run memqlmigrate --rewrite=attributes.
@shapespec and traitA spec binds its shape or concept in the signature (epic #2281): spec <boundName> <name> = row => <predicate>, with boundName resolved through the file-top use import.
@sideEffectactionThe authoritative side-effect class lives on the capability declaration the action calls, where an action cannot overstate or understate it; remove it from the action.
@timeouteverywhereRemoved from the allow-lists in memql#989; nothing reads it -- delete the annotation; run memqlmigrate --rewrite=attributes.
@uniqueeverywhereMemql#5375: declared metadata with no uniqueness check behind it (memql#2960), so it read as a constraint while constraining nothing -- delete it; run memqlmigrate --rewrite=attributes.
@useX, for any X that starts with an upper-case lettereverywhereDeclare the dependency with a file-top use <module>.{ ... } import instead, and put a bound concept in the signature (query <Concept> <name> { ... }).
@visibilityeverywhereRemoved in the genesis simplification; it chose which node types load a construct, and every binary now loads everything while build tags decide which integrations are active -- delete the annotation.

Refusals

Every refusal names the construct and the annotation, says what to write instead, and ends with its code in square brackets, as in [annotation_unknown].

CodeMeaning
annotation_unknownNo construct or field accepts the name. The refusal suggests the nearest name the construct does accept, and lists them all.
annotation_retiredThe name is retired where it was written. The refusal carries the migration hint, which says what to write instead.
annotation_misplacedOther constructs or fields accept the name, but not this one. The refusal names where it is accepted and shows it written there.
annotation_formThe construct accepts the annotation, but not written this way: a string where it takes a number, or arguments on a flag. The refusal says what it takes and shows its example.
annotation_keyA keyword argument the annotation does not have, or a key written in the wrong shape: a flag given a value, or a valued key written bare. The refusal lists the keys and shows the example.
annotation_repeatedAn annotation that is not repeatable is written twice on one declaration, so a reader cannot tell which one takes effect. Keep one.