Explore phase on main

OpenHands Architecture Deepening Review

Generated from main using the Claude improve-codebase-architecture skill and its /codebase-design vocabulary: module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, and locality.

Context Reviewed

No CONTEXT.md or ADR files were found in this checkout. The review uses repository instructions and code structure as the domain source.

Git State

Branch: main. Source tree clean except pre-existing untracked .claude/ and .worktrees/.

Primary Signal

Several modules have shallow interfaces that make callers and tests carry broad implementation knowledge.

Evidence Snapshot

2,614

lines in live_status_app_conversation_service.py

1,001

lines in conversation-websocket-context.tsx

62

frontend files outside query hooks import #/api

247

lines in the settings route loader module

Candidate 1

Deepen the conversation event stream module

Recommendation strength: Strong
Files
frontend/src/contexts/conversation-websocket-context.tsx, frontend/src/hooks/mutation/conversation-mutation-utils.ts, conversation stores, terminal store, browser store, metrics store.
Problem
The WebSocket provider has a shallow interface: connection state, event parsing, cache mutation, terminal output, browser observations, optimistic message cleanup, planning-agent handling, and error banners are in one implementation.
Solution
Create a deep in-process module that owns event interpretation and hides store/cache implementation behind adapters. The provider becomes a small transport adapter.
Benefits/Wins
Higher locality for event behavior, better leverage from one test surface, and a real seam for replaying observations without mounting the whole provider.

Before

flowchart TD
  WS["WebSocket provider"]
  WS --> Conn["connection state"]
  WS --> Parse["JSON parsing"]
  WS --> Chat["event store"]
  WS --> Cache["query cache"]
  WS --> Term["terminal store"]
  WS --> Browser["browser store"]
  WS --> Metrics["metrics store"]
  WS --> Errors["error banners"]
              

After

flowchart TD
  WS["transport adapter"] --> Stream["deep event stream module"]
  Stream --> Model["event interpretation"]
  Stream --> Effects["effect plan"]
  Effects --> CacheA["cache adapter"]
  Effects --> StoreA["store adapter"]
  Effects --> ObsA["observation adapters"]
  Tests["replay tests"] --> Stream
              

Candidate 2

Deepen backend conversation startup

Recommendation strength: Strong
Files
openhands/app_server/app_conversation/live_status_app_conversation_service.py, openhands/app_server/app_conversation/app_conversation_router.py, sandbox spec, secret lookup, agent-server request construction, pending messages.
Problem
Conversation startup mixes user identity, parent inheritance, sandbox readiness, secret assembly, model and tool selection, plugin messages, SDK options, transport, persistence, processors, and pending messages in one large implementation.
Solution
Make startup a deep module that accepts current conversation facts and hides request-building implementation. Sandbox, agent-server, and secret access become adapters at the edge.
Benefits/Wins
Better locality for startup rules, more leverage in tests around request shape, and a clearer seam between policy and transport.

Before

flowchart TD
  Start["live status conversation implementation"]
  Start --> User["user and org facts"]
  Start --> Parent["parent inheritance"]
  Start --> Sandbox["sandbox lifecycle"]
  Start --> Secrets["secrets"]
  Start --> LLM["model config"]
  Start --> Tools["tools and plugins"]
  Start --> Agent["agent-server transport"]
  Start --> Store["persistence"]
              

After

flowchart TD
  App["conversation orchestration adapter"] --> Startup["deep startup module"]
  Startup --> Rules["startup rules"]
  Startup --> Request["request model"]
  Startup --> Result["startup result"]
  Startup --> SandboxA["sandbox adapter"]
  Startup --> SecretA["secret adapter"]
  Startup --> AgentA["agent-server adapter"]
  Tests["startup tests"] --> Startup
              

Candidate 3

Deepen conversation cache mutation

Recommendation strength: Worth exploring
Files
frontend/src/hooks/mutation/conversation-mutation-utils.ts, use-v1-resume-conversation.ts, use-v1-pause-conversation.ts, use-unified-start-conversation.ts, use-unified-stop-conversation.ts.
Problem
Query key knowledge, optimistic updates, rollback values, sandbox invalidation, and VS Code URL invalidation are spread through several mutation hooks.
Solution
Move lifecycle cache behavior into one deep module so mutation hooks express intent and cache implementation remains local.
Benefits/Wins
Lower duplication, fewer missed invalidations, and better leverage from small tests over lifecycle transitions.

Before

flowchart TD
  Pause["pause hook"] --> Keys["query keys"]
  Resume["resume hook"] --> Keys
  Start["start hook"] --> Keys
  Stop["stop hook"] --> Keys
  Keys --> Cache["TanStack Query cache"]
  Pause --> Rollback["rollback copies"]
  Resume --> Rollback
              

After

flowchart TD
  Hooks["mutation hooks"] --> Intent["lifecycle intent"]
  Intent --> CacheModule["deep cache mutation module"]
  CacheModule --> Keys["query key implementation"]
  CacheModule --> Optimistic["optimistic change"]
  CacheModule --> Rollback["rollback value"]
  CacheModule --> Invalidate["invalidation set"]
              

Candidate 4

Deepen settings route policy

Recommendation strength: Worth exploring
Files
frontend/src/routes/settings.tsx, frontend/src/utils/settings-utils.ts, frontend/src/hooks/use-settings-nav-items.ts, frontend/src/utils/org/permission-checks.ts.
Problem
The route loader owns path classification, SaaS path guards, ACP gating, selected-organization lookup, billing visibility, admin permission checks, and redirect selection.
Solution
Move settings access decisions into a deep policy module. The route supplies facts and receives a decision.
Benefits/Wins
Improved locality for settings rules, higher leverage tests over redirect behavior, and fewer direct imports of low-level organization or config details.

Before

flowchart TD
  Loader["settings route loader"]
  Loader --> Path["path classification"]
  Loader --> SaaS["SaaS path rules"]
  Loader --> ACP["ACP flag"]
  Loader --> Org["organization lookup"]
  Loader --> Billing["billing visibility"]
  Loader --> Perms["role permissions"]
  Loader --> Redirect["redirect choice"]
              

After

flowchart TD
  Loader["settings route adapter"] --> Facts["route facts"]
  Facts --> Policy["deep settings policy module"]
  Policy --> Decision["render or redirect decision"]
  Policy --> Nav["navigation implementation"]
  Policy --> Access["organization access rules"]
  Policy --> Flags["feature flag rules"]
              

Candidate 5

Deepen enterprise organization modules

Recommendation strength: Speculative
Files
enterprise/server/routes/orgs.py, enterprise/storage/org_service.py, enterprise/server/routes/org_models.py, member, billing, git-claim, and conversation modules.
Problem
The route module spans organization creation, defaults, members, financials, git claims, conversation listing, conversation stats, CSV export, and stop actions.
Solution
First map organization axes, then deepen one high-churn axis rather than split by file size. Candidate seams are lifecycle, membership, financials, git claims, and conversations.
Benefits/Wins
Better locality for enterprise-only changes and clearer leverage from tests that target one organization axis. Churn data should choose the first seam.

Before

flowchart TD
  Route["organization route module"]
  Route --> Life["lifecycle"]
  Route --> Members["members"]
  Route --> Billing["financials"]
  Route --> Git["git claims"]
  Route --> Conv["conversations"]
  Route --> Export["CSV export"]
              

After

flowchart TD
  Route["organization route adapter"] --> Axis["selected deep module"]
  Axis --> Rules["axis rules"]
  Axis --> StoreA["storage adapter"]
  Axis --> ExternalA["external adapters"]
  Axis --> Response["response model"]
  Tests["axis tests"] --> Axis
              

Top Recommendation

Start with Candidate 1: deepen the conversation event stream module. It has strong leverage, a local-substitutable in-process seam, and a manageable deletion test: success should remove broad provider implementation detail while adding only a small interface.

Which of these would you like to explore?