Chloe

Reference

Settings

Every setting, its default and what it does, read out of the types the code itself reads. A value that is not a valid choice is refused at startup rather than quietly ignored.

SettingIn .envDefaultWhat it does
model.defaultModel——The model an agent uses when its `agent.ts` does not set one, like "anthropic/claude-sonnet-5". Default: "" (none). With no default, every agent must set its own model, or it fails to load. `npx chloe setup` writes this for you.
model.preferredRoute—[...ROUTES]The order in which chloe tries the ways to reach a model. For each call, it uses the first one that can run the model's provider and is set up on this machine (its program is installed, or its key is set). The others are skipped. Default: each plugin's routes, then `["direct", "gateway"]`, so a subscription (the claude, codex, antigravity and opencode routes) is used before a key that charges per call. `Routes` says what each one is. A model given in `agent.ts` as an AI SDK model, like `anthropic("claude-opus-5-5")`, always goes "direct", whatever this says.
model.models—[]The models you can pick from when you change the model of a chat, an agent or a job (with `/models` in a chat, or on the dashboard). The models the agents and jobs already use are always on the list too. Default: `[]`. Empty lists every model this machine can reach, which is often hundreds. Either way, only models this machine can run are listed.
model.gatewayUrl—"https://ai-gateway.vercel.sh/v1/chat/completions"The address of the gateway for the "gateway" route. Any gateway that takes OpenAI-style chat completions requests works. The address ends in `/chat/completions`. Default: "https://ai-gateway.vercel.sh/v1/chat/completions". chloe also asks this gateway for its list of models and their prices (at `/models` in place of `/chat/completions`). It uses the prices to work out what a call with `model.keys` cost.
model.keyCHLOE_MODEL_KEY
a key, named in the config
—The key for the gateway at `model.gatewayUrl`. It is a secret: put it in `.env`. Default: "" (none). With no key, the "gateway" route is skipped.
model.keysCHLOE_MODEL_KEYS_…
a key, named in the config
{ anthropic: "", openai: "" }Your own API key for each provider, `anthropic` and `openai`, from the provider's console. With a key, chloe can send that provider's models straight to its API (the "direct" route). Each call is charged. Default: "" for both (none). They are secrets: put them in `.env`. The "claude" route never gets the Anthropic key, so the Claude Code CLI stays on your subscription.
model.timeLimit—10The longest one model call may take, in minutes, on any route: one answer from Claude Code, Codex, opencode or a provider's API. A call still going then is stopped (a program is killed) and fails with a message that says so, so a stuck call fails its run instead of holding its job for ever. Default: 10. 0 is no limit. An agent step makes one call per answer, so this is not a limit on the whole step.
model.judgeModel—"anthropic/claude-sonnet-5"The model that grades the agent's answers when you run `npx chloe evals`. Default: "anthropic/claude-sonnet-5".
model.quickModel——The quick model: what an agent on Auto hands a message that does not need its own model, and what gives each new conversation in the dashboard chat a short name. Default: "", which takes the quick model of the route the agent's own model goes by, so the same account pays (the Claude CLI's "haiku", for one). With neither, Auto is not offered and the agent's own model names its conversations.
model.program—{ claude: "claude", codex: "codex", antigravity: "agy", opencode: "opencode" }The command each route that runs a program starts. Change one if the program has another name, or is not on the PATH (then give its full path). If chloe cannot find a program, it skips that route.
email.provider—"resend"The service that sends mail when a job calls `deliverEmail()` without naming a service. Default: "resend" while the Resend plugin is there, and "none" without it. - "none": sends nothing. It only writes the subject and the receivers to the log. Use it for tests, or on a machine with no mail account. - A plugin's, like "resend" (with `connections.resend.api_key`) or "gmail" (from the Google account in `connections.google`). `EmailProviders` lists them. The `gmail.sendEmail` and `resend.sendEmail` tools always use their own service. But with "none", nothing is sent at all, not even by them.
connections.google—{ account: "", client: "", callback: "" }Your Google sign-in. The Gmail, Calendar and Drive tools use it, and so do `email.provider: "gmail"` and the email channel with `mailbox: "gmail"`.
connections.resend—{ api_key: "", alerts: true, email_to: "", email_from: "" }Resend, a service that sends email.
connections.brave—{ api_key: "" }Brave Search, where `web.search` sends a query.
agentsCHLOE_AGENTS_…
a key, named in the config
{}Each agent's channel tokens, under the agent's `id` from its `agent.ts`. Default: `{}` (none). They are secrets: put them in `.env`, like `agents: { shop: { telegram: process.env.CHLOE_AGENTS_SHOP_TELEGRAM } }`. If you rename an agent, rename its entry here too. chloe writes a warning to its log when an entry matches no agent.
serve.host—"127.0.0.1"The network address the server listens on. Default: "127.0.0.1", so only this machine can reach it. "0.0.0.0" listens on every address the machine has. A container needs this, so the machine around it can reach the server. Warning: use "0.0.0.0" only with a proxy in front and the port closed to everything else. The login lockout trusts the visitor's address that the proxy writes. It does not protect you if a stranger can reach the port without going through the proxy.
serve.port—3067The port the server listens on. Default: 3067. If your host gives you the port in an environment variable, write `port: Number(process.env.PORT)`.
serve.url——The address the dashboard is opened at from other machines, like "https://chloe.example.com", when your own web server or private network puts it there. Agents use it to send their owner a link to a file in their memory. Read on each turn, so it needs no restart. Default: "" (unknown, and an agent gives the path on the dashboard).
serve.ownerKeyCHLOE_SERVE_OWNER_KEY
a key, named in the config
—A key that signs a request in as the owner, sent as `Authorization: Bearer <key>`. It is for the copy of chloe that made this one's machine, and shows this copy's agent on its page: that copy writes it here. It reaches everything the owner's login does, so keep it long and in `.env`. Default: "" (none: only the login signs anybody in).
serve.stopAfter—0Minutes with nothing to do before this copy stops itself: no request, no run going or waiting, and no job due in the next few. For a machine of its own, which the copy that made it starts again when it is asked for, so it costs nothing while it sleeps. A copy whose agents bind a channel other than api never stops, because that channel listens. Default: 0 (never).
machine—{} as MachineSettingsThe machine chloe runs in, when it runs in one of its own. The machine plugin fills in every field.
owner——The owner of every agent's runs, written as `channel:id`, like "telegram:123456789". When a job stops to ask a question or to get a tool call approved, it asks this person. A message from this person counts as a message from the owner. Default: "" (none). Then each agent's owner is the first person in the `allowFrom` list of its channel.
node——The folder that holds the `node` program the background service runs, like "/usr/local/bin". Only `npx chloe install` reads it. Default: "" (the folder of the `node` found on the PATH when you run `npx chloe install`).

Where each one goes

A setting is a choice about how the runtime behaves, so it goes in chloe.config.ts under settings, where TypeScript checks it and source control keeps it. What the file leaves out is the default, so there is nothing to write down twice. Every password, key and token goes in .env beside it instead, mode 600 and out of source control. The server reads both again when either changes.

A key is named in the config

The runtime reads no setting from the environment by itself. A key reaches it only where the config hands it over, as process.env and its name in .env, so reading the config shows every key there is and where each comes from. The keys are the rows marked above: the model's key, Resend's, the Google client, and every agent's channel tokens under agents.

settings: {
  model: { key: process.env.CHLOE_MODEL_KEY },
  connections: { resend: { api_key: process.env.CHLOE_CONNECTIONS_RESEND_API_KEY } },
  agents: { tempo: { telegram: process.env.CHLOE_AGENTS_TEMPO_TELEGRAM } },
}

npx chloe setup writes those lines, and a message that says a key is missing says both halves: the name in .env, and the line in the config.

Anything else from .env

Any other setting can be kept out of the file the same way, by handing it over as process.env and a name. The runtime still reads only what the config says. Where things are kept is the one exception: CHLOE_STATE, CHLOE_MEMORY and CHLOE_DB are read from the environment before the config is, and are not settings.

core/settings.ts