CoeOS box — the router
Your tools see a single model called
CoeOS; behind it, the box sends each request to the model that benchmarked best on that kind of work, with your keys, and nothing leaves your network unless you pointed it there.
CoeOS is the AI operating system: Nemo, the desktop client, plus the CoeOS box, the router. This page is about the box (Odyssai-eu/coeos-box); it describes the code as it is, not a roadmap.
In one sentence
Section titled “In one sentence”The box is a gateway with one address and two APIs, OpenAI-compatible and Anthropic-compatible. For each request it picks a competence axis, looks up the model your TMB Settings assign to it, and relays the call, with your keys, to the provider hosting that model: a local OdyssAI-X engine or a cloud endpoint. It does no inference itself.
The three concepts
Section titled “The three concepts”| Term | What it is |
|---|---|
| Competence axis | A benchmarked category of work: python, debug, legal_rgpd, creative, plan_spec, reasoning, fast_tools and the rest of the 18 competence axes of your TMB Settings. |
| TMB Settings | The recipe: which model serves each axis, plus the regime (cloud / local), the default axis and the decider. One file ships with the box (TMB Settings — 100 % Cloud (best-of-all)); you can write your own. |
| Provider | Who hosts a model: openrouter (cloud, key required), odyssai (a local OpenAI-compatible engine such as OdyssAI-X, no key), or any provider you declare with an api_base and a key. |
How the axis is chosen, in order:
- An explicit
x-coeos-axis: <axis>header. The agent knows what it is doing; the box obeys. - The decider, a small model you configure in the settings. Until you do, requests without an explicit header go to the default axis.
- The default axis (
default_axis,code_generalin the shipped file).
You can also name a model explicitly instead of CoeOS: or:<org/model> for
OpenRouter, or a logical name from your registry.
Install
Section titled “Install”Prerequisites: Docker, or Python 3.10 or newer.
git clone https://github.com/Odyssai-eu/coeos-box.git && cd coeos-boxdocker compose up -d --buildcurl -s http://localhost:4600/healthThe box listens on 4600 and serves the same surface on 4800, where
agent clients look for a control plane. /health reports ok, axes_bound,
axes_servable and providers_ready; a fresh box answers "status": "setup"
until a provider is ready, which is normal, not a fault. The console
is at http://<host>:4600/dashboard.
Without Docker: pip install . then coeos-box (--host, --port,
--console-port; 0 disables the second port).
Configuration lives in coeos-config.json (COEOS_CONFIG; in Docker
/data/coeos-config.json on the ./data volume) next to the accounts
database. Provider keys are encrypted at rest (Fernet) with COEOS_MASTER_KEY
or a coeos-master.key file (mode 0600) generated beside the database; keep it
out of the image and out of git.
The first key
Section titled “The first key”As long as no key exists, the instance is open (convenient on
localhost). From the first key issued, /v1/*, /admin/* and the console
require a token, and administration requires an admin account, so create
your first user with --admin or you lock yourself out:
python -m coeos.accounts create-user alice --admin --mode byokpython -m coeos.accounts issue-key alice --name laptop # prints ck_… oncepython -m coeos.accounts listpython -m coeos.accounts revoke ck_1234Tokens authenticate by SHA-256; an encrypted copy stays in the vault so the
operator can read them back from the console. COEOS_API_KEY (in
docker-compose.yml) is an alternative bootstrap, honoured as an implicit
admin token.
Two account modes: byok, the account brings its own provider keys
(PUT /v1/me/providers/{pid}), or platform, it uses the operator’s key. A
non-admin account never falls back to the operator’s global key.
Per-account quotas, requests per minute and tokens per day
(COEOS_RPM_DEFAULT, COEOS_DAY_TOKENS_DEFAULT, or
set-limits <user> --rpm N --day-tokens N), are counted locally (Usage tab,
GET /v1/usage).
Connect your models
Section titled “Connect your models”A local engine (OdyssAI-X)
Section titled “A local engine (OdyssAI-X)”The odyssai provider ships empty and keyless; the address is yours to give:
curl -X PUT http://localhost:4600/admin/providers/odyssai \ -H 'content-type: application/json' \ -d '{"api_base":"http://<your-engine>:8000/v1"}'Or use Providers in the console. The box sends OdyssAI-X the one reasoning
flag it listens to (enable_thinking) and strips internal fields before
relaying.
The cloud
Section titled “The cloud”- OpenRouter: paste the key in the console (Providers) or set
OPENROUTER_API_KEYat first start. - Any OpenAI-compatible provider:
curl -X POST http://localhost:4600/admin/providers \ -H 'content-type: application/json' \ -d '{"id":"myprovider","label":"My provider","api_base":"https://api.example.com/v1","api_key":"…"}'Say who serves what: the TMB Settings
Section titled “Say who serves what: the TMB Settings”In Settings, import the shipped file or compose your own (PUT /admin/coeos
with the file’s JSON does the same). One axis is one line,
{ "key": "python", "label": "Python / scripts", "model": "<registry id>" }.
The registry (models) gives each model its name, provider and native id:
"or": "moonshotai/kimi-k3" for OpenRouter, "endpoint": … for a local or
declared provider. Routing table shows which model answers each axis and
through which provider; Army lists the resolved fleet;
PUT /admin/mapping/axis/{axis} reassigns one axis.
The shipped file is 100 % cloud. A 100 % local profile is composed by hand: declare your engine, register your models, assign them to axes. A hand-written setting needs nothing from us; that is tested.
Use it from your tools
Section titled “Use it from your tools”One address, one token, one model.
export OPENAI_BASE_URL=http://<host>:4600/v1export OPENAI_API_KEY=ck_…| Tool | Setting |
|---|---|
| OpenAI SDK, Aider, Continue, Cline… | base_url = http://<host>:4600/v1, model = CoeOS |
| Claude Code, Anthropic SDK | ANTHROPIC_BASE_URL=http://<host>:4600, key = your ck_ token; /v1/messages and count_tokens are translated (Opus/Sonnet names go to the router, Haiku names to the fast axis) |
| curl | below |
curl "$OPENAI_BASE_URL/chat/completions" \ -H "authorization: Bearer $OPENAI_API_KEY" \ -H 'content-type: application/json' \ -d '{"model":"CoeOS","messages":[{"role":"user","content":"Review this contract against the GDPR…"}]}'Force the axis when the agent knows the step (faster, and safer):
-H 'x-coeos-axis: legal_rgpd'.
Know who answered: read the x-coeos-axis, x-coeos-model and
x-coeos-provider response headers; the Logs tab keeps them.
Reasoning: pass enable_thinking (or thinking / reasoning, per your
SDK); the box translates it to each provider’s field name. If a model refuses
no-think (“reasoning is mandatory”), the box replays without the flag instead
of failing, flags it in an x-coeos-thinking header on non-streaming calls,
and never swaps the model.
What the box guarantees
Section titled “What the box guarantees”Nothing leaves your machine except the request to the provider you chose: no
telemetry, no kill-switch, no mandatory call to us. Nine tests in
tests/test_sovereignty.py fail if any of these gives way:
- no notion of licence, expiry or activation anywhere in the code;
- a box cut off from us still routes;
- the only outbound addresses are your providers and, optionally, a
settings channel you can switch off (
COEOS_UPDATES_DISABLED=1); - an unreachable settings server is a non-event;
- usage is metered for you, never reported;
- what leaves the box contains none of your data;
- you can reassign every axis without our tables, and a hand-written setting needs nothing from us.
Guardian, the confidential-content detector, is a separate sidecar; see its page.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
503 on every axis | the axis’s model points to a provider with no address or no key | Routing table shows the failing axis; set api_base / the key, or reassign the axis |
429 on one axis | free tier of a cloud model | reassign the axis to a paid or local model |
| empty answer from a local reasoning model | no-think was sent under the wrong field name | make sure the provider is odyssai (which gets enable_thinking) or declared with the right thinking_field |
401 after creating a key | the instance locked itself (by design) | use the ck_ token |
403 admin key required | the token belongs to a non-admin account | issue a key for a user created with --admin |
| the console shows no local model | no api_base on odyssai | see “A local engine” above |
API surface
Section titled “API surface”| Area | Endpoints |
|---|---|
| Inference | POST /v1/chat/completions, POST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models, GET /v1/me, GET /v1/usage |
| Discovery (public) | GET /health, GET /.well-known/coeos.json (profile and version), GET /.well-known/inference-engine.json (capability contract), GET /endpoints |
| Administration (admin token) | /admin/providers[/{pid}] (declare, address, keys), /admin/providers/{pid}/test, /admin/platform/providers[/{pid}] (platform keys), /admin/coeos (import settings), /admin/settings*, /admin/mapping[/axis/{axis}], /admin/keys*, /admin/usage, /admin/army, /admin/coeos/decisions |
Read next
Section titled “Read next”- Nemo — the client — the desktop client that talks to the box or to the engine.
- Guardian — the confidential-content sidecar.
- CoeOS on the engine — the same router, built into OdyssAI-X.
- Replica pools — serving many users from one engine.
This page follows the box guide at version 0.23.0.