Repository navigation
Show who a conversation is waiting on, with the Bot's avatar - #843
Closed
guidovizoso wants to merge 13 commits into
Closed
guidovizoso wants to merge 13 commits into
guidovizoso wants to merge 13 commits into
Conversation
The busy signal said only that something was running, on the socket and nowhere else. Read what a conversation waits on from the rows that already say so: an unfinished hop it asked, folded with its relay, and an open routine run. The channel read and the roster carry the list, and every busy event carries the whole list at the moment it is sent.
…moves The delivery's busy signal fires inside the run, while the row still says the hop is in flight, so it cannot be what says the hop is over. The runner now settles after finish or release, with the relay already queued, and the desk announces a hop the moment it is offered.
Signalled around the firing and cleared after the run row has its outcome, since the open run row is what the conversation reads.
…el's read Busy events now carry the whole work list. It replaces the row's list in the roster and the channel read the conversation screen uses, and both are refetched with the roster after a reconnect or a resync. The sidebar stays lit while a hop or a routine is pending between turns.
…e answer will land "Asking Ana… 0:12" for a Bot its Bot asked, "Ana is running a routine…" for a routine, at the end of the transcript, from the server's list. Hidden while this tab's own turn runs. ProgressRow is the reusable line for later waits.
…every hop The work list is read on every roster page and every busy event. It filtered work_items on payload->>'threadId' with no index, and joined each relay to its hop by building 'relay:' || key for every row, so both sides scanned a day of finished hops. A partial index on unfinished rows by thread, and a join that cuts the hop key out of the relay key, turn it into two index lookups.
Every member of a shared channel has a thread of their own in it. The work list was broadcast to all members on every busy event, read from whichever thread the signal came from, so another member learned which Bot somebody asked and lost their own list to it; the roster also listed every member's routine runs. Hops are now read from the reader's thread and routines from the reader's own; the list goes only to the thread's owner, and the other members hear the busy flag alone, as before. Each list is stamped with the database clock just before it is read, so a browser can tell an older list that arrived late from a newer one.
Busy events are read on whichever replica sent them and can arrive out of order. The roster and the open conversation now keep the list with the latest stamp, so a late event no longer puts back a hop that has already settled.
The branch moved four allow-listed offences down their files, and its own tests added four new ones: an absent busy flag and work list asserted through a ?. chain, and an expected undefined key in a toEqual. The moved entries are re-keyed; the new ones assert the missing key directly instead.
Collaborator
Author
|
Landed in #847. |
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.
What this changes
When a Bot asks another Bot, the answer is worked out in a scratch thread nobody is shown, and a routine firing into a conversation runs with no browser attached. The busy signal said only "something is running", on the socket and nowhere else, so an open conversation looked idle while it waited and a reload forgot even that.
Now a conversation says what it is waiting on:
ProgressRowis a reusable line for later waits.Read from the rows that already say so, never written. A hop is a
work_itemsrow until it is finished, and a routine firing is aroutine_runsrow until it has a status.server/src/channels/working.tsreads those; a hop's relay (relay:<hop key>) is folded into the hop it answers, so a hop retried five times or on its way back is one entry. A given-up hop, the notice saying so, and a relay already being spoken are not waits. A second table of "busy reasons" would be a copy that a crashed pod leaves behind.When it is announced. Every busy event now carries the whole list (never a change to it). The handoff desk announces a hop the moment it is queued, the runner announces again once the queue has moved (finished with the relay already queued, released for another try, or dropped), and a routine signals busy around its firing and clears it after the run row has its outcome. Announcing is best-effort: a conversation that can't be told does not fail or refuse the hop.
Private to the person whose thread it is. Each member of a shared channel has their own thread in it. The list is read from the reader's own thread and their own routines, and sent only to the thread's owner; other members hear the busy flag alone, as before, and learn nothing about which Bot somebody asked.
Fast enough to read on every page. Migration
0063_work_items_open_threadadds a partial index on unfinishedwork_itemsby(kind, payload->>'threadId'), and the relay join cuts the hop key out of the relay key (a primary-key lookup) instead of building'relay:' || keyfor every row.Where it runs
work_itemsandroutine_runsin Postgres on every read and every announcement. The one schema change is an index.pg_notifyonchannel_activity. A browser that reloads on another replica gets the same list from the roster or channel read.workAt, the database'sclock_timestamp()taken just before the read, and the browser keeps the list with the latest stamp, so a late event cannot put back a hop that has already settled.pg_notify(channel_activity, …)as the busy flag already was, addressed bymemberIds: the list to the thread's owner, the bare busy flag to the other members. Every process's listener delivers to the sockets it holds. A missed event is corrected by the next one or by the roster refetch on reconnect.Boundary and audit
Changelog
Unreleased, naming the migration.Merge notes
This and the owner-app-grants PR both add a Drizzle migration numbered 0063 (
0063_work_items_open_threadhere,0063_owner_app_grantsthere). Whichever merges second has to regenerate its migration as 0064; the SQL is unchanged. The index is a plainCREATE INDEX, which holds writes towork_itemswhile it builds. Finished rows are purged by the queue, so the table is usually small, but the build is not free on a deployment with a large backlog.How it was checked
server/tests/channel-work.integration.test.ts, against Postgres: a hop from the moment it is queued, nothing once it is finished, one entry however many tries, two Bots asked as two entries oldest first, not a given-up hop or its notice (but its last attempt while still running), the answer on its way back as the same wait with the same clock, not once the answer is being given, not a scratch thread, a routine until its run has an outcome, only the reader's own thread and routines in a shared channel; and the announcements, including that another member hears nothing about whom it asked.server/tests/agent-handoff.test.ts,agent-handoff-runner.test.ts,routine-runner.test.ts,channel-routes.test.ts: when each piece announces, that a failed announcement never fails a hop or a firing, and that the roster and channel read carry the list.app/tests/channel-work-row.test.tsxandchannel-event-patch.test.ts: the row's wording and clock, one row per wait, gone when the list empties, back after a reload, and an older list arriving late not undoing a newer one.A refused address fails at once
A hop to a Bot whose address this deployment will not dial (here
localhost:4201) was retried once a minute for five tries, so the person watched "Asking Risk Analyst…" for about five minutes before hearing it could not be reached. That refusal cannot change between tries, so it now ends the hop on the first try, like a refusal by the deployment's controls. The AG-UI client keeps only the message of the error a fetch throws, soisEndpointRefusal(inendpoint.ts) recognises it by type or by the sentenceendpoint.tsitself writes. Checked in the running app: the person is told in about 12 seconds, down from about 5 minutes.