Installation
Requirements, provider keys, LLM modes, and exactly what the first boot copies into settings.
Installation is short — there is no database server to provision, no build step required to run the orchestrator, and no keys needed to see the office work. The interesting part is what happens on the first boot, because that is the one moment the environment is the authority.
Requirements
| Requirement | Detail |
|---|---|
| Node 24 or newer | Declared as "engines": { "node": ">=24.0.0" }. The suites run TypeScript directly via node --test --test-isolation=none, and the store uses the unflagged node:sqlite. |
| pnpm | "packageManager": "pnpm@11.22.0". Note there is no engines.pnpm — the pin itself is the floor, enforced through corepack. |
| A writable data directory | The SQLite file (./data/dev3d.sqlite), the plugin install directory (./data/plugins) and the discovery caches all live there by default. If it cannot be written the store falls back to memory and warns. |
| Disk for a workspace | A run reads and writes real files inside its project directory. DEV3D_WORKSPACE (default ./workspace) is what a run uses when it names no project; DEV3D_WORKSPACES_ROOT (default ./workspaces) is where new project folders are created. |
| Provider keys | Optional. Zero keys is a supported configuration, not a degraded one. |
Provider keys, and what each one actually enables
Keys are read from the environment and never leave the server: they are not stored in a settings document, not written to a log line and never sent to the browser. A provider with no key contributes no routing candidates at all — filtering happens in the provider registry before the router ever sees a model.
| Variable | Provider id | What it enables |
|---|---|---|
DEEPSEEK_API_KEY | deepseek | DeepSeek Flash (nano), DeepSeek V4 Pro (strong) and DeepSeek V4 Flash Vision (experimental, standard, the only curated vision model). |
OPENAI_API_KEY | openai | GPT-4o mini (small), o3-mini (strong) and GPT-4o (strong). |
ANTHROPIC_API_KEY | anthropic | Claude 3.5 Haiku (small) and Claude 3.5 Sonnet (strong), spoken over the Messages API rather than the OpenAI shape. |
OPENROUTER_API_KEY | openrouter | Three curated multi-vendor slugs (DeepSeek Chat, Qwen 2.5 Coder 32B, Llama 3.3 70B), plus the two pooled services that need a key: the benchmark index and upstream endpoint health. |
DEV3D_LOCAL_BASE_URL | local | Any OpenAI-compatible runtime you already run. Its presence is what makes this provider configured without a key — see below. |
ARTIFICIAL_ANALYSIS_API_KEY | — | Only the name is configured, via DEV3D_POOLED_QUALITY_KEY_VAR. It is the one optional quality source that makes an outbound call; no key means no request is made. |
Those are the five provider ids and twelve curated models that ship. The curated table is deliberately described in the source as an overlay rather than a roster: at boot, unless DEV3D_MODEL_DISCOVERY=false, the office asks each configured provider what models it actually serves and treats that as membership. A model the table has never heard of is still routable, flagged as unrated; a model the provider no longer serves is withdrawn rather than left to fail a turn. Mock mode never discovers, so a keyless office keeps its full demonstrable catalog.
DEV3D_LLM_MODE — and exactly what auto means
| Value | Behaviour |
|---|---|
auto (default) | Live if any provider is configured, mock otherwise. The decision is made once, at boot. |
live | Require real providers. If none is configured, the office still starts and every turn will fail — the boot reason says so in as many words. |
mock | Force the scripted provider even when keys are present. This is a legitimate choice for demonstrating the office without billing. |
| anything else | Read as auto, quietly. The value is lower-cased before comparison, so AUTO and Mock work. |
What auto means the moment a key appears. Adding any provider key — or setting DEV3D_LOCAL_BASE_URL — flips the installation to live at the next boot, and the office starts spending real money on real models. Nothing asks you to confirm. If you want to look around first, set DEV3D_LLM_MODE=mock before you paste the key in.
The server reports the reason, not just the result. The three literal shapes of that reason are:
DEV3D_LLM_MODE=auto, and deepseek is configured— the plural form is used when several are configured.DEV3D_LLM_MODE=auto, and no provider key or keyless base URL was found.DEV3D_LLM_MODE=mock forces scripted employees even though deepseek is configured— a case that used to be misreported as "no keys found", which wasted an afternoon for exactly the person who had deliberately forced mock.
Because the environment is read exactly once, a running process can also be asked whether it is stale. GET /api/health returns configStale plus a detail sentence. Two things count as drift: the .env file having been modified after boot, and a provider key having appeared in the process environment. A key being removed is deliberately not reported, because a running process cannot observe that reliably and claiming otherwise would be a guess dressed as a fact.
Local runtimes
One provider is special: local. It declares keyless: true, and it counts as configured on the strength of its base URL alone. Setting DEV3D_LOCAL_BASE_URL — the default is http://127.0.0.1:11434/v1, which is where Ollama listens — is therefore enough to move the installation into live mode, with no key at all. Any OpenAI-compatible runtime works: Ollama, vLLM, LM Studio and friends. DEV3D_LOCAL_API_KEY is optional and only for runtimes that do check a token.
Two honest caveats. First, the curated table describes exactly one local entry, local/default, at tier small; turn on discovery, or correct the entry in the Settings page, if your local model is stronger or weaker than that. Second, a local runtime is a live provider: turns are priced against the curated estimate and counted against the run budget like any other. If your goal is a free demonstration rather than real local inference, mock mode is the better choice — it is deterministic, and it exercises the whole pipeline.
A typical Ollama setup is one line, plus the model list the provider publishes:
# Ollama on its default port, discovered rather than curated
DEV3D_LOCAL_BASE_URL=http://127.0.0.1:11434/v1
DEV3D_MODEL_DISCOVERY=true
Note the consequence: because local counts as configured, this also moves DEV3D_LLM_MODE=auto to live. If you want a local runtime and a keyless demo at the same time, that combination does not exist — pick one, or force the mode explicitly and accept that a forced mode is what llmModeReason will tell you.
Where things live on disk
| Path | Default | Owned by |
|---|---|---|
| Database | ./data/dev3d.sqlite | DEV3D_DB — environment-only, forever. |
| Bundled plugins | ./plugins | DEV3D_PLUGINS_DIR; these are the ones that ship with the checkout. |
| Installed plugins | ./data/plugins | DEV3D_PLUGIN_INSTALL_DIR, kept apart on purpose so a download can never be confused with something that came with the repository. |
| Skills | ./skills | DEV3D_SKILLS_DIR; fifteen markdown files, loaded before the server starts listening. A malformed file is skipped with its reason and never fatal. |
| Default project | ./workspace | DEV3D_WORKSPACE, used when a run names no project. |
| New projects | ./workspaces | DEV3D_WORKSPACES_ROOT, copied into settings on first boot and editable from the UI afterwards. |
| Caches | ./data/*.json | Model discovery, pooled quality, benchmarks and endpoint health. Each has an off / none escape hatch, and all of them are environment-only. |
Everything under data/ is disposable except the database. Deleting a cache costs a network round trip on the next boot; deleting the database costs your history.
First boot: what the environment owns, and what it hands over
.env is a bootstrap, not the final word. On a first boot exactly eight engine-affecting values are copied into the installation settings:
| Copied into settings on first boot | Environment variable | Real default |
|---|---|---|
workspacesRoot | DEV3D_WORKSPACES_ROOT | ./workspaces |
allowExternalWorkspaces | DEV3D_ALLOW_EXTERNAL_WORKSPACES | true (only the literal false disables it) |
defaultRoutingPosture | DEV3D_ROUTING | balanced |
maxConcurrency | DEV3D_MAX_CONCURRENCY | 4, clamped to 1–16 |
softSpendApprovalUsd | DEV3D_SOFT_SPEND_APPROVAL_USD | 1.50; 0 disables the gate |
autoApproveShell | DEV3D_AUTO_APPROVE_SHELL | false (only the literal true enables it) |
approvalTimeoutMs | DEV3D_APPROVAL_TIMEOUT_MS | 600000, floored at 1000 |
logLevel | DEV3D_LOG_LEVEL | info |
From the second boot onwards, the saved settings win. Four of those are read back from settings on every start — maxConcurrency, the soft-spend threshold, autoApproveShell and approvalTimeoutMs — so editing them in .env after the first boot has no effect, and the Settings page is where they change. The same applies to the routing posture, with one twist worth knowing: DEV3D_ROUTING sets the installation default, and a floor can override it with its own posture.
Environment-only, for the life of the process — these can never be moved into a settings document, and it is deliberate:
- Provider keys. They are secrets, and secrets do not belong in a settings row, a log line or a browser payload.
DEV3D_DB. The database path has to be known before the database can be opened, so it cannot come from inside the database.- The plugin install gate (
DEV3D_ALLOW_PLUGIN_INSTALL). Whether code from the network may run in this process is a decision for whoever owns the machine, not for a UI. - Every discovery and cache path —
DEV3D_MODEL_DISCOVERY_CACHE,DEV3D_POOLED_QUALITY_CACHE,DEV3D_BENCHMARK_CACHE,DEV3D_ENDPOINT_HEALTH_CACHE— and theTTLvalues beside them.
Three parsing rules apply to all of them, and each has bitten someone: a real environment variable always beats the .env file; an empty string counts as absent, so DEV3D_LOCAL_BASE_URL= does not enable the local provider; and a non-finite number falls back to the default rather than propagating a NaN. Relative paths resolve against the repository root, not your shell's working directory.
The .env.example, trimmed
The shipped example is 220 lines and heavily commented; this is the subset that changes behaviour in a first hour. Copy the real file rather than retyping — the comments explain the trade-offs.
# --- server bind ---
HOST=127.0.0.1
PORT=8787
# --- engine behaviour ---
# auto (default) -> live when any provider key is present, otherwise mock.
# Note what 'auto' means the moment you add a key: the office starts spending
# real money on real models. Set this to 'mock' to look around first.
DEV3D_LLM_MODE=auto
DEV3D_LOG_LEVEL=info
DEV3D_SKILLS_DIR=./skills
# Let employees run shell commands without asking a human first. Leave this
# false unless you are running unattended: a shell is the most dangerous tool
# in the system, and approval is the only thing gating it.
DEV3D_AUTO_APPROVE_SHELL=false
DEV3D_APPROVAL_TIMEOUT_MS=600000
DEV3D_SOFT_SPEND_APPROVAL_USD=1.50
# --- providers (any subset; OpenAI-compatible unless noted) ---
DEEPSEEK_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
ANTHROPIC_API_KEY=
DEV3D_LOCAL_BASE_URL= # e.g. http://127.0.0.1:11434/v1 for Ollama
DEV3D_LOCAL_API_KEY=
# --- employee sandbox and projects ---
DEV3D_WORKSPACE=./workspace
DEV3D_WORKSPACES_ROOT=./workspaces
DEV3D_ALLOW_EXTERNAL_WORKSPACES=true
# --- plugins ---
DEV3D_PLUGINS_DIR=./plugins
DEV3D_PLUGIN_INSTALL_DIR=./data/plugins
DEV3D_ALLOW_PLUGIN_INSTALL=false
# --- persistence and routing ---
DEV3D_DB=./data/dev3d.sqlite
DEV3D_ROUTING=balanced
# --- daily driver limits ---
DEV3D_MAX_CONCURRENCY=4
One line in the real file is worth calling out because the code does not agree with it. .env.example describes DEV3D_RUN_BUDGET_USD as a hard ceiling per run that "the engine halts a run that would exceed". The variable is read into configuration and then never used anywhere else in the source. The real per-run limit is the project's budget.defaultRunUsd, which defaults to 5, overridable per submission with budgetUsd. How a run flows covers the actual mechanics.
First boot checklist
- Confirm the runtime.
node --versionmust be 24 or newer, andpnpm --versionshould match the pinned11.22.0. - Decide the mode before adding keys. If you are exploring, write
DEV3D_LLM_MODE=mockinto.envfirst. It is much easier than discovering the office has been billing you. - Decide where data lives. Set
DEV3D_DBandDEV3D_WORKSPACES_ROOTnow. The database path can never be changed from the UI afterwards, and moving it later means moving history by hand. - Decide the confinement posture.
DEV3D_ALLOW_EXTERNAL_WORKSPACES=falsemakes the workspaces root a hard boundary, at the cost of not being able to point a project at an existing checkout elsewhere. Set it before you create projects, because it is copied into settings on this boot. - Start the orchestrator and read the boot log. Check the mode line, the provider list, the skill count and the tool count. Anything unexpected is cheapest to fix now.
- Read
/api/health. ConfirmllmModeis what you intended, thatconfigStaleis false, and thatstoresayssqliterather thanmemory. - Run one run in mock mode before spending anything. Submit a short brief, watch where it stops, and check that approvals behave the way you want.
- Only then add a key, restart, and confirm the mode flipped and the provider is listed.
The plugin install gate
DEV3D_ALLOW_PLUGIN_INSTALL is false by default, and it stays environment-only. Installing a plugin runs someone else's code inside the orchestrator's own process. The gate is a real switch rather than a silent failure: the Marketplace tab explains that installing is disabled on this installation instead of offering a button that does nothing. Turn it on consciously, and treat it as a decision about the machine rather than about the office.
Two related facts make the gate worth taking seriously. Declarative plugins — models, skills, routing rules, role templates, pipelines, UI panels — ship no code and are limited to what the manifest can express. Code plugins register tools and can subscribe to the event stream, which is why they carry the ten permissions explicitly. And unloading is best effort: disabling a code plugin withdraws its contributions and calls deactivate(), but Node cannot unload an ES module, so the module stays resident in the process until you restart. Nothing is hot-swapped for real.
Common installation mistakes
| Mistake | Consequence | What to do instead |
|---|---|---|
Adding a provider key with DEV3D_LLM_MODE left at auto | The next boot is live and starts charging for turns. | Set mock first, then switch deliberately. |
Editing .env and expecting the running server to notice | Nothing changes. The file is read once, at module load. | Restart. configStale at /api/health will confirm the drift. |
Setting DEV3D_LOCAL_BASE_URL= (empty) to mean "no local provider" | Correct, but by accident: an empty string counts as absent, so the provider is not configured. Leaving the key out entirely is clearer. | Comment the line out or leave it unset. |
Editing DEV3D_MAX_CONCURRENCY after the first boot | No effect — saved settings win from the second boot onwards. | Change it in the Settings page. |
Assuming DEV3D_RUN_BUDGET_USD limits a run | It does not. It is read and unused, despite what .env.example says. | Set the project's defaultRunUsd, or pass budgetUsd on submission. |
Turning on DEV3D_ALLOW_PLUGIN_INSTALL with expectations of hot-swapping | Plugins you disable stay resident until restart. | Treat enabling a plugin as a change that takes effect on the next restart, and verify with the plugin state in the console. |
Running with an unwritable data/ directory | The store degrades to memory: everything works, and all history is lost when the process exits. | Fix permissions before doing anything you want to keep. Check store at /api/health. |
Next: Quick start to run it, Environment for the complete variable list, or Model routing to understand what the keys you just added actually buy.
Where to go next
Linked from
Did this page answer your question?