Mo Workbench
Mo Workbench is a macOS Skia native-first desktop agent shell. Its shared app
is a task workbench with a responsive task sidebar, Work / Code
/ Design top tabs, and a settings overlay. Code is the interactive
conversation surface; Work projects the session list into a task dashboard,
and Design exposes a live product-state and palette preview without claiming
a design-document workflow.
Package Shape
examples/mo_workbench/appowns the shared TEA shell model, itsCodeandSettingssub-models, task projection, update routing, and view composition. It is portable (native + wasm-gc) and does not depend on any native-only LLM SDK. The backend is injected as anAgentBackendRuntimeclosure boundary, so the app package can be tested without a network. Its public surface is intentionally narrow:program_with_backend,AgentBackendRuntime,AgentBackendFixture,AgentCommand,AgentEvent,ThinkingChoice,TurnStatus,SessionSummary,WorkbenchModel,WorkbenchMsg, andMoWorkbenchApp.examples/mo_workbench/openseek_native_transportis a native-only package that bridgesAgentCommand/AgentEventto the in-process OpenSeek agentexamples/mo_workbench/acp_native_transportis a native-only generic ACP stdio transport. It launches an ACP-compatible agent subprocess, speaks JSON-RPC newline messages over stdin/stdout, mapssession/updateandsession/request_permissionintoAgentEvent, and keeps client filesystem and terminal capabilities disabled for v1.examples/mo_workbench/macos_skiainjectsacp_native_transportwhensettings.jsonselects"agent_backend": "acp"andacp_commandis non-empty. Otherwise it usesopenseek_native_transportwhen an API key is configured, falling back toAgentBackendRuntime::stub().
Shell Architecture
WorkbenchModel holds the active TopTab, CodeWorkspace,
SettingsWorkspace, shared chrome state, and a derived sidebar task tree.
code.sessions is the source of truth for task history; sync_task_tree
rebuilds the searchable, filterable, expandable sidebar projection after code
state changes. CodeMsg(...) and SettingsMsg(...) are delegated to their
sub-updates and their effects are lifted back to Effect[WorkbenchMsg].
Top Tabs and Settings
- Work: a session-backed dashboard with total/running/failed/done summary
cards, next actions, and recent tasks. Its Review, Running, and All tasks
actions apply the matching sidebar filter and open the
Codetab. - Code (
CodeWorkspace): the conversation-first coding-agent surface: sessions, message timeline, prompt composer, steering/cancel, OpenSeek model and thinking controls, ACP mode/config controls, and streaming state. It ownsAgentBackendRuntimedispatch andAgentEventprojection. - Design: a live preview of task count, current model, approval/sandbox settings, typography, controls, and palette tokens. It is not a design-document editor.
- Settings (
SettingsWorkspace): an overlay with API, Agent, and Editor sections for backend/provider configuration, ACP command settings, approval policy, sandbox mode, working directory, appearance, and font size.
Message Routing
SwitchTab(TopTab)changes the selected Work/Code/Design surface.NewTaskandSelectTask(id)delegate toCodeWorkspaceand openCode.ShowAllTasks,ShowRunningTasks, andShowFailedTasksset the sidebar filter and openCode; search, folder expansion, and sidebar visibility are handled by the shell.CodeMsg(CodeMsg)delegates toCodeWorkspace::update(msg, backend)and lifts the resulting effect;ReceiveAgentEvent(AgentEvent)keeps backend event projection inside the Code workspace.SettingsMsg(SettingsMsg)delegates to the settings sub-model.ToggleAppearancecyclesSystem → Light → Dark → Systemfrom the sidebar footer;ToggleSettingsopens the settings overlay.
On startup, the shared app dispatches FetchSessionList. When the returned
session list is non-empty and no session is active yet, Code automatically
selects the first session and dispatches SwitchSession so the workbench
resumes an existing conversation instead of staying on a blank empty state.
Backend Boundary
The app package stays LLM-agnostic through the AgentBackendRuntime closure:
UI side -> Effect::run callback (sync) -> runtime.run(commands, emit)
backend -> async worker -> emit(event) -> dispatch(map(event)) into UI queue
AgentCommand:SendTask,Steer,NewSession,SwitchSession,SetModel,SetThinking,CancelTurn,SetSessionMode,SetSessionConfigOption,RespondPermission,FetchSessionList,Shutdown.AgentEvent:UserMessageAdded,AssistantMessageAdded,ToolResultAdded,RuntimeNoticeAdded,TurnTerminal,ProgressNotice, streamed message/thought chunks, ACP mode/config updates, permission requests, session info updates,SessionRebuilt,SessionListed,ActiveSessionChanged.
The AgentBackendFixture is a scripted backend for portable tests: it records
dispatched commands and replays a caller-supplied event sequence per command.
Backend injection only affects CodeWorkspace::update; task/sidebar routing
and Settings state remain portable app behavior.
UI Layout
- Sidebar: 280px on wide desktop, 248px below 1120px, and 200px below 760px. It contains the Work/Code/Design segmented control, New Task, a searchable/filterable session tree with expandable task groups, Settings, and the accessible appearance toggle.
- Topbar: a 50px strip inside the center column showing the selected tab identity and contextual subtitle.
- Center work surface: Work renders the task dashboard; Code renders starter cards or the message timeline with evidence-colored rows and a bottom composer; Design renders product-state, control, and palette previews.
- Settings overlay: grouped API, Agent, and Editor cards are rendered over the shell rather than as a top-level tab.
Message rows in the Code workspace are rendered by role: user (right-aligned bubble), assistant (left-aligned bubble with optional reasoning), tool result (diff/status-colored evidence card), runtime notice (muted caption), and terminal (status badge + message). Repeated cards use 8px radii; chips and badges remain pill-like through the view library.
The current UI intentionally does not implement design-document editing, phone pairing, scheduled-task execution, plugin installation, local diff application, or command catalogs. ACP v1 support is transport-level and deliberately does not advertise client filesystem or terminal capability.
OpenSeek integration (current)
The macos_skia entrypoint uses openseek_native_transport, which:
- enqueues commands from MoUI
Effect::runand runs them on a dedicatedopenseek_worker_looptask alongside the Skia async pump (@async.all); - persists session events through
@store.SessionStoreunderMO_WORKBENCH_SESSION_ROOT(default<workspace>/.openseek); - maps
SessionItem/TurnTerminalintoAgentEventfor the Code workspace; - enables only the model ids currently parsed by OpenSeek
(
deepseek-v4-flash,deepseek-v4-pro,kimi-k2.7-code,kimi-k2.7-code-highspeed) while showing other provider-discovered ids as disabled options; - supports
NewSession,SwitchSession, andFetchSessionListagainst the store.
Follow-up work: a long-lived AgentRuntime + background worker (serve-mode
shape) so mid-turn Steer shares the same runtime as the active turn, and
optional spawn_bg integration with the Skia async pump instead of per-turn
blocking.
ACP integration (current)
The macos_skia entrypoint can select acp_native_transport from
.mo_workbench/settings.json:
{
"agent_backend": "acp",
"acp_command": "acp-agent",
"acp_args": "[\"--stdio\"]",
"acp_process_cwd": ""
}
The ACP backend:
- launches the configured command as a subprocess and uses ACP stdio JSON-RPC newline messages;
- sends
initializewith conservative client capabilities (fs.readTextFile=false,fs.writeTextFile=false,terminal=false); - maps
session/new,session/prompt,session/cancel, optionalsession/list,session/load,session/resume,session/set_mode, andsession/set_config_optionthrough the neutral backend boundary; - handles agent
session/updatenotifications for message chunks, thought chunks, tool calls, plan/usage notices, mode/config updates, and session info; - handles agent
session/request_permissionrequests by rendering a permission card and replying with the selected option orcancelled.
Follow-up Scope
Future expansions are not part of the current prototype:
- richer Work history/status once transports expose sufficient session metadata;
- a real Design workflow beyond the current state and palette preview;
- cross-platform entrypoints: only macOS Skia is wired today, while
Linux/Windows/Web entrypoints can be added as
linux_skia/windows_skia/web_wasmsubpackages without changing the shared app.