FE-1437: Move brunch-agent into hashintel/hash with its history - #9274
FE-1437: Move brunch-agent into hashintel/hash with its history#9274lunelson wants to merge 65 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9274 +/- ##
==========================================
+ Coverage 59.71% 60.10% +0.39%
==========================================
Files 1423 1314 -109
Lines 138862 132988 -5874
Branches 6572 6190 -382
==========================================
- Hits 82915 79933 -2982
+ Misses 54881 52034 -2847
+ Partials 1066 1021 -45 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
b898a9d to
65c318b
Compare
PR SummaryHigh Risk Overview The app mounts a Gherkin elicitor, durable SQLite conversations, a local capture store, and Petrinaut gains a host-owned interactive-tool registry (patch changeset). The local-storage demo registers an inline Reviewed by Cursor Bugbot for commit 27fa5c5. Bugbot is set up for automated code reviews on this repo. Configure here. |
Expose the long-running fixture through the task graph so local Petrinaut verification follows HASH tooling conventions. Co-authored-by: Cursor <cursoragent@cursor.com>
Expose host interactive tools and align Brunch's AI SDK continuation protocol with real browser payloads. Remove the unrelated Actual-mode fixture wiring from this import. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep host extensibility at an opaque registry boundary while deriving Brunch wire types from their runtime schemas and separating Petrinaut decisions from persisted results. Co-authored-by: Cursor <cursoragent@cursor.com>
Bring the context's control surfaces in line with the executed FE-1437 import: - COORDINATION.md: rewrite the sequencing recommendation as of 2026-08-21 (cutover executed on the import branch, hashintel/hash authoritative) and flip the handoff threshold to past tense. - SPEC-LEDGER.md, topology.md, flue-architecture-cheatsheet.md: repair pre-import tense and vocabulary (apps/dev -> apps/brunch-agent, "For brunch-lite" -> "For Brunch", bun-era phrasing). - issue-tracker.md: correct the elicitation-kernel archive path (docs/history/planning/) and name the ds-wayfind skill. - domain.md: point at the ds-domain-modeling skill actually installed. - AGENTS.md: drop the duplicated protocol list. - apps/brunch-agent: remove a stray await on the synchronous agent.refresh() (the one eslint warning) and make CLAUDE.md a symlink to AGENTS.md, matching the context root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The context's PR guidance previously prescribed a free-form two-layer body, which conflicts with .github/pull_request_template.md. PRs now fill the template: the purpose section carries the one-or-two-sentence scan layer, and the what-does-this-change section carries a plain description followed by the collapsed agent-notes record. Linear issue guidance is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The imported agent docs mandated gh stack and prohibited gt — a late standalone-repo convention wrongly generalized to hashintel/hash. HASH's de facto convention is Graphite (the CI optimizer runs on it, and this clone carries live gt metadata), and gh stack proved buggy in practice, so the rule now points the other way: gt for stack operations, plain git for local reads and commits, gh stack not used here. The historical re-authoring instruction in the import plan is left as a record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two disjoint fixes for the per-package CI jobs, which run in a turbo-pruned tree: - prune-repository now adds @apps/brunch-agent whenever any Brunch library is in scope, so the boundary suite can scan the whole package family the way it does in a full checkout. - The three suites that read the Brunch context root (docs/, scripts/, AGENTS.md) skip when it is absent: turbo prune only copies workspace directories, and the context root belongs to no workspace. The linear-project-graph test now loads its module through a computed specifier so lint:tsc also passes in the pruned tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
data/linear-proposals-FE-1357.json tripped the preflight scan that blocks merging any pull request whose title names a ticket still referenced beside a task marker, for every ticket from FE-1437 through FE-1441. The snapshot's bytes remain in git history; the validator now carries the 29 removed proposals' identifiers and applied titles, so subtree coverage and the GitHub PR title checks still hold, and its expectations are recomputed for the 38 live proposals. Also repins the two raw source snapshots' frozen hashes: the monorepo formatter pretty-printed them during assimilation (parse-identical, and verified against the capture-time bytes), which had silently broken the validator. The frozen provenance manifest keeps the capture-time hashes in its generatedFrom block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adding @earendil-works/pi-ai (which pins @anthropic-ai/sdk@0.91.1) let yarn fold @anthropic-ai/bedrock-sdk's >=0.50.3 <1 range onto 0.91.1, splitting hash-ai-worker-ts across two incompatible SDK type trees and breaking its build. The resolution keeps bedrock-sdk on the 0.74.0 the worker already pins. Follow-up filed to evaluate the cost of carrying this pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
turbo prune copies workspace directories only, so the Brunch context root's docs/, scripts/, and agent contract files never reached the pruned tree, and the architecture suites that read them (docs index, baseline runner, Linear project graph) skipped in CI — leaving those invariants a local-only gate. A new EXTRA_PATHS table in the prune-repository action copies declared non-workspace paths into out/ after pruning, triggered by the same prefix matching as EXTRA_DEPENDENCIES. The skipIf guards in the tests stay as a fallback for a pruned tree where the copy rule is absent or has drifted. Reviewers may reasonably push back on adding a copy-arbitrary-paths mechanism to a shared CI action for one package family's benefit. If so, reverting this commit alone is safe: the guards make the affected suites skip in CI again rather than fail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The docs tree under libs/@hashintel/brunch-agent/docs is an imported record: it holds hash-pinned frozen data snapshots and prose whose bytes are referenced by the migration validator, so the monorepo formatter must not rewrite it. oxfmt's check was failing on one audit doc's list indentation; rather than reformat the record, ignore the tree in both oxfmt and markdownlint-cli2 and keep the imported bytes as they are. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The license-in-workspaces check requires a LICENSE.md in every Yarn workspace. The five imported Brunch workspaces declare AGPL-3.0 in their manifests but shipped no license file; each now carries the byte-identical AGPL-3.0 text the repo's other AGPL workspaces use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2bbb360 to
27fa5c5
Compare
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | 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
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |

🌟 What is the purpose of this PR?
Brunch — the elicitation-agent harness developed to date in the standalone
brunch-literepository — moves intohashintel/hash, which becomes its sole writable home. This branch imports the standalone history, renames the package family to@hashintel/brunch-agent*underlibs/@hashintel/brunch-agent/(application atapps/brunch-agent), and adopts the HASH toolchain (Yarn/Turbo, Biome, Vitest) in place of Bun.🔗 Related links
libs/@hashintel/brunch-agent/docs/planning/_shared/hash-monorepo-import-plan.mdlibs/@hashintel/brunch-agent/docs/adr/0004-in-petrinaut-staging-and-the-monorepo-import.md43a0022918861846344b96a32cb94f92e2ee96ae(read-only reference)🚫 Blocked by
Nothing — the import gates (FE-1434/FE-1435 spike verdicts, standalone review stack) all landed before the cutover.
🔍 What does this change?
Adds the Brunch package family as five new workspaces: the substrate-independent harness (
@hashintel/brunch-agent), the Flue binding, the AI SDK transport, the Gherkin target plugin, and the application atapps/brunch-agent. It also lands the first slice of the Petrinaut website integration — a generic interactive-tool extension point in@hashintel/petrinaut(with changeset) and an app-level Brunch ask tool inapps/petrinaut-website— which the trimmed FE-1440 builds on. Boundary rules survive as enforced tests: plugins depend only on the harness, transports never on a binding,@hashintel/petrinautstays Brunch-agnostic, and applications are the only Brunch–Petrinaut meeting point.🏗️ Agent notes
Final physical shape
The existing root workspace globs,
apps/**andlibs/**, already discover these nested packages. No new root workspace rule or context-rootpackage.jsonwas needed.Package identities and dependency graph
The standalone names became HASH-native:
@brunch/core→@hashintel/brunch-agent@brunch/binding-flue→@hashintel/brunch-agent-binding-flue@brunch/transport-aisdk→@hashintel/brunch-agent-transport-aisdk@brunch/plugin-gherkin→@hashintel/brunch-agent-plugin-gherkin@brunch/dev→@apps/brunch-agentThe planned invariants remain:
apps/brunch-agentremains independent of Petrinaut packages;apps/petrinaut-websiteis the Brunch/Petrinaut meeting point.All packages are private, use
0.0.0-private, carry the HASH license, useworkspace:*internally, and have versions reconciled through the HASH lockfile and constraints.Toolchain rewiring
The largest incidental adaptation was removing the standalone Bun toolchain.
bun:test→ Vitest.Bun.spawn→ a shared Node child-process helper.Bun.file→ NodefsAPIs.Bun.spawnSync→ Nodechild_process.Each workspace now exposes HASH-shaped tasks:
buildlint:eslintlint:tsctest:unitdevtasks where appropriateTypeScript configuration was split per package, using strict bundler resolution,
tsgo, explicit Node/DOM types, and explicit.tsextensions where Node executes source directly.Build reshaping
Each library became an independently built Vite ESM library:
client-tools,storage, andtestingentry points.client-toolsentry point.An attempted declaration-bundling setup exposed incompatibilities with Brunch's Valibot value/type same-name pattern. The resulting private-package contract is therefore deliberately:
That preserved type ownership without forcing changes to the domain schemas merely to satisfy a declaration bundler.
The app also gained a dedicated
vitest.config.ts: loading its full Vite/Flue configuration under Vitest caused the Flue build plugin to execute in an incompatible SSR context.Test ownership reshaping
The old repository-level tests were distributed according to what they govern:
core/test/architecture/.apps/brunch-agent/test/.The architecture scanner was rewritten for the new topology. It discovers:
libs/@hashintel/brunch-agent/packages/;apps/brunch-agentworkspace.Its dependency proof also changed. Bun's isolated installation allowed tests to assert that forbidden packages physically could not resolve. HASH's Yarn linker may hoist dependencies, so that would have become a false proof. The replacement checks:
Documentation and agent-context reshaping
The nested directory became a true domain context:
CONTEXT.mdremains the canonical Brunch glossary.AGENTS.mdandCLAUDE.mdwere rewritten for operation inside HASH.docs/spec.mdremains authoritative.docs/history/.Import-process adaptations
The Git history was imported in four deliberately separate stages:
That unusual order was discovered during execution:
Post-review follow-ups
The review follow-ups:
turbo.jsonfiles so build outputs and dependency builds are explicit;.flue/,.data-wipe-me/, and documentation drafts;Petrinaut integration
The imported transport is wired into the Petrinaut demo without coupling the Brunch application to Petrinaut:
brunch_askwidget.tool-brunch_askpayload shape.Type ownership
The host-tool extension preserves source-of-truth typing:
unknownvalues are confined to genuine runtime boundaries such as raw AI SDK dynamic-tool data.Post-import branch state
git log --follow/blame continuity into pre-import commits.gh stackto Graphite (matching HASH's repo-wide Graphite use).listen EPERM), breaking@hashintel/ds-components#codegenlocally; no branch commit touches ds-components.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
@hashintel/petrinautgains the interactive-tool extension point (patch changeset); the imported Brunch packages are all private.📜 Does this require a change to the docs?
The changes in this PR:
Brunch context, architecture, planning, and operating documentation were updated for HASH, and the Petrinaut AI assistant guide documents host-specific interactive widgets.
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
turbo.jsonfiles have been updated to reflect thismain; the full standalone history remains preserved on this branch and in the PR record, and the standalone repository stays available read-only at the frozen SHA.🐾 Next steps
apps/petrinaut-website— mode switch, browser identifier bootstrap, remote transport swap🛡 What tests cover this?
The final focused verification completed 23/23 Turbo tasks. Brunch core completed 114/114 tests.
❓ How to test this?
Run:
Run:
Start the Brunch agent and Petrinaut website development servers.
Open the Petrinaut local-storage demo and send an elicitation prompt.
Confirm the inline Brunch question renders, accepts an answer, and continues the same assistant message without errors or duplication.
📹 Demo
The local browser flow has been manually verified. A refreshed screenshot should be added before merge.
🤖 Generated with Claude Code