Skip to content

FE-1766: Make the chat transport break loudly when Flue or the AI SDK drifts - #9854

Merged
lunelson merged 35 commits into
mainfrom
ln/fe-1766-ai-sdk-transport-compat
Oct 12, 2026
Merged

lunelson merged 35 commits into
mainfrom
ln/fe-1766-ai-sdk-transport-compat

Conversation

@lunelson

@lunelson lunelson commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Brunch's chat panel talks to its Flue agent through a hand-written adapter that turns Flue's conversation stream and stored history into the AI SDK's chat messages. That adapter restated both libraries' behaviour in its own terms, with tests that compared its output to hand-written expectations, so a change in either Flue or the AI SDK could have kept compiling and passing while the panel quietly showed the wrong thing. It also mixed Brunch and Petrinaut conventions into what should be product-neutral code, including a stopped marker written into message metadata that agents can also write to.

This PR makes that adapter break loudly when either side drifts. It becomes the unpublished, product-neutral package @local/flue-aisdk-transport, tested against the AI SDK's own reducer, schema and chat state machine at the exact locked version, and against a real in-process Flue runtime whose stored history must reopen to the same message the live stream produced. Its types now bind to the upstream owners, and hosts declare their message metadata through the AI SDK's own schema type: the Petrinaut website's Brunch adapter validates metadata against Petrinaut's message type, and Brunch's stop marker lives in Brunch. The suites surfaced and this PR fixes two bugs: agent-authored response metadata was dropped, and a live tool subscriber could be disconnected by its first event. One behaviour remains an open decision, described under known issues.

🔗 Related links

🚫 Blocked by

Nothing. The branch is based on main; the persona CLI (#9833) no longer sits underneath it, and the two do not depend on each other.

🔍 What does this change?

  • Moves the transport and both halves of the live tool-input channel into @local/flue-aisdk-transport, deletes @hashintel/brunch-agent-transport-aisdk, and moves Brunch's browser-tool envelope, contextual message framing and conversation identity into core's browser-safe slices (4bd1516).
  • Checks every chunk sequence the unit tests produce against the AI SDK's wire schema, reducer and validateUIMessages, and adds a parity suite on a real Flue runtime (cbf6afa).
  • Carries Flue's agent-authored response metadata into live and reopened messages (d0eef82).
  • Keeps a live tool subscriber that joins after a full retained window (5e657ac). The package move exposed this; the old route test only passed because of an extra event-loop turn.
  • Lets only Flue's stream decide that a live tool proposal was not executed (24d4ce3). Before, the live channel ending, or a timer started when it reported the turn over, could mark a call "not executed" that Flue's stream then delivered. Bugbot found the first case on FE-1885: Keep chat turns streaming after Flue's SDK gives up on a reconnect #9963, and an inductive pass found the two timers.
  • Reproduces cases from the AI SDK's reducer and chat tests as real Flue turns, including the live channel and AbstractChat (1326bad), and labels each with its upstream source and as a guarantee, observed behaviour or decision (49db29b).
  • Replaces the loose transport options with createFlueAiSdkAdapter, one host configuration shared by the live transport and reopened history, with a required metadata schema for narrowed message types. Brunch's stopped projection moves to core, and the Petrinaut website builds one adapter for its panel and history (97ee2e5). Reopened history is typed as the adapter's message type, which removes the website's cast (cfef85d).
  • Binds the transport's types to Flue and the AI SDK so upstream renames fail to compile (5396ba4).
  • Keeps Petrinaut's packages free of agent-host concerns: the runtime metadata schema briefly added to petrinaut-core (56fdd78) is reverted, and the website's Brunch adapter owns it, checked at compile time against PetrinautAiMessageMetadata (a631f3d). A website test proves Brunch's stop marker and Petrinaut's schema compose, live and on reopen (b42bdcf).
  • Exports petrinautDiagnosticsContextMessageId from petrinaut-core, so the website recognises Petrinaut's diagnostics message without restating its id in two places (1eada80).
  • Makes the live route an exported constant, neutralises the package's test fixtures, moves the shared Oxlint base out of the Brunch folder (ad28fd7), keeps test support under test/ (065fc3f), moves the metadata projection contract out of a one-file shared/ directory (fb276a8), and lints the client and server entries against importing each other (62bc68c).
  • Lets all Brunch packages depend on unpublished @local packages while they are private (43550d5).
  • Removes repeated tests and unused exports (7e887f0, 2bc0f64), and documents the host contract and compatibility decisions (cbff748).

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ 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

⚠️ Known issues

With the live tool-input channel on, as it is in the Petrinaut panel, text the model writes just before a tool call can render below that tool's card while streaming, then move above it after a reload. The live channel opens the tool part before Flue delivers the earlier text, and the AI SDK reducer cannot reorder parts, so fixing it means trading away some of the channel's early display. This is an open decision; a unit test pins the current behaviour and the parity suite compares only the set of parts in that case.

Reopened messages have no step boundaries, because Flue's stored history does not keep them. Reopened tool parts take the host's tool types without a runtime check, as the AI SDK does for streamed tool chunks; only metadata has a schema. Petrinaut's metadata schema drops keys it does not render rather than rejecting them.

🐾 Next steps

  • Decide the live-channel ordering behaviour above.
  • Replace the copies of the brunchBrowserResult envelope check across apps/brunch-agent and apps/petrinaut-website, and its construction in plugin-sdcpn, with core's browserToolOutput.
  • Remove the stale MISSION.md reference in apps/brunch-agent/AGENTS.md.

🛡 What tests cover this?

  • libs/@local/flue-aisdk-transport: unit tests for the transport, projections and live channel, all reduced through the AI SDK, plus the in-process Flue suites in test/ (upstream reducer cases, AbstractChat cases, live/reopen parity, the metadata contract). Every real Flue turn asserts that the live message equals the reopened one.
  • Brunch core tests for the moved helpers and the message-metadata projection.
  • A website test running the Brunch adapter live and on reopen through Petrinaut's metadata schema, plus updated website and Brunch app unit and integration tests.

❓ How to test this?

  1. Check out the branch and run yarn install.
  2. Run yarn workspace @local/flue-aisdk-transport test:unit.
  3. Run npx turbo run build test:unit lint:tsc lint:eslint --filter @local/flue-aisdk-transport --filter @hashintel/brunch-agent --filter @hashintel/brunch-agent-plugin-sdcpn --filter @apps/brunch-agent --filter @apps/petrinaut-website --filter @hashintel/petrinaut --filter @hashintel/petrinaut-core.
  4. Build @apps/brunch-agent and run test:integration in apps/brunch-agent and apps/petrinaut-website.

Execution record

Compatibility baseline. ai@6.0.286 (vercel/ai 38f42fce), @flue/sdk and @flue/runtime 2.0.3. The decisions table in libs/@local/flue-aisdk-transport/README.md is the single record of decisions, observed behaviours and accepted gaps; re-derive any case whose upstream source changes when either dependency moves. Flue documents ConversationStreamChunk as a first-party presenter surface rather than semver-covered API, so the exhaustive switches over it plus the exact version pin are the protection.

Metadata contract. metadataSchema takes the Standard Schema member of the AI SDK's FlexibleSchema, because projection runs synchronously and the AI SDK's own schema wrapper validates asynchronously. A narrowed message type without a schema fails to compile (covered by a @ts-expect-error test). The projection returns raw values and the schema is the authority for the type.

Verification, on the branch rebased onto main (404cc96e9b), after FE-1763 merged:

  • turbo run build test:unit lint:tsc lint:eslint for the seven touched packages: 60 of 60 tasks pass. One earlier run, before the rebase, had a single @apps/brunch-agent unit failure while all 60 tasks ran in parallel; it passed on every rerun, alone and under Turbo.
  • @local/flue-aisdk-transport: 82 tests.
  • apps/petrinaut-website integration: 5 of 5. apps/brunch-agent integration: 21 of 21.
  • oxfmt --check clean on every changed file.
  • After 24d4ce3 and fb276a8: 86 package tests, lint:tsc and oxfmt --check clean, and apps/petrinaut-website integration 5 of 5.

Review. Two independent reviews ran: one on standards, spec, subtraction and type ownership before 56fdd78, and a structural review of authority and ownership at cbff748. Each finding was fixed in a later commit or is recorded above as a known issue or next step.

@vercel

vercel Bot commented Sep 28, 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 Oct 12, 2026 10:47am UTC
petrinaut Ready Ready Preview Oct 12, 2026 10:47am UTC
petrinaut-docs Ready Ready Preview Oct 12, 2026 10:47am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Oct 12, 2026 10:47am 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 type/eng > backend Owned by the @backend team area/tests New or updated tests area/apps type/legal Owned by the @legal team labels Sep 28, 2026
@lunelson
lunelson force-pushed the ln/fe-1766-ai-sdk-transport-compat branch from feba010 to d8c5b9f Compare September 28, 2026 16:24
@lunelson
lunelson deployed to pull-request September 28, 2026 16:27 — with GitHub Actions Active
@lunelson
lunelson deployed to pull-request September 28, 2026 16:27 — with GitHub Actions Active
@codspeed

codspeed Bot commented Sep 28, 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-1766-ai-sdk-transport-compat (1135def) with main (8c14928)

Open in CodSpeed

lunelson and others added 21 commits October 12, 2026 12:26
This reverts 8c97f37. Petrinaut's packages should not carry agent-host concerns, and the runtime metadata schema existed only because Brunch feeds agent metadata into the panel. Petrinaut keeps its PetrinautAiMessageMetadata type; the website's Brunch adapter now owns the schema, checked at compile time to produce that type and to cover every one of its keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
Every Brunch package is private and their publishing status is unsettled, so the ban that kept them independent of @Local packages protects nothing yet. Lifting it lets a Brunch front end use @local/flue-aisdk-transport.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ting each other

Co-authored-by: Cursor <cursoragent@cursor.com>
…remaining decisions

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… SDK transport

Co-authored-by: Cursor <cursoragent@cursor.com>
…ugin

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…o the SDCPN plugin

Co-authored-by: Cursor <cursoragent@cursor.com>
…the README

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…rt them

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…files

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
kube
kube previously approved these changes Oct 12, 2026
TimDiekmann
TimDiekmann previously approved these changes Oct 12, 2026
hashdotai
hashdotai previously approved these changes Oct 12, 2026
@hash-release hash-release Bot mentioned this pull request Oct 16, 2026

This branch was successfully deployed

5 active (2 outdated) deployments
Preview – hash — decf440d Deployed Oct 12, 2026 by vercel[bot]
Preview – petrinaut-docs — decf440d Deployed Oct 12, 2026 by vercel[bot]
Preview – petrinaut — decf440d Deployed Oct 12, 2026 by vercel[bot]
Preview – hashdotdesign-tokens — 0b662a3b Deployed Oct 2, 2026 by vercel[bot]
pull-request — 1135def8 Deployed Oct 2, 2026 by lunelson via Sourcemaps (@apps/hash-ai-worker-ts) #37966
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 > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team type/legal Owned by the @legal team

Development

Successfully merging this pull request may close these issues.

8 participants