?

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.failed and the model does not retry against a different provider. That is a roadmap item.
  • Not shared caching. The Anthropic prompt cache (see applyCacheMarkers in internal/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

Type to search 150+ pages. Ranking: BM25 with title/description boost.