Skip to content

FE-1574: Let Voice speak through canonical Brunch conversations - #9528

Open
lunelson wants to merge 31 commits into
mainfrom
ln/fe-1574-direct-voice-flue
Open

FE-1574: Let Voice speak through canonical Brunch conversations#9528
lunelson wants to merge 31 commits into
mainfrom
ln/fe-1574-direct-voice-flue

Conversation

@lunelson

@lunelson lunelson commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Make the mounted Flue conversation route the only product door into a Brunch conversation, and make Voice a faithful audio projection of that one canonical conversation. In the Petrinaut Brunch panel you can type or speak to Brunch in one conversation; what you hear is exactly what Brunch wrote; Stop asks Brunch to stop rather than only hiding its answer; and reopening the panel rehydrates the same canonical conversation from Flue without re-sending or replaying anything.

The production throughline is the local Petrinaut surface under yarn dev:brunch: the typed panel's useChat and the Voice control both reach one browser Flue client at the same-origin-proxied /agents/chat/:instanceId route, behind the single ownership guard, into the mounted ChatAgent. The former Brunch /api/chat handler, projector, and proxy are deleted.

What the proof establishes: one typed turn and one finalized spoken turn each cross the Flue route exactly once, the typed turn returns as one finite AI SDK stream, the Voice turn returns as visible text and TTS of the same canonical words, Stop uses Flue's durable abort, and reopen rehydrates canonical messages from Flue observation. What it does not claim: trusted production authentication or authorization, remote deployment, structured brunch_ask questions (that capability stays suspended), Petrinaut mutation tools, faithful reopen presentation, or Mission 5 product acceptance.

Landing decision. This PR lands as the transport tracer. The 2026-09-04 human witness (recorded in the evidence README and MISSION.md) passed typed and Voice admission, exactly-one visible Voice input, spoken canonical output, barge-in, and explicit durable Stop, but failed the faithful-reopen gate: canonical messages returned after reload, yet per-message typed/Voice provenance was absent and the stopped assistant entry rehydrated as ordinary truncated content. Rather than expand this branch, the owner deferred that remediation to Mission 6 (#9537, stacked on this PR), which owns preserving both distinctions across its second-tab resume proof. Mission 5 acceptance (proof leaf 8) therefore remains open in MISSION.md and is not a gate for merging this transport change.

🔗 Related links

🚫 Blocked by

🔍 What does this change?

  • @hashintel/brunch-agent-transport-aisdk becomes the browser-side adapter over public @flue/sdk: a ChatTransport that admits one send() per turn, projects the wait() event stream into UIMessageChunks, and projects Flue history snapshots into AI SDK messages. It no longer contains a server handler.
  • The Brunch app stops mounting /api/chat; the local launcher proxies /agents/chat/* instead. Ownership is derived from principal plus logical conversation id in one guard.
  • The Petrinaut website's Brunch demo supplies that transport to the panel, tracks admissions and response messages per conversation, hydrates canonical history through SDK observation, and exposes a durable requestStop.
  • The Voice control submits a finalized Realtime answer through the panel's transport, waits for the real Flue admission, correlates the reply by server submissionId, and speaks exactly the canonical text.
  • Petrinaut's AI panel accepts a host requestStop so Stop can become durable before the local stream is cancelled (changeset: .changeset/durable-brunch-stop.md).
  • Plugin packages move to source-authored skills with flue.ts entrypoints; YAML plugin machinery is removed.
🏗️ Agent notes

Mission authority is libs/@hashintel/brunch-agent/MISSION.md (live as of 2026-09-04). The six sections, condensed:

Imperative. One Flue route is the only product door into a Brunch conversation; Voice is an audio projection of that conversation. Now, because Mission 4 fixed the canonical agent composition while two transports still reached the same conversation.

Throughline. Petrinaut typed panel (useChat with a host-supplied browser ChatTransport) and Petrinaut microphone (OpenAI Realtime, one validated finalized continue_interview answer) → one browser createFlueClient() per principal + logical conversation id → one send() at the same-origin-proxied /agents/chat/:instanceIdagentOwnershipGuardcreateAgentRouter(ChatAgent) with useBrunchAgent() + useSdcpnPlugin()wait(admission, { onEvent }) for the finite per-turn stream, observe({ live: "sse" }) for canonical state and reopen → parts correlated by submissionId → panel projector terminated on submission-settled; Voice speaks canonical completed text unchanged.

Proof. Nine leaves with named oracles: (1) typed panel over the browser transport, (2) direct finalized admission, (3) no server-side AI SDK door, (4) canonical visible and TTS output, (5) cancellation and abort stay distinct, (6) reopen resumes without replay, (7) architecture and boundary integrity, (8) real Voice witness and retained proof bundle, (9) focused repository verification and truthful docs. Leaves 1 to 7 and 9 have automated evidence recorded in the evidence README; leaf 8 ran on 2026-09-04 and failed its reopen gate, with remediation deferred to Mission 6.

Constraints. Flue history is the sole canonical record; @flue/sdk is used directly with no re-specified offsets, retries, or recovery; one product route; the panel stays on useChat; only the validated finalized answer is submitted and never automatically retried; Brunch owns response content; the four cancellation kinds stay distinguishable; the local principal is a discriminator, not authentication; the transport package depends only on ai and @flue/sdk; brunch_ask is not mounted.

Fog-line. How Voice enters Flue (shape B, via the panel transport, is what shipped); the home of the identity and header contract; whether wait() alone gives a clean per-turn stream or reconnect needs observe(); whether reconnectToStream resumes; which parts of #9496/#9507/#9512 to port; speech selection for multi-block responses; whether one Stop affordance can express local interruption and durable abort; whether the same-origin proxy carries every SDK route.

Stop or reorient. Stop on a second conversation authority, any non-Flue turn route, provisional STT submission, automatic retry of ambiguous admission, canonical text rewritten by another model, hand-rolled recovery, restored stub agent, or activated brunch_ask; stop if client-tool resume semantics need a server helper; stop if local cancellation aborts durable work or Stop only cancels the browser request.

Deferred items are recorded in MISSION.md §Deferred and MISSION.next.md.

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/durable-brunch-stop.md and .changeset/voice-input-withdrawal-and-stop-correlation.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, Brunch evidence and mission records)

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

The changes in this PR:

  • affected the execution graph, and the turbo.json's have been updated to reflect this (plugin packages gained build, lint:*, and test:unit scripts; apps/brunch-agent, core, and plugin-dafny turbo.json updated)

⚠️ Known issues

The ten review-bot findings on this PR were read as samples of three underlying faults and all are addressed in the review-fix commit, with a failing-first test for each:

  • Stop was not correlated to the turn it targeted. A Stop pressed while send() was still in flight now waits for that admission before Flue's conversation-wide abort, so aborted: false no longer reads as "already settled" while the admitted turn keeps running. A durable Stop result or failure that lands after a newer turn started is ignored. A Voice turn whose submission settles aborted closes its Realtime function call without speaking, driven by Flue's settlement index rather than chat status, because the AI SDK reports ready between a step and its automatic client-tool follow-up.
  • Live-stream and snapshot projections encoded the same rules twice. The snapshot projector keeps providerExecuted on a server tool that is still running and folds a client-tool continuation back into the assistant message it resumed, matching the live stream. The transport reports the resumed assistant id to the host, and the tracker records every submission that wrote a message, so Voice correlates by membership and speaks a reply whether the continuation was admitted by the panel after a readPetrinautDoc call or by Voice answering a pending brunch_ask. One catalog (brunch-client-tools.ts) now feeds the panel transport and the history projection, and a test asserts every registered interactive widget answers a tool in it. Host-owned history is hydrated only once the snapshot carries every locally streamed reply.
  • A withdrawn Voice input could still be sent. A retained voice input carries an AbortSignal; Petrinaut drops it from the queue and rejects it when the signal fires.
  • Smaller fixes: a consumer stream.cancel() no longer enqueues on a closed controller, and first-canonical-text latency is recorded when the first completed canonical text block appears rather than at settlement.

brunch_ask remains suspended on the server. Its browser paths are now consistent but carried as latent code, not as a claim.

Second review round (four further Bugbot findings): two did not reproduce (the abort race attaches a handler to the losing branch through Promise.race, so no rejection is unhandled; an empty host snapshot never latches hydration because the panel returns before latching when both lists are empty). Two were real and share one root: the AI SDK reports ready between a step that ends in client tool calls and the follow-up it sends automatically, and hosts read that gap as the end of the turn. The Voice bridge closed its Realtime call on the step's partial text, and a Stop whose durable abort landed already-settled did nothing while the follow-up went out anyway. The panel now keeps its composer status busy until the follow-up starts, and a Stop pressed during that step withholds the follow-up and marks the response stopped, each with a failing-first test.

Final unresolved review thread: conversation-owned state could leak across an explicit conversation switch. Commit 2697d0a834 keys the stateful panel by conversation identity so useChat, pending continuation/error/stopped state, late callback refs, and pending input recovery are replaced together. A failing-first panel test holds one conversation in a client-tool continuation, switches identity, and requires the replacement to render ready.

🐾 Next steps

  • Mission 6 (FE-1575: Resume one Brunch workpiece and Petrinaut document across tabs #9537) carries the faithful-reopen remediation: per-message typed/Voice provenance and stopped-turn presentation across a second-tab resume, plus the observed discoverability gap between Show transcript, Exit voice mode, and durable Stop.
  • Consider an equivalence test between the live projector and the snapshot projector over one shared fixture, so the two cannot drift again silently.

🛡 What tests cover this?

  • libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts, transcript.test.ts, ui-stream.test.ts
  • apps/petrinaut-website/src/main/app/voice-interview/realtime-brunch-bridge.test.ts, voice-interview-control.test.tsx, voice-turn-controller.test.ts, canonical-speech.test.ts
  • apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.test.ts, local-storage-demo-app.test.tsx
  • libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel.test.tsx
  • apps/brunch-agent boundary and ownership tests
  • Focused command (recorded in the evidence README): yarn exec turbo run lint:tsc lint:eslint test:unit build --filter @apps/brunch-agent --filter @apps/petrinaut-website --filter @hashintel/petrinaut --filter @hashintel/brunch-agent --filter @hashintel/brunch-agent-plugin-sdcpn --filter @hashintel/brunch-agent-transport-aisdk

❓ How to test this?

  1. Run yarn dev:brunch and open the Petrinaut Brunch preview.
  2. Type one message and read the reply.
  3. Start Voice mode and speak one answer. Confirm exactly one new user message appears, then Brunch's reply appears as text and is read aloud in the same words.
  4. Speak over the playback once. Playback stops and the text stays.
  5. Ask a second question and press Stop while Brunch is still working. Confirm the conversation shows that turn as stopped, or the documented already-settled race, rather than a browser-only cancellation.
  6. Close and reopen the panel on the same conversation. Confirm nothing is re-sent or replayed.

📹 Demo

No recording. The 2026-09-04 human witness is recorded in prose in the evidence README; its artifact bundle was not retained after the reopen gate failed.

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

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

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

Request Review

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) 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 labels Sep 3, 2026
@lunelson
lunelson deployed to pull-request September 3, 2026 17:07 — with GitHub Actions Active
@lunelson
lunelson deployed to pull-request September 3, 2026 17:07 — with GitHub Actions Active
@codspeed-hq

codspeed-hq Bot commented Sep 3, 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-1574-direct-voice-flue (a69c2dd) with main (9c21578)

Open in CodSpeed

lunelson and others added 25 commits September 7, 2026 19:18
State the release note, no-engineer demo script on the local deployment
posture, and previously-impossible delta (Stop that really stops; one
shared typed/spoken conversation), pin completion to the contract stratum
rather than the first green tracer, and mark the single-route
consolidation as internal sequencing rather than the visible advance.
Name Mission 5's surface as the Petrinaut Brunch panel's typed and Voice
route and record that the live branch adopted the litmus on restack.
Now that the panel composer is itself Flue transport, let Voice enter
either through it (preferred, one visible store) or by a direct send();
reword proof leaf 3 to forbid only non-Flue submission and record the
choice and its fallback criterion on the fog-line.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Update the living Petrinaut integration spec and topology reference to name
the guarded Flue conversation mount, browser ChatTransport projection, and
current transport-package dependency boundary. Narrow the Mission 5 route
scan claim to its actual production paths while preserving superseded
/api/chat references as historical provenance.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n projections

Review of PR #9528 read the ten bot findings as samples of three faults.

Stop was not bound to the turn it targeted: a Stop during an in-flight
admission now waits for that admission before the conversation-wide abort, a
durable Stop result that lands after a newer turn started is ignored, and a
Voice turn whose submission settles aborted closes its Realtime call without
speaking, driven by Flue's settlement index because the AI SDK reports ready
between a step and its automatic client-tool follow-up.

The live-stream and snapshot projections encoded the same rules twice: the
snapshot keeps providerExecuted on a running server tool and folds a
client-tool continuation into the assistant message it resumed; the transport
reports the resumed assistant id and the tracker keeps the originating
submission for it; one client-tool catalog feeds the panel transport and the
history projection; host history hydrates only once it carries every locally
streamed reply.

A retained Voice input can be withdrawn through an AbortSignal, a consumer
stream cancel no longer writes to a closed controller, and first-canonical-text
latency is recorded when the first completed text block appears.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A client-tool continuation is projected onto the assistant message it resumes,
so one message can be written by several submissions, and either side may have
admitted the continuation: the panel after a readPetrinautDoc call, or Voice
when it answers a pending brunch_ask. Keeping only the first submission served
one direction and broke the other. The tracker now records every submission
per message, segments carry that set, and the bridge matches by membership
while excluding segments that predate the answer.

The Stop generation guard also covers the rejection path, so a durable Stop
that fails after a newer turn started no longer records an error on that turn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ld it

The AI SDK reports ready between a step that ended in client tool calls
and the follow-up it sends automatically. Hosts read that gap as the end
of the turn: the Voice bridge closed its Realtime call on the step's
partial text, and a Stop whose durable abort landed already-settled did
nothing while the follow-up went out anyway. The panel now keeps its
composer status busy until the follow-up starts, and a Stop pressed
during the step withholds the follow-up and marks the response stopped.
vilkinsons
vilkinsons previously approved these changes Sep 7, 2026

@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 1 potential issue.

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 acc97ae. Configure here.

Comment thread libs/@hashintel/brunch-agent/packages/transport-aisdk/src/index.ts
kostandinang
kostandinang previously approved these changes Sep 7, 2026

@kostandinang kostandinang 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.

Approving this PR, but noticed three correctness issues, which we may defer to #9537 or the reconciliation one #9564 (review).

  • Prevent replay of earlier client-tool results. Continuations are folded into the same assistant message. The transport currently collects every completed client-tool result from that message, so a later continuation can resend both the new result and results already delivered in previous steps. It should send only newly completed results or track delivered tool-call IDs.

  • Terminate Voice correctly when a continuation is withheld. If Stop is pressed after a tool-only step has completed, the panel suppresses the automatic follow-up. Flue reports the completed step rather than an aborted submission, so the Voice bridge does not recognize it as terminal and can leave the Realtime call waiting indefinitely. The withheld continuation needs an explicit terminal outcome.

  • Scope Stop so a delayed abort cannot affect a newer turn. Stop currently issues a conversation-wide asynchronous abort. The original turn can finish and a new turn can be admitted before that abort reaches Flue, allowing the stale abort to cancel the newer turn. New submissions should remain blocked until Stop settles, or cancellation should target a specific submission.

@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.7 \mathrm{ms} \pm 175 \mathrm{μs}\left({\color{gray}-0.717 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.60 \mathrm{ms} \pm 29.5 \mathrm{μs}\left({\color{gray}4.52 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$14.0 \mathrm{ms} \pm 130 \mathrm{μs}\left({\color{red}5.96 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$44.9 \mathrm{ms} \pm 414 \mathrm{μs}\left({\color{red}5.54 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$15.6 \mathrm{ms} \pm 143 \mathrm{μs}\left({\color{red}11.0 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$25.5 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}4.92 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$29.4 \mathrm{ms} \pm 305 \mathrm{μs}\left({\color{gray}2.50 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.83 \mathrm{ms} \pm 26.4 \mathrm{μs}\left({\color{gray}-1.120 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$14.4 \mathrm{ms} \pm 106 \mathrm{μs}\left({\color{gray}1.44 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$4.19 \mathrm{ms} \pm 39.1 \mathrm{μs}\left({\color{red}8.54 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.11 \mathrm{ms} \pm 22.4 \mathrm{μs}\left({\color{gray}1.58 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.54 \mathrm{ms} \pm 28.5 \mathrm{μs}\left({\color{gray}2.56 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.41 \mathrm{ms} \pm 42.5 \mathrm{μs}\left({\color{gray}2.57 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.67 \mathrm{ms} \pm 24.0 \mathrm{μs}\left({\color{gray}2.12 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.32 \mathrm{ms} \pm 32.8 \mathrm{μs}\left({\color{gray}2.65 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.72 \mathrm{ms} \pm 36.9 \mathrm{μs}\left({\color{gray}4.77 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.63 \mathrm{ms} \pm 31.4 \mathrm{μs}\left({\color{gray}4.11 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.47 \mathrm{ms} \pm 32.4 \mathrm{μs}\left({\color{red}5.54 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.80 \mathrm{ms} \pm 15.9 \mathrm{μs}\left({\color{gray}0.668 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.75 \mathrm{ms} \pm 16.8 \mathrm{μs}\left({\color{gray}0.598 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.86 \mathrm{ms} \pm 17.6 \mathrm{μs}\left({\color{gray}-0.352 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.12 \mathrm{ms} \pm 18.1 \mathrm{μs}\left({\color{gray}0.815 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.96 \mathrm{ms} \pm 20.1 \mathrm{μs}\left({\color{gray}2.23 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$3.27 \mathrm{ms} \pm 29.5 \mathrm{μs}\left({\color{gray}3.10 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.16 \mathrm{ms} \pm 19.6 \mathrm{μs}\left({\color{gray}0.420 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.83 \mathrm{ms} \pm 16.3 \mathrm{μs}\left({\color{gray}-0.688 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.03 \mathrm{ms} \pm 22.0 \mathrm{μs}\left({\color{gray}0.276 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.64 \mathrm{ms} \pm 23.9 \mathrm{μs}\left({\color{gray}1.86 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.13 \mathrm{ms} \pm 18.9 \mathrm{μs}\left({\color{gray}0.516 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.40 \mathrm{ms} \pm 19.8 \mathrm{μs}\left({\color{gray}1.49 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.58 \mathrm{ms} \pm 26.2 \mathrm{μs}\left({\color{gray}1.84 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.14 \mathrm{ms} \pm 20.9 \mathrm{μs}\left({\color{gray}2.11 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.48 \mathrm{ms} \pm 21.9 \mathrm{μs}\left({\color{gray}1.98 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$32.7 \mathrm{ms} \pm 178 \mathrm{μs}\left({\color{gray}2.88 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$72.2 \mathrm{ms} \pm 443 \mathrm{μs}\left({\color{gray}2.62 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$37.1 \mathrm{ms} \pm 189 \mathrm{μs}\left({\color{red}6.23 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$40.1 \mathrm{ms} \pm 288 \mathrm{μs}\left({\color{gray}2.11 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$43.6 \mathrm{ms} \pm 224 \mathrm{μs}\left({\color{gray}4.42 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$34.9 \mathrm{ms} \pm 190 \mathrm{μs}\left({\color{gray}3.94 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$423 \mathrm{ms} \pm 1.26 \mathrm{ms}\left({\color{gray}0.532 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$92.2 \mathrm{ms} \pm 534 \mathrm{μs}\left({\color{gray}1.86 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$80.7 \mathrm{ms} \pm 482 \mathrm{μs}\left({\color{gray}2.48 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$278 \mathrm{ms} \pm 1.13 \mathrm{ms}\left({\color{lightgreen}-8.583 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$10.6 \mathrm{ms} \pm 81.8 \mathrm{μs}\left({\color{gray}1.62 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$10.8 \mathrm{ms} \pm 107 \mathrm{μs}\left({\color{gray}1.79 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$10.7 \mathrm{ms} \pm 73.3 \mathrm{μs}\left({\color{gray}-0.230 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$10.7 \mathrm{ms} \pm 92.6 \mathrm{μs}\left({\color{gray}1.73 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$10.6 \mathrm{ms} \pm 55.3 \mathrm{μs}\left({\color{gray}-1.430 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$10.5 \mathrm{ms} \pm 81.3 \mathrm{μs}\left({\color{gray}-2.483 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$10.4 \mathrm{ms} \pm 53.2 \mathrm{μs}\left({\color{gray}-1.859 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$10.5 \mathrm{ms} \pm 74.0 \mathrm{μs}\left({\color{gray}-0.416 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$10.5 \mathrm{ms} \pm 71.8 \mathrm{μs}\left({\color{gray}-2.391 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$10.8 \mathrm{ms} \pm 65.1 \mathrm{μs}\left({\color{gray}-1.212 \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 $$11.2 \mathrm{ms} \pm 68.9 \mathrm{μs}\left({\color{gray}0.644 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.3 \mathrm{ms} \pm 71.7 \mathrm{μs}\left({\color{gray}1.08 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.0 \mathrm{ms} \pm 72.7 \mathrm{μs}\left({\color{gray}-1.440 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$11.2 \mathrm{ms} \pm 82.9 \mathrm{μs}\left({\color{gray}-2.284 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$11.2 \mathrm{ms} \pm 67.2 \mathrm{μs}\left({\color{gray}0.384 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.2 \mathrm{ms} \pm 87.4 \mathrm{μs}\left({\color{gray}1.30 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.2 \mathrm{ms} \pm 76.6 \mathrm{μs}\left({\color{gray}1.21 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.3 \mathrm{ms} \pm 68.2 \mathrm{μs}\left({\color{gray}1.33 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$11.3 \mathrm{ms} \pm 89.0 \mathrm{μs}\left({\color{gray}2.73 \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.21 \mathrm{ms} \pm 42.8 \mathrm{μs}\left({\color{gray}0.585 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$56.8 \mathrm{ms} \pm 448 \mathrm{μs}\left({\color{gray}0.085 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$110 \mathrm{ms} \pm 706 \mathrm{μs}\left({\color{gray}0.715 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$63.1 \mathrm{ms} \pm 551 \mathrm{μs}\left({\color{gray}0.256 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$72.6 \mathrm{ms} \pm 586 \mathrm{μs}\left({\color{gray}0.565 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$81.5 \mathrm{ms} \pm 667 \mathrm{μs}\left({\color{gray}0.263 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$88.0 \mathrm{ms} \pm 567 \mathrm{μs}\left({\color{gray}-0.687 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$46.4 \mathrm{ms} \pm 294 \mathrm{μs}\left({\color{gray}0.378 \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 $$74.6 \mathrm{ms} \pm 491 \mathrm{μs}\left({\color{gray}-0.350 \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 $$52.5 \mathrm{ms} \pm 322 \mathrm{μs}\left({\color{gray}-0.032 \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 $$61.5 \mathrm{ms} \pm 539 \mathrm{μs}\left({\color{gray}0.480 \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 $$64.4 \mathrm{ms} \pm 512 \mathrm{μs}\left({\color{gray}1.71 \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 $$64.1 \mathrm{ms} \pm 443 \mathrm{μs}\left({\color{gray}1.84 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$114 \mathrm{ms} \pm 901 \mathrm{μs}\left({\color{gray}-0.651 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$126 \mathrm{ms} \pm 868 \mathrm{μs}\left({\color{gray}-0.123 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$23.3 \mathrm{ms} \pm 154 \mathrm{μs}\left({\color{gray}-3.877 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$521 \mathrm{ms} \pm 1.59 \mathrm{ms}\left({\color{gray}1.59 \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 area/deps Relates to third-party dependencies (area) 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.

5 participants