Fragment Stream Contract¶
Status: Current contract with near-term extension points Authority:
tangl.service.response.RuntimeEnvelope,tangl.journal.fragments,ServiceManager, and the web fixtures underapps/web/tests/fixtures/. Renderer vocabulary:../story/STORYTANGL_WIDGET_VOCAB.md; repo-current implementation status:../story/WIDGET_CONTRACT_RECONCILIATION.md.
Story session methods return RuntimeEnvelope. The envelope carries ordered
journal fragments plus cursor metadata. Clients render those fragments directly;
they do not receive, infer, or rebuild a second block-shaped story model.
Layer Placement¶
The fragment stream sits at the boundary between story/runtime output and client presentation.
Story owns fragment meaning and journal composition.
Service owns session lifecycle, envelope metadata, and transport-safe dereferencing such as media payload shaping.
Clients own layout, widgets, accessibility mapping, and graceful fallback.
The service layer must not invent a parallel fragment hierarchy. Unknown
fragments remain BaseFragment values with their original payload preserved so
clients and remote managers can degrade safely.
RuntimeEnvelope¶
RuntimeEnvelope is the canonical story-session response:
RuntimeEnvelope
cursor_id: UUID | null
step: int | null
fragments: list[BaseFragment]
last_redirect: dict | null
redirect_trace: list[dict]
metadata: dict
create_story, resolve_choice, and get_story_update return this envelope
directly. Acknowledgement-only operations such as drop_story return
RuntimeInfo.
fragments are ordered for reading and replay. A fragment’s uid is also a
stable reference target within the envelope and across later update/delete
control fragments.
Fragment Registry Model¶
Clients should treat each envelope as a fragment event batch:
Normalize each fragment into a record with
uidandfragment_type.Apply
updateanddeletecontrol fragments to the local registry.Store all other fragments by
uid.Build presentation shells from
groupfragments, especiallygroup_type="scene".Render known fragment types with widgets and unknown types with visible fallback.
This registry model is what lets independent fragments update, disappear, or be grouped without collapsing the turn back into one display block.
Current Fragment Vocabulary¶
Core reusable fragment types live in tangl.journal.fragments; active
extension types preserve the same BaseFragment shape.
Fragment |
Purpose |
|---|---|
|
Prose, status text, or fallback text. |
|
Speaker-attributed content such as dialog lines. |
|
Media reference or placeholder; service may dereference RITs. |
|
Player-facing interaction offer backed by an |
|
Relational overlay tying peer fragments together by id. |
|
Targetable piece or object rendered inside a zone. |
|
Ordered key-value content for compact state/status surfaces. |
|
Control fragments mutating an earlier registry entry. |
|
User-facing notification or client hint. |
Unknown fragment types are valid extension points. A client that cannot render a fragment type must keep the stream alive and show or stash a diagnostic fallback rather than failing the whole turn.
Near-term command work may also introduce interpretation, a renderable
feedback fragment for raw command attempts that do not advance the cursor. A
client without a dedicated renderer may present its message as ordinary content.
Choice Fragments¶
Choices are interaction offers, not generic display buttons.
ChoiceFragment carries:
edge_id: the action id to send back toresolve_choicetext: user-facing labelavailable: whether the action can currently be committedunavailable_reason: short human-readable or code-like reasonblockers: structured diagnostics explaining unavailable stateaccepts: optional payload/input contractui_hints: optional rendering hints such as hotkey, icon, or widget family
Clients post:
choice_id = choice.edge_id
payload = renderer-collected payload, if any
accepts is intentionally generic. It describes the payload shape a renderer
should collect, not the widget class a particular client must use. The handler
remains the authority for validation and state change.
Canonical near-term variants:
|
Payload |
Meaning |
|---|---|---|
absent or |
|
The edge id is the whole answer. |
|
|
Freeform line such as a name, password, note, or command. |
|
|
Integer amount with optional min/max/unit/cost hints. |
|
|
Selection from a visible target zone. |
|
|
Text submitted to a reserved interpretation edge. |
compose may combine these later using role-keyed subpayloads. It should not be
the first implementation target.
A rich client may render sliders, steppers, piece chips, autocomplete, or form groups. A CLI should be able to ask the same values as sequential prompts and submit the same payload.
Media Fragments¶
MediaFragment preserves media indirection until service/client boundaries.
content_format="url":contentis directly renderable or remappable.content_format="data"/"xml"/"json":contentis inline payload.content_format="rit":contentrefers to aMediaRITor unresolved placeholder.
Pending or unresolved media is still renderable state. Service may turn a pending RIT into static fallback media, fallback text, or a structured media placeholder. Clients should show that placeholder when no final URL/data exists.
Groups And Scene Shells¶
GroupFragment is a relational overlay, not a nested object model.
member_idsreferences peer fragments in the registry.group_type="scene"creates a presentation shell for the current turn.group_type="dialog"associates attributed lines and peer media.Other group types such as
zone,hand,board, orstatus_sidecarare valid extension points.
Groups may reference other groups. Clients may flatten those references for presentation, but the registry remains id-based.
group_type="zone" is the current generic container for targetable piece
surfaces such as a hand, room contents, inventory, field, packet, or map. If a
choice references constraints.target_zone_ref, that zone must be rendered or
reachable in the current shell.
Command Resolution¶
Natural-language command input is backend-authoritative. The client may offer affordances, but the backend resolves and validates.
The portable shape is:
choice:
edge_id: interpret_command
accepts: {kind: raw_command}
commit:
choice_id: interpret_command
payload: {text: "take lamp"}
The backend either returns a RuntimeEnvelope with an advanced cursor or
returns the same cursor with a renderable explanation, such as an
interpretation fragment.
Advisory grammar hints may help capable clients preview a command, highlight
pieces, or show completions. Until there is a dedicated envelope field, such
hints should travel under metadata.grammar. If promoted to a top-level
RuntimeEnvelope.grammar field later, the semantics should stay the same:
grammar is a visible-surface projection and never a security boundary.
The first supported hint shape is intentionally small:
metadata:
grammar:
examples: ["take lamp", "open door"]
verbs: ["take", "open"]
nouns: ["lamp", "door"]
Clients may ignore these hints and still submit {text} to
interpret_command.
Decision Legibility¶
If an open choice references a piece, zone, blocker, state fragment, or other renderable object, the referenced state must be visible in the current shell or otherwise reachable through a supported client affordance.
This rule prevents choices like “Play a card from your hand” from appearing when the hand or card state is hidden from the renderer. It is a conformance rule for fixtures and future richer interaction widgets.
Compatibility Policy¶
Legacy JournalStoryUpdate[] payloads may be adapted at application boundaries
while old mocks or transports are retired. New tests, fixtures, and widgets must
target RuntimeEnvelope.fragments directly.
A compatibility adapter should be narrow, local, and removable. It should never convert canonical fragments back into a unified legacy block shape.
Test Contract¶
Canonical fixtures should live under apps/web/tests/fixtures/ and act as
executable examples of the service contract.
Required fixture behaviors:
a realistic whole-turn
RuntimeEnvelopea realistic
ProjectedStatelocked choices with blockers
freeform, quantity, and piece payload contracts
raw-command payload contracts through reserved interpretation choices
group flattening and dialog grouping
unknown fragment fallback
pending media placeholders
control update/delete
user events
decision legibility checks for references from open choices
CLI-equivalent payload collection examples for every
accepts.kind
Browser E2E should be added only after payload widgets and command feedback are stable enough that E2E coverage will not cement an interim UI shape.