Skip to content
omni

Server mode

ai serve exposes the router as an OpenAI-compatible API. It uses the same logins, keys, accounts, cooldowns and fallbacks as the CLI.

ai serve                       # http://127.0.0.1:8080/v1
ai serve --host 0.0.0.0 --port 9000 --max-concurrent 4 --timeout 600
ai serve keys [list | add NAME | remove NAME]

Administration API

Off by default. Configures a running server without shell access.

ai serve admin-keys add ops                    # its own key, shown once
ai serve --enable-admin
curl -H "Authorization: Bearer omni-admin-..." http://127.0.0.1:8080/admin/health
Route Purpose
GET/PUT /admin/config Models, fallback order (fallback_order) and routing
GET/POST /admin/keys, DELETE /admin/keys/{id} Provider keys. The id is openrouter or openrouter.work
GET /admin/accounts Accounts, login and cooldown
GET/POST /admin/api-keys, DELETE /admin/api-keys/{name} Keys for /v1 clients
GET /admin/health Server health
POST /admin/accounts/{account}/login Starts a subscription login: returns the link and the attempt id
POST /admin/accounts/{account}/login/complete Finishes the login
DELETE /admin/accounts/{account}/login Cancels, with the id in X-Omni-Login-Attempt
  • Separate keys: a /v1 key never opens /admin, and vice versa. Without an admin key, --enable-admin does not start.
  • Network: from outside this machine, it requires HTTPS through a reverse proxy, which must set X-Forwarded-Proto. --admin-allow-http turns this check off. It only prevents accidental exposure: whoever reaches the port directly can forge the header, so keep the port open only to the proxy. In Docker, the published port counts as external access.
  • Secrets: responses show only the end of provider keys. The exception is a newly created client key, shown once.
  • Protected fields: permission mode, binary paths and config_dir return 403.
  • Careful: this key can turn on paid models (openrouter_paid) and replace provider keys. In Docker, use SMART_AI_ADMIN=1.

Login through the API. {account} is primary and the other Claude accounts, codex, cursor, grok, or codex.NAME for an extra account. An attempt lasts 10 minutes and each account takes one at a time (409).

Tool How to finish
Claude Code Open the link and send {"attempt_id": "...", "code": "..."} with the code you get
Codex, Grok Open the link, type the user_code and send {"attempt_id": "..."}. The answer is 202 until it finishes
Cursor Open the link and send {"attempt_id": "..."}. The answer is 202 until it finishes

Antigravity and key-only tools return 400. Codes and tokens do not appear in responses or logs. The exception is user_code, which has to be typed in the browser.

Parallel writes

In write modes, requests run in parallel, up to --max-concurrent, without touching the original folder.

Folder with git Folder without git
Where it works Worktree on an omni/<id> branch, from the latest commit Copy of the folder, up to 1 GB
Result Commit on the branch, named in X-Omni-Branch. Merging is up to you Copy in ~/.local/share/smart-ai/runs/, named in X-Omni-Copy
Continue Send X-Omni-Branch on the next request Send X-Omni-Copy on the next request
Cleanup The worktree goes at the end. The branch stays until you delete it The copy stays until you delete it
  • No changes: a new branch or copy is discarded and the header is absent. When streaming, the header goes out first and may point at something discarded.
  • Failure or timeout: what was done is kept, and the header comes with the error response. After a server crash, changes are saved on the next start.
  • Concurrency: two requests on the same branch or copy get 409. Only omni/ branches can be continued.
  • Queue: over the limit, a request waits up to --queue-wait seconds (60), with up to --queue-size in line (16). Only then does it get 429.
  • Limitations: uncommitted changes and files ignored by git, such as node_modules, are not in the worktree. Isolation covers files: ports, databases and other services used by commands can still collide, and a command can write outside the folder. Memory counts too: each request is one CLI process. The server's own git commands do not run the repository's hooks or core.fsmonitor. Git filters, such as Git LFS, still run: only serve repositories you trust.
ai serve work --workdir FOLDER                 # lists
ai serve work remove ID --workdir FOLDER       # shows; with --yes, deletes
ai serve work prune --older-than 30 --workdir FOLDER  # also --merged; with --yes, deletes

Without --yes, nothing is deleted. The command only reaches omni/ branches and Omni's copies. An item in use, or a copy changed less than 15 minutes ago, is kept.

As a service

ai serve --install-service [--host H] [--port P] [--mode M] [--workdir FOLDER]
ai serve --service-status
ai serve --uninstall-service

Starts by itself and restarts on failure, with launchd on macOS and systemd user services on Linux. On Windows, in containers and on WSL without systemd, the command says so and installs nothing; run ai serve manually.

  • Secrets: the service file holds no keys. With no saved key, installing creates one and shows it once. Outside 127.0.0.1, a saved key is required first.
  • Environment: only PATH and the Omni folders reach the service. Keys that exist only in your shell do not: save them with ai --key. Installing lists them, along with any CLIs it could not find.
  • Folder and mode: the default is plan, in ~/.local/share/smart-ai/serve. --mode yolo and overwriting a file Omni did not generate require --yes.
  • Logs: ~/.cache/smart-ai/serve.log, rotated, and serve.out.log, cleared on each install.
  • Linux: to start at boot with no open session, run loginctl enable-linger $USER. Installing warns about it but does not do it for you.
  • Updates: ai --update restarts the service. On installs without git, reinstall the service afterwards.

Using the API

from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="omni-...")
client.chat.completions.create(model="omni-auto", messages=[{"role": "user", "content": "Olá"}])

On first start, the server creates a key and shows it once. Only its hash is stored. In a container, you can also set SMART_AI_SERVER_KEY. There is no unauthenticated mode.

Route Purpose
POST /v1/chat/completions Full response or streaming (SSE)
GET /v1/models omni-auto, which uses the router, and omni/<tool> for each configured tool
Response header Meaning
X-Omni-Provider Tool that answered
X-Omni-Usage reported (by the provider) or estimated. Not present when streaming
X-Omni-Ignored-Params Parameters received and ignored

Security:

  • By default, tools run in plan mode, in a temporary folder, whatever SMART_AI_MODE says.
  • To create, edit and run commands: ai serve --mode edits|auto|yolo --workdir FOLDER. The folder is required and nothing asks for approval. Each request works in isolation. Whoever has the key can change the folder and, with commands, reach the account logins.
  • Read-only does not prevent reading files. Anyone with the key can ask a tool to read files on this machine. Configured MCPs may also be available. Treat the key as access to your account.
  • The default is 127.0.0.1. To expose it on a network, use --host and HTTPS through a reverse proxy.
  • Above the connection limit (128 or more, depending on the system), the answer is 503. The start of each request must arrive within 30 seconds. On public exposure, also use the proxy's per-origin limits.
  • Sharing an instance means sharing the same accounts and subscriptions. Check each provider's terms.

Limitations:

  • When the server's mode refuses an action and the tool ends with an error, as Grok does, the response is 403 action_not_allowed.
  • temperature only applies to OpenRouter, DeepSeek and Grok over the API. CLIs ignore it, and it shows up in X-Omni-Ignored-Params.
  • max_tokens is approximate on CLIs (about 4 characters per token).
  • usage is estimated when the provider does not report it.
  • Each request can use a lot of quota, because agent CLIs load their own context. On Codex, a simple "OK" used about 19 thousand input tokens.
  • On CLIs, streaming delivers the answer at once, at the end. Only OpenRouter sends pieces during generation, except when personal data was masked.
  • Fallback is transparent until the first piece of the answer. If the provider fails after that, the client gets an error.
  • tools/function calling only on API providers (OpenRouter, DeepSeek and Grok with a key). The server never runs the tools: the call goes back to your code. Other models return 400, because agent CLIs cannot hand the call back, unless --tool-emulation is on. With omni-auto, the order is OpenRouter, DeepSeek and Grok. On OpenRouter, without openrouter_paid, only free models. When streaming, the call arrives in one piece at the end. Arguments outside the schema make the server try the next provider; only the core of JSON Schema is checked (type, required, enum, properties, items, additionalProperties). functions (old format) returns 400.
  • Experimental: ai serve --tool-emulation (SMART_AI_TOOL_EMULATION=1 in Docker) emulates tools on Claude Code and the Grok CLI, with their own tools switched off. The model writes the call as JSON, and the server checks it and retries once, which spends more quota. It only applies when no API provider is available. Codex and Cursor are left out, because their tools cannot be switched off. The model may get it wrong more often than in native mode, personal data is not masked, and client content can imitate the format.
  • Images return a 400 error.
  • The prompt reaches the tools as a command-line argument, so it is visible in the machine's process list. The limit is 100 KB per request.
  • Legacy Claude profiles, which swap the credentials file, limit the server to one request at a time.

This text comes from the project README. See the original on GitHub