Why you might want this
Rousseau's provider field is a single scalar (internal/config/config.go Config.Provider). A single rousseau process talks to exactly one provider. When you want more than one — most commonly, claudecli for interactive TUI use because it inherits an OAuth session, and a paid API provider (Bedrock, Anthropic direct, Vertex) for background daemons where subscription-tier claude OAuth is inconvenient — you run two rousseau processes with different config files.
Reasonable pairings:
| Interactive | Unattended | Why |
|---|---|---|
claudecli |
anthropic or bedrock |
OAuth for laptop chat, API key for a VPS daemon. |
claudecli |
vertex |
Same, on GCP. |
anthropic |
openai or ollama |
Compare answers, or fall back to a cheaper/local model for cron. |
claudecli |
openai (OpenRouter) |
Claude in TUI, cheap OpenRouter model for scheduled summaries. |
How rousseau resolves config
config.Load (in internal/config/config.go) applies flag > env > file > default. The file it reads defaults to ~/.config/rousseau/config.yaml, but the --config persistent flag on the root command (internal/cli/root.go) overrides it. That gives you a clean split.
Two-config layout
mkdir -p ~/.config/rousseau
cat > ~/.config/rousseau/chat.yaml <<'YAML'
provider: claudecli
claudecli:
binary: claude
log:
level: info
format: text
YAML
cat > ~/.config/rousseau/cron.yaml <<'YAML'
provider: bedrock
bedrock:
region: eu-west-2
model: anthropic.claude-sonnet-4-6-20250101-v1:0
log:
level: info
format: json
YAML
Run each command with the right file:
rousseau --config ~/.config/rousseau/chat.yaml chat
rousseau --config ~/.config/rousseau/cron.yaml whatsapp --allow YOUR_JID@s.whatsapp.net
Shared vs partitioned state
Both processes point at the same SQLite session store by default (~/.local/share/rousseau/sessions.db) — and that's usually what you want, so the WhatsApp bridge and your TUI chat share history.
To fully partition state, override state.path per config:
state:
path: /home/seb/.local/share/rousseau/chat.db
Cross-process SQLite access is safe because of WAL journaling and the 15-second busy_timeout set by Open() in internal/state/sqlite/store.go.
systemd wiring
Two Quadlet units, one per config. Each unit's Exec= includes --config /home/rousseau/.config/rousseau/<name>.yaml:
Exec=--config /home/rousseau/.config/rousseau/cron.yaml whatsapp --allow ...
See Deployment for the base unit.
Approver policies per config
Different providers deserve different approvals. Interactive claudecli can safely stay in mode: allow_all because Claude Code has its own per-call approval UI. The Bedrock/Anthropic daemon should run mode: pattern with default: deny. Put each under its own YAML.
Testing
Confirm each process talks to the right endpoint:
# Interactive shows the claudecli subprocess path in strace / lsof
lsof -c rousseau | grep -E 'claude|CLAUDE'
# Background shows outbound HTTPS to bedrock-runtime.<region>.amazonaws.com
ss -tanp | grep rousseau
What this does NOT give you
- Not per-request routing. Rousseau will not fall back from one provider to another inside a single turn. Failure of the configured provider surfaces as
whatsapp.handler_failed/turn.failedand the model does not retry against a different provider. That is a roadmap item. - Not shared caching. The Anthropic prompt cache (see
applyCacheMarkersininternal/llm/anthropic/client.go) is per-endpoint. A hit under Anthropic direct is not a hit against Bedrock, even for the same model family.
Related
- Providers — comparison of all five provider types.
- Configuration — every knob.
- Reference: Environment Variables — env-based overrides.
- Guides: Production Deployment.