Skip to content

Configuration reference

Every per-tenant value, where it lives, who can change it, and when a change takes effect. That last column is the one that causes trouble when it is not written down.

Consolidated from the ADRs listed against each row. Where an ADR and this page disagree, the ADR wins.

The three tiers

Set by ADR-0010, unchanged since.

flowchart LR
    S["<b>Secrets</b><br/>Ansible Vault<br/><i>per-tenant vault-id</i>"] --> H["Rendered to the host<br/>at deploy time"]
    I["<b>Infrastructure config</b><br/>tenants/{slug}/vars.yml<br/><i>git, reviewable</i>"] --> H
    B["<b>Business config</b><br/>Registry<br/><i>edited in the Console</i>"] --> R["Read by the brain<br/>per request"]
Tier Lives in Changed by Takes effect
Secrets Ansible Vault, per-tenant --vault-id A PR, then a playbook run On redeploy
Infrastructure infra/tenants/{slug}/vars.yml A PR, then a playbook run On redeploy
Business Registry The BOS Console New work items only

The bottom row is the important one. Business config never applies retroactively to in-flight work items, because each item is pinned to the config version in force when it was created (ADR-0006). Otherwise nobody could explain, six months later, why an old item behaved differently.


Identity and placement

Value Example Tier Effect Notes
tenant_slug greenhills Infra Immutable Appears in DNS, certificates, database names, vault IDs, directory and backup paths. Does not change if the client rebrands (ADR-0022 §7)
region au-mel Infra + registry Redeploy AU residency is hard (ADR-0008). Checked by the provisioning pre-flight gate against inventory, and a mismatch fails closed (ADR-0024 §6)
ansible_host Tailscale address Infra Redeploy Points at the physical box. Moving a tenant to dedicated hardware is this one line (ADR-0024 §1)
Vanity domain portal.client.com.au Infra Redeploy CNAME, never a redirect. Caddy config first, DNS second (ADR-0022 §5)

What is deployed

Value Options Tier Effect Notes
services [core, n8n] or [core, n8n, metabase] Infra Redeploy Metabase only for clients who bought the dashboard product. Around 2 GB each (ADR-0023 §5)
langfuse_enabled true or false Infra Redeploy Host-level, not per tenant. One shared Langfuse with a project per tenant. Around 6 to 8 GB for six containers (web, worker, ClickHouse, MinIO, Redis, Postgres), which is the largest single line on the box. Off by default and not deployed for client-zero. The role exists, so turning it on is config plus a playbook run. Losing it costs nothing permanent, because the durable record is Postgres
langfuse_retention_days 30 Infra Next prune Traces are for debugging the last few days. Retention also bounds residency exposure
Resource limits Per container Infra Redeploy Check the sizing worksheet before adding a dashboard tenant to a loaded host

Behaviour

Value Options Tier Effect Notes
approval_surface review_app or mailbox Business New items only One mode, never both (ADR-0032 §6h). Defaults to mailbox: the draft appears in their own mail client, where attachments are native and the tool is one they already know. In-flight items finish under the mode they started in
scratch_reply_surface review_app or mailbox Business New items only Where a human writes a reply for an item that has no draft. Writing from scratch is editing an empty draft, so the editor already exists either way. Defaults to mailbox, because attachments are the common need and drag-and-drop beats an upload flow
scheduling_mode none, propose or hold Business New items only Whether the draft may offer times, per ADR-0035. Defaults to propose, falling back to none where the connector cannot answer a find_slots question. Selecting a mode the connector cannot support is refused by the Console, never silently downgraded. hold additionally requires a genuine tentative_hold capability and a proven release job
scheduling_constraints Structured predicates Business New items only Tenant scheduling rules using the same {fact, operator, value} shape as everything else (ADR-0004): working hours, day exclusions, travel buffers, skill-to-resource mappings, lead time. Config, not code, so a client's preferences never require a deploy
hold_expiry Duration Business New items only How long a tentative hold survives before release. Two to three days typical. Only meaningful under hold. The release job must run reliably forever: if it fails silently, their calendar fills with phantom holds and nobody notices until a week looks fully booked and is not
store_human_replies true or false Business New items only Whether we keep the body of a reply a human wrote themselves. Off by default: a client opts in to us keeping their staff's writing. Detection is never optional, since reconciliation needs the Message-ID to avoid sending a duplicate. Off means the Draft prompt improves more slowly for that client, because a reply a human wrote from scratch is the richest signal available
multi_category_handling escalate_all or resolve_then_decide Business New items only What happens when one message carries two or more categories. escalate_all is the v1 default and the only behaviour built. resolve_then_decide runs the lookups first and drafts a single reply if nothing is left outstanding. Whether one action closes both cannot be known at classification time, since it depends on what the lookups find
category_selection first_mentioned or by_rank Business New items only Which of several categories becomes primary. The model does not choose: it reports every category present in the order they appear, and code applies this policy. by_rank is the default because the primary is what gets flagged to a human, so the more consequential ask should win
category_rank Ordered list Business New items only Ranking used by by_rank, defaulting from the vertical template. Not the same as priority, which is urgency on the work item. Rank only decides which category is primary
Enabled catalogue actions Subset of the catalogue Business New items only The brain cannot propose an action a tenant has not switched on (ADR-0032 §6f)
Autonomy level assist Business New items only Assist is currently the only defensible setting: there is no eval harness to justify more (OI-060)
actions_requiring_approval Subset of the enabled actions Business New items only The autonomy dial, per action. Defaults to every action at onboarding, so a client sees each one before it acts unsupervised. Relaxed action by action as they gain confidence: typically close_item first, since a wrongly closed item is recoverable and visible in history. Config may only add to the catalogue's floor, never remove from it (see below)
acknowledgement_enabled true or false Business New items only Whether draft_acknowledgement may be proposed. Off by default. An acknowledgement from a firm that always answers within the hour looks like padding. From one where things sit for two days it is the single biggest improvement available, because response time is what customers complain about. When false, an item that would have been downgraded gets no draft at all. Never sent alongside a substantive draft, and it makes no model call
acknowledgement_template Free text Business New items only What the acknowledgement says. Kept short, and it promises a timeframe the client can actually meet
acknowledgement_closes_item true or false Business New items only Whether sending an acknowledgement closes the work item. Some clients treat it as "we have it, we will handle this offline" and the item's job is done. Others want it held open until a real answer goes out. Defaults to false, because closing an item whose customer never got a real answer is the failure to avoid
close_review required, notify or none Business New items only What happens when the brain proposes close_item. required: nothing closes until a human confirms, and the correction is a labelled example for the eval set. notify: closes immediately and appears in a "closed today" list. none: closes silently, visible in history. The middle rung matters, because going from required straight to none loses sight of it entirely
max_brain_calls Integer, low Business New items only Halts an item that is looping and flags it. Counts brain calls, not model calls, since a call that hits the idempotency check costs nothing. A cycling agent is a cost failure and a correctness failure at once (ADR-0032 §3)
claim_timeout Duration Business New items only How long an item stays claimed by whoever opened it. Minutes, not hours. Someone else may read a claimed item but not act on it, and an Owner may force-release, which is recorded. Prevents two people in a shared queue both approving the same draft
Mailbox roles owner, approver, assistant Business Immediate Permission attaches to the mailbox, not the tenant, since a business may run several with different people on each. Owner: everything, including flagged items and config. Approver: routine drafts only, never a flagged one. Assistant: confirm closes and clear the confirmation queue, but nothing that sends. The Owner and Approver boundary is attention_reason being null or set, so it is one field rather than a permission matrix
max_calls_since_human Integer Business New items only Regenerations allowed before an item is flagged and regeneration stops. Counts calls since the last human action, not calls in total, because a total separates none of the three cases: a loop, an abandoned item and a legitimate six-week negotiation all show high totals. Counting since the last human action means a real conversation resets constantly and never approaches the threshold
draft_staleness Duration Business New items only How long a waiting draft may sit before it is regenerated. A draft written on Monday and approved on Thursday goes out saying things that were true on Monday, and ageing does not fix that. Same mechanism as a chase, triggered by time rather than by the customer. Floor 24 hours (below that you regenerate faster than people check a queue), recommended 48 to 72 hours, ceiling 7 days. Every regeneration is two model calls, so a one-day threshold on an item sitting a week costs fourteen. Approvals do not expire: ageing handles visibility, staleness handles content, and nothing auto-approves
priority_age_interval Duration Business New items only How long an item waits in the action queue before its effective priority escalates one level. Computed at read time, so stored priority keeps meaning "how urgent was this message" rather than drifting into "how long has it sat there"
priority_age_cap Priority level Business New items only Ceiling for age escalation. Defaults to one below urgent, so a genuinely urgent item is never buried under an old trivial one
chase_bump Integer Business New items only Levels added when a customer sends another message on an item already awaiting_approval. A chase is evidence rather than inference, so it does not decay. It also invalidates the draft sitting there, which was written before the follow-up arrived
Rules and thresholds Structured predicates Business New items only {fact, operator, value} over the published catalogue. No expression language, no eval (ADR-0004). Precedence: guardrails, then vertical template, then tenant authored, then learned (ADR-0005)
Taxonomy and categories Per client Business New items only Signed off by the client before the first run. Redefining a category in month four silently invalidates months one to three (OI-008)
reply_voice Free text Business New items only Formality, sign-off, standing instructions. Used by the Draft prompt
reply_html_template HTML Business New items only The wrapper a drafted body is rendered into. The model returns text and never authors HTML, which avoids broken markup and keeps branding consistent. Rendered by the brain, since only it reads the registry
reply_signature HTML Business New items only Appended by the template, so it is never something the model has to remember or get right
Tenant mode Per ADR-0013 Business Varies Pilot and demo modes carry expiry semantics

Alerting

Alert definitions are config, and the mechanism is ADR-0034.

An alert is a rule over aggregates, reusing the {metric, operator, value} shape from ADR-0004 but evaluated on a schedule rather than per work item.

Field Options Tier Notes
metric From the published catalogue Business e.g. abandoned_rate, parse_failure_rate
operator, threshold Business Count or percentage is just a choice of metric, not a special case
window Duration Business Rolling period the metric is computed over
scope tenant or global Business Global is what justifies the mechanism: one tenant's parse failures could be their prompt, and every tenant's at once is a provider-side model change no per-tenant view would catch
destination Console, email, Teams Business
kind incident or advisory Business Must be designed in, not added later. Every Console alert so far means "broken, fix today". An abandonment alert means "ring the client". On a shared surface the advisory gets triaged as a bug and dismissed
cooldown Duration Business Mandatory. A condition true for a week notifies once. Alert fatigue kills monitoring faster than missing alerts do
Client-visible true or false Business Off by default. Allowed, and not until the alert set is proven

Platform monitoring is not built on this (ADR-0034 §3). Host metrics have no tenant dimension and a language covering both would serve neither.

A default set ships enabled. A configurable system nobody configures is worse than a fixed one.

Backups

Value Default Tier Effect Notes
Backup schedule 02:30 daily Business Next run Per-tenant, so a client with a compliance obligation is accommodated without a code change (ADR-0026 §6)
Daily retention 30 Business Next prune
Monthly retention 12 Business Next prune
Approval log retention Floor applies Business Next prune Longer on request, never shorter. Shortening it discards our own defence, and the client is not the party it primarily protects. A request to reduce it is a conversation, not a config change (ADR-0026 §6)

Reporting

Value Tier Effect Notes
Metric definitions Business New items only Client-signed-off before the first run (OI-008)
Minutes-saved multiplier Business Next report A model, not a fact. Agreed at Blueprint and recorded, so a challenge is answered with their sign-off rather than our assumption (OI-009)
Sync cadence Business Next sync Get the client to state a number. 15 minutes is almost certainly enough and costs a fraction of the AroFlo API budget (OI-005)

Rules that hold across all of it

Config, not code. New behaviour is config in the control plane, never a per-client fork. If it genuinely needs new logic, the shared vocabulary is extended so every tenant benefits, then switched on as config (ADR-0003).

The control plane holds the map, never client content. Tenant records, resolved config, connector manifests, metadata. No emails, no invoices, no client data (ADR-0006).

Two writers only. Ansible creates the tenant record at bootstrap, and the Console edits config thereafter. The brain reads and never writes.

Business config never applies retroactively. Each work item is pinned at creation to the config and catalogue versions then in force.

The test that matters: could this tenant be rebuilt from the repo and its config rows alone? If not, that is drift, and drift turns ten clients into ten products.

System settings

Application settings that apply to every tenant, so no client has a view on them. They sit in the infrastructure tier, meaning environment variables templated by Ansible, and changing one is a redeploy.

The test for whether something belongs here rather than in business config: would a client ever reasonably ask for a different value? If the answer is no, it is a system setting.

Model calls

Setting Default Notes
llm_max_attempts 3 First attempt plus two retries
llm_backoff_base_seconds 1 Exponential, so roughly 1s then 4s
llm_request_timeout_seconds 60 Per attempt, not total

What is retried matters more than how often. Retrying the wrong class of failure is worse than not retrying at all.

Response Retry?
Timeout, connection reset, 5xx Yes. Transient
429 rate limited Yes, and honour the Retry-After header rather than guessing
400 bad request No. It will fail again, more expensively
401 or 403 No. A credential problem does not fix itself

Honouring Retry-After is the one most often missed. The provider is telling you when to come back, and guessing instead either wastes time or hammers a service that has just asked you to stop.

Brain API

Setting Value Notes
brain_request_timeout_seconds Derived, not set n8n's timeout on a brain call

Derived, because two hand-set numbers drift

n8n's timeout must exceed the worst case the brain can legitimately take. Set it lower and n8n gives up while the brain is still working, then retries a turn already in progress.

So it is computed, never typed:

brain_request_timeout = (llm_max_attempts × llm_request_timeout)
                        + total_backoff
                        + margin

With the current defaults: (3 × 60) + 5 + 30 = 215 seconds.

Enforced in two places:

Where What
Ansible, at provisioning The n8n timeout is templated from the brain settings, so both come from one source
The brain, at startup Asserts the relationship holds, as a backstop for anyone editing config on the box

The failure this prevents produces duplicate work rather than an error, which is why it is worth designing out. The idempotency key catches it (ADR-0032 §6c), but relying on a safety net for something a template can guarantee is the wrong trade.


Approval floors

actions_requiring_approval is the only config value with a floor set in code.

Set by May a tenant change it?
Catalogue requires_approval The action definition No. It is a floor
actions_requiring_approval Tenant config Yes, but only to add actions

Some actions require approval permanently, whatever a client asks: draft_reply and create_job both change something outside our control and cannot become unsupervised (ADR-0032 §6b). Others default to requiring approval during onboarding and may be relaxed later.

Config can only ever make approval more required, never less. A tenant asking to remove approval from draft_reply is a conversation about the "no review, no go" commitment, not a config change, and the Console must refuse it rather than accepting and silently ignoring it.

This mirrors the rule tiering: a lower tier never widens what a higher one narrowed (ADR-0005).

Open

Gaps identified while writing this page are tracked in the register, not here:

  • OI-062: nothing validates the config schema.
  • OI-063: no audit trail on config changes.