| type | reference | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| topic | runtime | |||||||||||||||||||||||||
| audience |
|
|||||||||||||||||||||||||
| search_hints |
|
Operator/agent-facing reference for the present layer — the present op's args, the
v1 component catalog, path binding, named-view registration, the op ack, the
presented audit event, and the replay/rewind behavior. For the why (the axis-B/C
problem, the LLM-sees-shape/user-sees-content asymmetry, the guard/renderer split), see
Concepts: Present layer. The op also appears in the
Control IR op catalog.
{
"kind": "present",
"data_ref": ".reyn/cache/tool-results/2026-.../structured.json",
"blueprint": {
"component": "table",
"rows": {"$bind": "/results"},
"columns": [
{"header": "Title", "path": "/title"},
{"header": "Author", "path": "/author"}
]
}
}Exactly one data source; at most one of view / blueprint (both omitted is
valid — see Optional view/blueprint below):
| Arg | Type | Notes |
|---|---|---|
data_ref |
string | XOR data_inline. Any zone-readable path. An offloaded structured_ref is re-hydrated to its full value (not read from the LLM-visible preview) via file.read semantics. |
data_inline |
any | XOR data_ref. Small data already in the LLM's context (convenience). |
view |
string | At most one of view / blueprint. A registered presentation name (see registration below). An unknown name is not an error — it falls through the fallback chain. |
blueprint |
object | array | At most one of view / blueprint. An inline declarative component tree (a single node or a top-to-bottom list). |
view is FP-0055 PR-1's rename of the original template arg — a clean break, no
alias. "Template" now means exclusively the future render_template op's Jinja2 text
templates; the declarative, registered-or-inline presentation description present uses
is a view.
- Tier 0 (
ask_user's sibling), fire-and-continue — presenting to the user (the trust root) has no output permission gate, and unlikeask_userit does not pause the run. The one gate:data_refread authority resolves identically tofile.read—presentcan never read more than the agent's file ops can (presentdenied ⇔file.readdenied).
All components are read-only. A blueprint node is {"component": <name>, ...slots}.
| Component | Slots |
|---|---|
text |
text (bind or literal) |
markdown |
text — rendered as CommonMark |
code |
text, language? |
diff |
text — unified diff |
keyvalue |
rows: [{label, value}] |
table |
rows (bind → array), columns: [{header, path}] |
list |
items (bind → array), item_path? (per-item path) |
image |
src, alt? — v1 renders an [image: <alt>] dim-text placeholder only, not yet routed to the multimodal delivery path |
There are no interactive components (no buttons / forms) in v1.
Data is joined to a view by JSON Pointer (RFC 6901) paths, expressed structurally:
{"$bind": "/results/0/title"}— a pointer string;""binds the whole document.- Anything that is not a
$bindobject is a literal (e.g. aheaderstring). tablecolumns[].pathandlistitem_pathresolve row-relative (relative to each iterated row).
Binding outcomes (§4): path hit → bind; path miss → soft-skip + record
path_not_found; type mismatch → coerce (a scalar into a table rows slot → a 1-row
table) + record type_mismatch; a leaf neutralized/size-capped by the guard → record
guard_stripped. When all bindings miss, the op reports all_bindings_missed and
routes to the fallback chain — never a hard failure.
The structural gate at op validation rejects a non-catalog component or a non-path
binding as a hard error (status="error") for an inline blueprint — that is a
blueprint bug, distinct from a soft binding drop.
Named views are registered in presentations.yaml (presentations.entries) — an
operator/config action. There is no install op; the LLM authors inline blueprints only.
presentations:
entries:
search_results:
blueprint: # required; inline component tree
- component: table
rows: {"$bind": "/results"}
columns:
- {header: Author, path: /author}
- {header: Title, path: /title}
description: "Search results table" # optional
enabled: true # optional, default trueThe blueprint is validated at load; the <project>/.reyn/config/presentations.yaml layer
hot-reloads at the turn boundary. Full field table + merge order:
reyn.yaml § presentations.
Resolution degrades until something renders (never a hard error):
- Registered
view→ 2. inlineblueprint→ 3. default viewer (a recognition ladder: declared content-type → diff-sniff → data shape — a markdown/code type →markdown/code; elselist[dict]→table,dict→keyvalue, scalar →text, diff-sniff →diff) → 4. generic (structured → YAML intotext, plain text as-is — always renders).
The declared-content-type step is populated for a data_ref source whose canonical
tool-result producer declared a content_type/mimeType — carried from the offload
producer to here as a renderer-only sidecar (never the LLM-visible tool-result
frontmatter): the producer's declared type drives the offload store's mime_type
(the on-disk ref extension), and present recovers it back from that extension when
resolving the ref. Inline data (data_inline) has no content-type source and always
degrades straight to diff-sniff → shape, unchanged.
The fallback fires on an all-miss view or an unknown view name. The ack reports the
requested view's stats plus a note naming the stage that actually rendered.
view and blueprint may both be omitted (FP-0055 PR-1) — present(data_ref=...)
alone is valid and means "no explicit view; just show it". Resolution then enters
directly at stage 3 (the content-type default viewer), skipping stages 1-2 entirely
— there is no requested view to look up or fall back from. The ack carries mode: "default" and the default viewer's own stats, with no note — this is the intended
rendering, not a fallback. A note still appears if stage 3 itself degrades further to
stage 4 (the default viewer's own bindings all miss), naming that further degradation —
distinct wording from the "view not registered" / "all bindings missed" notes below,
since there was no requested view to begin with.
The LLM's only feedback — compact + high-signal:
ok: true
mode: view # view | blueprint | default — which input the caller gave
bindings_resolved: 3
rows: 500
bindings_dropped:
- {path: "/results/0/author", reason: path_not_found}
# reason ∈ {path_not_found, type_mismatch, guard_stripped}
all_bindings_missed: false
note: "…" # present only when a fallback stage renderedpath_not_found across many rows → "view doesn't match this data shape";
type_mismatch → "right path, wrong component"; guard_stripped → "content neutralized by
the guard, not a view bug". The agent self-corrects without ingesting the data.
Every presentation emits one presented event carrying refs + stats only, never content
bytes:
| Field | Meaning |
|---|---|
data_ref |
the ref path, or <inline-data> for a data_inline presentation |
view |
the registered name, blueprint:<hash> for an inline blueprint (no blueprint bytes), or null when neither was given (mode: "default") |
mode |
view | blueprint | default — which of the three mutually-exclusive inputs the caller gave |
surface |
list, e.g. ["inline-cui"] (["null"] when no renderer is wired) |
ingested |
none | partial | full — OS-computed (was the data inline, or does a prior read_file on the ref appear earlier in the session?), never LLM-self-reported |
bindings_resolved |
count of resolved bindings |
bindings_dropped |
[{path, reason}] |
rows |
row count bound |
fallback_stage |
null | content_type_default | generic — which viewer actually reached the user. null = the requested rendering (or the mode: "default" stage-3 viewer) rendered directly; a non-null value = the requested view was unknown / all-missed and a synthesized viewer took over. This is what distinguishes a literal-only view rendered as requested (null) from an unknown / all-missed fallback — both otherwise carry bindings_resolved=0, bindings_dropped=[]. |
A presentation is a cache; the presented event is the truth. On replay
(reyn events <log>) or rewind, a presented event re-renders best-effort:
- Ref still readable → the content is re-synthesized from the data's shape (the event never stored the bytes, so this uses the default/generic viewer, not the caller's original inline blueprint) and reaches the surface.
- Ref gone (GC'd / unavailable), or the data was inline and never persisted → an
expiry placeholder pointing at the durable
presentedaudit event. Never a crash, never a stale render.
present pins nothing into a retention window; refs keep their existing lifecycle, and the
conversation history never contains the presented bytes (nothing new for compaction).
The CLAUDE.md recovery-feature truncate-falsify gate (a PR adding WAL-event-derived
reconstruction / PITR / rewind-restore state must prove the reconstruction source survives
WAL truncation) does not apply to the present layer. Replay here reconstructs no
authoritative state: it produces a display-only projection — a best-effort re-render
of an already-durable ref, or a placeholder — and present writes no recovery-core
state. Nothing derives recoverable state from presented events. Were a future revision
ever to reconstruct authoritative state from presented events, that PR would have to
carry the truncate-falsify test in-arc.
- Concepts: Present layer
- Control IR — the op in the catalog
- reyn.yaml § presentations — registration
- Events — replay and the audit log