Skip to main content
This page documents every configurable setting in config/local.yaml. For initial setup and environment variables, see Initial configuration.

Secrets and the vault

Secrets are not configured in YAML or .env. They live in an encrypted vault (AES-256-GCM, stored in Postgres) and are resolved at runtime through a scoped accessor. Only four bootstrap values stay in .env: DB_USER, DB_PASSWORD, DATABASE_URL, and SECRET_ENCRYPTION_KEY (the base64 master key that decrypts the vault). Seed a secret by setting it transiently and running the seed script — the value is read from the process environment, encrypted, and stored under its canonical vault key:
ANTHROPIC_API_KEY=sk-ant-... pnpm run seed-vault
Rotate the master key by supplying the old and new keys; every stored secret is re-encrypted under the new key:
SECRET_ENCRYPTION_KEY_OLD=<old-base64> \
SECRET_ENCRYPTION_KEY_NEW=<new-base64> \
DATABASE_URL=<url> \
pnpm exec tsx scripts/rotate-secret-key.ts
After rotation, replace SECRET_ENCRYPTION_KEY in .env with the new key and recreate the container. See Secrets vault for the full workflow, the canonical key names, and backup guidance.

Rate limiting

Curia enforces two independent rate limits in the dispatch layer to protect against message flooding from malfunctioning channels, abusive senders, or compromised integrations.
  • Global limit — total messages per window across all senders, enforced before any policy-gate processing
  • Per-sender limit — messages per sender per window, enforced after policy gates
Excess messages are dropped silently. Violations are audit-logged as message.rejected events with reason global_rate_limited or sender_rate_limited.
# config/local.yaml
dispatch:
  rate_limit:
    window_ms: 60000       # 1-minute fixed window
    max_per_sender: 15     # messages per sender per window
    max_global: 100        # total messages per window across all senders

Working memory summarization

When a conversation grows long, Curia automatically condenses older turns into a summary to prevent silent context-window overflow. You can tune when summarization triggers and how much recent context is preserved.
# config/local.yaml
workingMemory:
  summarization:
    threshold: 20   # number of turns that triggers a summarization pass
    keepWindow: 10  # most-recent turns to retain as active after summarization
Constraints: threshold must be 2 or greater, keepWindow must be 1 or greater, and keepWindow must be less than threshold.

Skill output length

All skill results are sanitized and truncated before being fed to the LLM. Raise this limit if you routinely hit it on large-payload skills (search results, page crawls, long calendar lists). Lower it to reduce LLM context pressure on installations with many concurrent agents.
# config/local.yaml
skillOutput:
  maxLength: 200000   # characters (~50k tokens at 4 chars/token)

Security rules

Curia checks all inbound messages against a set of built-in prompt injection patterns. You can add your own patterns without changing any code.Built-in patterns include: “ignore previous instructions”, “you are now”, “system:”, “act as”, and others.
# config/local.yaml
security:
  extra_injection_patterns:
    - regex: "forget everything above"
      label: "forget everything above"
    - regex: "new\\s+persona"
      label: "new persona"
Avoid patterns with unbounded nested quantifiers such as (a+)+ or (.+)+. These can cause catastrophic backtracking on adversarial input and may freeze the Node.js event loop. Prefer simple bounded patterns. These patterns run on every inbound message.
Each inbound message receives a trust score based on the channel it arrived on, the confidence of the sender’s contact record, and any detected injection risk. You can adjust the relative weights of these factors.
# config/local.yaml
security:
  trust_score:
    channel_weight: 0.4       # weight of channel trust level
    contact_weight: 0.4       # weight of sender contact confidence
    max_risk_penalty: 0.2     # maximum penalty for detected injection risk

  trust_score_floor: 0.2      # messages below this score are held regardless of channel policy
Channel trust levels normalize as: high = 1.0, medium = 0.6, low = 0.3. Contact trust levels follow a separate hierarchy: low < medium < high < ceo. See Contacts and trust for details.
The coordinator checks a sender’s messageTrustScore against these thresholds before taking action on their behalf. All values must be between 0.0 and 1.0. This block is required — startup fails if it is missing or malformed.
# config/local.yaml
security:
  trust_thresholds:
    information_query: 0.2   # answering questions, summaries
    scheduling: 0.5           # calendar changes, meeting requests
    data_export: 0.8          # sharing files, forwarding records
    financial: 0.8            # payments, financial commitments
These thresholds are compiled into the coordinator’s system prompt at startup. Changes take effect on restart. See Contacts and trust for how these thresholds are enforced.
This replaces the previous top-level trust_policy config key, which was dead config (typed but never read by the runtime). If you had overrides under trust_policy, move them to security.trust_thresholds.
Curia redacts common PII from LLM-facing error messages by default: email addresses, phone numbers, credit card numbers, and US Social Security numbers. Add custom patterns for PII types specific to your deployment.
# config/local.yaml
pii:
  extra_patterns:
    - regex: "EMP-\\d{6}"
      replacement: "[EMPLOYEE_ID]"
    - regex: "\\b\\d{3}[-\\s]?\\d{3}[-\\s]?\\d{3}\\b"
      replacement: "[CA_SIN]"
Avoid patterns with unbounded nested quantifiers. These patterns run on every LLM-facing error message.

Contact creation limits

When Curia processes email, it auto-creates provisional contacts for unknown participants. These limits protect against spam campaigns that flood your contacts database.
# config/local.yaml
contact_creation_limits:
  max_per_message: 10    # max new contacts from a single email's participant list
  max_per_hour: 100      # max new contacts per hour, per email account
When a limit is hit, remaining participants are skipped and the CEO is notified (at most once per limit type per hour). Skipped participants will be auto-created if they email directly in the future. See Contacts and trust for full details.

Knowledge graph decay

Curia’s dream engine runs a nightly background pass that reduces the confidence of facts in the knowledge graph based on their decay class. When a fact’s confidence falls below the archive threshold, it is soft-deleted — it no longer appears in queries but is retained in the audit log.
# config/local.yaml
dreaming:
  decay:
    intervalMs: 86400000       # how often the decay pass runs (default: daily)
    archiveThreshold: 0.05     # confidence below which facts are archived
    halfLifeDays:
      permanent: null          # permanent facts are never touched by the decay pass
      slow_decay: 180          # employer, residence — reliable for months
      fast_decay: 21           # coffee preference, current focus — unreliable after weeks

Intent drift detection

For long-running scheduled tasks, Curia periodically compares the agent’s current progress against the original task description. If the agent has drifted significantly from its goal, the task is paused and you are notified. In unattended mode, drift detection blocks — it does not just advise.
# config/local.yaml
intentDrift:
  enabled: true
  checkEveryNBursts: 1          # check on every burst (raise to reduce LLM call frequency)
  minConfidenceToPause: high    # high | medium | low
Confidence thresholds:
ValueBehaviour
highPause only on egregious, unambiguous deviations. Fewest false positives. (Default)
mediumPause on probable deviations. Some false positives expected.
lowPause whenever any drift is detected, regardless of LLM confidence.

Model routing

Agents declare a capability tier rather than a specific model. The model_routing config maps each tier to a concrete model. The provider is inferred automatically from a centralized model registry based on the model name prefix (e.g., claude-* resolves to Anthropic, google/gemini-* resolves to OpenRouter).
# config/local.yaml
model_routing:
  tiers:
    fast:
      model: claude-haiku-4-5
    standard:
      model: claude-sonnet-4-6
    powerful:
      model: claude-sonnet-4-6
To route a tier through OpenRouter (requires OPENROUTER_API_KEY in .env), set the model to an OpenRouter model ID:
model_routing:
  tiers:
    fast:
      model: google/gemini-2.5-flash    # routed through OpenRouter
    standard:
      model: claude-sonnet-4-6           # routed through Anthropic
    powerful:
      model: claude-sonnet-4-6
The ModelRouter validates at startup that every configured model exists in the model registry. Unknown models cause a startup failure with a clear error message.
The provider field was removed from tier config. The model registry infers the provider from the model name — you only need to specify the model.

Delegation and scheduler timeouts

Two related timeouts can be tuned per deployment based on your model’s latency profile.
delegate:
  defaultTimeoutMs: 90000   # how long the coordinator waits for a delegated specialist's reply (90 s default)

scheduler:
  defaultExpectedDurationSeconds: 600   # fallback expected duration for scheduled jobs that don't declare one (10 min default)
Raise delegate.defaultTimeoutMs if specialist agents are timing out under standard-tier models that run more slowly than the defaults assume. scheduler.defaultExpectedDurationSeconds is what the watchdog uses to compute the recovery timeout for a job — increase it if you run long jobs without an explicit expectedDurationSeconds hint. Both values are validated at startup; non-numeric or non-positive values cause a hard startup failure.

Outbound context bridge

Controls TTL for the registry that lets the coordinator link incoming replies to messages it previously sent. See Architecture › Outbound context bridge for the conceptual model.
contextBridge:
  defaultExpiryHours: 6      # auto-registered entries (proactive sends without explicit context_bridge metadata)
  explicitExpiryHours: 24    # entries with explicit context_bridge JSON
Every outbound message auto-registers an entry. Callers can pass a context_bridge input with expires_in_hours to override per-entry; that value wins over both defaults.

Meeting debrief

Configures the meeting-debrief specialist agent. The agent itself is defined in agents/meeting-debrief.yaml; this block controls runtime knobs.
debrief:
  channel: signal               # channel for debrief prompts (signal | email)
  reminderDelayMinutes: 120     # minutes before a reminder is sent for unanswered debriefs
  contextBridgeTtlHours: 48     # how long replies to a debrief prompt remain routable back to the agent
contextBridgeTtlHours overrides the global contextBridge.explicitExpiryHours for the outbound-context entry the agent registers when it sends a debrief prompt — giving the CEO a longer window to reply before the entry expires.

Task management

Tunes the task backlog and heartbeat. The heartbeat is the deterministic loop that wakes idle or stale tasks so open work doesn’t linger.
tasks:
  heartbeatIntervalMinutes: 60   # how often the heartbeat ticks
  heartbeatMaxWakesPerTick: 5    # global cap on agents woken per tick (per-agent cap is always 1)
  idleThresholdHours: 4          # an open/in-progress Curia-owned task is "idle" after this long untouched
  staleWaitThresholdHours: 48    # a waiting/blocked task with no pending wake is "stale" after this long
Whether an agent participates in the task system (auto-pinned task-* skills, the executor-discipline prompt block, and heartbeat eligibility) is set per agent with enable_task_management: true in agents/<name>.yaml — not in this file. The coordinator and ceo-inbox ship with it enabled.

Autonomy engine

The autonomy score is stored in Postgres and controlled via natural-language commands or the CLI — you do not set it in a config file. Ask Curia directly:
  • “What is your current autonomy score?”
  • “Set your autonomy score to 85.”
The score defaults to 75 (Approval Required) on first deployment. Changes take effect immediately without a restart. See Autonomy for the full band reference and Autonomy engine for the governance mechanics.