Page
Configuration
All Choir static resource configuration lives in ~/.choir.d/config.json. Secret values live in ~/.choir.d/secrets.json. Runtime state (sessions, events, memory) lives in Postgres.
Secret Management Pattern
Secret values are never stored in config.json. Every resource that needs a secret contains a "secret" field referencing a named secret in ~/.choir.d/secrets.json (authoritative source for v1). choirctl secret set|delete updates that file.
# Set a secret
echo 'sk-...' | choirctl secret set openrouter-key
# List secret names (never values)
choirctl secret list
# Push the current secrets.json snapshot to choird + running agents
choirctl secret apply
choirctl config apply does not reload secret values; secrets are refreshed only via choirctl secret apply.
Two-Phase Load/Apply
Configuration changes follow a two-phase workflow:
- Edit
config.jsonon disk. choirctl config load– read and validate the file, stage in choird.choirctl config diff– review staged vs running changes (categorized as hot-reloadable or restart-required).choirctl config apply– push hot-reloadable changes to running agents.- For restart-required changes, use
choirctl agent update <agent-id>orchoirctl agent update-all.
Full config.json Reference
global_repo
URL and ref for the shared tools/skills/identity git repository.
"global_repo": {
"url": "git@github.com:org/choir-global.git",
"ref": "main"
}
| Field | Type | Description |
|---|---|---|
url |
string | Git clone URL (SSH or HTTPS) |
ref |
string | Branch or tag to track |
Host-side choird git operations for global_repo and agent image builds inherit the host’s normal git behavior directly (~/.gitconfig, credential helpers, ssh-agent, ~/.ssh/config).
workspaces
Named workspace definitions with explicit host paths. Workspaces are exclusive resources – only one agent may lease a workspace at a time.
"workspaces": {
"main-ws": { "path": "/var/lib/choir/workspaces/main" },
"scratch": { "path": "/var/lib/choir/workspaces/scratch" }
}
| Field | Type | Description |
|---|---|---|
path |
string | Absolute host filesystem path |
models
Named LLM model/provider pairs. Models are shared resources. Must support OpenAI-compatible tool calling.
"models": {
"sonnet": {
"provider": "openrouter",
"model": "anthropic/claude-sonnet-4-20250514",
"endpoint": "https://openrouter.ai/api/v1",
"temperature": 0.7,
"reasoning_effort": null,
"secret": "openrouter-key"
}
}
| Field | Type | Description |
|---|---|---|
provider |
string | Provider identifier (e.g. "openrouter") |
model |
string | Model identifier for the provider |
endpoint |
string | API base URL |
temperature |
number or null | Sampling temperature. null for reasoning models (o1, o3) |
reasoning_effort |
string or null | "low", "medium", "high" for o-series models. null otherwise |
secret |
string | Named secret reference for the API key |
tts
Named text-to-speech provider configurations. Shared resources. v1 supports ElevenLabs only.
"tts": {
"eleven": {
"provider": "elevenlabs",
"model_id": "eleven_multilingual_v2",
"endpoint": "https://api.elevenlabs.io/v1",
"secret": "elevenlabs-key"
}
}
| Field | Type | Description |
|---|---|---|
provider |
string | TTS provider ("elevenlabs") |
model_id |
string | TTS model identifier |
endpoint |
string | API base URL |
secret |
string | Named secret reference |
voice_profiles
Named voice configurations referencing a TTS provider. Shared resources.
"voice_profiles": {
"default-en": {
"tts": "eleven",
"voice_id": "...",
"output_format": "opus_48000_128",
"voice_settings": {
"stability": 0.5,
"similarity_boost": 0.75,
"style": 0.0,
"use_speaker_boost": true,
"speed": 1.0
}
}
}
| Field | Type | Description |
|---|---|---|
tts |
string | Reference to a named TTS provider |
voice_id |
string | Provider-specific voice identifier |
output_format |
string | Audio format (opus_48000_128, mp3_44100_128, pcm_16000, etc.) |
voice_settings.stability |
number | 0.0-1.0. Lower = more expressive |
voice_settings.similarity_boost |
number | 0.0-1.0. Higher = closer to original voice |
voice_settings.style |
number | 0.0-1.0. 0 = minimal latency |
voice_settings.use_speaker_boost |
boolean | Boost speaker similarity (increases latency) |
voice_settings.speed |
number | 0.5-2.0. 1.0 = normal speed |
git_identities
Named git identities with name, email, and auth credentials for agents. Exclusive resources – at most one agent may lease a given identity at a time.
"git_identities": {
"dev-identity": {
"name": "Dev Agent",
"email": "dev@choir.local",
"secret": "git-dev-token",
"ssh_key": "git-dev-ssh-key",
"ssh_secret": "git-dev-ssh-secret"
}
}
| Field | Type | Description |
|---|---|---|
name |
string | Git user.name |
email |
string | Git user.email |
secret |
string | Named secret reference for HTTPS auth credentials. Supports either username:password or token-only values. |
ssh_key |
string | Named secret reference for the SSH private key |
ssh_secret |
string | Named secret reference for the SSH shared secret / key passphrase |
notion_integrations
Named Notion integrations. Exclusive resources.
"notion_integrations": {
"personal-wiki": {
"secret": "notion-personal-key"
}
}
| Field | Type | Description |
|---|---|---|
secret |
string | Named secret reference for the integration token |
email_accounts
Named email accounts with SMTP/IMAP configuration. Exclusive when sharing is "exclusive", shared when "shared".
"email_accounts": {
"primary-email": {
"smtp_host": "smtp.example.com",
"smtp_port": 587,
"imap_host": "imap.example.com",
"imap_port": 993,
"sharing": "exclusive",
"secret": "email-primary-creds"
}
}
| Field | Type | Description |
|---|---|---|
smtp_host |
string | SMTP server hostname |
smtp_port |
integer | SMTP server port |
imap_host |
string | IMAP server hostname |
imap_port |
integer | IMAP server port |
sharing |
string | "exclusive" or "shared" |
secret |
string | Named secret reference |
search
Named search provider configurations. Shared resources.
"search": {
"brave": {
"secret": "brave-api-key"
}
}
| Field | Type | Description |
|---|---|---|
secret |
string | Named secret reference |
embedding
Embedding model configuration. Singleton (one per installation). Used by choird for memory vectorization.
"embedding": {
"provider": "openrouter",
"model": "text-embedding-3-small",
"dimensions": 1536,
"endpoint": "https://openrouter.ai/api/v1",
"secret": "openrouter-key"
}
| Field | Type | Description |
|---|---|---|
provider |
string | Embedding provider |
model |
string | Model identifier |
dimensions |
integer | Output vector dimensions (must match model) |
endpoint |
string | API base URL |
secret |
string | Named secret reference |
gateways
Named Telegram bot instances. Each gateway is a bot with its own token.
"gateways": {
"bot-main": {
"type": "telegram",
"secret": "tg-bot-main-token"
}
}
| Field | Type | Description |
|---|---|---|
type |
string | Gateway type ("telegram") |
secret |
string | Named secret reference for the bot token |
dms
Named DM bindings. Each DM references a gateway and a Telegram user ID. The set of DMs for a bot implicitly forms that bot’s allowlist – messages from unconfigured user IDs are silently ignored.
"dms": {
"admin-dm": { "gateway": "bot-main", "user_id": "123456789", "admin": true },
"user-dm": { "gateway": "bot-main", "user_id": "987654321", "admin": false }
}
| Field | Type | Description |
|---|---|---|
gateway |
string | Reference to a named gateway |
user_id |
string | Telegram user ID |
admin |
boolean | true for full choirctl-equivalent access; false for bound-agent-only |
postgres
PostgreSQL connection configuration. choird manages per-agent schemas automatically.
"postgres": {
"host": "localhost",
"port": 5432,
"database": "choir",
"secret": "postgres-admin-creds"
}
| Field | Type | Description |
|---|---|---|
host |
string | Database hostname |
port |
integer | Database port |
database |
string | Database name |
secret |
string | Named secret reference for admin credentials |
agents
Named agent definitions. Each agent references a per-agent git repo and a set of default resource bindings.
"agents": {
"agent-1": {
"repo": {
"url": "git@github.com:org/choir-agent-1.git",
"ref": "main"
},
"defaults": {
"workspace": "main-ws",
"llm": "sonnet",
"voice_profile": "default-en",
"git_identity": "dev-identity",
"notion": "personal-wiki",
"email": "primary-email",
"dm": "admin-dm"
}
}
}
| Field | Type | Description |
|---|---|---|
repo.url |
string | Per-agent git repo URL |
repo.ref |
string | Branch or tag to track |
defaults.workspace |
string | Default workspace name |
defaults.llm |
string | Default LLM model name |
defaults.voice_profile |
string | Default voice profile name (optional) |
defaults.git_identity |
string | Default git identity name |
defaults.notion |
string | Default Notion integration name (optional) |
defaults.email |
string | Default email account name (optional) |
defaults.dm |
string | Default DM binding name |
All defaults are overridable at agent start time via flags.
Tunable Defaults
Top-level numeric fields for runtime behavior:
"heartbeat_interval_ms": 5000,
"crash_detection_threshold_ms": 10000,
"rate_limit_retry_ms": 1000,
"log_archive_threshold_lines": 100000
| Field | Type | Default | Description |
|---|---|---|---|
heartbeat_interval_ms |
integer | 5000 | Agent heartbeat interval in milliseconds |
crash_detection_threshold_ms |
integer | 10000 | Time without heartbeat before crash detection. Should be >= 2x heartbeat interval |
rate_limit_retry_ms |
integer | 1000 | Default retry delay for LLM rate limits when no Retry-After header |
log_archive_threshold_lines |
integer | 100000 | Line count threshold for choird log archival |
Resource Classification
| Type | Resources | Access Mode |
|---|---|---|
| Shared | Models, TTS, voice profiles, search, email (shared mode) | Any number of agents concurrently |
| Exclusive | Workspaces, git identities, Notion, email (exclusive mode), DMs, browser contexts | Leased to one agent at a time |
Exclusive resources are leased at agent start and released at session end (stop, crash, terminate). If an exclusive resource is already leased, the start is rejected.
For the full design specification, see DESIGN.md.