Skip to main content
OmniScript 2.0

Selectors and the document graph

Ids and @ref turn a document into a graph. Selectors are a small query language over that graph, short enough for an agent to write without documentation.

Ids

Any block may carry id: "…". Ids match [A-Za-z_][A-Za-z0-9_.-]* and must be unique in a document. Semantic and agent blocks need one. Blocks without an id get a derived id such as doc-2 or slide-1 (the type and its position among blocks of that type). Derived ids are handy in queries but change when you edit the file, so give blocks you refer to a real id.

References and edges

@ref("id") is a value that points at another block. The property that holds it names the edge:

EdgeUsed onMeaning
evidence@findingBlocks that support the claim
criteria@decisionBlocks that inform the decision
supports@hypothesisEvidence for
contradicts@hypothesisEvidence against
depends_on@requirement, @actionPrerequisites
produces@action, @taskArtifacts produced
source@sheet, @table, @chart, semantic blocksData source or origin
task@artifactThe producing task

A property that takes an array of references also accepts a single @ref. A reference to an id that does not exist is an osf lint error (dangling-ref). Renderers show references as links to the target. In Markdown, link to a block with [text](#id).

$ osf graph report.osf
F-1 --evidence--> budget
F-1 --evidence--> chart-actuals
D-1 --criteria--> F-1
D-1 --criteria--> risks

osf graph --dot prints Graphviz, osf graph --json prints { nodes, edges }, and so does graph() in omniscript-core.

Selector syntax

selector  = term { "," term }
term      = node { ("->" | "<-") edge }
node      = "#" id | type { "[" filter "]" } | "*" { "[" filter "]" }
filter    = key | key op value
op        = "=" | "!=" | "<" | "<=" | ">" | ">=" | "~"
  • type is a block name: finding, decision, table, doc, code (or osfcode), and so on. * matches every block.
  • key is a property name or a special key: id, type, tag (an element of tags) and text (the body text and the block's title-like property).
  • [key] alone means the property is present.
  • ~ is a case-insensitive "contains". Numbers compare as numbers; everything else, including ISO dates, compares as text. For arrays any element may match; for != no element may.
  • -> edge follows references out of the current blocks through that property; <- edge goes backwards. * as the edge follows every reference property.
  • Results come back in document order, without duplicates.

Examples

SelectorResult
#F-1The block with id F-1
findingAll findings
finding[confidence>=0.8]Findings with confidence of at least 0.8
finding[status=confirmed][confidence>0.7]Filters combine with AND
finding[tag="nordic"]Findings tagged nordic
decision[status=pending]Open decisions
decision[deadline<2027-01-01]Decisions due before 2027
#D-1 -> criteriaWhat decision D-1 is based on
#D-1 -> criteria -> evidenceThe evidence behind those criteria
sheet <- evidenceBlocks that cite a sheet as evidence
action[status!=done]Open actions
requirement[level=must]Mandatory requirements
finding, decisionUnion
*[text~budget]Any block that mentions “budget”

Where selectors work

SurfaceHow
CLIosf query report.osf 'finding[confidence>=0.8]' [--json]
JavaScriptquery(text, "finding[confidence>=0.8]")
MCPThe osf.query tool
A2AThe built-in osf.query skill, and osf.export with a selector
PlaygroundThe query box under the editor

There is no projection or aggregation: you get whole blocks as JSON and do the rest yourself.