feat(core): add embedded v2 session runtime and tool foundation#30632
Conversation
Introduce @opencode-ai/core/opencode as the first public Effect-native embedding facade. OpenCode.layer() composes the existing SessionV2 default layer so applications can call sessions.list() without wiring internal services manually. Make SessionV2.prompt() durably admit one user prompt by publishing session.next.prompted, projecting SessionMessage.User inline, reading the stored projection, and returning that admitted message. This intentionally stops before model execution: prompt admission remains a short durable boundary, while a later continuation will start or resume the model loop. Leave the next contracts explicit in code comments: Session-scoped application idempotency must return the original admitted message for exact retry and reject conflicting reuse, and OpenCode.create() should later provide the Promise facade over the same semantics.
Allow callers to opt into Session-scoped prompt retry safety with an optional bounded branded Prompt.IdempotencyKey. Calls without a key continue admitting distinct messages. Exact retries with the same key and prompt return the original admitted SessionMessage.User, while changed-payload reuse fails with Session.PromptConflictError. Persist immutable session_prompt_admission tombstones containing the application key, prompt contract, and schema-encoded admitted user-message snapshot. Keep tombstones independent of rebuildable message projections so projection cleanup cannot erase admission history and accidentally permit duplicate work. Handle concurrent same-key races transactionally: the losing projected event rolls back through a private PromptAdmissionRace defect, and prompt recovery catches only that known defect before loading the winning tombstone. Unrelated defects, typed failures, and interruptions remain visible. Expose the optional key and 409 conflict mapping through the experimental V2 HTTP route, add core and HTTP regressions for opt-out, exact retry, conflicting reuse, and concurrent convergence, generate the SQLite migration, and regenerate V2 SDK types.
Replace the global unbounded aggregate-ID wake PubSub with one scoped sliding-capacity-1 dirty signal per active tail and aggregate. Durable tails register before historical replay, re-query SQLite after each edge, and release signal state when they close. Schema changes: - none; durable storage, synchronized event versions, HTTP, OpenAPI, and SDK contracts are unchanged
Subscribe to each keyed capacity-1 dirty signal before registering it and starting historical SQLite replay. This preserves commits that land during replay handoff while retaining scoped cleanup and wake coalescing. Add a deterministic paused-read regression for the handoff window. Schema changes: - none; durable storage, synchronized event versions, HTTP, OpenAPI, and SDK contracts are unchanged
Record cross-process migration claiming and bounded-resource follow-ups as deferred cleanup tasks. Keep functionality slices unblocked unless canary work surfaces a concrete failure.
Add a Core-owned pure patch parser and Location-scoped apply_patch leaf for add, update, and delete hunks. Resolve and approve all targets before approved reads, preflight update/delete correctness, commit sequentially, and report partial application explicitly if a later commit fails. Keep moves and atomic rollback as separate follow-ups rather than implying unsupported transactionality. Schema changes: - add the model-facing apply_patch input schema and ordered applied-operation receipt schema - add the internal pure patch hunk representation - no database migration, durable-event version, HTTP, OpenAPI, or SDK regeneration required
Schema changes: - Add the model-facing V2 skill tool contract backed by the upstream Location-scoped SkillV2 registry. - Reuse the existing session.next.tool.failed event shape to settle local tools abandoned across process restart. - No database migration, durable-event version, OpenAPI, or SDK regeneration is required.
Schema changes: - Preserve required request bodies for experimental V2 OpenAPI and generated SDK mutation methods. - Add deprecated SDK filesystem DTO aliases for source compatibility. - Reuse existing durable tool failure and replay-owner schemas while tightening settlement and ownership behavior. - Keep pre-launch session.next.* databases disposable across incompatible experimental event iterations. Safety: - Fence replay aggregate and owner mismatches and propagate workspace replay ownership. - Restrict webfetch to globally routable literal IP destinations until DNS connections can be pinned. - Make apply_patch adds create-only, parser validation strict, and mutation commits interruption-safe. - Revalidate search roots before ripgrep traversal and harden pending permission/question transitions. - Sanitize public provider/model API URLs to origin-only values.
Schema changes: - brand embedded durable-event cursors and reserve admitted prompt IDs before projection - preserve provider call/result metadata separately and remove unused live tool-progress variants - regenerate OpenAPI and SDK types for projected tool metadata changes - document deferred provider retry/watchdog policy and foreground-only v2 bash
17ba99d to
fd271e2
Compare
|
One acceptance boundary I’d keep explicit for For this PR’s shape that could be: Location/session id, admitted That gives HTTP/SDK consumers a testable invariant: replay may rebuild history from SQLite, but execution only continues after the runtime can show which durable boundary it is trusting and what must be revalidated before side effects. |
|
This migration breaks upgraded beta DBs with pre-durable session projections. On an affected DB: 2,040 session_message rows total, 1,993 have no matching event.id across 237 sessions, 47 match across 1 session, and 0 sessions mix matched/unmatched rows. These are historical projections rather than partial corruption, but 20260603040000_session_message_projection_order rejects them with: Cannot migrate session_message projections without matching durable events. Suggested fix: backfill durable rows from event.seq; for legacy-only sessions assign seq using the prior deterministic (time_created, id) order; keep rejecting mixed sessions. Desktop currently surfaces this as an indefinite loading animation because the sidecar exits before readiness. |
potential fix: #30720 |
…alyco#30632) (cherry picked from commit 76ee87e)
Summary
This draft PR builds the Effect-native embedded OpenCode V2 foundation needed by local-first consumers such as OpenCord. It separates durable prompt admission from execution, routes Sessions through Location-scoped runtime graphs, refines the existing durable Session event model with replay-safe cursors and projection hardening, and adds the first Core-owned coding-agent tool catalog.
The ordinary V1 Session path remains the default. The new embedded runner and
session.next.*contracts remain experimental and opt-in while the remaining prompt-loop parity slices are designed and reviewed.What This Adds
Embedded Session Runtime
@opencode-ai/core/opencodefacade.session_inputinbox.steerandqueuedelivery modes.SessionStore -> LocationServiceMap -> SessionRunner.Native Provider Loop
llm.stream(request)provider turn at a time.Provider timeout, retry, and watchdog policy is intentionally deferred. The runner does not impose a universal inactivity or absolute stream timeout. A later slice should add bounded pristine-attempt retries and configurable provider-specific watchdogs deliberately.
Durable Events And Replay
session.next.*event family and projected Session-message model; this branch does not introduce them.session_inputinbox storage and delivery-aware pending indexes.session_message.seqand covering indexes so projected history follows durable aggregate order rather than timestamps or caller IDs.EventV2.Cursor.Tool Foundation
Adds Location-scoped Core-owned tools:
readwriteeditapply_patchglobgrepbashquestiontodowriteskillwebfetchwebsearchImportant behavior:
writepreserves UTF-8 BOM semantics.apply_patchpreserves sequential non-transactional behavior while reporting partial application honestly, using create-only add semantics, rejecting malformed grammar, and rejecting moves until their semantics are hardened.readpages oversized UTF-8 files with bounded line and byte limits rather than rejecting them.tool-output://resources with bounded paging and payload-size integrity checks.bashfollows normal configured-rule and saved-approval permission semantics. It remains foreground-only until owner-bound observation, cancellation, durable status, and completion delivery exist.webfetchfollows V1-like HTTP(S) hostname and redirect behavior while retaining requested-URL permission checks, timeout caps, response-size bounds, and text conversion.skilltool reuses the single Location-scopedSkillV2registry, bounds selected-skill output, and hardens remote discovery against cache-path traversal and cross-origin resource fetches.Shared Runtime Foundations
BackgroundJobregistry and keeps the legacy OpenCode adapter instance-scoped; V2 model-facing background execution remains deferred.PluginV2lifecycle: plugin child scopes now close with their Location layer, and same-ID add/remove operations serialize.SystemContextalgebra with namespaced keys, own-property-safe checkpoints, and duplicate-key validation at the interpreter boundary.SessionSystemContextscaffold for Location environment and date facts. It is not wired into provider request assembly yet.HTTP API And SDK
Legacy Compatibility
Draft Blockers Before Broad Exposure
This remains intentionally draft. The foundation is useful for supervised embedded canaries, but it is not a drop-in replacement for the full V1 prompt loop yet.
Highest-priority follow-ups:
AGENTS.md, nearby nested instructions, skills guidance, and plugin transforms.wait, and expose the remote lifecycle needed by HTTP consumers.SystemContextremoval semantics and wireSessionSystemContextinto provider request assembly.The detailed ledger and deferred hardening list live in:
Schema Changes
session.next.*family rather than introducing it: streamed deltas become ephemeral and embedded replay cursors become explicit.Pre-launch
session.next.*databases remain disposable experimental state. Reset experimental V2 data when upgrading across incompatible event-schema iterations. This is not a rolling-upgrade compatibility claim for previously persisted experimental histories.Verification
Passed locally after the latest stabilization commit:
The complete Core CI suite remains independently red only in the existing macOS filesystem-watcher readiness tests:
Refreshed GitHub Actions are running after push.