Specification
The expression language.
Lifted from @/theory/grid-expression-language, locked 2026-04-08.
The grid expression language is the surface that humans and AI (residents) both speak when they want to do anything to a grid. It is not SQL, not Python, not a query DSL bolted onto a database. It is the language Grid programs are interrogated, mutated, and observed in. The same evaluator runs an expression typed at a terminal, an expression embedded in an attention cell, and an expression that defines a capability slice.
Design discipline (locked)
The grammar is designed for completeness, not coverage. The test is not "does this verb match a scenario we listed" but "is the algebra closed under composition such that any reasonable scenario reduces to it." If a scenario cannot be expressed by composing the six axes below, that is a real hole, the algebra grows by exactly the missing axis, never by a new feature-flag verb.
The six axes (closed set)
1
SELECT
pick a set of cells out of the grid
2
PROJECT
render the selection in a chosen shape
3
FILTER
narrow a selection by a predicate
4
MUTATE
write, supersede, move
5
OBSERVE
react to changes over time
6
COMPOSE
pipe one expression's output into another's input
Every other operation is a combination of these. fetch is SELECT(addr) PROJECT(full). trace is SELECT(history-of addr) PROJECT(line). diff is SELECT(a) compared-with SELECT(b). count is SELECT PROJECT(count). The grammar freezes the six axes and nothing else.
Grammar (BNF)
expression := pipelinepipeline := stage ( "|" stage )*stage := select | mutate | observeselect := "study:" target ( "where:" predicate )? ( "as:" projection )?mutate := write | movewrite := "write" address "type:" typename "body:" body-string ( "by:" identity )?move := "move" address "to" addressobserve := "watch" select-or-pipelinetarget := address | glob | refs-of | type-query | history-ofrefs-of := "refs-of:" addresshistory-of := "history-of:" addresstype-query := "type=" typenamepredicate := atom ( ("and"|"or") atom )*atom := slot-pred | type-pred | address-pred | ref-pred | "not" atom | "(" predicate ")"slot-pred := "slot:" slotname op valuetype-pred := "type=" typenameop := "=" | "!=" | "<" | ">" | "<=" | ">=" | "contains" | "matches"projection := "full" | "line" | "compact" | "summary" | "addresses" | "types" | "json" | "count"address := "@/" segment ( "/" segment )*Operator note: contains and matches accept barewords on their right-hand side. where: slot:title contains widget works without quoting widget. Quoted strings are also accepted for values that need spaces or special characters.
The @, &, ?, :, =, #, + markers above are the seven sigils. See Sigils for the canonical reference on each one.
Examples
Each of these parses + round-trips against the real evaluator.
Selection
1. Single cell, full body, the default projection.
study: @/crm/contacts/cameron-silva2. Glob across a subtree.
study: @/crm/interactions/2026-04-*/*3. Reverse-ref selection (zero body reads), every cell that references this contact.
study: refs-of: @/crm/contacts/cameron-silva4. Type-indexed selection (no glob).
study: type=interactionProjection
5. Render as line digest, one row per cell.
study: type=interaction as: line6. Count projection, cheap probe of cardinality.
study: type=interaction where: slot:promised < today as: count7. Address projection, cheap navigation without reading bodies.
study: refs-of: @/crm/contacts/cameron-silva as: addressesFilter
8. Predicate with slot match against today.
study: type=interaction where: slot:promised < today9. Composite predicate (and / or).
study: type=interaction where: slot:promised < today and slot:direction = outbound10. Parens + negation.
study: type=contact where: not (refs @/crm/accounts/acme)History
11. Every supersession of one cell, projected as a line digest. git log for cells.
study: history-of: @/crm/contacts/cameron-silva as: lineCompose
12. Pipeline: select, then filter that result, then project.
study: type=interaction | study: where: slot:promised < today as: lineMutate
13. Write a new cell.
write @/crm/contacts/new-person type: contact body: name: jane doe, role: cto14. Move.
move @/crm/contacts/old-name to @/crm/contacts/new-namemove is canonical in the grammar. The executor is deferred by design: move reduces to a write at the destination plus a tombstone at the source, both of which already work via write and delete. Until the executor lands, compose those two stages directly.
Observe
15. Watch, wrap any select-or-pipeline. Re-evaluates whenever a write could change the result.
watch study: type=interaction where: slot:direction = inboundWhat falls out of the algebra (not separate features)
The CLI's surface area is much larger than its verb count suggests, because the grammar is closed under composition. A few capabilities that are not implemented as separate features:
- Live customer-support inbox in a terminal:
watch study: type=interaction where: slot:direction = inbound - git log for cells:
study: history-of: @/crm/contacts/cameron-silva as: line - See the grid the way another identity sees it:
grid as alice 'study: @/', spatial capability becomes a one-flag UX feature. - Query your own terminal history with grid expressions:
study: type=repl-history where: slot:identity = ant as: line - The access-control configuration is itself queryable:
study: type=capability, the security surface is the same surface as the data surface.