* refactor(repo): move OpenWork apps into apps and ee layout Rebase the monorepo layout migration onto the latest dev changes so the moved app, desktop, share, and cloud surfaces keep working from their new paths. Carry the latest deeplink, token persistence, build, Vercel, and docs updates forward to avoid stale references and broken deploy tooling. * chore(repo): drop generated desktop artifacts Ignore the moved Tauri target and sidecar paths so local cargo checks do not pollute the branch. Remove the accidentally committed outputs from the repo while keeping the layout migration intact. * fix(release): drop built server cli artifact Stop tracking the locally built apps/server/cli binary so generated server outputs do not leak into commits. Also update the release workflow to check the published scoped package name for @openwork/server before deciding whether npm publish is needed. * fix(workspace): add stable CLI bin wrappers Point the server and router package bins at committed wrapper scripts so workspace installs can create shims before dist outputs exist. Keep the wrappers compatible with built binaries and source checkouts to avoid Vercel install warnings without changing runtime behavior.
6.3 KiB
title, description
| title | description |
|---|---|
| openwork-orchestrator multi-workspace router | Keep a single opencode process alive and switch workspaces JIT via openwork-orchestrator daemon + CLI. |
Set context
OpenWork currently restarts the OpenCode engine whenever a local workspace changes. That is slow, drops session streams, and makes it impossible to keep multiple local workspaces warm at once. OpenCode already supports routing requests by directory, and caches per-directory instances in a single server process. The missing piece is a stable local control plane that keeps one OpenCode process alive, manages multiple workspaces, and exposes a CLI for programmatic tests and automation.
Define goals
- Keep a single OpenCode server running while switching workspaces.
- Support multiple local workspaces concurrently without process restarts.
- Provide a CLI that can add/list/switch workspaces and route OpenCode calls by directory.
- Provide a daemon control plane (local only) that owns process lifecycle and workspace registry.
- Provide 100 percent programmatic test coverage via CLI scripts.
- Keep the design ready for desktop integration later.
Call out non-goals
- No new UI flows in this phase (desktop wiring is future work).
- No remote workspace pooling or proxying (remote stays a registry entry only).
- No multi-user auth or network exposure beyond localhost.
- No opencode protocol changes.
User stories
- As a CLI user, I can add two workspaces and switch instantly without killing OpenCode.
- As a tester, I can run a script that proves workspace isolation and PID stability.
- As a desktop app, I can later call a local daemon to pick an active workspace without restarting OpenCode.
Proposed architecture
openwork-orchestrator daemon
- Runs on localhost only.
- Spawns a single
opencode serveprocess and keeps it alive. - Exposes a small HTTP control plane to manage workspaces and report status.
- Stores state in a JSON file under an OpenWork data directory.
- Provides JIT instance creation by calling OpenCode endpoints with
directory.
Workspace registry
- Stores local and remote workspaces in a shared state file.
- Uses a stable id derived from normalized path or URL.
- Tracks last used timestamps for future idle eviction.
Request routing
- openwork-orchestrator never changes OpenCode internals.
- It passes
directoryto the OpenCode SDK client. - OpenCode
Instance.providecreates or reuses per-directory instances.
JIT lifecycle
- Instances are created when the first request for a workspace is made.
- Instances can be disposed explicitly via
/instance/disposewithdirectory. - Future: daemon can evict idle instances based on last-used timestamps.
CLI design
Daemon
openwork daemon(foreground)openwork daemon start(background)openwork daemon stopopenwork status(includes opencode PID + baseUrl)
Workspaces
openwork workspace add <path> [--name]openwork workspace add-remote <baseUrl> [--directory] [--name]openwork workspace list [--json]openwork workspace switch <id>openwork workspace info <id>openwork workspace path <id>(calls OpenCode/pathwith directory)
Instances
openwork instance dispose <id>(calls OpenCode/instance/disposefor directory)
Programmatic output
- All commands support
--jsonfor machine parsing. - Errors are structured JSON in
--jsonmode.
Control plane API (localhost only)
GET /health
Returns daemon + opencode status, pid, baseUrl, workspace count, activeId.
GET /workspaces
Returns { activeId, workspaces }.
POST /workspaces
Adds a local workspace.
POST /workspaces/remote
Adds a remote workspace.
POST /workspaces/:id/activate
Sets active workspace id.
GET /workspaces/:id/path
Calls OpenCode /path for the workspace directory and returns path info.
POST /instances/:id/dispose
Calls OpenCode /instance/dispose with the workspace directory.
Data model (state file)
{
"version": 1,
"daemon": {
"pid": 12345,
"port": 6174,
"baseUrl": "http://127.0.0.1:6174",
"startedAt": 1730000000000
},
"opencode": {
"pid": 23456,
"port": 6175,
"baseUrl": "http://127.0.0.1:6175",
"startedAt": 1730000000000
},
"activeId": "ws-abc123",
"workspaces": [
{
"id": "ws-abc123",
"name": "OpenWork",
"path": "/Users/me/openwork",
"workspaceType": "local",
"lastUsedAt": 1730000000000
},
{
"id": "ws-remote",
"name": "Remote",
"path": "",
"workspaceType": "remote",
"baseUrl": "https://example.com",
"directory": "",
"lastUsedAt": 1730000000000
}
]
}
Testing strategy (CLI only, 100 percent programmatic)
- Multi-workspace test:
- start daemon in a temp data dir
- add two local workspaces
- call
workspace pathfor both - assert directories match and opencode PID is unchanged
- JIT test:
- add workspace without touching it
- call
workspace pathto trigger instance creation
- Disposal test:
- dispose a workspace and call
workspace pathagain to recreate
- dispose a workspace and call
- Failure test:
- invalid path returns a structured error
- opencode process kill triggers daemon restart on next request
Alternative implementations
- Per-workspace OpenCode processes
- Pros: isolation, per-workspace config
- Cons: heavy, many ports, slower switch
- UI-only router without daemon
- Pros: no new process
- Cons: no shared CLI and no centralized lifecycle
- Hybrid pool
- Pros: isolate heavy workspaces
- Cons: complexity, more scheduling logic
Rollout plan
- Phase 0: openwork-orchestrator daemon + CLI only, programmatic tests.
- Phase 1: OpenWork desktop can read openwork-orchestrator state and attach to baseUrl + directory.
- Phase 2: Add idle eviction and remote workspace helpers.
Risks and mitigations
- Long-running OpenCode process memory growth: add disposal CLI and optional TTL.
- Stale daemon state: health check and restart if pid is dead.
- Permission prompts across workspaces: keep directory-scoped instances to preserve isolation.
Open questions
- Where should openwork-orchestrator store its state on each platform (XDG vs OS app data)?
- Should openwork-orchestrator reuse OpenWork desktop workspace registry for compatibility?
- Should we standardize a
workspaceIdformat shared by desktop and CLI?