Reports
Sources
Docs: Plots, Policies, Powers Web App: Open a Parable in Ponder. The Refresh menu at the top right of the Parable editor leads with Refresh, which runs the Report now, and Plan Refreshes, which schedules it; below them it selects Latest or a stored run, which the UI calls a Refresh. Refresh in an empty run History of a Plot runs it too. A Report is one execution of a Parable’s Pipelines, the Plots whose Pipeline switch is on. Each run produces durable outputs that readers can select later, even after the Parable’s code changes. A Parable’s definition version identifies code and schema; a Report identifies produced data. Parables are product experiences, and a Page that makes its reader wait has failed. Make any expensive query a Pipeline, judged by what it scans, joins, aggregates or calls and by measured read times, so Pages read stored rows instead of computing them on every read. A reader sees when the data was produced and can refresh it. A Pipeline is as fresh as its last run, so schedule runs as often as the Pages need. A Parable delivered to a Workspace carries its Reports. The Workspace can run and schedule them without changing them, and publishing its own changes keeps them.Run a Report now
- Open the Parable and choose Refresh at the top of the Refresh menu,
or Refresh in a Plot’s empty run History. Both send the Plot Report
action (
plot). The menu offers it to a seat that may work in Publishing and moves you to Publishing first when you are Peeking or Pinging. - Parable captures the exact definition, the SQL implementation of each Plot it runs, and your identity as the requester.
- The run is queued and produces real outputs before it is shown as complete.
A run is
pendinguntil a Plot starts,runningwhile its Plots execute, andcompleteonly when every Plot in it has succeeded. The first Plot that fails makes the runfailed, and a cancelled Plot makes itcancelled; neither publishes any output. Each Plot result has its own status:pending,running,budget_exceededwhile it waits for a higher budget,complete,failedorcancelled. The run picker lists every run with its status, and only a complete run has rows to read. Selecting any other run shows its status, and its message when it failed, never rows. Pending, failed, empty, stale, unavailable, and incompatible results are shown as such; Parable does not switch readings to hide a failure. Each Plot’s History tab shows the same runs; see Plots.
Cancel a run or raise a paused Plot’s budget
A run that is still moving can be cancelled, from Cancel Refresh at the top of the Refresh menu or from its row in a Plot’s History tab. Every Plot still queued, running or paused at its budget stops. Model calls already paid for stay cached, so running the Report again does not pay for them twice. The run becomescancelled within a few
seconds and never becomes readable; the newest complete run stays the one
Latest reads. Cancelling a cancelled run changes nothing. A run that already
completed cannot be cancelled. A run that a failed Plot is failing becomes
failed, and the cancel says so.
Cancel also settles a run whose last Plot status never reached the Report, so
the run looks unfinished although nothing of it is still running. The Plots
that finished but never reported close as cancelled, and the run becomes
cancelled, or failed when one of its Plots failed. The Report can then run
again. On a run that already failed, Cancel closes any Plot it left showing
running. A cancelled scheduled run does not count as a failure of its Report
plan.
A Plot that reaches its cost, call or runtime cap pauses with
budget_exceeded while the run stays running. Raise budget on that
Plot’s History row shows what it has spent against its caps and takes new
caps for this run only. Each new cap must be above the current cap and above
what the Plot has used; the runtime cap counts from when the Plot started,
including the time it was paused. The Plot resumes from what it had spent,
without a publish, and its published caps do not change.
Cancelling uses the same check as Plot Report: Parable manage permission
and a Responsible seat. Raising a budget allows spend beyond the published
caps, so it needs the tenant.parables.runs.budget.override permission, which
Workspace Administrators hold, and the Primary seat. On /admin/ponder the
permission is admin.parables.runs.budget.override. Both are recorded in the
audit trail with the person, the run and, for a raise, the previous and new
caps.
query-runner executes every captured run, in every environment. Parable sends
the run to query-runner after capture commits, as one query run per member
Plot. Each query run executes the Plot’s captured SQL at the captured publication, so a
later publish does not change it. The two sides link both ways: the Report run
records enqueuedAt, each Plot result records its queryRunId, and each
query run records the Report run’s id as report_run_id. If query-runner
cannot be reached, the request returns an error and the run stays pending
without enqueuedAt; retrying with the same request key sends it again. If
query-runner refuses the run, or its captured inputs no longer match its
published version, the run fails.
A run also gets sent when nobody retries, for example because web-api stopped
between saving the run and calling query-runner. On every sweep of the Report
scheduler, query-runner reads the
Report runs it never accepted
from web-admin-api and enqueues each one itself. The read lists runs captured
for query-runner more than a minute ago that are still pending without
enqueuedAt, oldest first, each with the payload Plot Report sends. The
enqueue is the same idempotent one, so a run whose query runs already exist
gains no more. The run leaves the list when query-runner’s first progress
report sets enqueuedAt. A listed run whose captured inputs no longer match
its published version fails, as it would on retry. Runs are sent this way
only where the scheduler is on (QUERY_RUNNER_REPORT_PLAN_SWEEP_ENABLED). A run executes under its captured
principal: the person who chose Plot Report, or the Workspace for a
scheduled run. Planning applies that principal’s Policies (each Plot’s Pane
Policies and each Provider table’s Provider Policy), writes them into the plan
query-runner executes, and records the source authorities it read. A person
whose Provider Policies narrow a Provider table the run reads is refused
before the run starts, because the run would store only their rows for
everyone; no Provider Policy narrows the Workspace.
A Plot in a Report run reads four kinds of input:
- Provider and Plot tables (
providers.*,parables.*). Planning applies the principal’s Policies to each (each Plot’s Pane Policies and each Provider table’s data policy), writes them into the plan query-runner executes, and records the source authority it read. - Artifact tables (
artifacts.*). They have no Policies. query-runner reads each at its pinned version, as below. - Workspace tables (
workspace.*, such asworkspace.users). They have no Policies and hold only the run’s Workspace’s rows, as any query of the Workspace sees them. query-runner cannot reach them, so planning reads each table once and writes its rows into the Plot’s plan. - Settings (
workspace.preferences). Their rows depend on the person, so a Plot reads the settings shared for This Parable that the run captured, as below.
providers_quality.summary.*, and name it. A run of a
Pipeline published earlier refuses the table when it plans that Plot: the Plot
fails with QL-PL-016 (REPORT_SOURCE_UNGOVERNED), and its message says it
reads a table a Report run cannot read. Read such a table in a Plot that is not
a Pipeline.
Every Plot of a Report run reads its inputs as of one time, the run’s AS OF
time. Every run records an
AS OF time. For Plot Report it is the moment you chose it; for a scheduled
run it is the occurrence, even when the scheduler reaches it late. Each Plot’s
query run pins every Delta input to the last version committed before that
time: every Provider, Plot and artifact table. Times are compared to the whole
second, so a commit made in the same second as the AS OF time is not read. A
commit that lands while the run is queued, or between two Plots, is not read
either, and a restart pins the same versions. Each pin also records when its
version was committed, to the second (committedAt): how old the data the run
read from that table was. A pin of a Provider Pool table, and an empty input of
one, names its Provider as Plot SQL does (provider, zoom for
providers.zoom.meetings). Workspace tables have no versions.
A Plot reads them when its query run is planned, at or after the AS OF time,
and its pin set lists each one under unpinnedInputs with reason captured
and the time it read it. Every stage of that Plot reads the same rows. The
Plots of one run are planned separately, so two of them can read a Workspace
table a few seconds apart, and a restart from planning reads it again.
Settings are the exception: the run stores the settings shared for This Parable
when it is captured, and every Plot reads workspace.preferences as it was
then, keeping only the settings whose Pane’s view.preferences Policy allows
the run’s principal. A Pane showing that run also reads its captured settings,
filtered by the reader’s current Policies (see Workspace Pool).
An input whose table has never been written, or whose Provider is not
connected in the Workspace, reads as an empty table, and the run’s pin set
records it under emptyInputs with the reason never_written or
not_connected, so a Report can say there is no data yet rather than show
zero. An input with no readable version at that
time, because its table was created later or retention removed that version,
fails the Plot with AS_OF_INPUT_UNAVAILABLE instead of reading newer data.
Each Plot that completes also records the Providers it read, for the Refresh
menu: one row per Workspace connection of each Provider, with the newest commit
time among the Provider’s tables the Plot pinned (how old its data was), the
connection’s status and detail as the Providers directory settles them at that
moment (while a sync runs, the status before it), and the Provider’s logo as
the catalog had it (its grouping’s, else its vendor’s). A Provider the Plot
read as empty because the Workspace has not connected it gets a row with no
connection. The Plots of one run add to the same rows, each moving the data’s
age forward to the newest commit any of them read.
Time functions such as now() return the AS OF time too, in the Plot’s SQL and
in Policy conditions. Each Plot also runs with the SQL parameters its
captured plot_sql implementation holds.
A Page’s live Plots can read directories beside a Report’s stored outputs:
Team Time Spend’s teams reads the identity directory, and AI Usage’s
providerConnections reads workspace.providers. So a run also captures the
Workspace directory projections its Version’s Plots read, such as
workspace.users and workspace.providers, when it is requested. A reader who
picks an older Refresh reads each directory as that Refresh saw it: the
projections from the rows the run captured (a captured empty directory reads as
empty), and the derived identity directory (providers.identity.*) at its
Delta version as of the run’s AS OF time, under the reader’s current Provider
Policies. A run from before directories were captured, a directory the run did
not capture, and an identity version that retention removed read current rows
instead, and the Pane says Directory data is current, not as of this
Refresh. Other Provider data a live Plot reads still needs a snapshot the
run bound; current facts are never silently substituted.
Each query run writes its Plot’s output to a table of its own, once, and a
later run never overwrites it. The table lives in the Workspace’s own storage,
under the same provider root as its Provider tables:
gs://<Workspace data bucket>/providers/<workspace>/parables/<parable>/_outputs/<plot>/<query run>.
The vectors of an embed() column are kept beside it, in a Lance dataset under
that table’s _vectors/ directory, and every read of the run puts them back in
their column.
A Workspace without data storage has nowhere to put it: Query Layer refuses to
plan its Plots, and the run fails. When the query run succeeds, Parable records
that table, the version the run committed and a digest of that commit on the
Plot result. A Plot whose output cannot be recorded fails, and so does its run.
query-runner reports every status change of each query run back to its Report
run, usually within five seconds. Each Plot result records its status, when it
started and finished, its row count, and what it has spent: the cost of its
AI calls in US dollars, and its paid calls. Workspace and Admin readers see the
same values. A failed or cancelled
Plot carries a short message chosen by its error code, never the query’s own
error text. When one Plot fails, query-runner cancels the run’s other query
runs, and their Plot results say they were cancelled because another Plot
failed. A complete, failed or cancelled run keeps its status: a later change,
such as a cancelled Plot that finished anyway, updates only that Plot’s result.
A Plot pays for each AI answer once. On query-runner, all runs of a Plot read
and append one inference log, kept in the Workspace’s storage beside the Plot’s
outputs at …/parables/<parable>/_inference_logs/<plot>. When a ponder() or
embed() call has the same model, instruction, output type and input values as
an answer an earlier run of that Plot received, the run takes the answer from
the log. It makes no Provider call and costs the run nothing. A rerun over
unchanged input therefore records zero paid calls, and a run after an input
changed pays only for the rows whose input changed. A call that failed in an
earlier run is not reused; the next run tries it again. Plots do not share
logs, so two Plots that make the same call each pay for it once. When a run
finishes, query-runner removes from the log the failures of runs that ended and
any second copy of an answer, and never an answer a later run could reuse. See
EDR-0079 (amended 2026-09-29 and
2026-10-02).
Schedule Reports
Choose Plan Refreshes in the Refresh menu, then enter a schedule in your own words. For example, Every weekday at 9am, starting next Monday. Review the interpreted schedule and upcoming runs, then choose Save schedule.


parable_report_plan row saved with
the Workspace line’s draft and published with it; plans saved from Admin Ponder
live on the same Workspace lines. Capture reads the plan from the line’s current
published version, so a schedule first runs once the line publishes it, and a
published version without the plan stops it.
In the same transaction the publish writes the scheduler’s state for the line:
it switches on each plan the version enables and switches off
each plan the version disables or drops. The publish that first enables a plan
fixes the instant its recurrence counts from: the plan’s own start, else the
publish time. Later publishes keep that start, any pause and the run history.
A plan runs on one line, the line that first published it. An installed line’s
version also carries the plans of its upstream version, and those run only on
the upstream line, because the roster, query-runner’s fire ledger and capture
key a schedule on the plan alone.
A schedule saved before plans moved into the published version
was a reportPlan Preference with a plan row on the line’s root ref under the
same key. The move deleted the Preference and kept the plan row and its
scheduler state, but no published version carries that plan. Until the line
next publishes, the roster and capture read such a plan from the line’s root
ref: only the line’s own root ref, and only a plan row no published version
has sealed. The Report, its Plots, their Policies and the Workspace’s seat
still come from the published version. The line’s next publish carries the
plan into its version and keeps its start, pause and run history; from then on
the version is its only source.
A schedule can name any Report the Workspace reads on its line, including a
delivered Report the Workspace never changed. Until the Workspace first pulls a
delivered Parable, that is the delivered definition; after that, it is the
version it pulled. Capture checks the Report against the line’s published
version: an occurrence whose Report the line no longer contains is refused and
counts as a failure on its plan.
query-runner is the Report scheduler. Every minute it reads
every page of the
Report plan roster:
the enabled plans across every Workspace, with each plan’s recurrence,
timezone, start, pause, last run, and revision. If one line’s published definition
cannot be read,
its plans are reported as nonconforming and skipped; other Workspaces’ plans
remain in the roster. A switched-on plan the roster cannot fire is reported the
same way: its line has published nothing, no plan carries its key, or it has no
start. web-admin-api logs the reported plan keys on every roster page. Each
line is read in a separate transaction, so a failed database read cannot abort
the rest of the page. The scheduler computes each due
occurrence with the same expansion engine as the preview. A plan that engine
cannot expand, such as one whose start has fallen more than 200,000 runs behind
since it was saved, is skipped and counted in the sweep’s unsupported_plans,
never fired. It records each due
occurrence once in its fire ledger, query.report_plan_fires, asks web-api to
capture the scheduled Report run
in the plan’s Workspace, and then enqueues one query run per Pipeline. A plan
saved after query-runner read it produces no run for the old configuration. A
disabled or deleted plan leaves the roster, so query-runner stops firing it.
The scheduler runs where QUERY_RUNNER_REPORT_PLAN_SWEEP_ENABLED is on, which
is everywhere: local development, the Plot authoring e2e job, and ephemeral,
staging and production. It is the only
scheduler, and web-db stores no next run time: the first publish that enables
a plan stamps the instant its recurrence counts from when the plan sets no start. The
decision records are
EDR-0079 and
EDR-0092.
How long Report runs are kept
Each Report keeps its newest 30 completed runs, plus every run started with Plot Report that completed in the last 90 days. The newest completed run is always kept, so Latest (the API’s Current) never loses it. When a run completes and its Report holds more than that, the older runs leave the Refresh menu at once. A reading pinned to one of them answers unavailable, and Latest and SQL that namesparables.{parable}.{plot} never use it. A read that was already scanning it
fails rather than returning part of it. query-runner then deletes their output
tables, usually within an hour.
A run started with Plot Report outside the newest 30 stays until 90 days
after it completed. It leaves at the Report’s next completed run after that.
A failed or cancelled run was never readable. The output tables its finished
Plots wrote are deleted a day after they finish.
web-api sets both numbers for its environment: REPORT_RUN_RETENTION_COUNT
(at least 1) and REPORT_RUN_MANUAL_RETENTION_DAYS.
query-runner reads the
Report run outputs to delete
from web-admin-api. It deletes a table only when that table is the Plot’s own
query run output, under the Parable’s _outputs/ directory, and was recorded
by that query run in the same Workspace. It then
records the deletion
with web-api, which refuses any output a reader can still reach.
Read a Report
The Refresh menu selects Latest or one stored run, a Refresh:- Latest is the newest Refresh. For a Parable with a Pipeline, Pages read
the API’s Current for it: the newest completed Report whose output fits the
selected definition, which can come from an earlier compatible definition.
SQL that names
parables.{parable}.{plot}reads the same run, and a run that completes reaches it within moments (Plots). The menu shows the newest compatible complete run’s age and does not list that run again. Latest always sends Current; the age never pins a historical reading. After publishing, compatible stored Plots keep their last completed outputs while live Plots continue reading live tables. When a new run is moving and stored data is not ready for the selected definition, the menu says Refreshing…. Latest remains selectable for live Plots; stored Plots without a compatible output wait and read again when a Refresh completes. Their waiting response isPD-RESULT-002, not a raw Plot failure. Existing contract and access checks still apply. - A reader on the newest Release is served the newest Version whose stored
Plots can be served. Tyler’s rule (2026-10-10) is “Stay on old until
ready”. A Version can be served when it stores nothing, when a completed
Refresh is readable on it (its own, or an earlier compatible one), or when
its Refreshes cannot be read.
- After a publish that changes what its stored Plots return, readers stay on the newest earlier Version that can be served, with its data on screen. That is usually the Version they were already reading; a reader is never moved back past a Version that reads the last completed Refresh. Meanwhile Latest · Refreshing… and the progress bar show the new Version’s first Report.
- When that Report completes, the page reads again within a few seconds and switches to the new Version. The previous Version’s Page, with its data, stays on screen until the new one has its data on screen too and has stopped moving (its Settings read, its charts’ entrances run), or for at most 15 s after the new one starts populating, even when the two Versions share their Pages. This holds for every switch a reader on Newest did not pick, held or not (PARABLE-5947, PARABLE-5949). Updated to v1.40 shows when the new Page takes its place, not at the publish. Choosing a Version in the Release menu swaps as soon as the chosen Page has drawn, so it shows that Version’s own loading state at once.
- A Plot the newer Version deleted does not refuse the Version and the runs
that read it. Admitting a reader to a stored output that read a Plot a
later publish deleted judges that Plot as if it were still there, with its
owning Pane’s Policies as of the newest Version that had it, at the
reader’s current rank (PARABLE-5935). A read of the deleted Plot itself,
as the older Version’s deleted Pane makes, is judged the same way, and
every view filter Policy on the line that binds the reader narrows its
rows (PARABLE-5941). A reader who could read it before the deletion still
can, and one with no seat on the line now cannot. Two exceptions: when the
deleted Pane’s own Policies held a view filter Policy (PARABLE-6035), or a
Page or Pane above it held a
treeview filter that is no longer on the line, such as one on a Page the same publish deleted (PARABLE-6190), only the Primary reads its Plot after the deletion, because nothing on the line still applies that filter. This holds where the line’s own rows record the deletion; a Plot deleted only in an installed line’s upstream version is still refused there. - While a reader is held, a publish does not make their Pages read again: a Version’s content never changes, so only a setting, a seat or a newly served Version makes a Page read again. A Pane that does read again keeps its rows on screen until the new ones arrive.
- A publish whose stored Plots can read the earlier Refresh switches as soon as the page reads again, with no hold. The previous Version’s Page still stays on screen until the new one has its data on screen.
- The publisher’s notice says Published v1.40, that readers stay on the previous Version until its first Refresh finishes, and offers Open v1.40 to look early.
- Choosing a Version in the Release menu reads that Version, skeletons and all.
- For a Parable with no Pipeline, Latest is live Provider data with no age, and its stored runs are listed below it by age.
- When a newer run completes, Latest moves to it and the Pages read again.
- This month’s Refreshes follow, one line each with their age and, for a run with no rows to read, a status badge such as Failed. Two runs that would read alike add the clock, or the date, that tells them apart. Each row carries its exact time on hover and in its spoken name. Earlier months sit under Older Refreshes in month folders.
- Latest and each Refresh open on the Providers they read. A Provider’s row shows its logo and name, one colored icon for its connection (fine, read live, needs attention, failed, or not connected), what that means in the Providers directory’s customer wording, and how old its data was: Data as of 4:41pm, 16 mins before the Refresh. Staff also see the connection’s raw detail on hover. A Refresh’s own row carries the worst of its Providers’ icons, and its Providers list the worst first, then by name. These rows name external Providers only. Parable’s derived identity pool is not a connection and does not appear as Not connected, including when an older Refresh recorded it as a source.
- Latest’s Providers are read when the menu opens, as they stand now: each
connection’s status and the newest commit among the Provider’s tables
(Data as of 4:41pm, 19 mins ago), through the Query Layer’s
providers_quality.summary.table_commitsas the reader. Until that read lands, a Latest that stands for a Refresh shows what that Refresh read. - A Refresh belongs to the Version that saved it. One the selected Version cannot read is filed below, in a folder for the Version that saved it, newest Version first: v1.97 · 6 Refreshes. Choosing it switches to that Version and that Refresh together, and RELEASE then names the Version. A link to such a Refresh opens it on its Version the same way. A Refresh no Version can read (its saved outputs expired) is not listed. Version folders past what the menu shows without scrolling fold under one Older Versions (N) folder that opens in place; the newest Release’s folder and the folder of the Refresh on screen always stay in view. A compatible older Refresh continues to read even after the Plot’s SQL changes.
- A chosen Refresh that failed or was cancelled has no rows to read. Where the Page was, the editor and Ponder show This Refresh failed (or was cancelled), when it started and how long it ran, why, the Plots that failed and any Provider with an error, then Read Latest, Choose another Refresh and, for a seat that may, Refresh now.
- While a Refresh runs, a slim animated bar sits right after RELEASE and REFRESH, with Cancel beside it. Its fill and its tooltip, such as Est. Time Remaining: 4 min and 12 of 31 Plots done, are estimated from the median duration of this Report’s last ten completed Refreshes and the Plots the run has finished. The bar never goes backwards and slows near the end rather than sitting at 99%; with no history it moves without a value. Cancel asks first. Once confirmed, the bar holds where it was and says Cancelling… in Cancel’s place until the run’s Plots have stopped, then Refresh cancelled for a few seconds, and is gone.
- The Refresh trigger marks only the Refresh being read. A chosen Refresh names its own state after its age (2h ago · Failed); Latest is marked when the Refresh it stands for did not succeed. A newer run that failed or was cancelled never marks another Refresh the reader chose.
- An output is compatible when the rows the new Version declares match the rows
it was saved under. Naming a plain field as its scalar-lib type, a text field
as
Generic.Text, a number asGeneric.Float64or a yes or no asGeneric.Boolean, keeps it compatible: those types accept exactly the values the plain ones do, so no Refresh is needed and older Refreshes stay readable. Any other scalar (Identity.Name,Generic.Int64), a nativeDatebecoming aTemporal.DateTime, or another type is a new contract, and the stored Plot waits for a Refresh. - Every time in the menu, History and Results is in the reader’s time zone and clock, read from their browser: 12:31pm ET in New York, 17:31 BST in London. A locale that uses a 24-hour clock gets one; others get am and pm.
- An explicit Refresh stays pinned, in the address as
refresh=<run id>. A Failed or Cancelled one stays pinned and its Pages say it has no rows to read (web-api answers the Pane read with a refusal, not an error). A run id the Parable does not have, such as one that left retention, another Parable’s, or a typed word, is dropped once the runs are read, and the page reads Latest.
plotApi.readReport.
Current limits
- A Pipeline can read another Pipeline of the same Report: it runs after the members it reads and reads what they wrote in that run. Members that read each other in a cycle are refused. See Plots. A Plot the run reads but nothing stores is read as a view of its SQL (Plots read as views).
- A Report run reads each view of its own Parable from the Version it captured. A run captured before that view was published again is refused when it is planned; run the Report again.
- Provider historical-input replay is limited: principal-dependent filters need the captured principal, and full Parable-context Provider filters can refuse materialization.