| type | concept | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| topic | runtime | |||||||||||||
| audience |
|
|||||||||||||
| search_hints |
|
The present layer lets an agent show bulk data to the user without the data passing through LLM output tokens. An agent that has obtained a large result — a table of search hits, a file, a structured API payload — routes the data's handle plus a declarative view straight to the user-facing surface. The bulk bytes reach the user directly; the LLM never re-types them.
When an agent obtains external data and wants to show it, the data traditionally round-trips through the LLM twice:
- Input — the tool result enters the LLM's context. reyn already solves this: a large result lands in a ref file and the LLM sees only a schema + preview, reading the full value back on demand (see Workspace and the offload mechanism).
- Output — to show the data to the user, the LLM re-types it as output tokens. This is the axis the present layer solves. Re-typing 500 rows is expensive, and to fit its output budget the LLM summarizes or truncates — so the user also loses fidelity.
The offloaded ref file is already "data file + handle". What was missing is a primitive
that routes that handle plus a view to the user's surface directly. That primitive is
the present op.
The defining asymmetry of the present layer:
The LLM works from the schema + preview and binds paths; the renderer joins the view against the full data the LLM never ingested. The user sees everything; the LLM sees only the shape.
This is the designed contract, not a defect. Display is free; computation costs. Presenting N rows costs ~0 output tokens. The moment the agent must transform the data — sort it, filter it, answer a question about it — it must read the ref and pay for the tokens. Keeping display cheap and transformation costly is the whole point.
Whether the LLM had actually read the data before presenting it is a fact the OS can compute (was the data inline, or does a prior read of the ref appear in the session?). The present layer does not forbid blind presentation — it makes blindness auditable, recording it as an annotation on the audit event.
A view is a declarative component tree, never code. It is built from a fixed catalog of read-only components:
| Component | Shows |
|---|---|
text / markdown |
a string (markdown is rendered as CommonMark) |
code |
source with a language for highlighting |
diff |
a unified diff |
keyvalue |
a card of label/value rows |
table |
rows × columns |
list |
a bullet list |
image |
v1 renders an [image: <alt>] dim-text placeholder only — not yet routed to the multimodal delivery path |
Data is joined to the view by JSON Pointer (RFC 6901) path bindings — expressed
structurally as {"$bind": "<pointer>"}. table and list paths resolve row-relative
(relative to each iterated row). Everything that is not a $bind is a literal.
No markup, no HTML, no code ever crosses from the LLM to the renderer. Safety comes from the primitive's shape — a vetted catalog + path bindings — the same "safety by construction" philosophy as reyn's structural write-gate. v1 is display-only: there are no buttons, forms, or other interactive components. UI-spoofing (fake consent dialogs) only becomes harmful with interactivity, so reyn structurally avoids the whole class; interactivity, when it comes, will route through the existing intervention (consent) path.
Leaf-string safety is split across two layers, because a single "escape everything" strategy corrupts markup-inert sinks:
- Presentation-guard (surface-universal, the output seam). Runs unconditionally —
including, and especially, for never-ingested data — over every render leaf (labels,
literals, and bound values) at one seam. It neutralizes threats that are dangerous to a
surface regardless of which component receives the content. On the terminal that means
stripping ESC / control sequences (OSC / CSI — notably the OSC-52 clipboard escape).
That is the guard's whole terminal job: it does not HTML-escape (a
<div>is inert literal text in a terminal, and escaping would corruptcode/diff) and does not escape Rich console markup. A future web surface plugs in HTML/JS escaping as its guard strategy, without touching the core. - Renderer discipline (per-component, render-API safety). Rich console markup
(
[red]…[/]) is not a surface-universal threat — it is interpreted only by a specific print call, never by an inert sink. So the terminal renderer achieves markup safety structurally: it routes every untrusted leaf into a markup-inert Rich object and never asks Rich to interpret markup on leaf content. Injection becomes impossible with no escaping anywhere.
Per-binding size caps stop a root pointer bound into a text component from dumping a
whole file: a capped leaf string keeps a compact … (+N chars — full data in the ref) tail.
present also caps array bindings to head-N rows because a table/list binding is
otherwise unbounded by construction — a capped table/list node carries the same kind of
visible tail (…N more — full data: <ref>; the ref clause is omitted for inline data, which
has no re-fetchable ref) so the operator sees that rows were dropped, not just the LLM. The
ref is always the full-fidelity escape hatch: re-present with a filter or a higher cap,
or read it directly.
A binding miss never loses the user's access to the data. View resolution degrades through four stages, the last of which always renders:
- Registered view — a named view from the operator's registry.
- Inline blueprint — an LLM-authored component tree, structurally gated at op validation.
- Default viewer — a blueprint synthesized from the data's shape (
list[dict]→table,dict→keyvalue, scalar →text, a diff-sniff →diff), run through the same bind → guard → render path. - Generic — the final catch: structured data dumped as YAML into a
textcomponent, plain text shown as-is.
The fallback fires on an all-miss view or an unknown view name — never a hard
error. The op's ack reports the requested view's stats plus a note naming the
stage that actually rendered, so a blind agent self-corrects for a few tokens: many
path_not_found drops read as "my view doesn't match this data shape", type_mismatch
as "right path, wrong component", guard_stripped as "content neutralized by the guard,
not a view bug".
Named views are registered by an operator in a config file; the LLM only ever authors inline blueprints. This mirrors reyn's write-gate culture — the durable, reusable surface is operator-owned.
Omitting both view and blueprint is valid (FP-0055 PR-1): resolution enters
directly at stage 3, skipping stages 1-2 entirely — present(data_ref=...) alone "just
shows" the data. This IS the requested rendering, not a fallback: the ack carries the
default viewer's own stats and no note, unless stage 3 itself degrades further to
stage 4.
Every presentation emits a durable presented audit event carrying refs + stats
only, never content bytes — the data is already durable in the ref file, so the event
stays light. The event records the data ref, the view name (or a hash of the inline
blueprint, or null when neither was given), the mode (view | blueprint |
default), the surface, the OS-computed ingested annotation, and the binding stats.
A presentation is a cache; the presented event is the truth. When a session is
replayed or rewound, a presented event is re-rendered best-effort: if
the ref is still readable, its content is re-synthesized from the data's shape (the event
never stored the bytes); if the ref is gone (garbage-collected or unavailable) — or the
data was inline and never persisted — the replay shows an expiry placeholder pointing
at the durable audit event. It is never a crash and never a stale render. present pins
nothing into a retention window: refs keep their existing lifecycle, and because the
conversation history never contains the presented bytes, there is nothing new to compact.
- Reference: present op & surface — op args, catalog,
binding, registration, ack, the
presentedevent, and the replay note. - Control IR — the
presentop in the op catalog. - Events — replay and the audit log.
- Workspace — refs and the offload mechanism
presentconsumes.