The copilot is off by default. Enabling a hosted provider sends workspace content to a third party, and that is an operator’s decision to make explicitly.
COPILOT_ENABLED=trueThere is no “which provider” setting
This surprises people, so it is worth stating plainly.
A backend is registered only when it is configured, in the order the composition root lists them, and the first registered one serves a run that names no provider. That is also what the admin’s model picker opens on.
Configure nothing, and the catalogue is empty — a fresh clone has no
copilot. That is legal only while COPILOT_ENABLED is false, which is its
default; enabling the copilot with no backend registered fails at boot.
There is no defaultProvider variable, because a name in a variable could be
misspelt, could point at a backend nobody registered, and would have to be kept
in step with the list on every change. “Configured” is a fact the config can
read directly.
Configuring a backend
# Native Claude. Setting the key is what registers this backend.
ANTHROPIC_API_KEY=sk-ant-…
COPILOT_ANTHROPIC_MODELS=claude-opus-5,claude-sonnet-5,claude-haiku-4-5
# Any OpenAI-wire endpoint. There is no default — an endpoint nobody
# named is a backend that can only time out.
COPILOT_OPENAI_BASE_URL=http://localhost:11434/v1
COPILOT_OPENAI_MODELS=llama3.1
COPILOT_OPENAI_API_KEY=The first model in each comma-separated list is that backend’s default; the rest are what a user can switch to mid-conversation without a redeploy.
Leaving ANTHROPIC_API_KEY empty means there is no claude in the picker at
all — rather than one that appears and then fails on the first message.
See model providers for what each adapter does.
The run ceilings
Three limits bound a single run:
| Variable | Default | Bounds |
|---|---|---|
COPILOT_MAX_STEPS | 30 | Model calls in one run |
COPILOT_WALL_CLOCK_MS | 300000 | Wall clock for the whole run |
COPILOT_MAX_TOTAL_TOKENS | 400000 | Input plus output across every call |
COPILOT_MAX_OUTPUT_TOKENS | 8192 | A single model response |
A “step” is a model call plus any tools it asks for, so maxSteps bounds the
tool loop’s depth, not its width. A smaller local model needs more round
trips than a frontier one to answer the same question, which is the usual reason
to raise it.
Move the three ceilings together
They are checked in the same loop, so lifting one alone relocates the wall rather than removing it. A run given more steps and the same wall clock just stops on “ran longer than allowed” instead of “used too many steps”.
An answer that ends in “This answer is incomplete” is asking for bigger numbers here. Raising them costs tokens, not safety: every step is still authorised and audited, and a change is still recorded before it is applied.
The wall clock is what a user actually waits, so it is the one to be conservative with.
Where the settings live
Environment variables are read by apps/server/apograph.config.ts, which assembles
the plugin’s configuration. The provider connection settings live there rather
than inside the copilot packages, because the plugin is adapter-agnostic by
design and names no provider kind.
Registering two backends of the same kind is just another entry — ollamaFast
and ollamaBig, each with its own model list.
Per-run routing
By default the first registered provider serves any run that names none. To route per run instead, the composition root can supply a resolver:
CopilotPlugin({
providers,
resolve: (ctx) => (isBigWorkspace(ctx.workspaceId) ? 'claude' : 'ollama'),
config
});What a user needs
A user needs copilot:use. Beyond that, a run holds exactly their existing
permissions — there is no separate set of copilot grants to manage, and there is
no per-workspace copilot policy screen. That was removed deliberately; see the
authority model.
Trying it without signing up for anything
There is no keyless path any more. The scripted backend is a private test
fixture, not a shipped adapter, and
COPILOT_ENABLED=true with no backend registered fails at boot rather than
starting a chat that answers out of a script.
Run a local model instead. Ollama over the OpenAI-wire adapter needs no account and no key, and it is a real backend giving real answers:
ollama serve
ollama pull qwen2.5:7bCOPILOT_ENABLED=true
COPILOT_OPENAI_BASE_URL=http://localhost:11434/v1
COPILOT_OPENAI_MODELS=qwen2.5:7b
COPILOT_OPENAI_API_KEY=That is enough to see the chat surfaces, the permission prompt and the proposal review flow end to end.