Features Apograph CMS on GitHub

Setting up the copilot

Copilot plugin @apograph/copilot-server@apograph/copilot-admin

The kill switch, the run ceilings, and what you get with no key and no network.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

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=true

There 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:

VariableDefaultBounds
COPILOT_MAX_STEPS30Model calls in one run
COPILOT_WALL_CLOCK_MS300000Wall clock for the whole run
COPILOT_MAX_TOTAL_TOKENS400000Input plus output across every call
COPILOT_MAX_OUTPUT_TOKENS8192A 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:7b
COPILOT_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.