Rooms, grounded meeting agents, and a preset startup team - #343
Rooms, grounded meeting agents, and a preset startup team#343Deodat-Lawson wants to merge 2 commits into
Conversation
…up team Meeting agents were the one part of the product that reasoned about the workspace's documents without ever reading them: `MeetingConfig.context` existed and nothing populated it. Retrieval is now a port on the engine — `TurnGroundingProvider` — consulted before each turn for the persona about to speak, implemented over the existing ensemble retriever. Per-persona, because the analyst and the engineer ask the corpus different questions; capped at four passages, because each turn already carries the whole transcript; never fatal, because a meeting that dies when the index blinks is worse than an ungrounded one. Turns record what they read — label, page, score and a truncated excerpt — which keeps a grounded transcript checkable without the index still being around and lets `evaluateMeeting` tell a cited number from an invented one. Before this, a meeting grounded purely by retrieval scored "no context supplied — dimension not applicable" at weight zero. Adds a Core 6 preset team (founder, product, eng, design, growth, data) applied additively: a handle already in use is reported, never overwritten, because persona keys are referenced by past transcripts and frozen meeting rosters. Also fixes speaker election. `findMention` took the *first* @mention in a message, but a working turn opens by answering the last speaker and closes by asking someone else — so the floor kept going back to whoever had just spoken and the specialists were never reached. The last mention now wins. Every scripted line in the suite carries exactly one mention, which is why no existing test moved; this was found by running a real meeting. Adds three live harnesses that found all of the above: `meeting:live`, `meeting:compare` (one agent vs the room on identical evidence) and `meeting:probe` (planted false premises). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A meeting is a conversation — one speaker at a time, elected from the transcript, working toward a close. A room is a query: one question, every member, concurrently, no floor to hold. They share a channel log and nothing else. This is the shape the measurements pointed at. Six personas reading one shared corpus scored no better than a single agent across 39 live runs, at 3-5x the wall clock, because a room of members who all read the same material has nothing for deliberation to discover. A room is worth exactly as much as the information its members do not share — so members here are bound to different document sets, and retrieval runs per member. `askRoom` is a function rather than a method on `MeetingOrchestrator`. `stepInner` marks the whole meeting failed when no runtime serves a persona, which is the ordinary case for a room member that is unreachable; a room carries no state between rounds, so there is nothing to persist; and `maxConsecutiveFailures` is meaningful for a conversation and wrong for a fan-out. Each member settles independently as answered, declined, failed, timed out, or unserved, and a failure is a `system` message so the minutes extractor and the Slack bridge never read an error as content. Members are isolated: each sees the question only, never the other answers. Otherwise the first to finish anchors the rest and it stops being a fan-out. Rounds have no table. The question carries its id and expected roster, answers carry the round id, and `summarizeRounds` derives the rest from the log — the same rule the rest of this subsystem follows. Retrieval runs as the asking human, never the room's creator. The meeting path grounds using the creator's grants; copying that here would make a room a way to read documents you could not open yourself. The room's document set narrows the corpus, the asker authorizes it. A member whose sources return nothing declines without consulting the model at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_793082e3-20c1-4c76-9a9d-2c85a2fff164) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 30af2c502b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const persona of toCreate) { | ||
| await createPersona(companyId, persona); | ||
| created.push(persona.key); |
There was a problem hiding this comment.
Make preset application atomic under concurrent requests
When two tabs or replicas apply the same preset concurrently, both can read the same pre-insert roster and enter this loop with identical toCreate lists. The unique (company_id, key) index then rejects one request—potentially after it has already inserted earlier personas—so the advertised idempotent operation returns a 500 and leaves a partially applied pack. Use conflict-safe inserts or a transaction that derives created/skipped from the actual writes.
Useful? React with 👍 / 👎.
| ...(grounding.sources ? { grounding: grounding.sources } : {}), | ||
| ...result.meta, |
There was a problem hiding this comment.
Preserve host retrieval provenance over runtime metadata
When a custom or remote runtime returns a meta.grounding field, this trailing spread overwrites the sources produced by the host's grounding provider. The transcript UI and scoreGrounding() subsequently treat the runtime-supplied value as the passages actually retrieved, corrupting citations and evaluation results; the same spread order exists in settleMember() for rooms. Merge runtime metadata first and assign host-controlled grounding afterward.
Useful? React with 👍 / 👎.
Two related changes to the collaboration subsystem, plus the measurements that decided the shape of both.
Rooms
A meeting is a conversation: one speaker at a time, elected from the transcript, working toward a close. A room is a query — one question, every member, concurrently, no floor to hold.
Members are bound to different document sets, which is the whole point (see the measurements below).
askRoomis a function rather than a method onMeetingOrchestratorfor three concrete reasons:stepInnermarks the whole meetingfailedwhen no runtime serves a persona, which is the ordinary case for an unreachable room member; a room carries no state between rounds, so there is nothing to persist; andmaxConsecutiveFailuresis meaningful for a conversation and wrong for a fan-out.systemmessages so the minutes extractor and the Slack bridge never read an error as content.summarizeRoundsderives the rest from the log — the same rule the rest of this subsystem follows.No turn policy, moderator, minutes, completion marker, or
controlroute — a room has no state machine to drive.Grounded meeting agents
Meeting agents were the one part of the product that reasoned about the workspace's documents without ever reading them:
MeetingConfig.contextexisted and nothing populated it. Retrieval is now a port on the engine (TurnGroundingProvider), consulted before each turn for the persona about to speak. Per-persona, because the analyst and the engineer ask the corpus different questions; capped at four passages, because each turn already carries the whole transcript; never fatal, because a meeting that dies when the index blinks is worse than an ungrounded one.Turns record what they read — label, page, score, truncated excerpt — which keeps a grounded transcript checkable without the index still being around. Behaviour change:
evaluateMeeting's grounding dimension now reads those excerpts as well asconfig.context. Before this, a meeting grounded purely by retrieval scored "no context supplied — dimension not applicable" at weight zero.Also adds a Core 6 preset team (founder, product, eng, design, growth, data), applied additively — a handle already in use is reported, never overwritten, because persona keys are referenced by past transcripts and frozen meeting rosters.
Speaker election fix
findMentiontook the first@mentionin a message. A real turn opens by answering the last speaker and closes by asking someone else, so the floor kept returning to whoever had just spoken and the specialists were never reached — in one live run@engand@growthnever spoke at all despite being addressed repeatedly. The last mention now wins.Every scripted line in the suite carries exactly one mention, which is why no existing test moved. This was found by running a real meeting.
How the shape was chosen
Three live harnesses ship with this (
meeting:live,meeting:compare,meeting:probe). What they found, over 39 runs against real models:That is why rooms bind members to different document sets rather than shipping more personas over a shared one.
Follow-up
External sessions — a Claude Code or Codex session joining from another machine — are #342, along with the hub security floor that gates them. One item there is live today and independent of this PR:
listKnownNodesbinds any connected node to whichever company's settings page loads first.Verification
two-machinedistributed test.apps/webandpackages/adapters; lint clean;check-core-facadeok.drizzle-kit(never hand-written — snapshot lineage has broken before);drizzle-kit checkreports no drift.evals:meetingsstill passes with good/bad separation intact.Not done: the UI for rooms. The API is the deliverable here; a rooms pane modelled on
MeetingsPaneis a small follow-on.🤖 Generated with Claude Code
Note
High Risk
Touches collab orchestration, document retrieval authorization (asker-scoped room RAG), and speaker routing—security-sensitive access patterns plus behavior changes in live meetings.
Overview
Adds collab rooms (one question, all members in parallel, each from its own documents), per-turn meeting grounding via a
TurnGroundingProviderport, a startup-core preset persona pack with additive apply APIs, and fixes @mention speaker election to use the last handoff mention.Rooms introduce
askRoom, channel-log–derived rounds (summarizeRounds), isolated member context (TurnContext.mode: "room"), and web APIs pluspdr_ai_v2_collab_roomstorage. Members settle independently (answered / declined / failed / timeout / unserved); retrieval runs as the asker, not the room creator.Meeting grounding wires retrieval before each turn (per speaking persona, non-fatal on errors), stores citation excerpts on messages, and extends
evaluateMeetinggrounding scoring to use those excerpts—not only pinnedconfig.context. Meetings gaingrounding_enabledanddocument_idscolumns.Presets ship GET/POST
/api/collab/agents/presetswith idempotent, non-overwriting handle application.Speaker election: under
reactiveandmoderated,findMentionnow prefers the last valid@handle(skipping self-mentions), fixing floors returning to the previous speaker on multi-mention turns.Reviewed by Cursor Bugbot for commit 30af2c5. Bugbot is set up for automated code reviews on this repo. Configure here.