Skip to content

Investigation reports

Every surface in this tool produces evidence. A report produces the account of it: "across these records, these nodes and this window, here is what happened and why" — kept, re-checkable, and shareable, instead of living in a chat window that scrolls away.

References, never a document

A report is an ordered list of references with connective prose — never a free-form document. A finding flagged as kind: "confirmation" uses Observation / Assessment, including in exported PDFs. Existing fault findings retain their labels. Choose the category when writing the flag; the report uses that same finding.

Sections are typed: a finding points at a flagged record, a record at a cycle, a chart at a named graph, a topology at a named focus, a table at the query that derives its rows, and only narrative stores its own content. That one constraint keeps this a forensic instrument: a report assembles what other verbs produced, and every claim traces to something that can be checked.

Two rules follow, and both are enforced rather than encouraged:

  • A report includes findings; it never authors them. A finding section renders what flag wrote, byte-identical — the report verb has nowhere to put its own version of the diagnosis, so one record can never carry two accounts of itself.
  • Narrative is visibly narrative. Prose renders with its own colour and a standing label — "the author's account, not log evidence" — on screen and in the PDF alike, so an assertion can never pass as a record.

Evidence re-renders live

A report stores references, so opening it re-reads the records, re-extracts the charts and re-runs the queries against the loaded log. A reference that no longer resolves is reported — "2 of 5 anchors did not resolve against this log" — never silently dropped or silently stale. That also makes a report a re-runnable claim: open last month's investigation against today's log and find out whether it still holds.

Because the dangerous failure arrives resolved rather than dangling (record #42 exists in any log with 43 records), a report also captures its authoring context: a fingerprint of the log it was written against (name, record count, time range — and, where the log's provenance came from a project environment rather than a declaration, matched by directory, not declared) and the filter it was written under. Opening it against a different log names the mismatch before any section renders — announced, never refused, because comparing a finding against a later run is legitimate and valuable. Opening it under a different filter offers the stored view; declining renders under the current filter and says so on the page.

Tables: derived rows, declared presentation

A table section stores the query that produces its rows plus a column spec — headings, order, widths, alignment, number formats (0.00, percent, duration, time). Rows re-run with the log; the presentation persists. Numbers set right-aligned in tabular figures. A table can cite the same verbs an agent sees over MCP:

  • read {fields, recordIndex…} — the selected records;
  • aggregate {metric, groupBy, filter, limit} — count/rate buckets;
  • series {expr, buckets} — buckets, series {expr, crossings} — anchored crossing rows, or plain series {expr} — one statistics row;
  • coverage {filtered} — the full graph-ordered ledger: covered, uncovered, and deliberately excluded nodes with their reasons.

Each source prints its own scalar context under the table — population and scope for an aggregate, points and thresholds for a series, or the complete coverage denominator — and the same line appears in the PDF. A zero-result call keeps its header and says why it has no rows. Structured call values such as filter: {from, to, dimensions} are saved and reissued as data, rather than becoming display text after a restart.

A row highlight is a rule, not a paint: rowWhen: "book.mid > 17" re-evaluates with the data, strictly against each row's own record — a value the record didn't log cannot fire the rule — and the rule is printed with the table, because an unexplained red row would be a judgement wearing evidence styling. That makes it valid for read and series-crossing rows (which link back to a record), but not aggregate buckets, series buckets/statistics, or coverage rows; those refuse the rule with a visible warning rather than quietly applying a different column formula. Tables export to CSV (raw values — formatting stays on the page).

The Reports tab

An investigation report: labelled narrative, the flagged finding with its fix, a link to the chart it cites, and a derived table with its highlight rule printed

Reports appear in the Reports tab: pick one and its sections render in order, each evidence section clickable through to the thing it references — a finding selects its record, a chart opens that graph, a focus applies on the topology. A report is a navigation surface, not just an output.

Building one

Agents (or you, over REST) use the report verb:

{"name": "oversell-inv", "title": "Oversell investigation",
 "sections": [
   {"kind": "finding", "recordIndex": 99},
   {"kind": "chart", "graph": "stock vs revenue"},
   {"kind": "table", "call": {"verb": "read", "fields": "stockLedger.onHand, till.gross"},
    "rowWhen": "stockLedger.onHand < 0", "rowWhenLabel": "oversold"},
   {"kind": "narrative", "text": "Revenue is priced from the request, not from what the shelf could supply."}
 ]}

Re-issuing with the same name replaces the report. Add path to render the PDF, or csv: 2, path: "rows.csv" to export that table's rows — both write inside the exchange directory under the same Allow assistant file exchange opt-in as every other file the assistant writes. The Reports tab has the same door for you: Export PDF… renders the selected report to a file you choose — your chooser is the consent, so it is not confined to the exchange directory.

Reports persist with your profile, travel with projects, and share under their own category — because a shared report carries narrative written about your data, which deserves its own consent checkbox. See Sharing setups.