Skip to content

Reference

Environment variables, the plugin manifest field by field, and the wire protocol.

4 min readUpdated 13 Sept 2026Reviewed 12 Sept 2026Published 12 Sept 2026/docs/reference

The look-it-up section. Written to be read at the moment something is not working, which is why the tables say what a value does rather than restating its name.

There are three pages here and they answer three different questions. Environment answers "why is the office behaving like this, and what do I change to make it behave differently" — it is the complete list of variables the server reads, each with the default it actually has. Plugin manifest answers "what can I write in a plugin.json, and why was mine refused" — it is the field-by-field contract, including the validation rules that reject a plugin outright. The wire protocol answers "how do I drive this thing without the browser" — the HTTP routes and every message that travels over the single WebSocket.

None of the three is a tutorial. If you are trying to get the office running for the first time, Quick start is the page you want; if you are trying to understand why the router picked the model it picked, Model routing explains the mechanism rather than the switches.

Which page, when

PageUse it whenIts unit of detail
EnvironmentThe office binds to the wrong interface, routes too expensively, spends without asking, or never discovers the models you expected. Also: you set something in .env and nothing changed.One row per variable, plus the system each group configures.
Plugin manifestYou are writing a plugin, debugging one that will not load, or checking what a plugin you are about to enable is actually allowed to reach.One row per manifest field, then per contribution key, then per permission.
The wire protocolYou are driving a run from a shell, wiring the office into a dashboard, or writing something that consumes the event stream.One row per HTTP route and per socket message type.

Three things worth knowing up front

.env is a bootstrap, not the final word

The server reads .env exactly once, at startup, and never again — editing it while the process runs changes nothing. On a first boot, eight of its values are copied into the installation settings: workspacesRoot, allowExternalWorkspaces, defaultRoutingPosture, maxConcurrency, softSpendApprovalUsd, autoApproveShell, approvalTimeoutMs and logLevel. From that point on the saved settings win, and the Settings tab in the running office (127.0.0.1:8787, under the settings cog) is how they change — there is no environment variable that overrides a saved setting after first boot. Everything else — provider keys, DEV3D_DB, the plugin install gate, every cache and discovery path, the bind address — stays environment-only for the life of the process.

There is a second half to this that matters more often than the first: the environment the process was started in always beats the file. A real DEEPSEEK_API_KEY exported in your shell is not overridden by an empty DEEPSEEK_API_KEY= in .env, and a PORT set by whatever launched the server beats PORT=8787 in the file. So "I set it in .env and it did not take" usually means something else set it first.

The office tells you when the environment has moved on. /api/health carries a configStale flag and a configStaleDetail sentence naming what changed, and so does the OfficeState the console receives. It fires on two signals: the .env file being modified after boot, and a provider key having appeared in the process environment. It deliberately does not claim to detect a key being removed, because a running process cannot observe that.

apiVersion's major must match the host's

The host implements plugin API version 1. A manifest declaring 2 is refused rather than guessed at — the message is targets API "2", but this host implements "1". — because a host that does not know what it would be running should not run it. Only the leading integer is compared, so 1 and 1.4 are both fine and 1.9 works against a host at 1.

Both sides of the protocol compile against the same file

The orchestrator and the browser both import their message types from @dev3d/core, which has no dependencies at all: no network, no filesystem, no React. That is not a stylistic choice. There is no separate protocol document to drift out of date, and a shape change that only one side made is a compile error rather than a undefined is not a function in a browser console at three in the morning. It also means the type list is the authoritative count: 31 server events and 33 client commands, which is not what the project's own README says.

What is not on these pages

  • Model prices, tiers and quality tables. Those are data, not configuration: they live in the catalog the server ships and in plugin contributions, and the console's Models tab is where you correct one.
  • Web-development-only variables. DEV3D_SERVER_HOST, DEV3D_SERVER_PORT and VITE_WS_URL are read by the Vite dev server and the browser, never by the orchestrator. They are noted on the Environment page so you do not go looking for them in the server's config.
  • The marketplace's own catalogue format. That belongs with the publishing guide, which is about writing an entry rather than reading one.

If a page here is wrong

These pages are generated from a documentation set that is checked against the source, and the source moves. The two failure modes worth knowing about are a default that changed and a field that was added — if a manifest field or a variable you are using is not on these pages, that is a documentation bug rather than a sign that the thing does not work, and the running server is the tie-breaker. GET /api/health, GET /api/state and GET /api/providers between them report the resolved configuration the process is actually using, which is the version that matters.

Linked from

Did this page answer your question?