Journal Compose Contract

Status: Current contract Authority: This note defines the current compose_journal contract alongside engine/src/tangl/vm/dispatch.py and engine/src/tangl/journal/fragments.py.

compose_journal is the post-merge story seam for transforming ordered journal fragments after raw JOURNAL handlers run and before service projection or client rendering begins.

Current Runtime Contract

  • render_journal handlers produce ordered raw fragments.

  • compose_journal receives the merged fragment list in stream order.

  • compose_journal operates on normalized fragment values only; raw textlike inputs belong in render_journal, not this seam.

  • A compose handler may return:

    • None

    • one Record or BaseFragment

    • an iterable of Record or BaseFragment

  • Invalid replacement shapes raise TypeError.

  • Later compose handlers may inspect earlier compose results on ctx.results.

Reference Transform

  • The canonical reference implementation is tangl.story.system_handlers.compose_dialog_markup.

  • It rewrites only eligible ContentFragment values containing explicit dialog micro-block markup.

  • It is order-preserving except for the local replacement of those eligible fragments.

  • Non-eligible fragments pass through unchanged.

  • Richer peer fragments may continue to later service and client layers, which remain responsible for capability-specific handling.

Allowed Transformations

  • pass through raw fragments unchanged

  • split one fragment into many

  • merge many fragments into one replacement

  • annotate or enrich fragments with hints or speaker metadata

  • synthesize additional peer fragments

  • emit relational overlays such as GroupFragment(member_ids=[...])

Forbidden Transformations

  • client-format shaping such as HTML policy or transport DTO construction

  • media dereference or client capability negotiation

  • mutation of runtime, world, or graph state

  • silent erasure of provenance metadata when an equivalent replacement trail is possible

Metadata Preservation

When a fragment carries step, source_id, origin_id, tags, or hint models, a transform must preserve those fields unless the replacement fragment explicitly supersedes them and still carries an equivalent provenance trail.

Placement Rules

  • VM render: produce raw ordered fragment contributions

  • story compose_journal: normalize and enrich the fragment stream

  • service projection: convert engine-native fragments and projected-state models into transport-ready payloads

  • client render: ignore unsupported fragment kinds safely and apply client-specific presentation policy

Examples

Good example:

  • rewrite dialog micro-block text into attributed fragments

  • preserve source_id, step, and hint metadata

  • add a GroupFragment overlay when grouping is useful

Bad example:

  • emit HTML snippets or transport-specific card payloads from compose_journal

  • fetch media URLs or inline binary data for a specific client

  • mutate ledger or world state as part of output composition