Everything not listed in this document should behave the same as upstream OpenClaw. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable — see the Documentation section of
instructions.mdfor links.
OpenClaw is a self-hosted AI agent gateway: a web chat and control panel in front of an LLM, reachable from messaging channels, with a workspace and memory of its own. This package runs the gateway, wires it to either a cloud provider or a local model server on the same box, and can — if you ask it to — give the agent administrative control of StartOS itself.
- Upstream repo: https://github.com/openclaw/openclaw
- Wrapper repo: https://github.com/Start9-Community/openclaw-startos
- Image and Container Runtime
- Volume and Data Layout
- File Models
- Dependencies
- Network Access and Interfaces
- Installation and First-Run Flow
- Actions
- Tasks
- Health Checks
- Backups and Restore
- Limitations and Differences
- Quick Reference for AI Consumers
One image, built here.
| Property | Value |
|---|---|
| Image | Built from this repo's Dockerfile |
| Architectures | x86_64, aarch64 |
| Command | The gateway, bound to the LAN |
| Subcontainer | Purpose |
|---|---|
openclaw-sub |
The only daemon — the one to attach to |
The image also installs start-cli, pinned to a version by a build argument. That binary is what lets the agent administer the server when you grant it access, and it is why the container needs StartOS's root certificate.
Two oneshots run before the daemon, and three after it:
| Oneshot | When | Purpose |
|---|---|---|
install-root-ca |
Before | Installs StartOS's root CA so the container trusts the OS |
chown |
Before | Hands /data to the unprivileged user the gateway runs as |
check-login |
After | Raises a task if start-cli is not authenticated |
check-simplex-plugin |
After | Brings the SimpleX plugin up to the pinned version |
server-state-snapshot |
After | Writes a server inventory into the agent's memory file |
The gateway starts unconfigured on purpose. It is launched with the flag that allows that, so the interface comes up and shows you what is missing rather than refusing to start.
One volume, holding the agent and everything it knows.
| Volume | Mount Point | Purpose |
|---|---|---|
main |
/data |
The agent's entire home |
| Path | Written by | Holds |
|---|---|---|
.openclaw/openclaw.json |
Actions | The gateway and agent configuration |
.openclaw/workspace/ |
Both | The agent's identity, memory, and files |
.startos/auth-profiles.json |
An action | Provider API keys |
.startos/config.yaml |
The package | Where start-cli points |
simplex.json |
An action | Whether SimpleX file exchange is on |
SOUL.md and IDENTITY.md are re-copied from the image on every install and upgrade, so an upstream revision of the agent's own instructions reaches an existing install. MEMORY.md is not — it is seeded once and then left alone, because it is what the agent has accumulated.
Every start rewrites one section of MEMORY.md: a snapshot of the server's metrics, packages, notifications, gateways, disks, and backup targets. It is how the agent knows what it is running on — and it means the memory file contains an inventory of your server. A daily heartbeat refreshes the three most volatile subsections — metrics, packages, notifications. Its instructions are the heartbeat prompt in the configuration (agents.defaults.heartbeat), written by init and delivered nowhere (target: none): OpenClaw runs heartbeat instructions from its database, never from a workspace HEARTBEAT.md, and the default delivery route skips the run entirely until a chat channel has an owner.
Four models, each owning a different boundary.
| File | Format | Modelled | Written by |
|---|---|---|---|
openclaw.json |
JSON | Yes — FileHelper.json |
Actions, init, and OpenClaw itself |
auth-profiles.json |
JSON | Yes — FileHelper.json |
An action |
config.yaml |
YAML | Yes — FileHelper.yaml |
main and init |
simplex.json |
JSON | Yes — FileHelper.json |
An action |
The main configuration is shared with the application, not owned by the package. OpenClaw edits it too — changing the model from inside the chat writes to the same file — which is why the dependency declaration reads it reactively rather than trusting the action to be the only writer.
Two gateway settings are pinned with z.literal(true): the control UI being enabled, and the host-header origin fallback. The reason is structural: StartOS fronts the gateway with its own reverse proxy on addresses that OpenClaw's origin check rejects. With the fallback off, the interface simply refuses to connect. main also writes gateway.trustedProxies every start, set to the bridge address the proxy connects from — without it OpenClaw answers every proxied request with 403 proxy_attribution_required. What remains in front of the gateway is its password and a one-time approval per browser — see Network Access and Interfaces.
API keys are stored in one place and consumed in another. The action writes them into the auth-profiles file; OpenClaw reads them from the environment. main bridges the two at start, so a key added by the action reaches the gateway on the restart that follows. The file is the package's, which is why it lives under .startos/ and not in OpenClaw's agent directory: a file of that name there is a retired credential source that OpenClaw's doctor archives and that makes the gateway refuse an agent whose own credential store is empty.
The start-cli configuration is rewritten at every start with the server's current address, so the agent's administrative tooling follows the box rather than a value recorded at install.
Four, all optional, and each declared only while it is selected.
| Dependency | Required | Kind | Why |
|---|---|---|---|
| Ollama | No — only if chosen | running |
Local inference backend |
| vLLM | No — only if chosen | running |
Local inference backend |
| llama.cpp | No — only if chosen | running |
Local inference backend |
| SimpleX Websocket Bridge | No — only if enabled | running |
Exchanging files over SimpleX |
The inference dependency follows the model you are actually using. The declaration is derived from the primary model and its fallbacks, so selecting a local backend adds it and switching to a cloud provider drops it — including when the switch is made from inside the chat rather than through the action. Each is required to be running and passing its own health check, since an unhealthy model server is the same as an absent one.
A cloud provider needs no dependency at all, only an API key and internet.
Local backends are reached over the internal bridge, and their API key is read directly out of the backend's own published volume rather than being asked for again.
SimpleX is different in kind: enabling it mounts the bridge's file-exchange directories into this container so the two can hand files to each other, and resolves the bridge's control socket over the bridge network.
One interface.
| Interface | Id | Type | Port | Description |
|---|---|---|---|---|
| Web UI | ui |
ui | 18789 | The chat and the control panel |
Bound on the ui-multi MultiHost over HTTP and not masked.
The gateway password is the gate, and each browser is approved once. OpenClaw's origin checking is relaxed for the reason given under File Models; its device pairing is not, and cannot be. A browser that passes the password is held at "Approve this browser" until the Approve Browser Pairing action admits it, and it then keeps a per-device credential until it is removed in the Web UI's device list. So anyone who can reach this address, knows the password, and can run that action has the agent — and, if StartOS access has been granted, the server. A critical task blocks the service from starting until that password is set, so there is no window where it is reachable without one.
Outbound, the gateway talks to whichever provider is configured, to any messaging channel you connect, and — for local backends and SimpleX — to the sibling service over the internal bridge.
Install creates the agent's directory structure, seeds its workspace from the image, points start-cli at the server, and pins the gateway settings. It then raises two critical tasks: set a gateway password, and configure an AI provider.
The first visit to the Web UI from any browser ends at "Approve this browser" after the password; running Approve Browser Pairing admits it and the page connects on its own.
Neither can be skipped — critical blocks startup, and an agent with no model and no password is not a usable state.
Once running, the gateway comes up on its interface and two more things happen automatically: it checks whether start-cli is authenticated and raises a task if not, and it writes the server snapshot into the agent's memory.
Granting StartOS access is opt-in and deliberately not a critical task. It is offered only after the gateway is up and only because the agent could not authenticate — see the action below before running it.
Updating from an older release migrates OpenClaw's state. The migration moves the auth-profiles file out of the agent directory, then runs OpenClaw's own doctor --fix --non-interactive and its session import against the stopped service — the same repair OpenClaw's updater runs. A nonzero exit fails the update with doctor's output, leaves the data version where it was, and re-runs the migration on the next init; the Repair OpenClaw action is the manual route for anything doctor reports it cannot fix on its own.
Nine actions.
Generates the password for the web interface and shows it once.
- What it changes: the password in the configuration.
- Cost: the service restarts.
- Repeat safety: each run generates a new password and invalidates the old one.
Chooses the backend — a cloud provider with an API key, or a local model server — plus the model and an optional fallback.
- What it changes: the API key in the auth-profiles file, the model selection in the configuration, and for a local backend, a provider entry pointing at that service's endpoint on the internal bridge.
- Cost: the service restarts, and the dependency set changes to match.
- Repeat safety: idempotent, pre-filled with the current selection.
- Choosing a local backend makes that package a required dependency; choosing a cloud provider removes it.
Enables the Telegram channel with a bot token and a policy for who may direct-message the agent.
Enables the WhatsApp channel with a DM policy and an allow-list of numbers.
- Requires the service to be running, since pairing happens against the live gateway.
Enables the SimpleX channel and its DM policy, and turns file exchange with the bridge on or off.
- What it changes: the channel configuration, the plugin policy, and whether the bridge's directories are mounted.
- It repairs the plugin's enablement and allow-list entries when it skips an install because the plugin is already current — an installed plugin still has to be enabled and named to load.
Admits every browser waiting at "Approve this browser" on the Web UI.
- Requires the service to be running, since the pending requests live in the gateway.
- What it changes: each pending pairing request becomes an approved operator device with a durable per-device credential. Remove one from the Web UI's device list.
- Repeat safety: approves whatever is pending at that moment — nothing, if nobody is waiting — so run it right after your own login attempt, not on a schedule.
Authenticates the agent's start-cli against this server, using your StartOS master password.
- This grants the agent root-equivalent control of the server. It can then start and stop packages, read logs, change network settings, and run backups. The action's own warning says to do it only on a server designated for development, and that warning should be taken literally.
- What it changes: a
start-clisession stored on the volume. - Everything that reaches the chat can then reach the server, which makes the gateway password and the channel DM policies load-bearing for the whole box.
Removes that session.
- What it changes: the stored authentication, deleted.
- The agent keeps working; it just cannot administer the server until you log in again.
Runs one of OpenClaw's own maintenance commands against the stopped service and returns its output and exit code.
- Requires the service to be stopped. Doctor takes ownership of the state database.
- Two commands, each with a report-only default:
doctor --lintreports,doctor --fix --non-interactiverepairs config, plugin policy and state;doctor --session-sqlite dry-runcounts importable legacy session history,--session-sqlite importimports it. - What it changes: with a toggle on, whatever doctor decides to repair — config normalization, state-database migrations, legacy file imports. Back up first; the action's warning says so.
- The package update already runs the repairing forms for you (see Installation and First-Run Flow); this action is for a gateway that still refuses to start afterwards, or for reading doctor's report.
Three, two of them blocking.
| Task | Severity | Raised when | Cleared when |
|---|---|---|---|
| Set Gateway Password | critical |
Any init that finds no password | The action runs |
| Configure AI Provider | critical |
Install with no credentials stored | The action runs |
| Login to StartOS | important |
A start-up that finds start-cli unauthenticated |
The action runs |
critical blocks the service from starting, so a fresh install shows the two setup tasks and nothing else. important is advisory — the login task appears every start until it is either done or ignored, and ignoring it is a legitimate choice.
One check, on the only daemon.
| Check | Displayed as | Method | Grace |
|---|---|---|---|
primary |
"Web Interface" | The gateway's /healthz answers on its own address |
40s |
It queries the gateway over the internal bridge using the service's own resolved address rather than a hostname, so it survives the address changing and does not depend on name resolution between containers.
It reports that the gateway is serving. It says nothing about the model: a wrong API key, a rate limit, or a local backend that is running but not loaded all show a green check and an error in the chat.
The main volume is copied wholesale — sdk.Backups.ofVolumes('main'). That is the whole agent: configuration, provider API keys, the gateway password, channel tokens, the workspace, and the accumulated memory.
The backup contains every credential the agent holds, in recoverable form: provider keys, the Telegram bot token, and — if StartOS access was granted — the session that administers your server. Treat it accordingly.
It also contains the server snapshot written into memory, which is an inventory of what is installed on this box.
A restored instance comes back configured and remembers what it knew. The start-cli address is rewritten to the new server on the first start, but the session is not — an agent restored onto a different server has to be logged in again before it can administer that one.
- Granting StartOS access gives the agent root-equivalent control of the server. It is optional and revocable, and it is the single most consequential thing this package can do.
- Origin checking is relaxed and the reverse proxy is trusted, because StartOS's addresses do not satisfy OpenClaw's checks. The gateway password and a one-time approval per browser are the gate.
- The backup holds every credential, including the server session.
- The configuration is co-owned with the application, which edits it at runtime — a change made in the chat is as real as one made through an action.
- The agent's memory contains a server inventory, rewritten every start.
- Local backends must be running and healthy, or the gateway has no model.
- A cloud provider sends your conversations to that provider. Only a local backend keeps them on the box.
SOUL.mdandIDENTITY.mdare overwritten on every upgrade, and the heartbeat prompt is rewritten on every init; edits to them do not survive. Put your own heartbeat checklist in the monitor scratch (openclaw cron scratch), which is appended to the prompt and left alone.- One agent. The package configures the default agent only.
package_id: openclaw
image: built from ./Dockerfile # also installs a pinned start-cli binary
architectures:
- x86_64
- aarch64
subcontainers:
- openclaw-sub # gateway runs as `node`; oneshots that chown run as root
volumes:
main: /data # HOME and OPENCLAW_STATE_DIR both live here
file_models:
- .openclaw/openclaw.json # gateway + agent config; OpenClaw writes it too
- .startos/auth-profiles.json # provider API keys; package-owned, not OpenClaw's agent dir
- .startos/config.yaml # start-cli host, rewritten each start
- simplex.json # whether SimpleX file exchange is enabled
startos_managed_env_vars:
- HOME
- OPENCLAW_STATE_DIR
- NODE_EXTRA_CA_CERTS
- ANTHROPIC_API_KEY # bridged from auth-profiles.json when present
- OPENAI_API_KEY
- GEMINI_API_KEY
- XAI_API_KEY
dependencies:
- ollama # optional, kind: running + primary health check, only while selected
- vllm # same
- llama-cpp # same
- simplex-websocket-bridge # optional, only while file exchange is enabled
interfaces:
ui: { type: ui, port: 18789 } # gateway password + one-time browser approval (approve-devices)
actions:
- set-password
- configure-api-credentials
- connect-telegram
- connect-whatsapp # only-running
- configure-simplex
- approve-devices # only-running; admits browsers waiting to pair with the Web UI
- login-to-os # grants root-equivalent StartOS control
- revoke-startos-access
- repair-openclaw # only-stopped; runs `openclaw doctor`
tasks:
- { action: set-password, severity: critical } # reactive
- { action: configure-api-credentials, severity: critical } # install
- { action: login-to-os, severity: important } # raised at start when unauthenticated
health_checks:
- primary # checkWebUrl against the service's own bridge address; says nothing about the model