OpenHands · Agent Canvas · whole-codebase architecture review

Ten seams worth fixing

A read-only pass across the entire Agent Canvas frontend found a consistent failure mode: state that belongs to one backend, one source record, or one runtime instance is represented as if it were global. The code is generally well-factored; the sharp edges appear where identity and ownership stop one layer too early.

Snapshot · 11d6b4a
Date · 2026-08-27 · Method · whole-codebase audit protocol · Result · ten issues filed as enyst

The through-line: make the owner part of the data. Backend identity belongs in caches and health checks; source records belong underneath UI ordering; unsaved edits belong in an overlay; embedded runtimes belong to their React root.

13explicit subsystems inspected, including runtime, UI, packaging, telemetry, and CI
10actionable, independently rechecked issues filed with reproduction paths and acceptance criteria
3existing issues treated as owners instead of creating duplicates
0source changes made to Agent Canvas; this was deliberately a read-only review

What the findings say together

The audit did not uncover one giant subsystem that needs a rewrite. It found small but consequential breaks in otherwise sensible boundaries. A React Query key is scoped correctly while the service cache beneath it is not. Canonical events are sorted correctly while their derived UI projection remembers arrival order. An embedded provider exposes instance-shaped dependencies while mutable globals and body portals remain document-shaped.

These are representation problems before they are implementation problems. Each proposed fix reduces the number of independently mutable truths: use scoped cache keys, derive display objects from current source data, represent edits as an overlay, and pass runtime ownership explicitly instead of consulting an ambient singleton.

The ten filed issues

target isolation

Scope the local settings cache by backend, revision, and exposure mode

A process-global cache can return backend A’s redacted—or encrypted conversation-start—settings while backend B is active. Independent keys and timestamps make the real target explicit.

#16945 · filed issue →
privacy correctness

Retry Automation telemetry consent until the backend acknowledges it

The hook records an attempt before it succeeds, so one transient failure can suppress synchronization for the rest of the session. Track in-flight and last-successful keys separately.

#16946 · filed issue →
event correctness

Make UI event projection independent of history arrival order

Older pagination can load an action after its observation and leave both visible. The sorted event log should be canonical; rebuild its UI projection when history is inserted before the live tail.

#16947 · filed issue →
form state

Preserve unsaved SDK settings edits across background refetches

Two mutable snapshots—values and dirty flags—are replaced together during hydration. A server baseline plus a local edits overlay makes refetch, save, and failure behavior unambiguous.

#16948 · filed issue →
source of truth

Keep Files and Changes source-driven after refresh

Cloud file lists sit outside the invalidated key family, while Changes ordering retains stale objects rather than stable path identities. Both views can look refreshed while presenting old data.

#16949 · filed issue →
scope identity

Scope Home launch targets and recents by backend and organization

Independent nullable fields can retain a workspace or repository after a same-kind backend switch. One discriminated, scoped launch target rules out cross-account and impossible combinations.

#16950 · filed issue →
policy ownership

Centralize the effective configuration for the next Home conversation

Readiness, display, ACP switching, and launch reconstruct policy separately and can disagree about which LLM profile will actually run. One pure resolver should own that decision.

#16951 · filed issue →
contract boundary

Use TypeScript-client event contracts instead of redeclaring the wire

Canvas maintains a parallel event model that has already drifted from the canonical client. Keep frontend refinements narrow and move presentation metadata off server event objects.

#16952 · filed issue →
embedding boundary

Make embedded runtime state and portal styling instance-scoped

React-scoped query and i18n dependencies still swap global state, and dialogs portal outside the root’s CSS/theme scope. Runtime context and a per-root portal host complete the public embedding boundary.

#16953 · filed issue →
target isolation

Scope MCP health by backend and connection revision

Identical server shapes on different backends share one global verdict even when credentials and network reachability differ. Capture backend scope when each probe starts and resolves.

#16954 · filed issue →

A practical order of work

1 · Protect boundaries

Start with #16945 and #16946. Cross-backend encrypted settings and unacknowledged privacy choices have the widest consequence even when the triggering races are narrow.

2 · Restore single sources

Then address #16947–#16951 and #16954: canonical event projection, edit overlays, source-driven workspace views, and backend-scoped Home/MCP state.

3 · Finish public boundaries

Land #16952 and #16953 in staged migrations. They pay down contract drift and make the embeddable package honestly instance-safe without forcing a flag-day rewrite.

Coverage ledger

The protocol required an explicit decision for every major area, including areas where the right answer was “no new issue.”

SubsystemDecisionResult
API, backend registry, Cloud adaptersfiled#16945: the service cache under correctly scoped React Query state was still backend-agnostic.
Conversation runtime, events, WebSocketfiled#16947: UI event projection depended on arrival order across history pages.
Local stack, ingress, packagingno filingSupervisor consolidation and universal desktop build defaults were noted, but not strong enough to become standalone defects.
Chat and conversation presentationfiled#16947 owns the action/observation and paginated transcript correctness seam.
Workspace, files, Git, browser, terminalfiled#16949 unifies file-list invalidation and removes stale mirrored GitChange objects.
Settings, profiles, providers, secrets, ACPfiled#16948 and #16951 separate edits from baselines and centralize launch-time configuration.
Skills, plugins, MCPfiled + existing#16954 covers MCP health; existing #16843 and #16893 already own plugin and skill scoping.
Automations and manifestsfiled#16946 covers consent acknowledgement; manifest ownership was intentionally left in the automation architecture.
Home, onboarding, sidebar, navigationfiled#16950 and #16951 cover target identity and the effective next-launch decision.
Telemetry and analyticsfiled#16946 is the material retry seam; a broader event-union redesign did not survive validation.
Shared UI and public library surfacefiled#16953 makes runtime dependencies and portals root-owned for embedded consumers.
Agent-facing Canvas UI bridgeno filingThe bridge ownership matched the repository boundary and did not justify a new issue.
Tests, CI, developer toolingno filingStable test QueryClient and legacy shim cleanup are useful maintenance, but below the filing threshold.

What deliberately was not filed

Duplicate backend-scope reports

Plugin queries, skill queries, and saved-workspace/directory-search ownership already had homes in #16843, #16893, and #16844. The audit linked to those owners instead of inflating the issue count.

Broad telemetry event union

The idea looked attractive in inventory, but the current typed tracking layer already centralizes the important contracts. The remaining duplication did not demonstrate a material user-facing failure.

Automation manifest ownership

Canvas and Automation have distinct responsibilities by design: Canvas edits and presents; Automation schedules and dispatches. A second “canonical manifest” in the frontend would blur that boundary.

Maintenance without a failure path

Dev supervisor consolidation, modal-action typing, a stable test client, legacy Python shim ownership, and derived health booleans remain worthwhile follow-ups—but not all good refactors deserve issues.

Method and confidence

The audit began with a repository map and ownership boundaries, then traced data from its origin through caches, stores, adapters, hooks, and presentation. Candidate findings were checked against tests, current issues, and intentional cross-repository ownership. Every filed issue includes a concrete failure path, a proposed representation, acceptance criteria, migration risks, and the audited commit.

No Agent Canvas source files were changed and no corrective patch was mixed into the review. That separation is useful: the issue set records what should change without letting an implementation choice retroactively define the problem.