Fix Electric Agents quickstart runner registration#4610
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4610 +/- ##
==========================================
+ Coverage 59.00% 59.38% +0.38%
==========================================
Files 385 385
Lines 42642 42928 +286
Branches 12205 12355 +150
==========================================
+ Hits 25160 25493 +333
+ Misses 17407 17359 -48
- Partials 75 76 +1
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:
|
Electric Agents Mobile BuildLocal mobile checks ran for commit The EAS Android preview build was skipped because the |
5d35ef3 to
f3b6804
Compare
f3b6804 to
f13d3b3
Compare
samwillis
left a comment
There was a problem hiding this comment.
Summary
Interactive review with GPT-5.5.
I reviewed PR #4610 in an isolated worktree against origin/main, focusing on the quickstart runner registration changes, principal/header handling, the electric-ax observe live timeline rewrite, and the related tests.
Finding
- P2:
electric-ax observenow readsentityStoppedviadb.collections.entityStopped.toArray.lengthoutside a live query. If the stop event is the only new data, React/Ink may not rerender, so the observe UI can keep accepting input and fail to show the stream-closed state.
Validation
- PR CI is green, including
Test packages/electric-ax,Test packages/agents-runtime, andTest packages/agents. - I attempted targeted local
electric-axtests in the review worktree, but the local shell is on Nodev18.20.5while the repo requires>=20.19.0 || >=22.12.0.
| ) | ||
|
|
||
| const closed = (stopped as Array<EntityStopped>).length > 0 | ||
| const closed = db.collections.entityStopped.toArray.length > 0 |
There was a problem hiding this comment.
This should probably stay subscribed like the previous implementation. Since closed is derived from a direct toArray read, a stop event by itself will not trigger a React/Ink rerender, so observe can keep showing the input and miss the "Stream closed" state until some other live timeline row changes. A small useLiveQuery((q) => q.from({ entityStopped: db.collections.entityStopped })) here would preserve the old behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
samwillis
left a comment
There was a problem hiding this comment.
Looks good. The observe stopped-state regression is covered, electric-ax checks are green, and the remaining Codecov uploader issue cleared on rerun.
Executive Summary
Fixes the Electric Agents quickstart path so the printed CLI commands can spawn, route, send to, and observe Horton entities without extra principal or dispatch-policy setup. It also prevents newly-started agent runs from being left in
startedwhen a wake handler errors before ending them. The user-visible result is thatelectric agents quickstartcreates a usable local runner-backed environment,electric agents observeuses the same shared timeline live query as the other UIs, interactive terminal sends remain visible while they reconcile with the entity stream, and runtime failures no longer leave the chat UI stuck on "Thinking" for that failure path.Root Cause
There were several related quickstart gaps:
electric agents spawn /horton/onboardingcould create an entity that no pull-wake runner would claim.invalid onError methodduring type registration.electric agents observeand the shared UI timeline query were resolving different@tanstack/dbpackage instances. TanStack validates live-query sources with package-localCollectionImplidentity, so valid StreamDB collections were rejected as non-collections.msg-0:10, lexicographic ordering placed that chunk beforemsg-0:2, which only surfaced in consumers reading the live query path.observesends posted successfully but did not await the returned streamtxidor send a stable client key, so the optimistic inbox row could disappear before the real stream event reconciled.createEntityStreamDBwrapper used by the web UI. That skipped runtime row-offset materialization (_timeline_order), so the shared timeline query fell back to source tie-breakers and grouped inbox rows ahead of run rows.electricax/agents-server:latest, so a released CLI could start a server image with mismatched runtime contracts.status: startedand then throw before the run was ended. The existing handler failure path wrote an error event, but did not terminally update that run, so the UI continued to treat it as the latest streaming response.Approach
user:<ELECTRIC_AGENTS_IDENTITY>so the no-env quickstart commands match the built-inprincipal_kind=usergrants.electric-axwith the same TypeScript/TanStack DB peer set asagents-runtime, so both packages resolve the same@tanstack/dbinstance.observetocreateEntityTimelineQuery(db)plususeLiveQuery, then adapt the shared query rows into the existing Ink terminal sections._seqin the shared live query, matching the materialized timeline view._seq, text items by first text-delta_seqwith text-row_seqas fallback.txidis observed, using a stable client key and pending timeline order so the optimistic row and synced inbox row reconcile._timeline_orderfor shared timeline row ordering.@electric-ax/agents-serverpackage version into the CLI Docker image tag at build time, and makeci:publishrebuild packages after Changesets updates versions.started, with a produced-event fallback for runs that were emitted but not yet visible through the local collection.Key Invariants
ELECTRIC_AGENTS_PRINCIPALremains the explicit override for non-default deployments.electric-ax,agents-runtime, and UI consumers must resolve a single TanStack DB module instance for shared live-query collection identity checks.createEntityTimelineQuery(db); only the rendering adapter should differ._timeline_orderare materialized consistently.electric-axbuilds should start an agents-server image matching the release version unless explicitly overridden.started; a completed run must not be regressed to failed if later cleanup throws.Non-goals
Trade-offs
The CLI observer now shares the same timeline query as the desktop/web UIs, which keeps query semantics in one place and prevents the terminal observer from drifting. The terminal still has a small adapter because its Ink renderer expects simpler records than the desktop timeline component.
The timeline query now creates a few
BasicIndexindexes before building joins. That is a small setup cost per StreamDB instance, but it avoids repeated TanStack warnings and keeps the shared query suitable for CLI and UI use.The Docker image pin is injected at build time rather than hard-coded because Changesets owns release version updates. Rebuilding during
ci:publishensures the generated artifacts see the bumped package versions.Verification
Manual isolated quickstart verification:
cd /tmp ELECTRIC_AGENTS_COMPOSE_PROJECT=electric-agents-verify \ ELECTRIC_AGENTS_PORT=4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents quickstartIn a second terminal:
ELECTRIC_AGENTS_URL=http://localhost:4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents types ELECTRIC_AGENTS_URL=http://localhost:4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents spawn /horton/onboarding ELECTRIC_AGENTS_URL=http://localhost:4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents inspect /horton/onboarding ELECTRIC_AGENTS_URL=http://localhost:4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents send /horton/onboarding "Onboard me to Electric Agents" ELECTRIC_AGENTS_URL=http://localhost:4448 \ node /Users/kylemathews/programs/electric/packages/electric-ax/dist/index.js agents observe /horton/onboardingObserved results:
builtin-agentsstarted.agents typeslisted built-ins without a manual principal override.agents spawn /horton/onboardingsucceeded.agents inspect /horton/onboardingshoweddispatch_policy.targets[0].runnerId = builtin-agents.agents send /horton/onboarding "Onboard me to Electric Agents"was picked up by the runner.agents observe /horton/onboardingrendered the timeline through the shared live query instead of throwingInvalid source for live query, with no TanStack join-index warning._seqordering replaced key ordering for live deltas.Files Changed
.changeset/quickstart-runner-principal.md: patch release note for affected packages..changeset/fail-started-agent-runs.md: patch release note for the started-run handler failure fix.package.json: rebuild packages before publish/tag so injected version constants are current after Changesets versioning.packages/agents-runtime/src/create-handler.ts: include registration endpoint and fetch cause in entity type registration failures.packages/agents-runtime/src/process-wake.ts,packages/agents-runtime/test/process-wake.test.ts: fail newly-started runs when handler errors escape before run completion, without regressing already-completed runs.packages/agents-runtime/src/entity-timeline.ts: create shared timeline join indexes, keep CLI/UI consumers on the same live query, and order live deltas by stream sequence.packages/agents-runtime/test/create-handler.test.ts,packages/agents-runtime/test/entity-timeline.test.ts: regression coverage for fetch detail, live delta ordering, top-level live timeline row interleaving, and live run child item ordering.packages/agents/src/server.ts,packages/agents/test/builtin-pull-wake-registration.test.ts: default built-in type dispatch to the local runner and cover it.packages/electric-ax/package.json,pnpm-lock.yaml: alignelectric-axwith the same TanStack DB/TypeScript peer instance asagents-runtime.packages/electric-ax/src/start.ts,packages/electric-ax/src/index.ts: align quickstart runner owner/server headers and default CLI principal behavior.packages/electric-ax/src/entity-stream-db.ts,packages/electric-ax/test/entity-stream-db.test.ts: delegate CLI stream setup to the agents-runtime helper so timeline ordering matches the web UI.packages/electric-ax/src/observe-ui.tsx,packages/electric-ax/test/observe-ui.test.tsx: render observe from the shared live query, fix terminal wrapping, send interactive messages with stable optimistic keys, and await send txids.packages/electric-ax/src/version.ts,packages/electric-ax/tsdown*.config.ts: inject the matching agents-server Docker image tag into release builds.packages/electric-ax/test/cli.test.ts,packages/electric-ax/test/start.test.ts: coverage for principal/default header behavior.packages/agents-server-ui/src/components/settings/pages/CredentialsPage.tsx: clarify browser-only provider credentials copy.