Use Query workspace (rollout.query) for an authorized, read-only query over a workspace source. Filters use the shared query grammar, for example object:ledger created_at:>=-7d, and order_by accepts comma-separated field:direction keys.

Pick the source

Query has one records contract. Choose the source and filter it with the same grammar on every surface. Task turn context comes from the authenticated Start context, not a Query setting. To authenticate a persisted step result, query WorkflowStepRun by exact ID, include workflow_run, and compare its returned canonical reference with the expected value using reference_matches.

Actions belong to consumers

Saved actions belong to an App, Agent or Workflow AI node. Tool and ToolImpact are not Query sources. Inspect the owning consumer's actions for configuration and readiness, and use Calls for execution evidence. Query does not expose another consumer's settings, grants or credentials.

Shape the response

fields accepts dotted paths such as data.external_id and data.amount. exclude removes dotted paths after that projection, which is useful for dropping source payloads such as data.raw. The record id is always retained. Unknown fields reject the whole query, including with strict: false. Strict validation additionally checks operators and enumerated values; omitting the setting uses the strict default. Use labels:; label: is no longer accepted.

Rich text reads as Markdown: a Task, Project, Objective, Key Result or Milestone description, an Activity body, a Document content, and a custom record's rich-text values under data. Every row of a source with rich text also carries a plain-text summary of up to 160 characters. For an Activity, summary is a one-line excerpt, so a step that reads the whole comment or note reads item.body (and names body when it sets fields). Set Rich text as to json to get the rich-text document instead of Markdown.

The summary is an excerpt of the rich text, so exclude treats it as part of it: excluding description, body, content, data or any data.<field> also removes summary. Excluding summary alone leaves the rich text.

Large workflow-history fields are opt-in. Add error to include for WorkflowRun records, or inputs / outputs for WorkflowStepRun records. They are otherwise neither selected from PostgreSQL nor emitted in node output.

Add workflow_run to include on WorkflowStepRun records to get the identity of the run that executed the step, nested under workflow_run: id, workflow_id, release_id, release_name, source_revision, flow_source, status, outcome, run_mode, run_type and partial_flow. That is enough to tell a published run from a debug, replay or partial one, and to reconcile a step's output with the exact release that produced it — one query rather than a second lookup. The run's flow, inputs, results and credentials are never part of it, and a step whose parent is unavailable simply has no workflow_run key. WorkflowRun records carry the same identity in their own projection.

flow_source: "editor" covers both the main Draft and a branch Draft, and branch_id is what separates them — null means main. source_revision is the Draft's lock version at admission, which separates two runs of the same Draft taken at different revisions; a release has none and a run recorded before the field existed reports null, meaning unknown rather than 0. The branch's NAME is not part of the identity: it lives on an association, and a name missing because it was not loaded would read exactly like main.

The same identity is filterable on WorkflowRun: branch:<uuid> selects one branch's runs, branch:main the main-Draft ones (null and none are accepted spellings of the same thing), and branch:!<uuid> everything else, main-Draft runs included. release:<uuid> filters the published snapshot and flow_source:editor the source kind. order_by accepts source_revision.

Add branch to include on WorkflowRun records to name the branch: the record gains branch as {id, name}. It is an identity-only association, so naming a branch never pulls its flow or draft graph out of Postgres. A run that executed main has no branch and therefore no branch key — read branch_id, not the key's absence, to tell main from a branch.

Complete and latest-per-group reads

Every result carries a page object with returned_count, exact total_count, and truncated; drag the one you need out of its popover. A configured aggregate arrives as aggregate (series and meta). on_truncation controls a partial result: warn (default) logs a warning, fail returns query_truncated with both counts, and ignore accepts the page while keeping truncated: true.

Enable fetch_all to page through all matches. The operation fails with query_fetch_limit_exceeded before materialization when more than 10,000 rows match; narrow the query or use a checkpoint-oriented domain API in that case.

distinct_on keeps the first row for each group. The node automatically makes the distinct key the first ascending order key, then applies order_by. For example, distinct_on: "data.account_id" with order_by: "created_at:desc" returns the newest Record per account. A data.<field> key must be an active sortable scalar field on the one entity type selected by the query.

For a Record query, the object setting restricts results to that object, exactly like an object:<slug> term in the query. A query that also names a different object fails instead of returning nothing.