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:
CloudSessionEventRecordCloudSessionProjectionEventRecordCloudSessionEventTypeCloudProjectedSessionEventType
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/questionresolveAssistantMessageContent— append vs replace assistant textderiveToolStatus— tool running/complete/errorupsertProjectionById— 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
CloudSessionEventTypevalues. - Projection reducers live in shared code and produce the
SessionViewshape Desktop already consumes. - Gateway renders only event types marked
channelRenderablein the shared contract. snapshot.requiredandchannel.deliveryare 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.createdsession.importedsession.project_source.boundprompt.submittedassistant.messagetool.calltask.runpermission.requestedpermission.resolvedquestion.askedquestion.resolvedtodos.updatedcost.updatedartifact.createdartifact.updatedsession.statussession.idlesession.abortedruntime.error
Control events are delivered over Cloud APIs but do not update the session projection:
snapshot.requiredchannel.delivery
Extension Rules¶
When adding an event:
- Add it to
CLOUD_SESSION_EVENT_CONTRACTwith facets, producers, consumers, channel renderability, and a short description. - If it is projected, update
reduceCloudSessionProjectionEvent. - Add SDK normalization fixtures when it comes from OpenCode.
- Add Desktop/Web/Gateway tests for every surface that consumes it.
- 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.