Skip to content

Configuration ​

Config lives at ~/.cicero/config.yaml. The setup wizard writes it, with a comment on every key it sets. CLI flags override config file values. config.yaml.example is the reference for every option; you do not need to copy it. Run cicero doctor after editing to verify the whole chain.

Privacy ​

yaml
privacy:
  mode: local          # or cloud
  allow: [agent]       # optional: agent, telegram, board

mode is required when privacy is present; allow lists what may reach the network in local mode (Telegram and board need their entry in either mode). It is a declared policy, not a firewall: the wizard offers only what it allows, and cicero doctor (and cicero doctor --json) warns on a non-loopback brain, llm or helper endpoint in local mode, a cloud CLI or ACP agent without agent, Telegram without telegram, and an enabled notify.kanban without board. What it means day to day is in Using Cicero.

Escalation on any front desk ​

brain.escalate adds a second, heavier ACP agent that takes a turn when you say "think hard" (the phrases are in Using Cicero; override them with triggers). The front desk can be any brain, including a local model:

yaml
brain:
  backend: ollama
  mode: subprocess
  ollama_model: gemma4:26b-a4b-it-qat
  escalate:
    binary: bunx
    binary_args: ["@agentclientprotocol/codex-acp@2.0.0"]
    # unset_env: [OPENAI_API_KEY]   # use your subscription login, not a per-token key

Tier presets (legacy defaults) ​

yaml
deployment: local-cuda    # NVIDIA GPU
deployment: local-mlx     # Apple Silicon (the default with no config on a Mac)
deployment: local-cpu     # CPU only

A tier preset fills in any stt, tts and llm block you leave out. The wizard writes those blocks explicitly, and an explicit block always overrides the preset, so the stacks below are what a hand-written config with only deployment: gets.

Full example ​

yaml
tts_enabled: true
hotkey: "ctrl+shift+space" # the current macOS helper supports only this fixed chord
terminal: auto            # auto-detect; or kitty | tmux | wezterm | none
voice: default
barge_in_enabled: false

brain:
  backend: claude-code
  mode: tab-inject
  target_tab: "cicero-brain"

# Explicit backend config (overrides tier defaults):
stt:
  backend: faster-whisper
  port: 8083
  model: Systran/faster-whisper-large-v3-turbo
  timeout_ms: 90000
  language: en
  vocabulary: [Cicero, TypeGPU]
tts:
  backend: kokoro
  port: 8082
  voice: am_onyx
  timeout_ms: 60000
llm:
  backend: ollama
  port: 11434
  model: qwen3.5:0.8b
  timeout_ms: 120000

stt.language is optional and sets the recognition language for audio.cpp, faster-whisper, and Cicero's MLX Whisper sidecar. Omit it to use each backend's default. A well-formed language tag such as en-US, pt-BR, or yue-Hant-HK is accepted. Audio.cpp receives the tag as written (the reference Nemotron configuration uses en-US). Whisper-family providers send only the lowercased primary code (en-US → en). Their sidecars reject codes outside Whisper's supported set with HTTP 400; doctor warns about an unsupported primary code. Language is not supported by the current Wyoming STT integration and is ignored there. stt.vocabulary is an optional list of recognition hints: at most 100 non-empty terms, 64 characters per term, and a joined prompt of at most 1 KiB. The prompt is sent to audio.cpp, faster-whisper, and MLX Whisper; the current audio.cpp Nemotron ASR model ignores prompt, so vocabulary has no effect with Nemotron today, though language does apply. Faster-whisper uses initial_prompt and, where its installed version supports it, hotwords. These hints are best-effort and do not guarantee a spelling. Wyoming vocabulary is not supported and is ignored. If stt_fallback is configured, set its own language and vocabulary keys for that provider; they are not inherited from stt. doctor and status show the language and term count, without printing the terms.

stt.streaming: true opts browser voice into live audio.cpp recognition. It requires a live-capable backend (audiocpp, or soniox, where it is on by default); any other backend is a configuration error. Configure Nemotron's model entry in servers/audiocpp_server.local.json with "mode": "streaming". The setup page writes that mode when its live streaming checkbox is selected. The browser sends bounded, ordered 16 kHz PCM during speech and shows partial captions. At speech end, the final live transcript feeds the ordinary turn pipeline. A failed live stream retries the full WAV via batch STT. The local microphone path continues to use batch STT.

Cloud speech (paid) ​

Cloud recognizers and voices run nothing on this machine and need an API key, either inline as apiKey (written to the owner-only config.yaml) or in the provider's environment variable. cicero setup offers each one on the Hear and Speak steps with a password field, a Test key button that calls the provider's free, read-only key check, and a link to the provider's key page. Choosing one adds speech to privacy.allow; without it, doctor warns that speech leaves the machine. doctor and status prove the key the same way, and every configured key is redacted from logs and the dashboard.

stt.backendKey variableDefault modelHow audio is sent
sonioxSONIOX_API_KEYstt-rt-v5Live over a websocket while you speak (browser voice); a finished WAV otherwise
deepgramDEEPGRAM_API_KEYnova-3Each finished utterance
openaiOPENAI_API_KEYgpt-4o-mini-transcribeEach finished utterance
groqGROQ_API_KEYwhisper-large-v3-turboEach finished utterance
mistralMISTRAL_API_KEYvoxtral-mini-latestEach finished utterance
tts.backendKey variableDefault modelVoices
sonioxSONIOX_API_KEYtts-rt-v2Soniox presets (default Iris); set tts.language for non-English replies
elevenlabsELEVENLABS_API_KEYeleven_multilingual_v2 (setup pre-fills eleven_flash_v2_5)Your cloned or library voice ID

Soniox is the only cloud recognizer that transcribes during speech, so its transcript is ready almost as the turn ends. It is on by default; set stt.streaming: false to send finished utterances instead. One measurement on the ryzen-ai box (a 6-second clip streamed at real time, 2026-10-02): the final transcript arrived 24–41 ms after the last audio. The same clip uploaded whole took about 3.3 s, because Soniox processes uploaded audio at roughly twice real time, so the live path is what makes it fast. The other cloud recognizers are request/response: their latency is the upload plus the provider's processing time, which these docs do not measure.

language and vocabulary apply to cloud recognizers too: Soniox gets the primary language code as a hint and the terms as context; Deepgram gets the tag as written and one keyterm per term (keywords on Nova-2 and older models); OpenAI and Groq get the primary code and the vocabulary prompt; Mistral gets the primary code only.

Cloud voices still synthesize one sentence group per request, the same as the local engines, so a cloud voice starts speaking when its first sentence is fully generated; Cicero does not yet play audio that is still streaming in. That makes the whole-sentence time the number that matters. One measurement from the ryzen-ai box (2026-10-02, one ~3-second English sentence, two runs each):

VoiceFirst byteWhole sentence
ElevenLabs eleven_flash_v2_5240–280 ms240–320 ms
ElevenLabs eleven_v4_turbo~580 ms~600 ms
Soniox tts-rt-v2200–260 ms~2.1 s

Soniox streams its audio at roughly 1.5x real time, so it is fast to start but slow to finish; it will only be competitive once Cicero plays audio as it arrives. For the lowest reply latency today, use ElevenLabs Flash.

An optional classifier: section takes the same shape as llm: and holds a small model apart from the reply model for per-utterance decisions. It is off by default; absence means the features that need it stay off, never that they borrow the reply model. See the classifier backend.

Computer-use file tools default to the daemon's working directory, and public LLMs are refused unless data egress is explicitly enabled:

yaml
compute:
  root: /path/to/allowed/workspace
  max_read_bytes: 262144
  allow_cloud: false

timeout_ms is an absolute request lifetime, including upload, response headers, and body consumption. Defaults are 90 seconds for STT, 60 seconds for TTS, 120 seconds for LLM completions, 10 seconds for Smart-Turn, and 5 seconds for tone. Configured deadlines must be whole milliseconds from 1 through 900000 (15 minutes). Health probes use a fixed 5-second deadline. Successful JSON is capped at 8 MiB, audio at 64 MiB, and diagnostic error prefixes at 16 KiB; streamed LLM tokens remain incremental rather than being buffered into the JSON cap.

Default model stack (tier presets) ​

macOS 14+ on Apple Silicon (MLX, default) ​

ComponentModelSize
RouterQwen3.5-0.8B-MLX-4bit0.8B
TTSQwen3-TTS-12Hz-0.6B-Base0.6B
STTWhisper large-v3-turbo (MLX)809M
BrainClaude Coden/a

CUDA (deployment: local-cuda) ​

ComponentModelVRAM
RouterQwen3.5-4B GGUF via llama.cpp llama-server~2.5 GB
TTSKokoro-82M via Kokoro-FastAPI (am_onyx voice)~1 GB
STTWhisper large-v3-turbo via faster-whisper~3 GB
BrainClaude Coden/a
Total~6.5 GB

All models run locally. No API keys are required. The local-cuda preset keeps the configured backend as llama-cpp: Cicero launches llama-server with its Hugging Face GGUF model ID (or an explicit .gguf path), and does not route through Ollama. Install llama.cpp's llama-server on PATH; repository models download on first launch. TTS alternatives (cloning engines, fallback chains) are in voice cloning; brain backends in brains; the optional classification model in classifier.

Quick intents — your own zero-latency phrases ​

Switchboard transfers, think-lane triggers, and "details" expansion are all zero-latency lexical fast-paths: pattern-matched before the brain ever sees the turn, so the response starts in microseconds. That layer is open to you — map your own phrases (or regexes) to instant spoken answers, with misses always falling through to the model (the pattern layer accelerates, never blocks). {time} and {date} expand at match time:

yaml
quick_intents:
  - phrases: ["what time is it", "time check"]
    reply: "It's {time}."
  - pattern: "^ping\\b"
    reply: "Pong. All systems up."

It's also the natural place to encode phrasings for your language, accent, or STT's quirks as pure YAML edits — contributions welcome.

Custom voice actions ​

User-configurable via ~/.cicero/actions.yaml: custom voice actions that map an utterance pattern to a shell command. Example stubs ship in src/config.ts for "check slack" and "calendar today" — wire these up to your own slack-cli / calendar-cli scripts to make them work. They're documentation-by-example, not built-in features.

yaml
actions:
  search_notes:
    category: cli
    command: "notes search {query} | head -20"
    tts_mode: summary
    examples: ["search notes for {query}"]
    timeout_s: 90        # optional; default 30, maximum 3600
    output_limit: 131072 # optional bytes per stream; default 65536, max 1048576

Commands are operator-authored shell programs, so trusted syntax such as pipelines and redirects works normally. Values captured from speech or a router use {name} placeholders. Cicero passes each value as a quoted positional argument rather than inserting it into shell source, so spaces, quotes, newlines, command substitutions, and separators remain literal data. Placeholders can be unquoted as above; existing single- or double-quoted placeholders are also supported. Use \{name} when the command needs a literal placeholder-like string. Run logs and brain context show the template plus escaped bindings for debuggability; bindings whose names look like credentials, passwords, secrets, or tokens are redacted.

Every command has an absolute wall-clock deadline and bounded stdout/stderr. Use timeout_s for a legitimately long-running custom action and output_limit when it needs more retained diagnostic or result bytes. Both values are validated at startup and on hot reload; unsafe, non-finite, or out-of-range limits are rejected instead of weakening the process boundary.

Built-in actions: focus tab, list tabs.

Validation and startup failures ​

Cicero validates configuration before it starts providers, subprocesses, listeners, or network servers. Malformed YAML, wrong value types, invalid ports, unknown deployment tiers, and malformed actions stop startup with the config path and every detected issue. Unknown STT, TTS, and LLM backend names also fail closed; Cicero never substitutes a different provider for a misspelled name.

ACP turn notices ​

brain.tool_start_notice defaults to true. On the first ACP tool call of a turn, Cicero speaks a short working line if reply audio has not started and a thinking filler is not playing. Set it to false to disable that line. Spoken-confirmation requests matching brain.confirm_tools say a short kind-based approval line on the active voice surface; the spoken line never includes command arguments or paths. Telegram approval buttons remain available when configured.

Switchboard intent settings ​

Top-level switchboard controls the model fallback for brain.lanes.

Optional: intent_url sends classification to the local Laya switchboard sidecar (~30 ms on GPU) instead of the summarizer prompt.

Checkpoint required — bring your own for now. The base Laya model does not route zero-shot; the sidecar needs a switchboard fine-tuned checkpoint. A public checkpoint trained on synthetic data only, plus the fine-tuning recipe, is a planned follow-up.

yaml
switchboard:
  # intent_url: http://127.0.0.1:8096 # opt-in Laya sidecar; needs a fine-tuned checkpoint
  front_desk_aliases: [cicero, jarvis] # names of the main assistant
  intent_min_confidence: 0.7 # finite number, 0 through 1
  intent_timeout_ms: 1500    # integer, 1 through 300000 milliseconds

These defaults apply when omitted. front_desk_aliases replaces the default names used for return-to-front-desk commands and in the intent classifier prompt. Supply 1 to 8 names, each at most 40 characters using ASCII letters, digits, spaces, underscores, or hyphens. Names cannot match a lane name or alias (case insensitive). While a lane is pinned, asking for one of these names means release.

Exact commands bypass classification; other foreground utterances classify before ordinary brain dispatch, adding the classifier's latency (about p50 330 ms on the reference local model), via web_voice.tldr.summarizer_url and summarizer_model (not the separate classifier: backend). JSON-schema output is requested when supported, with strict JSON parsing otherwise. Invalid answers or deadline/error fallbacks are ordinary brain turns. Only request_now: true with sufficient confidence acts. Routing depends on model quality; check it with bun run bench/intent-bench.ts --runs 3 --misses. Existing explicit 600 ms settings remain 600 ms; change them to 1500 to use the new budget. See switchboard behavior.