Reports
Write up what an experiment showed as a markdown document that embeds live insights, experiments, Context and run evidence.
A report is a document your organization writes together: a title, a description, and a markdown body that embeds live insights, experiments, Context objects and excerpts from runs. Members edit it in the web app; agents and scripts write the same document through ax report, the HTTP API and the report_* MCP tools. Whatever surface wrote a paragraph, everyone with the report open sees it appear.
| Operation | What it does |
|---|---|
list | The org's reports, most recently changed first, without bodies. |
get | One report with its body as markdown. |
create | A new report from a title, an optional description and an optional body. |
append | Add markdown after the current body. |
edit | Replace the title, description and/or whole body, guarded by the updatedAt you last read. |
delete | Remove the report and its body. |
The document format
A body is CommonMark. Headings, paragraphs, lists, tables and code blocks are what you expect. Three conventions carry everything Fiveonefour-specific, so a body survives any markdown editor and reads back exactly as written.
Blocks
An embedded block is a fenced code block whose info string is ax-block and whose body is one JSON object. kind names the block; the other keys are that kind's fields.
```ax-block
{"kind":"insight","experimentId":"<EXPERIMENT_ID>","block":"scatter","scope":{},"view":{}}
```| Kind | Shows | Fields |
|---|---|---|
insight | One view from an experiment's Results tab, read live at the scope you set. | experimentId, block (scatter, tests, metric), scope, view |
experiment | An experiment's live card (name, run count, last activity), or its definition at the pinned version. version is a fingerprint or an ordinal such as v3; omit it to follow the latest registered version. | experimentId, show (card, definition), version |
context-object | A product, use case, persona or other Context object, as its card with its current name and description. | contextKind, id, show: "card" |
evidence | The quoted excerpt with the run's status, the object it came from and an "Open in run" link to that tool call, span or entry. | runId, evidenceKind (run, tool_call, timeline_entry, span, prompt), targetId, excerpt, seqStart, seqEnd, id, findingId |
evidence-chart | One metric across the runs of the experiments it cites, read live and grouped by the axis you choose. | id, variant (bar, box, coverage, line), experimentIds, metric, groupBy, title, caption |
experiment-links | Links to experiments with their live run counts. | id, experimentIds, label |
trace-quote | One assistant message from a run, with the agent, harness and model recorded for that run and links to its source. Choosing a source seeds the quote's text, which you can then edit. Older quotes without text show the source's message live. | title, text, source (experimentId, runId, message.entityId) from block_list_data_sources with Trace quote filters |
version-selection-rate-evidence | A/B Comparison: the selection rates of two sources, A and B, read live. Each side is a test result, a Context object, an experiment, or one of an experiment's agents, models, agent modes, prompts, products or environments. Set references only, never counts or rates. Find test results with block_list_data_sources, using block_kind: "version-selection-rate-evidence", filters: {"category": "test-result"}, and the authorized report ID or name. Blocks that compare two versions of one product through the legacy fields are still supported; list their ids with ax report version-selection-rate-evidence-sources. | comparisonRows (an ordered list of rows, any number, each {id, label?, source}: a stable id unique in the block, an optional shown name, and a typed source or null), or comparisonSources (a and b, each a typed source or null). A typed source is test-result, context, experiment, experiment-item, product-version, or manual (a hardcoded figure: label, value, unit of percent, rate, count, number or custom with unitLabel, optional provenance saying where the figure comes from). measuredBy (on a linked source that is not a test result, the test-result that measures it). Bars share one axis only when every figure is a rate or percentage. Legacy: experimentId, productId, baselineVersionFingerprint, newVersionFingerprint, testName, scope |
recommendation-card | One recommended change to a selection test. It compares two measured scopes of the test, Now and Target, with each scope's selected and eligible run counts as a mini bar and the signed percentage-point difference (Target minus Now), all read live. It links a draft experiment and up to five supporting runs, and can offer a Run of the draft experiment that only authenticated agents configure. Set references only, never counts, rates or percentage points. | title, description, comparison.now.label, comparison.target.label, comparison.request (a selection-rate request: current is the Target scope, baseline the Now scope; list its values with ax report selection-rate-sources), references.experiment, references.runs, actions.run |
In the editor, an A/B Comparison shows rows A and B. Use Select A or Select B to choose a source type (Test result, a Context object kind, Experiment, or an experiment's agent, model, agent mode, prompt, product or environment) and then the source. A source that is not a test result shows Measured by, where you choose the test result that measures it. Each choice saves immediately, and each row shows its own selection rate or the reason it has none. Blocks saved with an experiment, product and selection test keep those controls above the rows.
A/B Comparison blocks save each unfinished choice immediately. Unfinished fences keep
configuration: "unconfigured" and an empty string for each source you have not
chosen; they read no measurements. Once all choices are complete, the marker is
removed. Changing an experiment clears copied product, version, test and scope
choices in the same edit; changing a product clears copied versions.
Omitting scope.experimentFingerprint or setting it to null selects all experiment
versions. Switching between those representations keeps your choices.
Use report_block_kinds to choose a supported kind, then report_block_schema with the selected add or update operation and kind to read the exact schema, example and source guidance. When the corresponding write tool is available over the AX Agent connection (a read-only connection has none), the contract also carries a write envelope: the write tool (report_add_block or block_update), its input schema and the _ax_catalog_ref the call must include, so writing needs no separate read of the write tool's schema. A fence whose payload does not match its kind is kept verbatim and shown as code; nothing is dropped or rewritten.
Every block accepts an optional layout object that says how it sits in the flow. Omit it for the default: the full column width, in line with the text.
layout key | Values |
|---|---|
width | 0.25, 0.333, 0.5, 0.667, 0.75, 1 as a fraction of the column, or "wide" / "full" to break out of it |
align | left, center, right |
float | left, right (text wraps around the block) |
height | Pixels; honored only by kinds that draw at a height, such as insight views and charts |
Callouts
A callout is a blockquote whose first line is a tag and a title. Three tags exist: FINDING for what the evidence showed, ACTION for what to do about it, and NOTE for anything else. Any other tag reads as a note.
> [!FINDING] Checkout is slower on Fridays
> The p95 doubles between 16:00 and 18:00 UTC across all three models.Mentions
A mention is a link whose target is an ax:// address. The app shows the object's current name and links to it, so a renamed product or experiment stays correct in every report that cites it.
| Target | Points at |
|---|---|
ax://experiments/<EXPERIMENT_ID> | An experiment |
ax://context/<KIND>/<ID> | A Context object, where <KIND> is the plural type from ax context objects (products, use-cases, personas, ...) |
The [checkout flow](ax://context/use-cases/<USE_CASE_ID>) regressed in [Model sweep 12](ax://experiments/<EXPERIMENT_ID>).What the app reads from the body
The report page shows two rows above the body, both derived from it as it changes. Contents lists the headings and scrolls to the one you pick. Referenced names every experiment, Context object and run the body cites, whether through a block or a mention, with each object's current name linked to its page. Nothing needs declaring: cite an object where you discuss it and it appears in Referenced.
Columns
Side-by-side content is an HTML block: a <div data-ax-columns> holding one <div data-ax-column> per column, each with an optional data-width fraction. Separate the tags from the markdown inside them with blank lines.
<div data-ax-columns>
<div data-ax-column data-width="0.5">
Left column.
</div>
<div data-ax-column data-width="0.5">
Right column.
</div>
</div>Front matter
A front-matter group opens a report or a section by naming what it covers: a heading, a one-line lead, and three rows for the products it is about, the metric it tracks and the experiments it draws on. Each row stores ids, never names, and shows each object's current name linked to its page. The metric row names one test of one experiment version (test-<uuid> from the version's tests); a null version follows the experiment's current one.
<div data-ax-frontmatter-group>
## Checkout onboarding
How agents set up the checkout SDK.
<div data-ax-frontmatter-row data-row-kind="product" data-values="<URL_ENCODED_JSON_IDS>"></div>
<div data-ax-frontmatter-row data-row-kind="metric" data-values="<URL_ENCODED_JSON_TEST_KEY>" data-metric-source="<URL_ENCODED_JSON_SOURCE>"></div>
<div data-ax-frontmatter-row data-row-kind="experiments" data-values="<URL_ENCODED_JSON_IDS>"></div>
</div>Use the front-matter MCP tools to configure these rows: report_frontmatter_group_sources reads a report's groups and an experiment's versions and test keys. Read report_get_content, then use report_add_block for an explicit insertion or block_update with the group's full block handle and a typed frontmatter-group patch. Both writes require expected_updated_at from that read; omitted patch fields stay unchanged. A wrapper that does not match this shape is kept verbatim as markdown.
Editing in the web app
Open a report from Reports and write into the page. Members with the report open see each other's edits as they type, and the document autosaves. Everything below writes the format above: a block you place from the menu is the same ax-block fence a script would append, so the two ways of writing never diverge.
Type / at the start of a line or after a space to open the insert menu. Keep typing to narrow it; the arrow keys move, Enter chooses, Escape closes. The menu has three groups:
| Group | Entries |
|---|---|
| Text | Headings, bulleted, numbered and task lists, a quote, a code block, a table, a divider, and a FINDING, ACTION or NOTE callout wrapped around the current line |
| Blocks | One entry per block kind. Insight opens a picker: choose an experiment, then arrange its Results view (block type, filters, version, axes) and choose Use this view; the block reads that view live at the scope you pinned. A kind that cannot be added from the menu yet says so and stays put. |
| Mentions | An experiment, or a Context object of any kind you can open; choosing one opens a search and inserts a chip with the object's current name |
Type @ to open the mentions group on its own.
Every block has a toolbar above it while you edit. It moves the block up or down, sets its width (a fraction of the column, Wide or Full width), aligns or floats a block narrower than the column, opens the block's settings, and duplicates or deletes it. Drag the block's edge to resize it to the nearest fraction. Kinds that draw at a height take one in their settings. An insight block's settings switch between the scatter, test results and metric views and set what each shows; the block re-reads its data as you change them.
Drag a block by its handle to move it. Drop it in the flow to reorder, or drop it onto the left or right edge of another block or paragraph to put the two side by side as columns. Drag the gutter between columns to change their widths; when a column empties, it is removed, and a row left with one column unwraps back into the flow. Rows never nest.
Readers who cannot edit see the same page: blocks render their live views and chips show current names, with no toolbar and no menu.
Adding evidence from the app
Wherever the app shows something a report can embed, Add to report… puts it into one. Pick a report from the list, or start a new one titled after what you are adding, and the block is appended after the report's current body. The report opens from the confirmation.
| Where | What is added |
|---|---|
| The Results tab, beside a block's Share menu | An insight block of that block, at the filters, version and view shown. It reads live, so it follows the experiment. |
| An experiment page's action menu | The experiment's card. |
| A Context object's page | The object's card. |
| A run page's test rows and setup log | An evidence excerpt quoting the test's outcome and script, or the setup log, linked to the run. An empty log offers nothing to add. |
A new report starts with a section heading naming what was added; an existing report receives the block alone. Blocks take the default layout; arrange them in the editor afterwards.
Writing from the CLI, API and MCP
The three programmatic surfaces take and return the same shapes: a report row (id, title, description, createdBy, updatedBy, createdAt, updatedAt) and, for a single report, its bodyMarkdown.
Two writes exist. Append adds markdown after the current body, after a blank line, and is the usual way to add a finding or a chart from a script. Edit replaces the title, the description and/or the whole body and requires the updatedAt you last read; when the report changed since, the write is refused and nothing is written, so a stale copy never overwrites a newer body. Read the report again and retry with its new stamp. Append accepts the same stamp optionally.
# Create a report and add a finding from stdin
ax report create "Checkout latency" --description "Where the p95 went"
printf '> [!FINDING] Slower on Fridays\n> The p95 doubles after 16:00 UTC.\n' | ax report append <REPORT_ID>
# Rewrite the body from a file, guarded by the stamp you last read
ax report get <REPORT_ID> --json | jq -r .report.updatedAt
ax report edit <REPORT_ID> --body-file report.md --expected-updated-at <UPDATED_AT>| Surface | Reference |
|---|---|
| CLI | ax report |
| HTTP API | /api/v1/reports |
| MCP | report_* tools |
Deleting a report removes its body and the links from findings to it. The findings and the Context objects it cited stay.