refactor(core): make v2 session inputs event sourced#30785
Open
kitlangton wants to merge 11 commits into
Open
Conversation
2030cf1 to
e35abee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why This Exists
V2 already separated prompt recording from model execution, but an accepted prompt lived only in
session_inputuntil it became visible. That meant pending work survived a local restart but could not be reconstructed from synchronized Session history.This PR makes prompt admission and promotion explicit durable facts.
Vocabulary
evt_*msg_*Native V2 Prompt Lifecycle
Admission and promotion are separate facts because accepted intent and model-visible history are different concepts.
PromptPromotedcarries the prompt payload and original creation time. A live renderer can show the newly visible user message directly from the event without refetching transcript history.Event And Schema Inventory
New Durable Events
session.next.prompt.admitted1timestamp,sessionID,messageID, fullprompt, immutabledeliverysession_inputwithadmitted_seqsession.next.prompt.promoted1timestamp,sessionID,messageID, fullprompt, originaltimeCreatedpromoted_seqand inserts visible usersession_messageNew Schema Definitions
SessionMessageID.ID: brandedmsg_*transcript-resource identity with sortable internal generation.SessionInput.LifecycleConflict: tagged error used when admission, promotion, or another message-producing event conflicts with an existingmsg_*lifecycle.Changed Event Payload Schemas
AgentSwitched,ModelSwitched,Prompted,Synthetic,ShellStarted, andCompactionStartednow carry explicitmessageID: msg_*.StepStarted, every text and reasoning event, every tool event, and step settlement now carry explicitassistantMessageID: msg_*.Changed Core, Storage, And API Schemas
EventV2.IDnow enforcesevt_*;SessionMessage.IDnow uses the distinctmsg_*schema.SessionInput.Admittedreplaces inbox-localseqwith event-derivedadmittedSeqand retains optionalpromotedSeq.session_inputmakesidits primary key, replaces autoincrementseqwithadmitted_seq, and adds admission/promotion sequence uniqueness.SessionV2.prompt(...), HTTP, OpenAPI, and generated SDK success schemas now returnSessionInput.Admittedinstead ofSessionMessage.User.Identity Model
Events and transcript resources have separate identities. They are never converted by prefix swapping.
Every message-producing event carries an explicit
msg_*resource ID:PromptAdmitted,PromptPromotedmessageIDPromptedcompatibility eventmessageIDStepStartedassistantMessageIDassistantMessageIDmessageIDPromptAdmittedcarriesmessageID, the full prompt, immutabledelivery, and the admissiontimestamp.PromptPromotedcarriesmessageID, the full prompt, a promotiontimestamp, and the originaltimeCreated; it deliberately does not repeatdelivery.Example assistant turn:
This makes assistant-turn ownership explicit across turns, delayed fragments, and provider-local call-ID reuse.
ShellEndedstill resolves its projected shell bycallID,CompactionDeltaandCompactionEndedresolve the current compaction, andRetriedis durable but not projected.msg_*values are opaque sortable handles for identity and index locality; transcript chronology always follows the durable aggregateseq, never message-ID ordering. Event IDs enforce theevt_*prefix and projected Session-message IDs enforcemsg_*. Admitted prompt message IDs are reserved against compatibility prompts and other transcript-row creator events, and distinct creator events cannot reuse one projected message ID.Exact Retry Semantics
When a client omits the ID, Core creates a new sortable
msg_*. Browser-safe SDK-sidemsg_*generation remains follow-up work tracked locally asa5ei0w.Queue And Steer Semantics
flowchart TD safe[Safe provider-turn boundary] --> cutoff[Capture aggregate sequence cutoff] cutoff --> active{Active activity?} active -->|yes| steers[Promote eligible steers through cutoff] active -->|no, steers exist| steers active -->|no steers| queue[Promote one oldest queue item] queue --> queuedSteers[Promote eligible steers through same cutoff]steerqueuesteerqueueA steer admitted after boundary capture waits for the next boundary instead of joining a turn that already opened.
Durable Post-Commit Delivery
Transactional guards and projectors run before the durable commit and may reject publication. After commit, sync handlers and listeners are observers: non-interruption defects are logged and isolated so they cannot retroactively fail the committed publisher or block later observers. Live-only event publication retains its existing fail-fast listener behavior, and interruption still propagates.
Replay
Core replay reconstructs durable state and never directly invokes Session execution, a provider, or tool side effects. By default it emits no live notifications.
replay(..., { publish: true })additionally notifies listeners and subscribers after accepting a new durable event, but does not invoke sync handlers; arbitrary listeners may perform their own effects. Exact stale replay is a no-op only when event ID, versioned type, and encoded payload match. Divergent stale replay, sequence gaps, and event-ID reuse at another aggregate position fail. Strict replay-owner claims fence conflicting owners even when they resend an exact historical event.The fresh-target regression uses a physically separate SQLite database:
msg_*is visible in context.Retained V1 Shadow Bridge
Ordinary sessions still execute through V1. Prompt, synthetic, shell, assistant-output, retry, and compaction mirroring remains behind
experimentalEventSystem. Agent-switch and model-switch events are currently published unconditionally during V1 prompt construction, so the bridge is not wholly behind the flag.The bridge performs V1-specific translation for prompts, files, references, synthetic text, shells, assistant steps, text, reasoning, tools, retries, and compaction. Keeping the whole bridge avoids malformed shadow transcripts with assistant output but no owning prompt.
Promptedremains an already-visible compatibility event. Projecting it atomically creates the visible user message and a promotedsession_inputrecord so exact retries can reconcile it; it does not emit the native admitted/promoted lifecycle pair.Compaction-summary assistants are represented by
CompactionStartedandCompactionEnded. They intentionally do not also mirror normal assistant step/text/reasoning events.Debug V2 TUI
For native V2 lifecycle prompts, the internal debug viewer renders promoted history only. It also continues rendering already-visible
Promptedcompatibility events and live assistant events:PromptPromoted.Disposable Beta Reset
This is an unreleased V2 beta cutover. The migration clears incompatible synchronized beta state instead of adding compatibility machinery.
eventsessionevent_sequencemessagesession_inputpartsession_messageworkspacesession.workspace_idlinksThe migration also replaces the autoincrement inbox sequence with
admitted_seq, makessession_input.idthe primary key, and installs unique constraints for event aggregate sequence, projected-message Session sequence, inbox admission sequence, and inbox promotion sequence.The SQL migration deletes local workspace rows and Session workspace links only. This PR does not delete adapter-managed external workspace resources; rollout must discard those resources before startup so adapters do not rediscover stale remote history.
Surface Area
sessions.prompt(...)returns the fullSessionInput.Admittedshape:admittedSeq,id,sessionID, full prompt, immutable delivery,timeCreated, and optionalpromotedSeq.sessions.messages(...)returns promoted transcript rows only.messageID/assistantMessageIDpayload fields flow through OpenAPI and the generated SDK event schemas.Validation
a8e885050is pushed and CI is rerunning against the complete PR.152 pass.24 pass, 4 skip.packages/opencodetypecheck is not usable from the auxiliary worktree because plugin types resolve from both the main checkout and the temporary worktree, producing duplicate protected SDK client identities insrc/plugin/index.ts. CI provides the clean-environment check.This supersedes #30759.