Skip to content

feat(service,features,host): a plan's inbox and an agent's are read through the central live layer - #3232

Merged
luke-agent[bot] merged 1 commit into
mainfrom
luke/neon-realtime-scope-inbox
Oct 21, 2026
Merged

luke-agent[bot] merged 1 commit into
mainfrom
luke/neon-realtime-scope-inbox

Conversation

@charleslpan

@charleslpan charleslpan commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

A plan's inbox and an agent's are read through the central live layer: the sealed live procedures on the service, and liveRows on the host. This replaces the service's own held inbox read for the window.

Stacked on #4228 (luke/live-same-user). It will be retargeted to main once #4228 merges.

What changes

Service (live-procedures.ts, inbox-store.ts)

  • New live.taskInbox and live.agentInbox procedures. Each:
    1. checks the read names the caller's own account (sameUser);
    2. reads the plan or agent as that account, so one it doesn't reach is not found;
    3. answers three statements: the open items with the latest settled ones, their threads' latest messages, and their files.
  • With LUKE_REALTIME off, as main runs, the rows are answered inline (sealedParts), held until they move past after.
  • Every statement calls only immutable functions (live-statements.test.ts).

Features

  • scopeInboxLiveAnswerSchema and scopeInboxLiveRowsSchema.
  • inboxOfLiveRows, which joins the three parts back into the held read's items.
  • taskInboxLive and agentInboxLive on the client. Each names the account, and reads a session of another account as signed out.

Host (scope-inbox-follow.ts)

  • One liveRows read per plan or agent (an RcMap), kept while windows read it and for 30 s after. Each read names the account that was signed in when it started.
  • codingAgents.inbox and codingAgents.agentInbox with a cursor answer as soon as the items move past it, or unchanged when the 20 s hold ends. So the renderer doesn't change in this PR.
  • A sign-out lets every read go.

The account inbox stays on its held read.

Tests

  • live-procedures.test.ts: each read is sealed for the bearer, answered polled when asked, not found for a stranger, and signed out when it names another account.
  • live-statements.test.ts: the immutability check covers the new statements.
  • inbox-store.test.ts: the live rows equal the held read's items, read markers included.
  • compose-coding-agents.test.ts: the host answers reads from the followed rows.

@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Oct 21, 2026 1:30am UTC

Request Review

@luke-agent
luke-agent Bot changed the base branch from main to luke/neon-realtime-held-reads October 16, 2026 23:05
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-held-reads branch from e15c855 to d8ed1ae Compare October 16, 2026 23:09
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 874c503 to 0a6e6cd Compare October 16, 2026 23:09
This was referenced Oct 17, 2026
@luke-agent

luke-agent Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Heads-up from the Inbox thread work: #3469 (open, merges on green) adds messages to an Inbox item (MESSAGES in packages/service/src/inbox/inbox-store.ts). #3337, already merged, added answerFiles. This PR's live statement for a task's or an agent's inbox projects neither one, so a followed inbox would drop them. Whichever of #3469 and this PR lands second should add both to the live projection, to match inbox-store.ts's reads.

@luke-agent

luke-agent Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Another note from the Inbox work: #3618, which merges on green, adds a per-member read marker in a new inbox_item_read table, and each read carries the reader's unread. A followed inbox needs to treat a change to the reader's own row there as moving their inbox. Whichever of #3618 and this PR lands second should wire that in. The same applies to #3217's account follow.

@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-held-reads branch from d8ed1ae to d1e6acb Compare October 18, 2026 19:15
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 0a6e6cd to 35d1920 Compare October 18, 2026 19:21
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-held-reads branch from d1e6acb to a8cbc21 Compare October 18, 2026 21:12
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 35d1920 to ff729c0 Compare October 18, 2026 21:12
luke-agent Bot added a commit that referenced this pull request Oct 20, 2026
…leAdapter()

Neon's guide seals a Drizzle select through its Drizzle adapter rather than
raw SQL over toSQL(). LiveStatement now carries the select it was rendered
from (LiveStatement.query), and the sealer passes it to
createRealtime({ adapter: drizzleAdapter() }).seal({ query }), which also
refuses a select whose key is not the name Postgres answers it under. A
statement written as text alone, as the tests' stand-ins are, or one
binding an instant, which the adapter does not encode, is still sealed
with rawSql.

@neon/realtime-drizzle 0.1.0 declares drizzle-orm >=0.45.2 <0.46.0 as its
peer; it reads only what 1.0.0-rc.4 builds the same way, so the peer is
allowed in pnpm-workspace.yaml and the version pinned.

live-statements.test.ts now also seals every statement /api/live/inbox
seals through the adapter. The rows a subscription delivers are the same:
this changes no user-visible behaviour.

Split out of #3232 so the Realtime plumbing can land without a feature.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
luke-agent Bot added a commit that referenced this pull request Oct 20, 2026
…othing yet

live-follow.ts follows a read the service sealed as Neon's guide says to:
every part's sealed statement subscribed to on one socket, every seal
renewed ten seconds before the earliest expires (a failed reseal asked
again a second later), and each change answered as every part's rows
whole, undecoded, once each part has its first result (liveRows). It fails
with LiveBroken where Realtime's client does not load, a subscription stops
for good, or a renewal is refused. openRealtimeClient loads Realtime's own
client only when a read is first followed, so @luke/host depends on
@neon/realtime again (#4074 dropped it with the account inbox's follow).

No feature follows a read through it yet, so this changes no user-visible
behaviour. Split out of #3232, whose scope inbox is its first user.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-live-follow branch from ea54779 to ab80e71 Compare October 20, 2026 17:52
Base automatically changed from luke/neon-realtime-live-follow to main October 20, 2026 18:21
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 9710e65 to 6c444fa Compare October 20, 2026 23:40
@luke-agent
luke-agent Bot changed the base branch from main to luke/live-same-user October 20, 2026 23:40
@luke-agent luke-agent Bot changed the title feat(service,host): a plan's inbox and an agent's are answered from Neon Realtime feat(service,features,host): a plan's inbox and an agent's are read through the central live layer Oct 20, 2026
Base automatically changed from luke/live-same-user to main October 20, 2026 23:55
@luke-agent
luke-agent Bot marked this pull request as ready for review October 20, 2026 23:57
@luke-agent

luke-agent Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Both notes are handled in this version:

The account follow (#3217) isn't part of this stack, and the account inbox stays on its held read.

@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 6c444fa to 0add6f0 Compare October 21, 2026 00:08
devin-ai-integration[bot]

This comment was marked as resolved.

…hrough the central live layer

Service: live.taskInbox and live.agentInbox on the oRPC live contract. Each
first checks the read names the caller's own account (sameUser), then reads
the plan or the agent as that account, as the held read does, so one it does
not reach is not found, and answers its open items and latest settled ones as
one statement, and their threads' latest messages and the files they carried
as two more, sealed or, as main runs it, polled and held until they move
(sealedParts). Every statement calls only immutable functions
(live-statements.test.ts).

Features: scopeInboxLiveAnswerSchema and scopeInboxLiveRowsSchema, the reader
that joins the parts back into the held read's items (inboxOfLiveRows), and
taskInboxLive / agentInboxLive on the client, naming the account and read as
signed out under another's session.

Host: scope-inbox-follow.ts keeps one liveRows read per plan or agent (an
RcMap) while windows read it and 30 s after, named for the account signed in
when it started. A window's codingAgents.inbox / agentInbox read with a
cursor is answered as soon as the items move past it, or unchanged when the
20 s hold ends, so the renderer is unchanged. A sign-out lets every read go.

The account inbox stays on its held read.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@luke-agent
luke-agent Bot force-pushed the luke/neon-realtime-scope-inbox branch from 782179c to 81e378e Compare October 21, 2026 01:25
@luke-agent
luke-agent Bot added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit ae4c395 Oct 21, 2026
13 checks passed
@luke-agent
luke-agent Bot deleted the luke/neon-realtime-scope-inbox branch October 21, 2026 01:38

This branch was successfully deployed

1 active deployment
Preview — 81e378e3 Deployed Oct 21, 2026 by vercel[bot]
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