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.”
| Subsystem | Decision | Result |
| API, backend registry, Cloud adapters | filed | #16945: the service cache under correctly scoped React Query state was still backend-agnostic. |
| Conversation runtime, events, WebSocket | filed | #16947: UI event projection depended on arrival order across history pages. |
| Local stack, ingress, packaging | no filing | Supervisor consolidation and universal desktop build defaults were noted, but not strong enough to become standalone defects. |
| Chat and conversation presentation | filed | #16947 owns the action/observation and paginated transcript correctness seam. |
| Workspace, files, Git, browser, terminal | filed | #16949 unifies file-list invalidation and removes stale mirrored GitChange objects. |
| Settings, profiles, providers, secrets, ACP | filed | #16948 and #16951 separate edits from baselines and centralize launch-time configuration. |
| Skills, plugins, MCP | filed + existing | #16954 covers MCP health; existing #16843 and #16893 already own plugin and skill scoping. |
| Automations and manifests | filed | #16946 covers consent acknowledgement; manifest ownership was intentionally left in the automation architecture. |
| Home, onboarding, sidebar, navigation | filed | #16950 and #16951 cover target identity and the effective next-launch decision. |
| Telemetry and analytics | filed | #16946 is the material retry seam; a broader event-union redesign did not survive validation. |
| Shared UI and public library surface | filed | #16953 makes runtime dependencies and portals root-owned for embedded consumers. |
| Agent-facing Canvas UI bridge | no filing | The bridge ownership matched the repository boundary and did not justify a new issue. |
| Tests, CI, developer tooling | no filing | Stable 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.