Docs
Reference

Outputs And Tables Reference

Load this reference before creating or editing final outputs, public artifacts,
persisted resources, table viewers, or run evidence.

Output Defaults

  • Public/end-user flow outputs should default to one readable Markdown artifact, or a deliberate table/media viewer when that is better for the user.
  • The default rich output pattern is a Markdown report. Use fenced breyta-resource blocks when that report needs real Breyta tables, charts, downloads, images, video, nested Markdown, text, or JSON resources.
  • Keep raw debug maps, internal ids, res:// refs, and implementation-only fields out of final public output unless the user explicitly asked for them.
  • Include enough summary context that a user can understand the result without reading step internals.
  • Include exact failure or skip reasons when that is the user-facing outcome.
  • Do not paste EDN/JSON maps into Markdown prose. Return them as :raw viewer values, embed persisted JSON with :view :json, or put supporting values in fenced code blocks.

Artifact Audience Review

After a run returns output or artifacts, inspect the actual user-facing result
before calling the workflow done. Review it from the perspective of the flow's
intended user and the audience of the artifact, not from the perspective of the
flow author.

Use the right inspection path:

  • Markdown/report: open or read the rendered output and check whether the first screen explains the result, the content answers the user's request, links/resources resolve, and there are no raw debug maps or placeholders.
  • Table: read the table resource, verify it has rows and useful columns, and judge whether the table is scannable for the intended user.
  • Image: open the image artifact/resource and use vision analysis to check whether the visual content is legible, correctly framed, complete, and aligned with the requested audience/use.
  • Video: if browser tooling is available, capture screenshots from several moments and review them. Otherwise use the best available frame extraction path and report the limitation.
  • Download/blob: inspect metadata and, when safe and useful, read or preview the file enough to confirm it is the expected artifact.

Audience review should catch empty outputs, cut-off text, broken embeds,
illegible images, video frames that never show the intended content, excessive
debug detail, wrong tone, missing context, and outputs that satisfy the data
contract but fail the end-user experience.

Output Shape Decision Table

User needPreferred output shape
Narrative summary or report{:breyta.viewer/kind :markdown ...}
Short explanatory table inside textMarkdown table inside a Markdown viewer
Durable grid for scan/copy/exportPersist rows as a table resource, then return :table or embed it with breyta-resource
Filtered or aggregated table inside a reportMarkdown viewer with breyta-resource :view :table and bounded query/aggregate options
Chart from table rowsMarkdown viewer with table :chart options in the resource fence
Source/export affordanceSeparate breyta-resource fence with :view :download
Image/audio/videoPersist a blob and return or embed :image, :audio, or :video
Structured map or JSON as the product:raw viewer, not Markdown prose
Supporting structured detail in MarkdownFenced code block with clojure, edn, or json

Markdown resource embeds are authoring syntax. The end-user output should render
as one continuous document; users should not see the raw fenced blocks or
technical res:// URIs.

The floating Copy Markdown action exports resolved embeds, not Breyta-only
fences, whenever possible. Tables become Markdown tables, JSON/raw resources
become readable fenced Clojure blocks, download/video/audio resources become
links to the Breyta resource viewer, and image embeds keep an image preview
wrapped in a Breyta resource-viewer link.

For field-by-field syntax, use the product docs:
breyta docs find "output artifact reference breyta-resource table aggregate".

Full Breyta URLs

Report full Breyta URLs whenever the CLI returns them:

  • flow URL
  • run URL
  • run output URL
  • activation/setup URL
  • resource URL
  • public template URL
  • Discover/install URL

Prefer CLI-returned URLs:

  • data.flow.webUrl
  • data.run.webUrl
  • run.webUrl
  • outputWebUrl
  • data.webUrl
  • meta.webUrl
  • error.actions[].url

Hand-build URLs only when the CLI does not return one and all required ids are
known.

Markdown Table Or Breyta Table Resource

When the user asks for "table output", decide first which output type they
mean.

Use a markdown table when:

  • the table is short
  • the table is explanatory
  • the table belongs inside a readable report
  • copy/export/filter behavior is not important

Use a Breyta table resource when:

  • there are many rows
  • the user needs scan/copy/export behavior
  • the result should be durable structured data
  • another step or human should consume the table as a resource

When the table belongs in a larger written report, embed the persisted table in
Markdown with a breyta-resource fence instead of returning a separate table
viewer as the whole output. Use a separate :view :download fence when the user
should get a CSV/source download.

Canonical Table Ownership

Treat persistent workspace tables as shared data products, not flow-local
scratchpads. Before creating one:

  1. Inventory related table resources and flows that read or write tables. Use
    indexed listing only as a convenience scan, then verify the result against
    the canonical resource store.
  2. Read representative rows from plausible candidates.
  3. Trace each candidate's grain, key, schema, writers, scheduled rebuilds, and
    consumers. Verify whether its writers preserve fields they do not own.
  4. Classify the table as canonical current state, source/campaign data,
    derived output, or append-only audit history.
  5. Designate one system of record for each durable entity grain that multiple
    flows must coordinate around.

Use bounded page sizes for discovery:

# Fast indexed candidate scan. This is not authoritative on its own.
breyta resources list --workspace <workspace-id> --accept application/vnd.breyta.table+json --limit 100

# Authoritative resource-store scan. Keep result resources whose contentType is
# application/vnd.breyta.table+json.
breyta resources list --workspace <workspace-id> --type result --limit 100

# Catalog/list cross-check. Repeat with --cursor while meta.hasMore is true,
# but do not treat this projection-backed listing as authoritative on its own.
breyta flows list --workspace <workspace-id> --include-archived --limit 100 --page-size 100
breyta flows list --workspace <workspace-id> --include-archived --cursor <nextCursor> --limit 100 --page-size 100

# Direct source reads for every returned flowSlug and every configured target.
breyta flows show <flow-slug> --workspace <workspace-id> --target live --full
breyta flows show <flow-slug> --workspace <workspace-id> --target draft --full

# User-visible installed-app candidate scan. Authenticate as a service account
# with installations.read for a workspace-wide inventory.
breyta flows installations list --all --workspace <workspace-id>
breyta flows installations show <profileId> --workspace <workspace-id>

# Exact source read for a local installation only: sourceWorkspaceId is absent
# or equals the consumer workspace. Linked-app full source is provider IP and is
# not available through the installer CLI.
breyta flows show <flowSlug> --workspace <workspace-id> --version <version> --full

# Optional index-backed candidate scans. These are not authoritative on their
# own.
breyta flows grep ":type :table" --or "flow/step :table" --workspace <workspace-id> --scope workspace --target live --limit 100 --from 0
breyta flows grep ":type :table" --or "flow/step :table" --workspace <workspace-id> --scope workspace --target draft --limit 100 --from 0
breyta flows grep "<table-name-or-key>" --workspace <workspace-id> --scope workspace --target live --limit 100 --from 0
breyta flows grep "<table-name-or-key>" --workspace <workspace-id> --scope workspace --target draft --limit 100 --from 0
breyta resources read <table-uri>

Do not treat the indexed --accept listing or the first page as complete. For
the authoritative resources list --type result scan, continue with
--cursor <next-cursor> while has-more is true and retain every item whose
contentType is application/vnd.breyta.table+json. Do not add --accept or
--query to this verification pass because those select the search-backed
picker path.

Treat flows list as a catalog-backed cross-check, not an authoritative
flow-store enumeration. Continue with --cursor <nextCursor> while
meta.hasMore is true, require the cursor to advance, and collect every
returned flowSlug. For every returned slug, run flows show --full
separately with --target live and --target draft, then inspect
data.flowLiteral for table reads, writes, and persisted table references. A
Draft not found response or the CLI's draft target is not configured error
records the draft target as absent. A no_active_version code,
Flow has no active version response, or the CLI's
live target is not configured error records the live target as absent. Any
other unreadable target or listing error makes the cross-check incomplete.
Process one flow/target at a time so large definitions do not accumulate in
memory. Archived flows are not active writers, but include them to recover
prior ownership and migration contracts.

Before authorizing a new table, require either an operator/admin-provided direct
flow-store enumeration or proof that the flow catalog has been reconciled
against the flow store for this inventory. If neither is available, report the
flow inventory as incomplete and do not create a new table. Exhausting
flows list alone cannot prove that every authored flow was returned.

Also inventory installations for the consumer workspace. A normal user's
flows installations list --all result is filtered to that user and is not a
workspace-wide inventory. Run it as a service-account principal with
installations.read, use an operator/admin direct profile-store inventory, or
prove that every workspace member's installation inventory has been included.
If none is available, report the installation inventory as incomplete and do
not create a new table.

The workspace installation endpoint is paginated to 100 items and returns
data.nextCursor with data.hasMore. The current CLI command exposes the first
page but no installation cursor flag. If data.hasMore is true, use an
operator/admin API or store path that accepts data.nextCursor, require the
cursor to advance, and continue until data.hasMore is false. Do not repeat the
same first-page CLI command or call that result complete.

For every returned item, retain its profileId, flowSlug,
sourceWorkspaceId, sourceFlowSlug, owner when available, enabled state, and
exact installed version; inspect details with
flows installations show <profileId>. When sourceWorkspaceId is absent or
equals the consumer workspace, read the local exact version with
flows show <flowSlug> --version <version> --full.

When sourceWorkspaceId differs, the installation is linked. Do not run the
consumer-workspace flows show command: it can select an unrelated same-named
local flow, and the installer's supported read is intentionally a redacted
summary rather than the provider's full source literal. Account for the linked
item as an opaque potential writer with its exact pinned metadata. For any
table grain that may overlap the app's purpose or observed resources, require a
source-authorized operator/provider table-ownership contract for that version;
otherwise fail closed for that grain. An opaque linked app does not make an
unrelated grain incomplete when the non-overlap is explicitly documented.
Enabled installations are potential current writers; disabled installations
still carry prior ownership and migration history.

Use flows grep only to accelerate candidate review, never to choose the only
flows whose sources are inspected. Run every grep query separately with
--target live and --target draft; the default latest target can hide an
active live writer when an unpublished draft exists. Before using any grep
page, inspect timedOut in both the result and metadata. It must not be true;
an absent field is the normal non-timeout response. Discard a timed-out partial
page and retry with a narrower pattern or surface. On a non-timed-out page,
continue with --from <offset>, advance by the returned size, and stop when
the page returns fewer results than the requested limit.

Only consider the inventory complete after the authoritative resource-store
scan has has-more false; the direct flow-store inventory or catalog
reconciliation proof is complete; every returned authored slug's configured
live/draft sources have been read; every installation inventory page and owner
has been accounted for; every local installed source has been read; and every
linked installation has either a resolved ownership contract for the relevant
grain or a documented non-overlap. Exhausted flows list and grep pages are
useful cross-checks, not proof of flow-store completeness.

Use this ownership model:

Table rolePolicy
Canonical entity/current stateKeep one logical shared table or partitioned family per entity grain. Upsert by an immutable internal entity id.
Source or campaign dataMay remain separate, but reference the canonical entity id and do not make global decisions independently.
Event or audit ledgerKeep immutable history separately, ingest idempotently by a stable source event id, and reconcile relevant state into the canonical entity row.
Replacement tableUse only as a bounded migration. Name the old and new authority and define coexistence and retirement.

Extend or migrate the canonical table by default. Do not create a new table
merely because the flow, campaign, or source is new. A separate table is
justified when the data has a genuinely different grain or lifecycle, requires
different security or retention, or is part of a documented migration. Do not
reuse a table based only on its name: an incompatible key or a scheduled writer
that rebuilds a narrow schema can silently lose fields and recreate duplicates.

CRM Pattern

  • Keep one canonical contact table or partitioned table family shared by all
    lead research, outreach, reply, and monitoring flows in the workspace.
  • Generate an immutable internal contact_id once. Treat LinkedIn ids, emails,
    and profile URLs as mutable aliases, never as the canonical row key. Resolve
    aliases through an explicit matching and merge process; preserve the
    surviving internal id when an alias changes or duplicate people are merged.
  • Put alias matching, canonical-id allocation, and alias attachment behind one
    enforceable identity-resolution boundary before a contact becomes eligible
    to send. Either start one dedicated resolver flow through a concurrency-aware
    top-level HTTP/interface/event or API invocation with
    :concurrency {:type :singleton :on-new-version :drain} and no direct
    contact-id creation elsewhere, or use a transactional external store with a
    uniqueness constraint for each provider/account/alias-type/normalized-value
    key. Never invoke the resolver with flow/call-flow or another child-flow
    path: child execution does not enter the top-level active-lane/drain
    coordinator. Prove top-level behavior with concurrencyDecision, run timing,
    and the run list. Inside that boundary, normalize and re-read every supplied
    alias, reuse or merge the winning contact, and allocate a new contact_id
    only when none exists. A serialized table-backed resolver must verify all
    deterministic alias-index writes before marking the contact eligible. On a
    uniqueness conflict, partial write, rejection, timeout, or unknown result,
    re-read and reconcile; never return both candidate contacts as send-eligible.
  • Plan capacity before a concrete table reaches 50_000 live rows. Keep one
    logical CRM by using first-class :partitioning with a deterministic bounded
    bucket such as hash(contact_id) mod N, where N is at most 128. Store the
    bucket in a field such as contact_partition, use a :field partition
    strategy, and compute the same bucket for every point read or write. Use an
    external database instead when identity matching or eligibility requires
    broad cross-partition scans.
  • An ordinary canonical-table upsert is not a lock. Never use
    resolve/upsert -> eligibility check -> claim across independent sending flows
    because two runs can both pass the check.
  • Put every first-touch side effect behind one enforceable serialization
    boundary. Either start one dedicated sender flow through a concurrency-aware
    top-level HTTP/interface/event or API invocation with keyed concurrency on the
    raw immutable contact_id and :on-new-version :drain, with no other flow
    allowed to send directly, or use a transactional external store/provider
    that conditionally creates a unique policy-scoped send key such as
    first-touch:<contact_id>. Never use flow/call-flow or another child-flow
    path to enter this sender lane. Prove the top-level start's
    concurrencyDecision and concurrencyKey. Inside the serialized sender lane,
    re-read eligibility and persist the claim before the side effect. If neither
    boundary is available, fail closed and do not automate the send. Before
    selecting the keyed sender lane, resolve current aliases again to the
    surviving canonical id; an alias disagreement, superseded id, or pending
    merge remains ineligible.
  • Record pending, sent, failed, and ambiguous outcomes against the canonical
    contact. A timeout or unknown post-send state remains ineligible until
    reconciled; do not retry it as a fresh first touch.
  • Keep campaign qualification and social-post evidence in campaign/source
    tables when useful, but reference the canonical contact id.
  • Keep send, acceptance, and reply events as audit ledgers. Ingest them
    idempotently with a stable provider event or delivery id, scoped by provider
    and account when needed, and upsert or deduplicate on that key while keeping
    the accepted event row immutable. A workflow id, run id, or workflow-derived
    table operation id does not deduplicate a provider redelivery that starts a
    new run. These ledgers inform the canonical row; they are not competing CRMs.
  • Never create another CRM table for a new outreach flow when the canonical
    contact table family can be extended, partitioned, or migrated.

If a new persistent table is still required, report the candidates rejected
and why, the new table's grain and key, its owner and consumers, whether it is
canonical or supporting data, and its migration or retirement plan.

Uploaded File To Downloadable Artifact Checklist

Use this uploaded-file-to-downloadable-artifact checklist when the user asks to
upload a document/file and receive a downloadable result:

  • use :file for a new browser or CLI upload, and :resource when the user can
    pick an existing workspace resource
  • declare an invocation input with :type :file for a new upload, or
    :type :resource when the user can pick an existing workspace resource
  • set :accept to the expected MIME types
  • treat uploaded values as resource refs, not inline bytes
  • call installed/public apps by outcome first when OCR or another common
    substep is needed
  • persist generated Markdown, text, JSON, DOCX, PDF, or binary output with
    :persist {:type :blob ...}
  • use :load only where a downstream function needs hydrated persisted content
  • return a Markdown viewer with a breyta-resource :view :download fence
  • smoke test with breyta flows run --upload, then inspect
    breyta resources workflow list <workflow-id> and
    breyta resources read <uri>

Full recipe:
breyta docs show guide-persisted-results-and-resources --section "Uploaded File To Downloadable Artifact Recipe".

Breyta Table Resource Verification

For a Breyta table resource, verify all of these before calling the workflow
done:

  • final output has :breyta.viewer/kind :table
  • viewer value has :type :resource-ref
  • resource content type is application/vnd.breyta.table+json
  • preview.rows-written > 0
  • breyta resources read <table-uri> returns rows

Do not consider breyta resources workflow list alone sufficient verification.
It proves a resource exists, not that the user-facing table resource is readable
and populated.

Table Write And Read Shapes

For :persist {:type :table ...} writes, use the table-name form in the
:table field. Table queries and reads can use table resource refs, but a
result table URI in a persist write can pass draft validation and still fail at
live runtime parameter validation.

After writing rows, read the table back with breyta resources read <table-uri>
or the table step/query path that downstream consumers will use. Confirm the
returned URI/ref is non-empty and that rows are present before storing or
reporting the table as ready.

If a flow stores a table URI/ref in KV or a registry, never overwrite a known
good value with nil or an empty value from a later upsert/write result. Keep
the previous ref or fail loudly so downstream reads do not inherit a corrupted
registry entry.

Large Artifact Hygiene

Agents should inspect large artifacts through refs and bounded previews, not by
pasting full tables or reports into chat. Use breyta resources read <uri> for
the default compact blob preview or default bounded table preview and add
--full only when the full payload is required for the current decision.
Treat --pretty as formatting only; it must not imply full payload access.

When a flow produces a long Markdown report, transcript, JSON body, or research
artifact, persist the body as a blob/resource and pass the resource ref, signed
URL, and a short summary through run results or table rows. Keep durable tables
scan-friendly: store status, counts, source refs, output refs, and short previews
instead of giant cells like report_markdown that contain the whole artifact.

For blob resources, choose the tier up front: retained/default for durable or
user-visible artifacts; :tier :ephemeral for temporary streamed HTTP blobs,
downloads, exports, generated media, and intermediate API responses. Function,
table, and KV persists use the retained/default path today.

Persist And Resource-Ref Handoff

Default to :persist when payload size is unknown or unbounded: provider
exports, paged API responses, generated media, long Markdown reports,
transcripts, JSON snapshots, files, and tables.

Persisting changes the shape passed to later steps:

  • persisted blobs carry resource refs in :uri / :resource-uri
  • persisted blobs also carry storage loader details under :blob-ref
  • persisted tables should be treated as canonical {:type :resource-ref :uri ...} maps
  • UI/API/report handoff should use the resource URI or canonical resource-ref
  • downstream blob hydration should pass the whole persisted step result and use
    :load on the function step

Use this handoff shape:

(let [resp (flow/step :http :fetch-export
             {:connection :api
              :path "/export"
              :accept :json
              :persist {:type :blob
                        :tier :ephemeral
                        :content-type "application/json"}})
      rows (flow/step :function :extract-rows
             {:input {:resp resp}
              :load [:resp]
              :code '(fn [{:keys [resp]}]
                       (get-in resp [:body :items]))})
      table (flow/step :function :persist-rows
              {:input {:rows rows}
               :code '(fn [{:keys [rows]}] {:rows rows})
               :persist {:type :table
                         :table "export-rows"
                         :rows-path [:rows]
                         :write-mode :upsert
                         :key-fields [:id]}})]
  {:breyta.viewer/kind :table
   :breyta.viewer/value table})

Use (:resource-uri result) or (:uri result) when a command, table row, or
Markdown breyta-resource fence needs a URI string. Keep the full persisted
result when a downstream step can load it. Do not use [:blob-ref :path] as a
public/user-facing ref; it is a storage pointer for loaders and storage-backed
steps.

Persisted Resources

Use breyta resources ... to inspect persisted result refs like res://....

  • breyta resources list lists refs by filters.
  • breyta resources workflow list <workflow-id> lists refs for a run.
  • breyta resources get <uri> fetches metadata.
  • breyta resources read <uri> reads persisted results.
  • breyta resources url <uri> gives a shareable URL.

For large outputs, pass refs through the flow rather than large bodies. Keep
inline final output small and user-facing.

Runtime Proof

Runtime proof should include:

  • exact command run
  • workflow/run id
  • terminal status
  • user-facing output summary
  • artifact audience review notes
  • required side-effect evidence
  • full Breyta URLs
  • unresolved risk or blocked verification

Do not report success based only on flows push or resources workflow list.
Behavior is proven by a representative run, by reading the outputs/resources
that matter, and by reviewing the actual artifact as the intended user would.

For notification flows, include the rendered or submitted payload in the proof:
recipient, sender, subject, relevant body copy, and any required absence checks
such as "no PR link" or "no debug map".

As of Jul 29, 2026