Skip to content

Session Projection Contract

Open Cowork syncs product state through shared Cloud session event and projection record types. The contract is versioned in packages/shared/src/cloud-session-contract.ts and consumed by Desktop, Cloud Web, and Gateway. OpenCode still owns runtime execution; Open Cowork only normalizes runtime output into durable product events.

Version

Current contract version: 1.

Public shared types:

  • CloudSessionEventRecord
  • CloudSessionProjectionEventRecord
  • CloudSessionEventType
  • CloudProjectedSessionEventType

Session state machine ownership (JOE-846)

Three machines exist for reopen parity and multi-device sync. They share pure helpers in packages/shared/src/session-machine-reducers.ts but keep separate inputs:

Machine Code Owns Inputs
Live packages/runtime-host/src/session-engine.ts Streaming deltas, busy/awaiting flags, live tool/task upserts RuntimeSessionEvent
History packages/runtime-host/src/session-history-projector.ts Reload/hydration from OpenCode history API Session messages/parts
Cloud packages/shared/src/cloud-session-projection.ts Durable product-event projection for web/gateway/paired desktop CloudSessionEventRecord

Shared pure policy (do not fork per machine):

  • deriveSessionInteractionFlags — generating vs awaiting permission/question
  • resolveAssistantMessageContent — append vs replace assistant text
  • deriveToolStatus — tool running/complete/error
  • upsertProjectionById — id-keyed list upserts

Convergence plan: keep three machines until reopen + multi-device sync stay proven; grow shared reducers, not parallel type maps. Parity coverage lives in tests/session-machine-parity.test.ts.

Changing the meaning of an existing event or projection field requires a version bump, a migration or compatibility path for stored projections, and tests proving older snapshots still hydrate safely. Adding a new event type is allowed without a version bump only when older clients can ignore it without losing required chat state.

Event Flow

flowchart LR
  OpenCode["OpenCode runtime events"] --> Translator["Shared translator\n(opencode-event-translator)"]
  Translator --> DesktopLive["Desktop live handlers"]
  Translator --> Adapter["Cloud runtime adapter\npayload fan-out"]
  Translator --> Standalone["Standalone channel events"]
  Adapter --> Events["CloudSessionEvent contract"]
  Events --> Store["Durable event log"]
  Store --> Reducer["Shared projection reducer"]
  Reducer --> View["SessionView"]
  View --> Desktop["Desktop"]
  View --> Web["Cloud Web"]
  Events --> Gateway["Gateway renderer"]

Rules:

  • Raw OpenCode SDK events stop at the shared translator (packages/shared/src/opencode-event-translator.ts, JOE-838). Surfaces receive a normalized envelope + product disposition and only then fan out.
  • Cloud workers append only canonical CloudSessionEventType values.
  • Projection reducers live in shared code and produce the SessionView shape Desktop already consumes.
  • Gateway renders only event types marked channelRenderable in the shared contract.
  • snapshot.required and channel.delivery are control events, not projected session state.
  • Standalone Gateway maps the same classification table into its slimmer channel vocabulary (tool.started / tool.completed / session.error).

Projected Event Vocabulary

Projected events update durable session state:

  • session.created
  • session.imported
  • session.project_source.bound
  • prompt.submitted
  • assistant.message
  • tool.call
  • task.run
  • permission.requested
  • permission.resolved
  • question.asked
  • question.resolved
  • todos.updated
  • cost.updated
  • artifact.created
  • artifact.updated
  • session.status
  • session.idle
  • session.aborted
  • runtime.error

Control events are delivered over Cloud APIs but do not update the session projection:

  • snapshot.required
  • channel.delivery

Extension Rules

When adding an event:

  1. Add it to CLOUD_SESSION_EVENT_CONTRACT with facets, producers, consumers, channel renderability, and a short description.
  2. If it is projected, update reduceCloudSessionProjectionEvent.
  3. Add SDK normalization fixtures when it comes from OpenCode.
  4. Add Desktop/Web/Gateway tests for every surface that consumes it.
  5. Keep the Gateway renderer on canonical Cloud events. Do not render raw SDK event names in channel providers.

This contract is the compatibility boundary between synced product surfaces. It should grow through typed events and tests, not through surface-specific parsing of runtime internals.

SessionEngine injection (JOE-872)

Desktop production code continues to use the process singleton sessionEngine from @open-cowork/runtime-host/session-engine.

Multi-tenant and unit tests should call createSessionEngine() for an isolated engine instance so session state, view caches, and busy maps do not leak across cases.