flybrain/docs/design/session-framework/publishing-v1.md
dev 7ce645f1dc session: a restored boundary is an installed one, and the revision follows the composition
A group restore re-establishes a committed boundary this epoch did not run a
transition into, so its snapshot carries no decision and no controls for any
agent, and a fresh epoch is a new compositionDigest, so the descriptor takes the
next revision rather than republishing revision 1 with different contents.

CommittedSnapshot's rule becomes: null at boundary 0 and at an installed
boundary, always together, and for every agent or none -- a snapshot where one
fly acted and another did not would be two boundaries in one value. Dated
amendment to publishing-v1 section 3, with the schema set, the fixtures and the
TypeScript package moved together and the digest regenerated.
2026-09-22 19:24:44 +00:00

12 KiB
Raw Blame History

Application, presentation and audience contracts

Status: draft 2, 2026-09-18. All internal communication uses Flybus. This document defines ownership and logical data contracts, not the public feed v2 byte format. Existing public v1 contracts remain unchanged for the legacy application.

1. Applications orchestrate; presentation owns the show

Fly Plays Pokémon is an application assembled from simulation and presentation components. Its supervisory code and interface share an application-owned, versioned state/event schema. A Melee competition or ecosystem can choose another schema. Director, tournament, bracket, cast of 32 personas and story segments are examples, not mandatory framework services/types.

The framework supplies sessions, agents, backend/task interfaces, capability descriptions, native observations and generic UI/client primitives. The application chooses lifecycle, profiles, game-aware macros/recovery, identities/history, interventions and narrative behavior. Frame-by-frame scheduling remains inside the session; the application need not RPC each tick.

Presentation owns selection/layout, resizing, overlays, compositing, audio mixing, encoding, browser-facing delivery, recording choices and stream output. Native 480p game data is a perfectly valid framework output. Neither the router nor generic session assumes Twitch, 1920×1080, particular colors, specific React components or an automatic tournament dashboard.

2. One bus, complementary data sources

Session ── committed observations/events + artifact handles ──┐
                                                           ├─ Flybus ── presentation application
Application ── own state/events/presentation cues ────────────┘
Presentation gateway ── application-defined browser delivery ── frontend

Example addresses (chosen by composition, not recognized by router code):

Address Pattern / purpose
session.demo RPC: domain lifecycle/status/capabilities; exact methods require session API schemas
session.demo.descriptor Pub/sub, retained latest: framework descriptor
session.demo.snapshots Pub/sub, latest: committed simulation values and native media refs
session.demo.events Pub/sub, bounded: scoped domain events; not a durable log
app.pokemon.state Pub/sub, retained latest: application-specific state
app.pokemon.cues Pub/sub: application narrative/presentation events under declared delivery policy
app.pokemon RPC: application queries/admission, e.g. restore UI state or request a supported effect

Amendment, 2026-09-22 (PUBLISH-01). The repair path above needs exact methods, and "exact methods require session API schemas" left the row unbuildable. The session registers one read-only service, session.<id>.query, with exactly two methods, both ordinary session RPCs answering from what the session already published: Session.GetDescriptor takes an optional {revision: U64} and returns that SessionDescriptor or, with no revision, the newest; Session.GetSnapshot takes no parameters and returns the latest CommittedSnapshot. A revision the session never published is IDENTITY_MISMATCH, not an empty answer. Nothing on this service mutates, selects a participant or reaches a worker, so it is not the controller API section 7 rules out; adding a third method that did would be. These two names are internal and provisional: they are what the internal boundary needs in order to be buildable now, and the later public v2 step is free to rename them, supersede them or expose a different repair surface entirely. Nothing about them is browser-facing, and the public step does not inherit them by default merely because they landed first.

Descriptor revisions and scope link observations to schemas. Cross-topic ordering is not guaranteed; a subscriber receiving an unknown descriptor revision must fetch it through the application/session query contract or buffer a bounded number of snapshots, not infer shape. Latest retained descriptors accelerate startup; RPC querying remains the repair path.

3. Common simulation descriptors and committed values

Types use session RPC, workers, state/media:

interface SessionDescriptor {
  sessionId: Id; revision: U64; compositionDigest: Digest;
  schedulerId: "lockstep-v1";
  environment: EnvironmentDescriptor; taskSchema: SchemaRef;
  agents: {
    agentId: Id; portId: Id; profileDigest: Digest;
    datasetDigest: Digest; indexDigest: Digest; neuronCount: U64;
    rateRoles: Id[]; supportedStimuli: Id[];
  }[];
  assets: AssetRef[];
}
interface CommittedSnapshot {
  descriptorRevision: U64; publisherIncarnation: Id;
  scope: Scope; episodeId: Id; sequence: U64; worldTime: RationalNs;
  agents: {
    agentId: Id; telemetry: AgentTelemetry;
    selectedDecision: TypedValue | null;
    appliedControls: PortControl | null;
  }[];
  progress: TypedValue;
  media: { views: ViewRef[]; audio: AudioRef[] };
  eventIds: Id[];
}

Amendment, 2026-09-22 (PUBLISH-01). "Null at initial boundary 0" is the rule for a boundary this epoch produced. A group restore (state/media section 5) re-establishes a committed boundary k > 0 that this epoch did not run a transition into, and the abandoned epoch's decisions are not this session's to republish under a new epoch. So the rule is: selectedDecision and appliedControls are null at boundary 0 and at a boundary installed by a restore, present otherwise, and always together and for every agent or none. A snapshot where one fly carries an action and another does not would be two different boundaries in one value, and is refused. Without this, the section 6 requirement to publish the recovery could not be met at all: the restored boundary's snapshot would be unrepresentable.

Publish only after all agent commits establish Ready(k). Decisions/controls describe the transition ending at that boundary, null at initial boundary 0. Health updates are separate and never claim an uncommitted future boundary. Every transient media reference is a declared bus attachment held through publication admission. Ordinary snapshot publication is latest/ bounded and never waits for a spectator to consume it.

Publication sequence is monotonic within publisherIncarnation. Epoch determines simulation timeline; router topicSequence only determines bus acceptance order. Never equate these. Geometry/spike mapping requires indexDigest, not merely the same number of neurons. Persistent AssetRefs survive release packaging; ephemeral ArtifactRefs never become permanent asset URLs.

4. Flexible data, authored UI

Common UI primitives should understand media, agents, controllers, typed measurements, progressions, collections and events. Descriptors change infrequently; values change frequently. Application-specific structures remain namespaced, schema-validated extensions, such as pokemon.progress.v1 or melee.match.v1. They are not mandatory fields on every snapshot.

Proposed measurement vocabulary to formalize with public v2 schemas:

Definition: id, owner, label, kind, unit, optional range, schema revision
Sample: id, producing scope/time, validity, value
Validity: measured | unknown | unsupported | stale

Kinds include number/counter, gauge, duration, state, progression and collection with typed item schemas. A measured zero is distinct from unknown. Stale values retain original timestamps. Units/ranges are metadata, not pixel sizes. Unknown optional extensions may be omitted or shown generically; unsupported required schemas are visible errors. No remote executable UI payloads.

Application state carries whatever the experience needs: progress history, featured fly, competition records, season state or sponsor effects. It is developed with its presentation, not forced into a framework-wide “show state”/tournament schema. An application can reuse generic components and add its own panels without changing worker/transport contracts.

5. Artifact consumption and browser boundary

The native presentation client is a regular bus subscriber. Its renderer may hold an extracted Artifact after dropping the message; the SDK delays consumption until actual use finishes. Latest coalescing only drops queued values. A stalled consumer is constrained by finite credits, owners and store budgets; it cannot make the router overwrite an in-use image.

The browser does not receive private owner tokens or local storage paths. A presentation gateway resolves/copies/encodes artifacts into its chosen browser transport and then drops its bus handles. That is an application-edge adapter, not a second framework communications stack. Compositor/encoder/recorder processes inside the application can exchange their own artifacts through the same bus when useful.

Dense spike publication is optional and identifies agent, index digest and covered ticks. The runtime need not publish every neuron every millisecond. Required sensory data and optional spectator data have distinct budgets; UI focus never changes an agent's input, controller assignment or an already resolved stimulation/effect target.

6. Events, persistence and recovery visibility

Events identify session/epoch, source boundary, episode, optional agent, kind and typed payload. Task events are emitted after committed transitions; capture/admission events describe their actual phase. Bus publish acceptance and delivery consumption are not durable acknowledgments.

When durability is required, call a configured event-store client/service (over the same bus) and await its append/commit acknowledgment under the session's configured policy. Pub/sub remains useful for live observers; reconnecting clients query durable history through ordinary RPCs. The initial conformance policy pauses at the next safe boundary if durable event admission/commit fails, retaining only a bounded pending batch. No hidden durable broker queue.

After rollback publish old/new epochs, checkpoint identity and abandoned step ranges. Application history can mark outcomes aborted/superseded; it does not erase records merely because emulator time moved backward. Media reports the corresponding discontinuity.

7. Supervisory and audience effects

Application supervision uses bus RPCs for configured lifecycle/intervention capabilities and pub/sub for application state/cues. The Twitch adapter can be a constrained bus client of the application's admission service; viewers/browser clients never obtain worker control. Legacy HTTP bridge behavior remains until deliberately migrated.

Effects are declared by the task/backend/profile: valid targets, parameter schema, timing, duration/stacking, implementation capability and outcome events. Examples include neural stimulation or future game items/modifiers, where verified implementations exist. Generic game boons and a public v2 admission schema are follow-on work; initially new-session audience effects remain disabled. No arbitrary controller/game-memory write endpoint is introduced.

requested → rejected
          → accepted(target, epoch, earliestStep) → scheduled → applied → expired
                                                └─ failed / cancelled
                                                    applied → rolled-back

The application persists interaction identity/target and defines retry, redemption, refund and recovery policy. A gift is an intervention, not automatically an earned neural reward. Presentation cues may be immediate; simulation effects apply at declared boundaries. The supervisor does not bypass the complete-batch step barrier. Chat text remains presentation data; template-only replies/quiet mode and existing no-public-button rules continue.

8. Presentation acceptance criteria

  • Per-agent/session stores and rate/afterglow state, not one mutable global fly.
  • Framework plus application schema streams, with descriptor repair/reconnect behavior.
  • Native view dimensions and aspect; application-controlled output resolution and composition.
  • Explicit audio ownership, timestamped overlays and bounded queues/discontinuities.
  • Last-use artifact release, cached/replay-safe references and slow-observer isolation.
  • Game-specific labels/views without Pokémon fields in generic runtime/router schemas.
  • Actual UI changes reviewed as PNGs with browser/legibility gates. This contract is not screen approval.