Skip to content

feat(coder): split coder into thin entity + coding-session resource#4267

Open
kevin-dp wants to merge 2 commits intokevin/coder-sdk-runnersfrom
kevin/coder-session-resource
Open

feat(coder): split coder into thin entity + coding-session resource#4267
kevin-dp wants to merge 2 commits intokevin/coder-sdk-runnersfrom
kevin/coder-session-resource

Conversation

@kevin-dp
Copy link
Copy Markdown
Contributor

@kevin-dp kevin-dp commented May 4, 2026

Summary

The coder entity used to own its session's full history on three of its own collections (sessionMeta, cursorState, events), so the durable session state was bound to one entity instance. This PR pulls the durable, portable parts onto a shared-state resource at a stable id (`coder-session/`), and slims the wrapper entity down to just its run lifecycle bookkeeping (runStatus, inboxCursor).

This is the prerequisite for forking sessions, attaching multiple wrappers to the same history, sharing a coder URL across devices/users, and surfacing the same session through specialised viewers — all without entangling those use cases with the SDK runner that produces events.

What changed

  • New module packages/agents-runtime/src/coding-session-resource.ts: schema + helpers (codingSessionResourceSchema, codingSessionResourceId, CODER_RESOURCE_TAG).
  • packages/agents/src/agents/coding-session.ts rewritten: handler creates the resource via `mkdb` on first wake, observes it on every wake, and writes events / sessionInfo to it. Tags the entity with `coderResource` pointing at its resource id.
  • `useCodingAgent` (`agents-runtime`) attaches to the resource via `observe(db(...))` so the `CodingSessionHandle` surface (events, meta) keeps working.
  • `useCodingSession` (`agents-server-ui`) now opens two streams in parallel — the entity stream (run lifecycle + inbox) and the resource stream (history + sessionInfo), recombining the two into the legacy `CodingSessionMetaRow` shape.
  • New helper in entity-connection: `connectSharedStateStream`, with bounded retry on 404 to cover the spawn → first-wake race (the resource isn't registered server-side until `mkdb` commits).
  • Old constants `CODING_SESSION_META_COLLECTION_TYPE`, `CODING_SESSION_CURSOR_COLLECTION_TYPE`, `CODING_SESSION_EVENT_COLLECTION_TYPE` removed (clean break, pre-1.0).

Naming note

The transcript collection on the resource is called transcript, not events. Reason: the runtime's `ObservationHandle` reserves the field `events: ChangeEvent[]` and `Object.assign`s it onto the SharedStateHandle returned by `observe(db(...))`, silently clobbering any like-named collection. Renaming sidesteps the collision.

Out of scope

Forking sessions and a `forkCodingSession` helper come in a follow-up. The resource shape is what makes that possible; this PR doesn't ship the operation.

Stacked on top of #4263 (coder SDK runners). Base is set to `kevin/coder-sdk-runners` so the diff stays focused.

Test plan

  • `pnpm --filter '@electric-ax/agents' test` — 84/84 passing (was 80; +4 resource-focused tests).
  • `pnpm --filter '@electric-ax/agents-runtime' test` — 386/386 passing.
  • Typecheck clean across all three affected packages.
  • End-to-end: spawned a fresh Claude coder, sent a prompt, watched events stream into the resource (`/_electric/shared-state/coder-session/`) while the entity stream carried only `runStatus` / `inboxCursor` / `inbox`. Tag `coderResource` set on the entity as expected.

🤖 Generated with Claude Code

kevin-dp and others added 2 commits May 4, 2026 16:21
The coder entity used to own three collections (sessionMeta,
cursorState, events) so the session history was bound to one
entity instance. Pull the durable, portable parts (event history +
session-info facts) onto a shared-state resource — the entity
becomes a thin wrapper over the SDK runner that reads/writes the
resource and tracks only its own run lifecycle (runStatus,
inboxCursor).

This is the prerequisite for forking sessions, sharing a session
across devices/users, and surfacing the same history through
multiple entities — the resource sits at a stable shared-state id
(`coder-session/<entityId>`) and survives independently of any
particular entity.

Why two collections, not one:
- `sessionInfo` carries the static facts about which session this
  is (agent, cwd, electricSessionId, nativeSessionId, createdAt).
- `transcript` carries the normalized event stream. Originally
  named `events`, but ObservationHandle reserves that field for
  ChangeEvent[] and Object.assign clobbers any like-named
  collection on the SharedStateHandle the runtime returns from
  `observe(db(...))`. `transcript` sidesteps the collision.

The wrapper entity tags itself with `coderResource` pointing at
its resource id; the UI fetches the entity, reads the tag, and
attaches to the shared-state stream alongside the entity stream.
On a freshly spawned coder the resource stream isn't registered
on disk until the entity's first wake commits its mkdb manifest
entry, so `connectSharedStateStream` retries on 404 with bounded
backoff to cover the spawn → first-wake race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 36.76471% with 129 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.98%. Comparing base (b9d4702) to head (681eb6d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ges/agents-server-ui/src/hooks/useCodingSession.ts 0.00% 59 Missing ⚠️
packages/agents-runtime/src/context-factory.ts 0.00% 30 Missing ⚠️
...ages/agents-server-ui/src/lib/entity-connection.ts 0.00% 22 Missing ⚠️
packages/agents/src/agents/coding-session.ts 72.88% 15 Missing and 1 partial ⚠️
...ages/agents-runtime/src/coding-session-resource.ts 94.11% 2 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                     @@
##           kevin/coder-sdk-runners    #4267      +/-   ##
===========================================================
- Coverage                    64.83%   60.98%   -3.86%     
===========================================================
  Files                          147      123      -24     
  Lines                        19373    16925    -2448     
  Branches                      4785     4166     -619     
===========================================================
- Hits                         12561    10321    -2240     
+ Misses                        6807     6601     -206     
+ Partials                         5        3       -2     
Flag Coverage Δ
packages/agents 57.31% <72.88%> (+0.31%) ⬆️
packages/agents-runtime 78.44% <50.00%> (-0.17%) ⬇️
packages/agents-server 66.03% <ø> (ø)
packages/agents-server-ui 0.00% <0.00%> (ø)
packages/electric-ax 38.59% <ø> (ø)
packages/experimental ?
packages/react-hooks ?
packages/start ?
packages/typescript-client ?
packages/y-electric ?
typescript 60.98% <36.76%> (-3.86%) ⬇️
unit-tests 60.98% <36.76%> (-3.86%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant