Skip to content

Documents & Reports

The last mile of most AI programs is a document: a report, a case summary, a briefing. The tempting shortcut is to ask a model for Markdown and hope it comes back well-formed — then patch it with regexes when it doesn’t. Sema splits the job cleanly: the model fills a typed structure, and rendering that structure to Markdown is a pure, deterministic function. Layout is structural, not a prompt convention.

This guide uses the std.document module for the report IR and anchors on the finops-ledger example for grounded, review-gated report generation.

Concern Construct Where
Typed report IR Report struct + render std.document
Model fills a typed value simulate def … by <model> simulate & Models
Grounding the output check semantics(…) Contracts
Docs from the program sema doc (docstrings + reflection) this guide, §“Docs as artifact”

std.document defines a Report the model (or your code) fills, and a deterministic render(report, nl) that emits Markdown. nl is the newline separator — parametrizable so the same renderer emits a single-line form for tests or real newlines for output.

struct Report:
title: str
context: str
confidence: f64 # < 0 means "no confidence section"
rationale: str
takeaways: list[str]
section_titles: list[str]
sections_text: str
conclusion: str

Rendering is a function, so what you build is what you get — no regex repair of freeform LLM Markdown, and section placement is structural:

from std.document import Report, render
r = Report(
title="Renewable Energy Findings",
context="Auto-synthesized from 2 sources.",
confidence=0.82,
rationale="Confidence is a Beta-Bernoulli posterior over iteration evidence.",
takeaways=["costs fell", "capacity grew"],
section_titles=["Findings"],
sections_text="Solar capacity grew [1]. Costs fell [2].",
conclusion="Costs continue to decline as capacity scales.",
)
print(render(r, "\n"))

Set confidence below zero to omit the confidence section entirely — the renderer treats it as a signal, not just a number.

For a report whose prose comes from a model, don’t ask the model for Markdown — ask it for the typed fields. A simulate def … by <model> has the model implement the body, decoded into your struct, with contracts on the result. The finops-ledger example drafts a compliance case summary this way:

simulate def draft_suspicious_activity(
decision: ReconciliationDecision,
bank: BankLine,
ledger: list[LedgerEntry],
) -> SuspiciousActivityDraft by anomaly_writer:
sem "Draft a cautious case summary for a compliance analyst"
sem "Do not claim criminality; state uncertainty and cite evidence references"
budget tokens=768, time="3s"
ensure len(result.reasons) >= 1
ensure result.subject_counterparty_id != ""
check semantics(
"draft is grounded in the reconciliation decision and does not overstate certainty",
decision,
result,
judge=report_grounder,
alpha=0.01,
)

Three things make this an auditable document rather than a hopeful one:

  • ensure contracts are hard — a draft with no reasons raises ContractViolation.
  • check semantics(…) is a soft, monitored guard: a judge model verifies the draft is grounded in the decision and doesn’t overstate certainty, at a calibrated significance alpha.
  • budget tokens=…, time=… caps what the draft may cost.

See Contracts for the full contract vocabulary and Schemas & Typed Decode for how the model’s output is decoded into the struct.

Gating a report before it leaves the building

Section titled “Gating a report before it leaves the building”

A generated document usually needs a review gate before it becomes an artifact. In finops-ledger, the export path requires a human approval record, sanitizes the draft, and wraps the write in a semantic guard so a non-conforming draft is quarantined instead of shipped:

@RegulatedExport
def export_after_approval(draft: SuspiciousActivityDraft, approval: AnalystApproval) -> None !{fs.write, net.connect, model.invoke}:
safe = sanitize_draft(draft)
report_path = validate f"out/regulatory/{approval.decision_id}.json":
sem "Local regulated-report path derived from analyst approval"
ensure path.is_relative_to(value, "out/regulatory")
ensure not path.contains_parent_ref(value)
expect semantics("regulated draft contains only approved evidence and no raw account number", safe, judge=policy_judge, alpha=0.01):
write_report(report_path, safe)
log.info("regulated export prepared")
submit_report("https://regulator-gateway.internal:443/drafts", safe)
except SemanticsViolation as violation:
quarantine(safe, evidence=violation)

The validate f"…" block turns an interpolated path into a checked value — the ensure guards keep the write inside out/regulatory and reject .. traversal — so the document’s destination is as governed as its contents.

Your program itself is a document too. In Sema, documentation is generated by reflection over the code, merged with prose you write inline as docstrings — a triple-quoted string as the first statement of a module, def, struct, or enum (as in Python). Unlike a comment, a docstring is a real value the runtime can reflect:

"""Geometry helpers."""
def norm(x: f64, y: f64) -> f64 !{}:
"""
The Euclidean norm of a 2-D vector: $\|v\|_2 = \sqrt{x^2 + y^2}$.
> [!NOTE]
> The result is always non-negative.
```sema
n = norm(3.0, 4.0) # -> 5.0
```
"""
return math.sqrt(x * x + y * y)

sema doc <project> then emits Markdown that combines reflection (the exact signature, params, return type, effect row, struct fields with their sem descriptors, enum variants — always accurate because it is the code) with your docstring prose (Markdown, LaTeX, > [!NOTE] admonitions, sema examples, passed straight through). Docstrings are dedented like Python’s inspect.cleandoc, and being triple-quoted they are raw — LaTeX backslashes survive untouched.

Two flags close the loop:

  • --html renders a self-contained page (KaTeX-typeset math, admonitions, code blocks) with no build step — the “nice page”.
  • --skills emits each module’s doc with skill frontmatter, so generated docs load as model context via skills.load (see Tools, Skills & MCP) — code that documents itself to humans and to the models that read it.

From the sema/ directory:

Terminal window
sema check examples/finops-ledger
SEMA_STRICT=1 sema run examples/finops-ledger
sema assure examples/finops-ledger --grade gold
sema doc examples/finops-ledger --html

finops-ledger is an assure gold project — the strongest grade, which mutation-tests on top of running test blocks and fuzzing ensure properties.

  • A single-page HTML report — render a Report, write it with fs.write, then sema doc --html the module for the reflected companion page.
  • A briefing instead of a report — the crisis-logistics example drafts a public safety PublicBriefing with the same simulate def + check semantics shape, then redacts and gates it before publication.
  • Test the exact Markdown — because render is pure, put a golden string in a test block and let sema assure verify layout stability.