Summary
The legacy V1 session stack and the V2 Session Core share one per-session durable event log (aggregate = sessionID), but their process-local execution registries are mutually unaware. Nothing prevents V1 and V2 from executing the same session concurrently in one process, semantically interleaving durable events on that session's aggregate sequence.
Evidence
- V1 execution is serialized by
SessionRunState (packages/opencode/src/session/run-state.ts) — a per-instance runners map with BusyError. It has zero references to SessionV2 / SessionExecution / SessionRunCoordinator.
- V2 execution is serialized by
SessionRunCoordinator (packages/core/src/session/run-coordinator.ts) — a per-key fiber serializer. It has zero references to SessionPrompt / SessionRunState / V1.
- Both stacks publish durable events into the same per-session aggregate via the single commit path in
packages/core/src/event.ts (commitDurableEvent), so an immediate transaction protects seq integrity but not semantic interleaving (duplicate promotions, double tool settlement, mixed context-epoch baselines).
- The
event_sequence.owner_id claim checks (event.ts) apply only to replay, not live publish — they don't guard this.
- Related: V1 prompts never produce
session_input rows (no V2 inbox entries for V1-driven sessions), and specs/v2/session.md (~line 35) still describes a "V1-to-V2 shadow bridge" republishing V1 prompts as V2 Prompted events that does not exist in code.
Why it's latent today (and why that changes soon)
No first-party client drives V2 yet (TUI / app / slack / acp / run CLI all use the V1 /session/* handlers; V2 is consumed by sdk-next). But V2 is expanding fast (compact in #30986, resume-after-restart in #36105, embedded live-tail fix in #34017), so the first realistic concurrent-stack scenario — e.g. a TUI session (V1) while an embedded host wakes the same session (V2) — is getting closer.
Proposal sketch
A shared process-local per-session execution arbiter in core, consulted by both SessionRunState.ensureRunning and SessionRunCoordinator.run/wake/resume, failing with BusyError on cross-stack conflict. Deliberately process-local and non-durable, consistent with the "Session drains are process-local" design rule — this closes the same-process hole without prejudging clustered placement.
Question for maintainers
Is this the right shape, or is it subsumed by the remote workspace lifecycle seam in #37437? Happy to PR the process-local subset with tests if you agree it's worth closing ahead of (or as part of) that work.
Summary
The legacy V1 session stack and the V2 Session Core share one per-session durable event log (aggregate =
sessionID), but their process-local execution registries are mutually unaware. Nothing prevents V1 and V2 from executing the same session concurrently in one process, semantically interleaving durable events on that session's aggregate sequence.Evidence
SessionRunState(packages/opencode/src/session/run-state.ts) — a per-instancerunnersmap withBusyError. It has zero references toSessionV2/SessionExecution/SessionRunCoordinator.SessionRunCoordinator(packages/core/src/session/run-coordinator.ts) — a per-key fiber serializer. It has zero references toSessionPrompt/SessionRunState/ V1.packages/core/src/event.ts(commitDurableEvent), so an immediate transaction protectsseqintegrity but not semantic interleaving (duplicate promotions, double tool settlement, mixed context-epoch baselines).event_sequence.owner_idclaim checks (event.ts) apply only to replay, not live publish — they don't guard this.session_inputrows (no V2 inbox entries for V1-driven sessions), andspecs/v2/session.md(~line 35) still describes a "V1-to-V2 shadow bridge" republishing V1 prompts as V2Promptedevents that does not exist in code.Why it's latent today (and why that changes soon)
No first-party client drives V2 yet (TUI / app / slack / acp /
runCLI all use the V1/session/*handlers; V2 is consumed bysdk-next). But V2 is expanding fast (compact in #30986, resume-after-restart in #36105, embedded live-tail fix in #34017), so the first realistic concurrent-stack scenario — e.g. a TUI session (V1) while an embedded host wakes the same session (V2) — is getting closer.Proposal sketch
A shared process-local per-session execution arbiter in core, consulted by both
SessionRunState.ensureRunningandSessionRunCoordinator.run/wake/resume, failing withBusyErroron cross-stack conflict. Deliberately process-local and non-durable, consistent with the "Session drains are process-local" design rule — this closes the same-process hole without prejudging clustered placement.Question for maintainers
Is this the right shape, or is it subsumed by the remote workspace lifecycle seam in #37437? Happy to PR the process-local subset with tests if you agree it's worth closing ahead of (or as part of) that work.