Architecture
Node-Only Daemon
Synced from github.com/CoWork-OS/CoWork-OS/docs
Goal: run CoWork OS on Linux servers (VPS/headless) as a Node.js daemon with no desktop window and no Xvfb.
This is an alternative to the Linux “headless Electron” mode. It’s designed for:
- packaged Linux server releases
- VPS/systemd installs
- headless Docker installs
- a CLI/web-dashboard driven workflow (no desktop UI required)
Important naming detail: “Node-only” describes the process entrypoint (node bin/coworkd-node.js) and the absence of a desktop UI/Xvfb. The packaged server tarball can still include the electron npm package as a compatibility dependency while shared runtime helpers are being decoupled; users do not launch Electron from this package.
What It Runs
The Node daemon (coworkd-node) wires up:
- SQLite database + secure settings storage
- provider factories (LLM/search) + env import (optional)
- agent daemon + task execution
- WebSocket Control Plane + minimal HTTP UI (
/+/health) - optional channel gateway (Telegram/Discord/Slack/etc)
- optional MCP + cron (best-effort)
- access-profile resolution and fail-closed enforcement shared with the desktop runtime
- the memory engine and the workspace kit writers (
.cowork/CROSS_SIGNALS.md,MISTAKES.md,LORE.md). The kit writers run only while the daemon owns the profile's kit-writer lease: when the desktop app runs on the same profile it takes the lease over and the daemon stops writing kit files until the desktop quits (see Architecture)
At shutdown the daemon cancels scheduled memory consolidations, waits up to 3 s for running consolidation, Dreaming and playbook learning, flushes and releases the kit writers, and lets a running memory compression batch or markdown index sync finish (up to 5 s) before the database closes.
Recommended Install (Packaged Server Release)
For production VPS installs, use the GitHub release tarball documented in Linux VPS:
version=<version>
curl -LO "https://github.com/CoWork-OS/CoWork-OS/releases/download/v${version}/cowork-os-server-linux-x64-v${version}.tar.gz"
curl -LO "https://github.com/CoWork-OS/CoWork-OS/releases/download/v${version}/cowork-os-server-linux-x64-v${version}.tar.gz.sha256"
sha256sum --check "cowork-os-server-linux-x64-v${version}.tar.gz.sha256"
sudo mkdir -p /opt/cowork-os
sudo tar -xzf "cowork-os-server-linux-x64-v${version}.tar.gz" -C /opt/cowork-os --strip-components=1
The package includes built daemon assets, runtime dependencies, resources, connectors, and systemd templates.
Source Install
npm ci
npm run build:daemon
npm run build:connectors
# Start the daemon (Control Plane on 127.0.0.1:18789 by default)
node bin/coworkd-node.js --print-control-plane-token
Notes:
bin/coworkd-node.jswill rebuildbetter-sqlite3for the current Node ABI if needed.- By default the Control Plane binds to loopback (
127.0.0.1) for safety. Use SSH tunnel/Tailscale for remote access. - Managed/headless startup blocks public Control Plane binds (
0.0.0.0/::) unless Tailscale is enabled,COWORK_CONTROL_PLANE_BIND_CONTEXT=containeris set for a privately published container, orCOWORK_CONTROL_PLANE_ALLOW_INSECURE_PUBLIC_BIND=1is set as an explicit break-glass override. - Reverse-proxied dashboards should set
COWORK_CONTROL_PLANE_ALLOWED_ORIGINSto the public HTTPS origin. Only setCOWORK_CONTROL_PLANE_TRUST_PROXY=1behind a proxy you control.
Remote Use (No Desktop Required)
- SSH tunnel from your laptop:
ssh -N -L 18789:127.0.0.1:18789 user@your-vps
- Open the minimal dashboard:
http://127.0.0.1:18789/
- Or use the CLI:
export COWORK_CONTROL_PLANE_URL=ws://127.0.0.1:18789
export COWORK_CONTROL_PLANE_TOKEN=... # printed on first token generation or via --print-control-plane-token
node bin/coworkctl.js call config.get
node bin/coworkctl.js call llm.configure '{"providerType":"openai","apiKey":"sk-...","model":"gpt-4o-mini"}'
node bin/coworkctl.js call llm.configure '{"providerType":"openrouter","apiKey":"sk-or-...","model":"openrouter/pareto-code","settings":{"paretoMinCodingScore":0.8}}'
node bin/coworkctl.js call workspace.create '{"name":"main","path":"/srv/cowork/workspace"}'
node bin/coworkctl.js call task.create '{"workspaceId":"...","title":"Test","prompt":"Say hi","agentConfig":{"accessProfileId":"ask_for_approval"}}'
node bin/coworkctl.js watch --event task.event
Headless tasks use the same access profiles as desktop tasks. Pass
agentConfig.accessProfileId for a task-specific choice, or configure the default profile through
the target node's settings. The daemon resolves the profile on the target, applies its sandbox,
command-tool, filesystem, network, domain, and approval boundaries, and fails closed if a named
profile is missing or invalid. shellAccess and legacy permissionMode values remain compatibility
inputs only.
Ordinary work inside a named profile's granted boundary does not create an approval request. The
local daemon also runs without popup approvals by default: an unresolved ask becomes an
assistant message and durable inline Deny / Allow once input card for interactive tasks.
This covers network access, credentials, exports, MCP/external side effects, eligible outside-
workspace paths, and explicit no-auto-approve requests. Automated tasks without a human-input
channel fail closed. Set COWORK_APPROVAL_PROMPTS=on to restore the legacy queue for diagnostics.
Bounded shell execution still requires an available OS sandbox, and hard denials, administrator
policy, protected paths, and explicit tool opt-outs remain enforced. approval: never suppresses
requests while retaining the profile's filesystem and network restrictions. Pending approval
state fails closed across restart.
Application Work in the Daemon
The daemon exposes the same application.* Control Plane methods as the desktop app, so application operations can be created, executed, reconciled and cancelled without a desktop. API and MCP routes work headless, including remote OAuth MCP servers: an agent-key identity needs no person, and a person's sign-in made once in the desktop app keeps renewing headless until its refresh token expires. Routes that drive a local app (native_app and the local Photoshop route) need the macOS desktop app in an unlocked session. See Working with apps in the background.
Headless Limitations (Expected)
Some tools are desktop-only (clipboard, screenshot capture, opening files in Finder/Explorer, etc). In the Node daemon these will return a clear error instead of trying to use Electron APIs.