Skip to content

Put the seat side of the room behind three JSON calls - #51

Open
andreisavu wants to merge 2 commits into
mainfrom
claude/pr3-wire-seat-actor
Open

Put the seat side of the room behind three JSON calls#51
andreisavu wants to merge 2 commits into
mainfrom
claude/pr3-wire-seat-actor

Conversation

@andreisavu

Copy link
Copy Markdown
Contributor

PR 3 of the stack that SPLIT_PLAN.md on #48 cuts from that branch. Branches from main after #50. The plan says nothing in the branch is this PR alone, so it is built by hand from the final tree.

The one idea

The seat side becomes a client of the room, over three calls whose every shape is plain JSON.

  • wire.ts names what crosses. The seat calls view, commit and lease; the room calls wake. A request and its response survive a round trip through JSON.stringify unchanged.
  • seat/activation.ts is one activation over a view. seat/hands.ts holds say, summarise and seat, every one committing through the room's commit call. seat/seat.ts holds the routing rule, SeatActor and inProcessTransport: the actor takes a wake, claims the lease, reads the view, builds the Pi Agent with the hands the view names, runs it, renews the lease while it runs, and releases it when it stops.
  • room/lease.ts derives an activation's id from the record (activationId, draftId, parseId, seatOf). room/assistant.ts keeps what the assistant is and what the room holds of it while it runs.
  • session.ts answers the three calls from a lease table it keeps in memory. A seat is live from the wake that was sent until the lease it claims ends, so settled(), quiet() and the exchange's close read the same facts they read before. The table is scaffolding: PR 5 moves it onto the log, and the wire does not change.
  • host/runtime.ts gains Transport, RunningRoom as the wire sees it, and the optional transport.

Two decisions the plan left open

  • Wake.steer lands here, not in PR 7. The room decides who is at work, and a seat at work has to hear the message somehow. The plan's alternative was a direct call on the actor, which is not on the wire. The wake carries the rendered line instead, so every shape stays JSON now, as the plan's "watch for" asks. PR 7 still moves the decision to the seat side and adds heard.
  • A steer is held until the model has been asked. Pi polls its steering queue once before the first request, so a steer that lands between the view and that poll was folded into the request instead of following it. On main the prompt started in the same tick as the routing and never hit that window; over the wire it did, and two assistant tests hung on it. The seat side owns the stream function it hands Pi, so it marks the moment the model is asked and releases held steers then. The rule is deterministic across transports: a steer follows the request it lands during, never joins it.

Trimmed per the plan

cut on the port and the race in take (PR 9), the deadline branch of renewUntil (PR 10), heard on the lease and the taken seq (PR 7), the over flag and the queue as an array (PR 8; one slot, as main holds one activation), the row types on the wire (PR 4, PR 5), Message.activationId and wakes (PR 5). Two things the plan did not list: test/support/transport.ts brings serializing now, because it is the test that pins this PR's claim in a real run; and persistTurns moves from log/log.ts to seat/activation.ts, where the plan keeps it.

The tests that pin it

  • test/wire.test.ts: every request and response round-trips unchanged, and assertWire refuses what would not survive.
  • test/matrix.test.ts: every scenario on both storages runs over a transport that serializes every request and response, and fails on a violation.
  • test/seat.test.ts: the actor claims, runs and releases; a steer goes into the activation that runs and starts none beside it; a wake that queued behind an activation runs next, and one sent twice runs once.
  • The existing session, roster, presence, assistant and workspace suites pass unchanged, which is the point.

What a later PR adds on top

  • PR 4: the composition and close rows, room/fold.ts, room/view.ts.
  • PR 5: leases on the log, decide, resumeSession; the in-memory table goes.
  • PR 7: wakes naming every seat, heard on the lease, retries.
  • PR 8: the actor's queue as an array and the over flag.
  • PR 9: cut on the wire.

Checks

pnpm format && pnpm check is green, 138 tests. Every doc link resolves. The build's three TS4023 lines from test/live/support.ts are on main too.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SJjjR3S6iwqEbDXsRDJgiq


Generated by Claude Code

The seat side becomes a client of the room. wire.ts names what crosses:
the seat calls view, commit and lease; the room calls wake, which
carries the line a running activation is steered with when a message
caused it. Every shape is plain JSON, and survives a round trip through
JSON.stringify unchanged.

seat/activation.ts is one activation over a view. seat/hands.ts holds
say, summarise and seat. seat/seat.ts holds the routing rule, SeatActor
and inProcessTransport. The actor takes a wake, claims the lease, reads
the view, builds the Pi Agent with the hands the view names, runs it,
renews the lease while it runs, and releases it when it stops. A steer
that lands before the model has been asked is held until it is, so a
steer follows the request it lands during and never joins it.

room/lease.ts derives an activation's id from the record. room/assistant.ts
keeps what the assistant is, and what the room holds of it while it runs.
session.ts answers the three calls from a lease table it keeps in memory:
a lease ends when the seat releases it or the room revokes it. A seat is
live from the wake that was sent until that lease ends.

The runtime gains Transport and the optional transport option. The
scenarios run over a transport that serializes every request and
response, and fail on a value that would not survive the wire.
seat.test.ts drives the actor by hand over a room the test plays.

This is PR 3 of the stack SPLIT_PLAN.md cuts from #48.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJjjR3S6iwqEbDXsRDJgiq
… loop

A steer still queued on the agent when a run ends says the record moved,
beside a renewal whose lastSeq is past what the activation heard. Main
rebuilt on the queue alone; over the wire the renewal's lastSeq is read
before a message that lands during the same tick, so the activation
checks both, and nothing awaits between the check and the release.

A seating changes the roster inside the log's landed callback: before
the message routes, and once per key. A repeated seated commit hands
back the message that landed and changes nothing twice.

The renewal loop stops for good when the activation ends: a renewal in
flight arms nothing when it comes back.

EndReason loses `revoked`, which nothing writes yet. The docs say what
ends a lease: the seat side releases it, and the room cuts an activation
it ends. Held loses a field nothing read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJjjR3S6iwqEbDXsRDJgiq
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.

2 participants