# Fragment Stream Contract > Status: Current contract with near-term extension points > Authority: `tangl.service.response.RuntimeEnvelope`, > `tangl.journal.fragments`, `ServiceManager`, and the web fixtures under > `apps/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: ```text 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: 1. Normalize each fragment into a record with `uid` and `fragment_type`. 2. Apply `update` and `delete` control fragments to the local registry. 3. Store all other fragments by `uid`. 4. Build presentation shells from `group` fragments, especially `group_type="scene"`. 5. 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 | | --- | --- | | `content` | Prose, status text, or fallback text. | | `attributed` | Speaker-attributed content such as dialog lines. | | `media` | Media reference or placeholder; service may dereference RITs. | | `choice` | Player-facing interaction offer backed by an `Action` edge. | | `group` | Relational overlay tying peer fragments together by id. | | `piece` | Targetable piece or object rendered inside a zone. | | `kv` | Ordered key-value content for compact state/status surfaces. | | `update` / `delete` | Control fragments mutating an earlier registry entry. | | `user_event` | 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 to `resolve_choice` - `text`: user-facing label - `available`: whether the action can currently be committed - `unavailable_reason`: short human-readable or code-like reason - `blockers`: structured diagnostics explaining unavailable state - `accepts`: optional payload/input contract - `ui_hints`: optional rendering hints such as hotkey, icon, or widget family Clients post: ```text 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: | `accepts.kind` | Payload | Meaning | | --- | --- | --- | | absent or `pick` | `{}` or omitted | The edge id is the whole answer. | | `text` | `{text: string}` | Freeform line such as a name, password, note, or command. | | `quantity` | `{quantity: int}` | Integer amount with optional min/max/unit/cost hints. | | `pieces` | `{piece_ids: string[]}` | Selection from a visible target zone. | | `raw_command` | `{text: string}` | 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"`: `content` is directly renderable or remappable. - `content_format="data"` / `"xml"` / `"json"`: `content` is inline payload. - `content_format="rit"`: `content` refers to a `MediaRIT` or 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_ids` references 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`, or `status_sidecar` are 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: ```text 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: ```text 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 `RuntimeEnvelope` - a realistic `ProjectedState` - locked 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.