Skip to content

FE-1580: Reconcile Voice turn behavior on the shared Brunch conversation - #9564

Open
lunelson wants to merge 13 commits into
ln/fe-1575-resumable-workpiece-petrinautfrom
ln/fe-1580-reconcile-voice-resumable-workpiece
Open

FE-1580: Reconcile Voice turn behavior on the shared Brunch conversation#9564
lunelson wants to merge 13 commits into
ln/fe-1575-resumable-workpiece-petrinautfrom
ln/fe-1580-reconcile-voice-resumable-workpiece

Conversation

@lunelson

@lunelson lunelson commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Make Voice's completed-transcript, half-duplex experience work on the same canonical Brunch conversation that Mission 6 already uses for a resumable workpiece and browser mutation. In the local Petrinaut Brunch panel you can type, then speak, let Brunch change the prepared net, use Your turn or Stop, and reopen the same conversation without replaying speech or duplicating the change.

This imports KA's Voice hardening onto Mission 6's repaired fixture path and reconciles the joins that neither parent owned: deferred browser-tool execution, continuation ownership, Stop withholding, canonical stopped-entry projection, and surviving Voice origins after history fold.

What the accepted proof establishes: scoped automated suites plus the 2026-09-07 owner witness cover half-duplex admission, a causally necessary spoken browser mutation, causal per-step client results, Your turn, coherent Tab-B resume, active-submission durable Stop, Tab-C stopped-entry recovery, playback controls, and no duplicate/autoplay. It does not claim direct spoken-user attribution after hydration, durable recovery of locally withheld work after a settled tool-call step, comparative latency, or remote deployment.

🔗 Related links

🚫 Blocked by

  • The parent resumable-workpiece PR, #9537
  • Mission 6b's narrowed local claim was accepted by Lu on 2026-09-07
  • KA's original PR remains untouched; retirement requires separate authorization

🔍 What does this change?

  • Imports KA's Voice contribution onto the Mission 6 branch with attribution, then reconciles it with Mission 6's fixture, catalogue, and coherent-bundle path instead of replacing either.
  • Keeps the panel's useChat / Flue browser transport as the only Voice admission door; Realtime has no tools and cannot submit.
  • Holds the composer busy across deferred browser-tool execution and automatic continuation; Stop withholds work that has not been admitted; Your turn cancels audio without aborting admitted Brunch work.
  • Projects canonically aborted entries as stopped history, skips runnable parts of those messages on reopen, and preserves folded Voice origins on continuations.
  • Surfaces automatic-tool failures to Voice, keeps compact/expanded Voice setup and exact full-response / marked-question replay, and adds a Petrinaut changeset for the consumer-visible Voice/stop behavior.
🏗️ Agent notes

Mission authority is libs/@hashintel/brunch-agent/MISSION.md (accepted with explicit limitations on 2026-09-07). This is the explicit exception to one new issue per mission: the owner directed a PR that references FE-1580 without rewriting that issue.

Imperative. Make KA's completed-transcript, half-duplex Voice experience work safely over Mission 6's resumable browser mutations and coherent workpiece/document recovery. Preserve both capabilities. Distinguish committed prose, submission settlement, pending browser work, coherent document settlement, and terminal provider output at the shared boundaries.

Throughline. Completed current-turn microphone transcript → shared panel submitVoiceInputWithAdmission / useChat → browser ChatTransport over the memoized Flue client → same-origin /agents/chat/:instanceId and mounted Brunch ChatAgent → committed canonical prose, hidden server question marker, browser-tool requests → existing canonical browser validation and effects → original call-id outputs resume the same conversation → canonical speech queue and acknowledged cancellation → coherent workpiece/document settlement → canonical history reopen and another real turn.

Proof. The accepted owner witness used the real microphone and prepared fixture. An SDCPN negative control performed one read and no mutation; an explicit spoken confirmation then produced one addArc, a separate verification read, two model workpiece revisions, coherent revision 2, one audible reply, Tab-B continuation, canonical durable abort and Tab-C stopped-entry recovery. The witness first exposed cumulative cross-step client results and a non-causal prepared answer; both received failing-first regressions and repairs. Direct spoken-user hydration attribution, post-settlement durable withholding and comparative latency were explicitly deferred with no claim.

Constraints. One conversation/log and the mounted Flue route; no direct Voice send or live brunch_ask. Realtime has tool_choice: none and create_response: false. Only durably completed, submission-correlated canonical assistant prose may speak. Local playback cancellation, local withheld browser work, and durable Flue abortion stay distinct. Preserve Mission 6 fixture identity, scoped catalogue, no-op honesty, and prior-coherent-bundle refusal. KA's original branch/PR stay untouched.

Fog-line. Question-marker compliance remains a model limitation. Direct-user attribution after hydration and comparative latency are explicitly deferred. Stop is durable for active Flue submissions; locally withheld browser work after a settled tool-call step may reappear as pending after reopen. The negative-control answer was too verbose and durable Stop was poorly discoverable while Voice remained active.

Stop or reorient. Stop if another conversation route appears, admission auto-retries, speech is rewritten, Stop lets withheld work execute, failures disappear, or coherent settlement is falsely reported. Reorient rather than invent a durable marker, forge aborted settlements, or disable ordinary pending-tool recovery to paper over the local-Stop/reopen gap.

Deferred. Mission 7 consumes this local reconciliation and must re-pin the prompt/tool baseline before paid runs; it cannot inherit acceptance. Retirement of KA's original PR needs separate authorization.

Implementation record

  • Source contribution range: 58f7584080..be56a18ff0, imported with attribution; see import.md.
  • Latest restack verification is in docs/evidence/implementations/voice-resumable-reconciliation/main-restack.md.
  • Newly confirmed limitation: after a completed Flue tool-call step, parent Stop can return already-settled. The panel can withhold pending browser work locally, but a fresh process cannot infer that withholding from the snapshot, so reopen can recover the tool as pending work. User docs warn about this. It is an owner reorientation point, not a solved projection case.
  • Direct spoken-user Voice chip after snapshot-only reopen is still unsupported by SDK 2.0.3.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s) (@hashintel/petrinaut: .changeset/flue-voice-safety.md; the @hashintel/brunch-agent* packages are private)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR (libs/@hashintel/petrinaut/docs/ai-assistant.md, apps/petrinaut-website/README.md, and the Brunch reconciliation evidence)

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph (dev:brunch now also builds @apps/brunch-agent first; no turbo.json change)

⚠️ Known issues

  • Local Stop is not a durable stopped record. If Flue has already completed a tool-call step, Stop can withhold the browser continuation in this process and still leave that pending tool recoverable after reopen. Canonically aborted submissions project metadata.stopped and are skipped; this local-withholding case is not the same thing.
  • Direct spoken-user attribution after snapshot reopen is unsupported on the current AI SDK. The chip cannot be invented locally.
  • Direct spoken-user attribution after hydration is unsupported. Canonical text survives; the live VOICE chips disappear after fresh hydration.
  • Post-settlement local withholding is not durable. Stop is durable for active Flue submissions; locally withheld pending browser work may reappear after reopen.
  • No comparative latency claim. The 10 donor + 10 candidate campaign was explicitly deferred.
  • Interaction strain. The negative-control answer was too verbose, and durable Stop required exiting Voice mode before the streaming Stop action was discoverable.

🐾 Next steps

  • Mission 7 (#9562) is restacked on this accepted narrowed foundation; its own scenario evidence remains required.
  • Re-enter the three deferred claims only under the conditions recorded in the owner witness.
  • Separate authorization is still required to retire KA's original PR.

🛡 What tests cover this?

  • Transport: deterministic client-tool keys after reordering, surviving folded Voice origins, aborted-entry projection.
  • Petrinaut panel: busy-across-continuation, Stop-before-deferred-execution, textless automatic-tool failure, StrictMode continuation, conversation-switch isolation.
  • Website Voice: half-duplex handoff, completed-transcript admission, canonical speech selection, cancellation acknowledgement, combined voice-browser-tools.integration.test.tsx.
  • Core: question-marker helper used by exact-question replay.
  • Scoped verification recorded 39 uncached build/test/type/lint tasks and 1,318 tests; full GitHub CI and live audio evidence were not run for that record.

❓ How to test this?

  1. Run yarn dev:brunch.
  2. Open http://127.0.0.1:4915/?brunch-fixture=crew-reservation-v1.
  3. Make a typed turn, then a spoken confirmation; watch the single crew-reservation arc and coherent bundle settle.
  4. During another response, use Your turn, wait for safe fresh capture, and speak again.
  5. Separately Stop before completion.
  6. Reopen the same URL in a second tab; confirm conversation and net, then continue without duplicate preparation, mutation, or autoplay.
  7. Inspect compact/expanded Voice, exact full-response and marked-question replay, and a visible tool failure.

This local demo and its acceptance gates, not merely green unit tests, define the visible advance.

📹 Demo

The accepted owner witness records the real microphone/browser path, canonical summary, screenshots, explicit deferrals, and evidence-bundle limitation. The complete pre-registered telemetry/network/latency bundle was not retained and is not inferred.

@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hash Ready Ready Preview Sep 7, 2026 6:14pm UTC
petrinaut Ready Ready Preview Sep 7, 2026 6:14pm UTC
petrinaut-docs Ready Ready Preview Sep 7, 2026 6:14pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 7, 2026 6:14pm UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/tests New or updated tests area/apps area/apps > hash.design Affects the `hash.design` design site (app) labels Sep 7, 2026
@lunelson
lunelson marked this pull request as ready for review September 7, 2026 12:57
Copilot AI balanced review requested due to automatic review settings September 7, 2026 12:57
@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Large, stateful changes to Voice realtime handoff, Flue admission/abort, and conversation history projection—areas where race or reopen bugs would affect user turns and durable history.

Overview
Reconciles Petrinaut Voice with the same canonical Brunch / Flue chat path used for typed turns: completed microphone transcripts (not Realtime tools) are admitted through the shared panel transport, while OpenAI Realtime is media-only with tool_choice: "none" and half-duplex capture (mic off during assistant audio, Brunch work, and cancellation).

Brunch question marking moves to a new core question-marker export (brunch_mark_question + data-brunch-question). The marker is hidden in the UI but preserved in history so Voice can offer Repeat question and Read full response from exact canonical text, with stricter selection rules than the old brunch_ask path. Production Brunch preview drops brunch_ask interactive tools; BrunchPanelConversationTracker gains admission failures, response start/complete, and immediate Stop notifications wired into VoiceInterviewControl.

OpenAIRealtimeSession drops function-call bridging, adds async cancelOutput handoffs, and rejects transcripts that overlap playback or lack a speech boundary. History hydration keeps Voice-origin client-tool metadata across reopen; docs and the crew-reservation fixture text are tightened around idempotent delivery, stopped entries, and honest reservation claims.

Local yarn dev:brunch now builds @apps/brunch-agent first; mergePetrinautPanelConfig keeps Petrinaut website API plugins for Voice dev. A @hashintel/petrinaut patch changeset documents the user-visible Voice/stop behavior.

Reviewed by Cursor Bugbot for commit a0ca1cb. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.32%. Comparing base (386ff76) to head (355edd8).

Additional details and impacted files
@@                              Coverage Diff                              @@
##           ln/fe-1575-resumable-workpiece-petrinaut    #9564       +/-   ##
=============================================================================
+ Coverage                                     15.41%   66.32%   +50.90%     
=============================================================================
  Files                                           208     1773     +1565     
  Lines                                          6557   191593   +185036     
  Branches                                       1141     7835     +6694     
=============================================================================
+ Hits                                           1011   127077   +126066     
- Misses                                         5445    63035    +57590     
- Partials                                        101     1481     +1380     
Flag Coverage Δ
apps.hash-ai-worker-ts 1.99% <ø> (?)
apps.hash-api 15.41% <ø> (ø)
apps.hash-graph 13.32% <ø> (?)
blockprotocol.type-system 38.15% <ø> (?)
local.claude-hooks 0.00% <ø> (?)
local.harpc-client 51.49% <ø> (?)
local.hash-backend-utils 3.27% <ø> (?)
local.hash-graph-sdk 10.02% <ø> (?)
local.hash-isomorphic-utils 12.22% <ø> (?)
rust.antsi 2.36% <ø> (?)
rust.error-stack 90.81% <ø> (?)
rust.harpc-codec 84.70% <ø> (?)
rust.harpc-net 96.29% <ø> (?)
rust.harpc-tower 67.03% <ø> (?)
rust.harpc-types 0.00% <ø> (?)
rust.harpc-wire-protocol 92.23% <ø> (?)
rust.hash-codec 72.76% <ø> (?)
rust.hash-config 78.41% <ø> (?)
rust.hash-graph-api 19.71% <ø> (?)
rust.hash-graph-atlas 80.33% <ø> (?)
rust.hash-graph-authentication 96.02% <ø> (?)
rust.hash-graph-authorization 63.14% <ø> (?)
rust.hash-graph-embeddings 91.88% <ø> (?)
rust.hash-graph-postgres-store 32.15% <ø> (?)
rust.hash-graph-store 48.41% <ø> (?)
rust.hash-graph-temporal-versioning 50.18% <ø> (?)
rust.hash-graph-types 0.00% <ø> (?)
rust.hash-graph-validation 84.71% <ø> (?)
rust.hash-middleware 90.92% <ø> (?)
rust.hashql-ast 89.63% <ø> (?)
rust.hashql-compiletest 28.39% <ø> (?)
rust.hashql-core 78.92% <ø> (?)
rust.hashql-diagnostics 72.51% <ø> (?)
rust.hashql-eval 79.82% <ø> (?)
rust.hashql-hir 89.09% <ø> (?)
rust.hashql-mir 87.92% <ø> (?)
rust.hashql-syntax-jexpr 94.04% <ø> (?)

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

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lunelson lunelson changed the title Cut Mission 6b Voice reconciliation authority FE-1580: Reconcile Voice turn behavior on the shared Brunch conversation Sep 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It spans transport durability, Voice state, browser tools, and public UI behavior while required human microphone, reload, and latency gates remain open.

Pull request overview

Reconciles Petrinaut Voice with the canonical, resumable Brunch/Flue conversation path.

Changes:

  • Adds half-duplex Voice handoff, replay controls, compact docks, and transcript authority.
  • Strengthens transport idempotency, error handling, history projection, and stopped/Voice metadata.
  • Adds question markers, persistent diagnostics, extensive tests, documentation, and mission evidence.
File summaries
File Description
package.json Prebuilds Brunch dependencies for local development.
.../ai-assistant-panel/types.ts Extends message metadata for Voice origins and stopped responses.
.../voice-dock/transcription-icon.tsx Removes the obsolete transcription icon.
.../voice-dock/playback-menu.tsx Adds Voice replay actions.
.../ai-assistant-contents/voice-dock.tsx Adds collapse, replay, handoff, and notice controls.
.../ai-assistant-contents/tool-list.tsx Displays complete tool errors inline.
.../defer-voice-messages.ts Removes deferred transcript behavior.
.../defer-voice-messages.test.ts Removes obsolete deferral tests.
.../ai-assistant-contents.stories.tsx Adds compact and collapsed Voice stories.
.../components/voice-session-labels.ts Adds labels for new Voice controls.
.../types/ai-assistant-composer-control.ts Extends public Voice controls and stopped state.
.../voice-session/use-voice-session.ts Adds granular Voice capability hooks.
.../voice-session/types.ts Extends Voice session state.
.../voice-session/store.ts Extends Voice actions.
.../notifications/toaster.tsx Adds persistent, detailed, copyable notifications.
.../notifications/provider.tsx Supplies notification details and persistent errors.
.../notifications/provider.test.tsx Tests notification duration and detail behavior.
.../notifications/context.ts Adds notification detail support.
.../panda-preset.ts Removes an unused Voice animation.
.../docs/ai-assistant.md Documents the revised Voice and error UX.
.../transport-aisdk/test/ui-stream.test.ts Tests hidden tools and error projection.
.../transport-aisdk/test/transcript.test.ts Tests durable Voice and stopped metadata.
.../transport-aisdk/test/chat-transport.test.ts Tests admission identity, failures, and callbacks.
.../transport-aisdk/src/ui-stream.ts Hides internal tools and preserves error details.
.../transport-aisdk/src/transcript.ts Reconstructs history metadata and hides marker tools.
.../transport-aisdk/src/index.ts Adds typed admission failures and deterministic delivery.
.../transport-aisdk/src/error-text.ts Adds bounded error serialization.
.../core/vite.config.ts Builds the question-marker entry point.
.../core/test/question-marker.test.ts Tests marker schemas, tool behavior, and prompt instructions.
.../core/src/question-marker.ts Defines the durable question-marker contract.
.../core/src/prompts/SYSTEM.md Instructs Brunch to mark direct questions.
.../core/src/index.ts Exports question-marker APIs.
.../core/src/flue.ts Mounts the question-marker tool.
.../core/package.json Exposes the question-marker subpath.
.../MISSION.next.md Updates the mission dependency and planning record.
.../voice-resumable-reconciliation/verification.md Records verification results and remaining gates.
.../voice-resumable-reconciliation/main-restack.md Records the restack and route verification.
.../voice-resumable-reconciliation/import.md Records imported source provenance.
.../mission-5-voice-safety-parity/witness-blocker.md Documents the outstanding human witness.
.../mission-5-voice-safety-parity/provenance-blocker.md Documents the direct-user provenance limitation.
.../mission-5-voice-safety-parity/donor-behavior-matrix.md Records adopted Voice behavior and acceptance status.
.../mission-5-question-marker-and-provenance-decision-2026-09-04.md Records question-marker and provenance decisions.
.../server/voice/openai-voice-policy.ts Makes Realtime transcription-only and half-duplex.
.../server/voice/openai-voice-policy.test.ts Tests the revised Realtime policy.
.../server/voice/openai-realtime-call.test.ts Tests forwarding the half-duplex policy.
.../voice-interview/voice-session-state.ts Projects replay, handoff, and notice state.
.../voice-interview/voice-session-state.test.ts Tests the new projected state.
.../voice-interview/voice-interview-control.test.tsx Tests admission, setup, microphone, and controls.
.../voice-interview/voice-browser-tools.integration.test.tsx Exercises the combined Voice/browser-tool lifecycle.
.../voice-interview/canonical-speech.ts Selects exact canonical response and question speech.
.../voice-interview/canonical-speech.test.ts Tests exact marker-based question selection.
.../local-storage-demo/use-flue-chat-history.ts Applies hidden-tool and history projection rules.
.../local-storage-demo/use-flue-chat-history.test.ts Tests persisted Voice origins across reopen.
.../local-storage-demo/local-storage-demo-app.tsx Wires Voice lifecycle tracking and removes brunch_ask.
.../local-storage-demo/local-storage-demo-app.test.tsx Tests production Voice registration and durable Stop.
.../local-storage-demo/brunch-panel-transport.ts Tracks response lifecycle and admission failures.
.../local-storage-demo/brunch-panel-transport.test.ts Tests tracker events and failure propagation.
apps/petrinaut-website/README.md Documents current Voice behavior and limitations.
apps/brunch-agent/test/petrinaut-chat.test.ts Verifies marker persistence and hiding.
apps/brunch-agent/test/petrinaut-chat.integration.ts Extends the real Flue integration scenario.
apps/brunch-agent/test/petrinaut-chat-result.ts Extends integration result types.
apps/brunch-agent/test/local-dev-origins.test.ts Verifies launcher dependencies and API plugins.
apps/brunch-agent/test/architecture/boundaries.integration.ts Registers the new package boundary.
apps/brunch-agent/petrinaut-local.vite.config.ts Preserves website plugins while merging local config.
.changeset/flue-voice-safety.md Records the Petrinaut patch release.
Review details
  • Files reviewed: 79/79 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@codspeed-hq

codspeed-hq Bot commented Sep 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 6 benchmarks measured no execution time

Nothing ran under measurement, usually because the compiler removed the code under test. These results are not comparable, so they count as unchanged.

Preventing compiler optimizations

✅ 98 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚠️ as_constant < 1 ns < 1 ns N/A
⚠️ constant_equal < 1 ns < 1 ns N/A
⚠️ constant_not_equal < 1 ns < 1 ns N/A
⚠️ access < 1 ns < 1 ns N/A
⚠️ runtime_equal < 1 ns < 1 ns N/A
⚠️ runtime_not_equal < 1 ns < 1 ns N/A

Comparing ln/fe-1580-reconcile-voice-resumable-workpiece (a0ca1cb) with main (9c21578)1

Open in CodSpeed

Footnotes

  1. No successful run was found on ln/fe-1575-resumable-workpiece-petrinaut (275d1f3) during the generation of this report, so main (9c21578) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

lunelson commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@lunelson
lunelson deployed to pull-request September 7, 2026 16:11 — with GitHub Actions Active

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a0ca1cb. Configure here.

this.#handleConnectionFailure("invalid-response", "connection");
return;
}
this.#emit({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent TTS completion sticks the microphone

Medium Severity

A completed canonical response stays in #authorizedResponseIds until output_audio_buffer.stopped or output_audio_buffer.cleared. #syncMicrophoneTrack treats any remaining authorized id as assistant-owned, so a completed TTS that never emits those buffer events leaves capture closed and the turn appearing still in progress.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a0ca1cb. Configure here.

if (
functionCalls.length > 1 ||
(functionCalls.length > 0 && this.#canonicalResponseIds.has(responseId))
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Late output after cancel drops session

Medium Severity

#handleResponseDone deletes a cancelled response from #cancelledCanonicalResponseIds as soon as the terminal event arrives. #handleOutputBufferEvent still uses that set to ignore leftover output_audio_buffer.started events, so a started event after the cancelled response.done looks unauthorized and tears down the Voice connection.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a0ca1cb. Configure here.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$27.8 \mathrm{ms} \pm 223 \mathrm{μs}\left({\color{gray}1.20 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.52 \mathrm{ms} \pm 29.4 \mathrm{μs}\left({\color{gray}1.81 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$13.3 \mathrm{ms} \pm 114 \mathrm{μs}\left({\color{red}7.23 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$44.2 \mathrm{ms} \pm 382 \mathrm{μs}\left({\color{gray}1.96 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$14.7 \mathrm{ms} \pm 122 \mathrm{μs}\left({\color{gray}4.23 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$24.8 \mathrm{ms} \pm 220 \mathrm{μs}\left({\color{gray}2.70 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$29.2 \mathrm{ms} \pm 202 \mathrm{μs}\left({\color{gray}1.66 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.77 \mathrm{ms} \pm 24.9 \mathrm{μs}\left({\color{gray}-0.591 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$14.2 \mathrm{ms} \pm 139 \mathrm{μs}\left({\color{gray}2.73 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.88 \mathrm{ms} \pm 26.2 \mathrm{μs}\left({\color{gray}0.821 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.05 \mathrm{ms} \pm 19.4 \mathrm{μs}\left({\color{gray}1.80 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.42 \mathrm{ms} \pm 21.3 \mathrm{μs}\left({\color{gray}1.20 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.28 \mathrm{ms} \pm 44.2 \mathrm{μs}\left({\color{gray}-0.027 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.62 \mathrm{ms} \pm 25.5 \mathrm{μs}\left({\color{gray}0.496 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.22 \mathrm{ms} \pm 31.6 \mathrm{μs}\left({\color{gray}-0.532 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.60 \mathrm{ms} \pm 39.8 \mathrm{μs}\left({\color{gray}2.42 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.50 \mathrm{ms} \pm 24.5 \mathrm{μs}\left({\color{gray}1.71 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.16 \mathrm{ms} \pm 33.5 \mathrm{μs}\left({\color{gray}0.818 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.77 \mathrm{ms} \pm 19.8 \mathrm{μs}\left({\color{gray}0.510 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.73 \mathrm{ms} \pm 24.8 \mathrm{μs}\left({\color{gray}1.37 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.82 \mathrm{ms} \pm 15.7 \mathrm{μs}\left({\color{gray}0.045 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.11 \mathrm{ms} \pm 19.0 \mathrm{μs}\left({\color{gray}0.599 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.90 \mathrm{ms} \pm 19.8 \mathrm{μs}\left({\color{gray}0.488 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$3.21 \mathrm{ms} \pm 23.5 \mathrm{μs}\left({\color{gray}1.19 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.19 \mathrm{ms} \pm 21.0 \mathrm{μs}\left({\color{gray}2.65 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.87 \mathrm{ms} \pm 23.4 \mathrm{μs}\left({\color{gray}2.11 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.04 \mathrm{ms} \pm 20.7 \mathrm{μs}\left({\color{gray}2.25 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.63 \mathrm{ms} \pm 25.8 \mathrm{μs}\left({\color{gray}1.13 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.11 \mathrm{ms} \pm 16.0 \mathrm{μs}\left({\color{gray}0.736 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.44 \mathrm{ms} \pm 28.1 \mathrm{μs}\left({\color{gray}3.98 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.54 \mathrm{ms} \pm 23.2 \mathrm{μs}\left({\color{gray}1.76 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.10 \mathrm{ms} \pm 18.3 \mathrm{μs}\left({\color{gray}-0.281 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.44 \mathrm{ms} \pm 28.0 \mathrm{μs}\left({\color{gray}1.78 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$32.2 \mathrm{ms} \pm 347 \mathrm{μs}\left({\color{gray}1.78 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$71.1 \mathrm{ms} \pm 583 \mathrm{μs}\left({\color{gray}1.50 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$35.2 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}-0.522 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$39.3 \mathrm{ms} \pm 291 \mathrm{μs}\left({\color{gray}0.817 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$41.6 \mathrm{ms} \pm 333 \mathrm{μs}\left({\color{gray}-0.347 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$33.8 \mathrm{ms} \pm 235 \mathrm{μs}\left({\color{gray}1.75 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$426 \mathrm{ms} \pm 1.60 \mathrm{ms}\left({\color{gray}2.95 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$91.5 \mathrm{ms} \pm 692 \mathrm{μs}\left({\color{red}7.55 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$79.4 \mathrm{ms} \pm 567 \mathrm{μs}\left({\color{gray}0.334 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$274 \mathrm{ms} \pm 1.44 \mathrm{ms}\left({\color{gray}0.731 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$10.4 \mathrm{ms} \pm 61.4 \mathrm{μs}\left({\color{gray}0.754 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$10.7 \mathrm{ms} \pm 77.1 \mathrm{μs}\left({\color{gray}2.51 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$10.7 \mathrm{ms} \pm 68.8 \mathrm{μs}\left({\color{gray}1.10 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$10.5 \mathrm{ms} \pm 54.4 \mathrm{μs}\left({\color{gray}0.477 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$10.6 \mathrm{ms} \pm 70.1 \mathrm{μs}\left({\color{gray}0.744 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$10.4 \mathrm{ms} \pm 65.5 \mathrm{μs}\left({\color{gray}-0.410 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$10.6 \mathrm{ms} \pm 54.8 \mathrm{μs}\left({\color{gray}2.36 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$10.4 \mathrm{ms} \pm 66.2 \mathrm{μs}\left({\color{gray}0.913 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$10.4 \mathrm{ms} \pm 60.8 \mathrm{μs}\left({\color{gray}0.323 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$10.7 \mathrm{ms} \pm 58.7 \mathrm{μs}\left({\color{gray}0.234 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$10.9 \mathrm{ms} \pm 68.5 \mathrm{μs}\left({\color{gray}0.713 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.2 \mathrm{ms} \pm 91.1 \mathrm{μs}\left({\color{gray}2.86 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$10.8 \mathrm{ms} \pm 51.6 \mathrm{μs}\left({\color{gray}-0.175 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$10.9 \mathrm{ms} \pm 65.0 \mathrm{μs}\left({\color{gray}0.279 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$10.9 \mathrm{ms} \pm 59.3 \mathrm{μs}\left({\color{gray}0.607 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.1 \mathrm{ms} \pm 73.4 \mathrm{μs}\left({\color{gray}0.845 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.0 \mathrm{ms} \pm 70.9 \mathrm{μs}\left({\color{gray}0.573 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$10.9 \mathrm{ms} \pm 73.6 \mathrm{μs}\left({\color{gray}-0.133 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$10.9 \mathrm{ms} \pm 59.0 \mathrm{μs}\left({\color{gray}0.265 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$8.14 \mathrm{ms} \pm 53.5 \mathrm{μs}\left({\color{gray}1.83 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$54.0 \mathrm{ms} \pm 353 \mathrm{μs}\left({\color{gray}-0.583 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$107 \mathrm{ms} \pm 690 \mathrm{μs}\left({\color{gray}0.043 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$60.5 \mathrm{ms} \pm 604 \mathrm{μs}\left({\color{gray}0.172 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$69.0 \mathrm{ms} \pm 408 \mathrm{μs}\left({\color{gray}-1.018 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$78.2 \mathrm{ms} \pm 462 \mathrm{μs}\left({\color{gray}0.023 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$85.0 \mathrm{ms} \pm 462 \mathrm{μs}\left({\color{gray}-0.170 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$45.0 \mathrm{ms} \pm 325 \mathrm{μs}\left({\color{gray}1.08 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$73.0 \mathrm{ms} \pm 506 \mathrm{μs}\left({\color{gray}1.01 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$51.0 \mathrm{ms} \pm 386 \mathrm{μs}\left({\color{gray}-0.416 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$59.4 \mathrm{ms} \pm 406 \mathrm{μs}\left({\color{gray}-0.369 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$61.7 \mathrm{ms} \pm 397 \mathrm{μs}\left({\color{gray}-0.423 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$62.4 \mathrm{ms} \pm 522 \mathrm{μs}\left({\color{gray}1.23 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$121 \mathrm{ms} \pm 737 \mathrm{μs}\left({\color{red}5.61 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$132 \mathrm{ms} \pm 611 \mathrm{μs}\left({\color{gray}4.35 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$23.8 \mathrm{ms} \pm 192 \mathrm{μs}\left({\color{gray}2.35 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$525 \mathrm{ms} \pm 1.17 \mathrm{ms}\left({\color{red}6.46 \mathrm{\%}}\right) $$ Flame Graph

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

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

2 participants