Configuration
Ways to provide configuration
There are multiple ways to provide configuration to Erato.
All sources are configuration are merged together into one, so configuration can be specified via e.g. erato.toml, as well as via environment variables.
As of now, there is no specified precedence order when it comes to values provided for the same configuration key that are provided in different sources.
erato.toml files
The erato.toml file is the preferred way to provide configuration for Erato.
The file must be placed in the current working directory of the Erato process.
In the Helm chart, a secret from where the erato.toml file should be mounted can be specified via backend.configFile.
*.auto.erato.toml files
In addition to the main erato.toml file, Erato will also auto-discover all files matching the pattern *.auto.erato.toml in the current working directory.
This is useful if you e.g. want to split out all the secret values (LLM API keys, Database credentials) into a different file that is not checked into source control.
Environment variables
Configuration can also be provided via environment variables.
Though it is not recommended, values for nested configuration can also be provided via environment variables.
In that case, each nesting level is separated by double underscores (__). E.g. CHAT_PROVIDER__BASE_URL is equivalent to chat_provider.base_url.
Common config options
prompt
Several configuration fields accept prompts in the same structure:
- string — plain prompt text used directly as the prompt value.
- object — a prompt source specification with fields:
source:"static"or"langfuse".prompt: prompt text used whensource = "static".prompt_name: Langfuse prompt identifier used whensource = "langfuse".label: optional Langfuse environment/label value.fallback: optional fallback prompt text if the Langfuse prompt cannot be loaded.
The prompt format also supports template placeholders where applicable.
Configuration reference
🚧Work in progress; Covers ~80% of available configuration options 🚧
server
server.encryption_key
Optional encryption key used by the backend for encrypting application data before storing it in the database.
The key must be base64-encoded and must decode to exactly 32 bytes. This matches the AES-256-GCM-SIV key size used by the backend.
To generate a suitable key:
openssl rand -base64 32When providing this value via an environment variable, use SERVER__ENCRYPTION_KEY.
Default value: None
Type: string | None
Example
[server]
encryption_key = "replace-with-openssl-output"runtime_configuration
runtime_configuration.enabled
When enabled, the backend mirrors each loaded erato.toml or
*.auto.erato.toml source file into the database during startup. Mirrored
documents are encrypted at rest with server.encryption_key. The encryption
key value is redacted from the mirrored document before encryption, while the
rest of the source file formatting and contents are preserved.
This setting requires server.encryption_key.
Default value: false
Type: boolean
Example
[runtime_configuration]
enabled = truegeneration
generation.max_tool_calls_per_message
Initial tool-call budget while generating one assistant message. Increase this value for tasks that require longer chains of tool use.
The limit applies across all sequential and parallel tool calls made for the message.
When another tool call would exceed the budget, the chat pauses for your decision. Continue tool calls doubles the total budget, retaining calls already used; repeated extensions double it again. Generate answer now generates a final answer using the results collected so far, with no tools available. Stop ends generation and keeps the partial response. Delegated task runs retain their own tool budgets and backstop behavior.
Default value: 15
Type: integer (greater than 0)
Example
[generation]
max_tool_calls_per_message = 30frontend
frontend.theme
The name of the theme to use for the frontend.
When provided, the theme must be part of the frontend bundle (usually located in the public directory), and placed in the custom-theme directory, under the name provided here.
E.g. if frontend.theme is set to my-theme, the theme must be placed in public/custom-theme/my-theme.
See Theming for more information about themes and theme directory structure.
If not provided, the default bundled theme will be used.
Default value: None
Type: string | None
Example
[frontend]
theme = "my-theme"frontend.translation_po_compilation_mode
Controls how frontend Lingui translation catalogs are served. precompiled serves existing messages.json files. just_in_time compiles sibling messages.po files when messages.json is requested and caches each compiled result for the server lifetime.
Default value: precompiled
Type: precompiled | just_in_time
Example
[frontend]
translation_po_compilation_mode = "just_in_time"frontend.disable_upload
Whether to disable file upload functionality in the UI. This is useful for embedded scenarios where file uploads should not be available.
Default value: false
Type: boolean
Example
[frontend]
disable_upload = truefrontend.max_files
Maximum number of files that can be attached to one chat message. The backend enforces this limit for message submit/edit requests and multipart chat uploads; the frontend uses it to limit file selection.
Default value: 10
Type: integer (greater than 0)
Example:
[frontend]
max_files = 10frontend.disable_chat_input_autofocus
Whether to disable automatic focusing of the chat input field. This prevents unwanted scrolling behavior when navigating to pages with embedded chat widgets.
When enabled, the chat input field will not automatically receive focus on page load/navigation.
Default value: false
Type: boolean
Example
[frontend]
disable_chat_input_autofocus = truefrontend.disable_logout
Whether to hide logout functionality from the UI. This is useful for embedded scenarios where logout should be handled by the parent application.
When enabled, the logout button/option will be hidden from the user interface.
Default value: false
Type: boolean
Example
[frontend]
disable_logout = truefrontend.enable_message_feedback
Whether to enable message feedback functionality in the UI. When enabled, users can submit thumbs up/down ratings with optional comments for individual messages.
This feature allows collecting user feedback on message quality, which can be useful for improving model performance and understanding user satisfaction.
Default value: false
Type: boolean
Example
[frontend]
enable_message_feedback = truefrontend.enable_message_feedback_comments
Whether to enable the comment text field in message feedback. When enabled, users can add optional text comments along with their thumbs up/down ratings.
This setting requires enable_message_feedback to be true to have any effect.
Default value: false
Type: boolean
Example
[frontend]
enable_message_feedback = true
enable_message_feedback_comments = truefrontend.enable_pinned_chats
Whether to enable the pinned chats section in the chat history sidebar. When enabled, users can pin chats from the chat menu and access them in a dedicated section.
Default value: false
Type: boolean
Example
[frontend]
enable_pinned_chats = truefrontend.pinned_chats_limit
Maximum number of pinned chats displayed in the sidebar when pinned chats are enabled.
Default value: 5
Type: integer
Example
[frontend]
enable_pinned_chats = true
pinned_chats_limit = 10frontend.message_feedback_edit_time_limit_seconds
Time limit in seconds for editing message feedback after creation. When set, feedback can only be edited within this time window after being submitted. When not set (default), feedback can be edited at any time.
This setting only affects editing existing feedback - initial feedback submission is always allowed regardless of this setting. Attempts to edit feedback after the time limit will return a 403 Forbidden error.
Default value: None (unlimited editing)
Type: integer (seconds)
Example
[frontend]
enable_message_feedback = true
message_feedback_edit_time_limit_seconds = 300 # 5 minutesfrontend.error_report.show_verbose_assistant_errors
Whether assistant error messages should include the detailed backend-provided error description in the chat UI.
By default, users see a generic assistant error message unless Erato can resolve a more specific user-facing explanation. Enable this option in development or internal-support environments where technical users should see provider or server diagnostic details directly in the message.
Default value: false
Type: boolean
Example
[frontend.error_report]
show_verbose_assistant_errors = truefrontend.error_report.show_copy_error_report
Whether assistant error messages should show a Copy error report button.
When enabled and the backend can render an error report for an errored assistant message, the frontend shows a button that copies the report to the user’s clipboard. This is intended to make bug reports and first-level support triage more consistent without exposing verbose error details inline.
Default value: true
Type: boolean
Example
[frontend.error_report]
show_copy_error_report = falsefrontend.error_report.error_report_template
Handlebars template used by the backend to render the copyable assistant error report.
The template is rendered on backend Message items that have an assistant generation error. Missing optional values are rendered as <none>.
Available template fields:
environmenttimestamp- the errored chat messagecreated_attimestampchat_idassistant_idplatformfacets_activeerror
Default value:
## Error Report
- Environment: {{environment}}
- Timestamp: {{timestamp}}
- Chat ID: {{chat_id}}
- Assistant ID: {{assistant_id}}
- Platform: {{platform}}
- Active facets: {{facets_active}}
Error:
```text
{{error}}
```Type: string
Example
[frontend.error_report]
error_report_template = """
## Assistant Error
- Environment: {{environment}}
- Timestamp: {{timestamp}}
- Chat: {{chat_id}}
- Assistant: {{assistant_id}}
- Platform: {{platform}}
- Active facets: {{facets_active}}
```text
{{error}}
```
"""frontend.sidebar_collapsed_mode
Controls the behavior of the collapsed sidebar state.
When set to "slim", the collapsed sidebar shows icon-only navigation elements. When set to "hidden" (default), the sidebar is completely hidden when collapsed.
Default value: "hidden"
Type: string
Supported values:
"hidden"- Sidebar is completely hidden when collapsed (default behavior)"slim"- Sidebar shows icon-only navigation when collapsed
Example:
[frontend]
sidebar_collapsed_mode = "slim"frontend.sidebar_logo_path
Optional path to a sidebar-specific logo file. When provided, this logo is displayed in the sidebar header instead of the theme logo. Useful for showing a compact logo variant in the sidebar.
Default value: None (uses theme logo)
Type: string | None
Example:
[frontend]
sidebar_logo_path = "/custom-theme/my-company/sidebar-logo.svg"frontend.sidebar_logo_dark_path
Optional path to a sidebar-specific logo file for dark mode. When provided along with sidebar_logo_path, this logo is displayed in the sidebar when dark mode is active.
Default value: None (uses sidebar_logo_path or theme logo)
Type: string | None
Example:
[frontend]
sidebar_logo_path = "/custom-theme/my-company/sidebar-logo.svg"
sidebar_logo_dark_path = "/custom-theme/my-company/sidebar-logo-dark.svg"frontend.additional_environment
Additional values to inject into the frontend environment as global variables.
These will be made available to the frontend Javascript, and added to the window object.
This is a dictionary where each value can be a string or a map (string key, string value).
This may be useful if you are using a forked version of the frontend, which you need to pass some configuration to.
Default value: None
Type: object<string, any>
Example
[frontend]
additional_environment = { "FOO": "bar" }This will be inejcted into the frontend as:
window.FOO = "bar";frontend.extra_frame_ancestors
Additional Content-Security-Policy frame-ancestors sources that may embed the frontend.
Erato always includes 'self'. When the Microsoft Office add-in is enabled, Erato also includes https://outlook.office.com and https://outlook.cloud.microsoft.
Default value: []
Type: array<string>
Example
[frontend]
extra_frame_ancestors = ["https://outlook.cloud.microsoft"]frontend.allow_any_frame_ancestor
Whether to omit the frame-ancestors directive from the frontend Content-Security-Policy header.
Use this only when Erato must be embeddable from unrestricted parent pages.
Default value: false
Type: boolean
Example
[frontend]
allow_any_frame_ancestor = truefrontend.component_kits.directory
Directory containing runtime-loaded frontend component kits.
Each direct subdirectory is treated as one component kit. For each kit, Erato
serves files under /public/component-kits/<kit-name>/ and injects a root-level
index-<hash>.js module script before the main frontend bundle. If a root-level
.css file is present, it is injected as a stylesheet in the same kit order.
The backend infers kit metadata from files and directories. There is no manifest file in the current component-kit format.
Default value: /app/component-kits
Type: string
Example
[frontend.component_kits]
directory = "/app/component-kits"desktop_sidecar
Settings for distributing and configuring the local Erato desktop sidecar.
desktop_sidecar.local_delegation
Native-reviewed local evidence collection is disabled by default. Keep enabled
false until the paired native/backend/frontend release passes recovery and host
qualification. The initial supported operation is erato/local_collect_evidence in an
explicitly configured asynchronous delegated leaf task. It requires
delegation.tasks.enabled, client_tools.durable_operations_enabled and the
task’s frozen tool budget; ordinary chats,
Office actions, arbitrary scripts and local inference are outside this profile.
backend_origin is the canonical HTTPS issuer, for example
https://erato.example.com. signing_key_id selects an Ed25519 key in
verification_keys, a map of 1–8 key IDs to Ed25519 public keys encoded as
base64url without padding (32 decoded bytes per key).
signing_private_key_pem is the matching PKCS#8 PEM secret, held only by the single
backend replica. This task feature supports one backend replica only. Personalized
native downloads receive only the origin and public verification keys. Retain
old public keys for at least the maximum seven-day job lifetime plus the
seven-day receipt/tombstone retention period; update installations before rotation.
The authenticated web/Office shell starts review. Native UI authorizes exactly selected bytes from a frozen snapshot. Existing always-allow file preferences do not approve these snapshots. Before approval, content and derived metadata remain native; the shell holds local handles and coarse status. Installed native code and its private storage are trusted. This profile does not provide OS-enforced confinement. After approved plaintext reaches the frontend, its scripts and normal backend/provider processing can access that plaintext.
Jobs survive pane closure. Approved exports, receipt retries, generic operation
attempts and the parked message survive process restarts. Cloud continuation requires a fresh normal
user session and current authorization; no access/refresh token is stored in the
operation. Cancellation/expiry and generation takeover fence older workers.
Unreferenced approved blobs from a crash before database acceptance require the
storage provider’s orphan cleanup policy. Strict downloads require the native
strict_snapshot_v1 distribution profile; this declaration does not itself
qualify an operating system or Office host.
desktop_sidecar.file_upload_approval
Default approval policy for files retrieved by client tools: never_allow,
ask, or always_allow. Defaults to ask. Users can override this in the
Desktop Sidecar settings; their preference is stored in Erato’s database.
In ask mode, files are previewed locally and individually selected before any
file bytes are uploaded to Erato or shared with the AI provider. Rejected files
are omitted from the tool’s file references. The selection appears inline in the
originating message, using the same consent card as MCP tool approvals. Tool timeouts still apply; configure client_tools.tools.<key>.timeout_ms to allow
enough time to review files (for example, 600000 for ten minutes).
desktop_sidecar.show_settings_tab
Shows a Desktop Sidecar tab in the web frontend’s Preferences dialog. The tab checks the local sidecar connection and can launch the sidecar when it is not running.
Default value: false
Type: boolean
desktop_sidecar.show_settings_tab_office_addin
Overrides desktop_sidecar.show_settings_tab for the Microsoft Office add-in.
When omitted, the add-in inherits the general setting.
Default value: None
Type: boolean | None
Example
[desktop_sidecar]
show_settings_tab = true
# Set this only when the Office add-in should behave differently.
show_settings_tab_office_addin = falsedesktop_sidecar.allowed_origins
Browser origins that are embedded as immutable policy in Windows and macOS
desktop-sidecar downloads. Entries must be normalized, non-loopback origins in lowercase
scheme://host[:port] form; do not include a path, trailing slash, query, or
fragment. An empty list is valid and refuses all production browser origins.
Default value: []
Type: array of strings
Example
[desktop_sidecar]
allowed_origins = ["https://app.example.com"]Desktop sidecar installation identity
Configure a distinct port and directory suffix for each installation:
[desktop_sidecar]
port = 23124
directory_suffix = "-staging"
display_name = "Erato Staging"
# icon = "<base64 PNG bytes, without a data URL prefix>"These immutable fields are injected into personalized downloads. The port must
be 1–65535 (default 23123). It also sets DESKTOP_SIDECAR_URL for both web and
Office add-in frontends, with HTTPS when bootstrap TLS is configured. An explicit
port takes precedence over frontend.additional_environment.DESKTOP_SIDECAR_URL;
local VITE_DESKTOP_SIDECAR_URL remains a development override.
The suffix is appended literally to sidecar configuration directories and autorun
identities, with no separator added. It allows up to 64 ASCII letters, digits,
hyphens and underscores. Omitted or empty suffixes retain existing paths.
The display name defaults to Erato Desktop Sidecar and allows up to 128 bytes,
without control characters. The optional tray icon is a base64 PNG with dimensions
between 1 and 256 pixels. Omit it to use the bundled icon. Windows executable
bootstrap data, including TLS and icon, must fit the 4070-byte payload slot;
use a small, optimized PNG or an installer when more space is needed.
desktop_sidecar.tls
TLS identity added to the immutable bootstrap data of personalized Windows
EXE/MSI and macOS .app.zip downloads. With all PEM fields unset, bootstrap TLS
is disabled. Configure one complete pair: fixed certificate_pem / private_key_pem, or
intermediate_certificate_pem / intermediate_private_key_pem. Values are inline
PEM strings, not file paths. Incomplete, mixed, invalid, or mismatched pairs make
the distribution unavailable. Private-key configuration values are redacted.
Fixed mode copies the supplied server certificate chain (leaf first) and matching
unencrypted key into each download. The leaf must include the 127.0.0.1 IP SAN
and permit TLS server authentication. For example (replace the abbreviated PEM):
[desktop_sidecar.tls]
certificate_pem = """
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
"""
private_key_pem = """
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"""Dynamic mode generates a fresh P-256 leaf key and signs its certificate during
each download’s bootstrap injection. The intermediate chain is included in the
bootstrap; the intermediate private key stays on the backend. Generated leaves
cover 127.0.0.1, ::1, and localhost, with server-authentication usage.
validity_days defaults to 365, accepts 1–3650, and applies only to dynamic mode.
The leaf expires no later than the intermediate chain. For example:
[desktop_sidecar.tls]
intermediate_certificate_pem = """
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
"""
intermediate_private_key_pem = """
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"""
validity_days = 365Set the frontend endpoint to match the HTTPS listener (a build-time
VITE_DESKTOP_SIDECAR_URL override takes precedence):
[frontend.additional_environment]
DESKTOP_SIDECAR_URL = "https://127.0.0.1:23123/erato/sidecar/rpc"Clients must trust the issuing CA and use the HTTPS loopback URL. Bootstrap TLS
automatically enables HTTPS on the sidecar; certificate trust installation and
automatic renewal are separate deployment responsibilities. Re-download and
redeploy before expiry. Copies of one download share its key. Treat personalized
artifacts and installed bootstrap files as secrets. EXE bootstrap JSON has a
4070-byte limit including escaped PEM; oversized identities are rejected. MSI
bootstrap files and macOS application ZIPs do not have that slot limit. macOS
downloads automatically include Contents/Resources/bootstrap.json inside the app
bundle, so normal extraction and installation also installs the TLS identity.
Linux installers must still supply an adjacent bootstrap.json.
desktop_sidecar.distribution.enabled
Enables the desktop-sidecar download page and artifact API. When enabled, Erato loads and validates the complete distribution manifest during startup. Invalid or missing manifests and artifacts are logged and the distribution remains unavailable instead of exposing a partial artifact set.
Default value: false
Type: boolean
desktop_sidecar.distribution.directory
Directory containing the unversioned desktop-sidecar artifact set. It must
contain one manifest.json and every artifact declared by that manifest.
Default value: /app/desktop-sidecar-artifacts
Type: string
Example
[desktop_sidecar.distribution]
enabled = true
directory = "/app/desktop-sidecar-artifacts"desktop_sidecar.organization_configuration.show_tray_icon
Controls whether the desktop sidecar displays its system tray icon. When this value is omitted, a user-specific sidecar setting may take precedence; if neither layer provides a value, the sidecar shows the icon.
The sidecar applies changes at runtime and persists the complete configuration in the platform-default per-user configuration directory.
Default value: None
Type: boolean | None
Example
[desktop_sidecar.organization_configuration]
show_tray_icon = falsei18n
Internationalization-related settings used by the backend when determining user language preferences.
i18n.language
Configuration for language detection and defaulting.
i18n.language.language_detection_priority
Ordered list of language sources to evaluate when resolving a user’s preferred language.
id_token_any expands to checking id_token_xms_pl then id_token_xms_tpl in that order.
Supported values:
id_token_anyid_token_xms_plid_token_xms_tplbrowser_accept_language
Type: array<string>
Default value: ["id_token_any", "browser_accept_language"]
Any value omitted in the order is treated as unsupported for that source and detection continues to the next value.
i18n.language.default_language
Fallback language used after no configured sources match.
Type: string
Default value: "en"
Example:
[i18n]
[i18n.language]
language_detection_priority = ["id_token_xms_tpl", "browser_accept_language"]
default_language = "de"guardrails
Global guardrail definitions used by chat-provider filtering. Guardrail definitions do not run by themselves; enable them through chat_providers.all_providers.guardrails or a provider-specific chat_providers.providers.<provider-id>.guardrails block.
guardrails.prompt_patterns.<pattern-id>
Defines a prompt-injection pattern that can later be selected by ID or by tag.
Type: object
Fields:
type- Matching strategy. Supported values:"fixed"for substring matching,"regex"for Rustregexcrate regular expressions.pattern- The substring or regular expression to detect.language- Optional BCP-47-style language hint such as"en".tags- Tags used to select groups of patterns, for example["input", "prompt_injection"].
Example:
[guardrails.prompt_patterns.ignore_previous_instructions]
type = "fixed"
pattern = "ignore all previous instructions"
language = "en"
tags = ["input", "prompt_injection"]chat_provider (deprecated)
⚠️ Deprecated: Please use chat_providers.providers.<provider-id> instead for multiple provider support and better flexibility.
Configuration for a single chat provider (LLM) that Erato will use for generating responses. This is maintained for backward compatibility but will be automatically migrated to the new chat_providers format.
chat_providers
Configuration for multiple chat providers with priority ordering. This is the recommended way to configure chat providers as it supports multiple providers, fallback mechanisms, and better flexibility.
chat_providers.priority_order
An ordered list of provider IDs that defines the priority for provider selection. The first provider in the list has the highest priority.
Type: array of strings
Required: Yes
Example: ["primary", "backup", "local"]
Embedded PDF image retrieval
enable_embedded_image_retrieval_tool enables the retrieve_embedded_image tool for PDFs included in the conversation. It defaults to false. A provider’s explicit true or false overrides the all_providers setting. Use an image-capable model and enable model_capabilities.supports_image_understanding.
PDF image markers include an embeddedId such as file-embed://<file-id>/0. The model can request that ID to receive a tool response followed by the image. Retrieval is restricted to images offered in the current prompt and checks file access permissions again. Retrieved images remain available in later conversation turns.
[chat_providers.all_providers]
enable_embedded_image_retrieval_tool = true
[chat_providers.providers.text_only]
enable_embedded_image_retrieval_tool = falsechat_providers.all_providers.guardrails.filter_input_prompt_injection
Default prompt-injection filtering settings for all configured chat providers.
If chat_providers.providers.<provider-id>.guardrails is configured for a provider, that provider-level block completely overrides this all_providers block instead of merging with it.
Type: object
Fields:
enabled(default: false) - Enables prompt-injection filtering before requests are sent to the model.filter_pattern_ids(default: []) - Explicit pattern IDs fromguardrails.prompt_patternsto apply.filter_pattern_tags(default: []) - Pattern tags to apply.exclude_pattern_ids(default: []) - Explicit pattern IDs to skip after applyingfilter_pattern_idsandfilter_pattern_tags. Use this to opt out of individual patterns from a broader tag-based rule set.
Example:
[chat_providers.all_providers.guardrails.filter_input_prompt_injection]
enabled = true
filter_pattern_tags = ["input"]
exclude_pattern_ids = ["customer_specific_false_positive"]chat_providers.providers.<provider-id>
A map of provider configurations, where each key is a unique provider ID and the value is a provider configuration object.
Type: object
Required: Yes
Example:
[chat_providers]
priority_order = ["primary", "backup"]
[chat_providers.providers.primary]
provider_kind = "openai"
model_name = "gpt-4"
model_display_name = "GPT-4 (Primary)"
model_description = "Best quality for complex tasks"
model_icon = "builtin-chatgpt"
api_key = "sk-primary-key"
[chat_providers.providers.primary.model_capabilities]
context_size_tokens = 8192
supports_image_understanding = false
cost_input_tokens_per_1m = 30.0
cost_output_tokens_per_1m = 60.0
[chat_providers.providers.backup]
provider_kind = "openai"
model_name = "gpt-3.5-turbo"
model_display_name = "GPT-3.5 Turbo (Backup)"
model_description = "Faster and cheaper fallback model"
model_icon = "simpleicons-anthropic"
api_key = "sk-backup-key"
base_url = "https://api.backup-provider.com/v1/"
[chat_providers.providers.backup.model_capabilities]
context_size_tokens = 16385
supports_image_understanding = false
cost_input_tokens_per_1m = 1.0
cost_output_tokens_per_1m = 2.0Provider Configuration Fields
Each provider in chat_providers.providers supports the following fields:
provider_kind- The type of chat provider ("openai","azure_openai","ollama")model_name- The model identifier for the providervalidate_availability- Optional; whentrue, query the provider’s model listing endpoint and only expose this model when it is returnedmodel_display_name- Optional display name for the model (falls back tomodel_name)model_description- Optional frontend description shown below the model name; translatable viachat_models.<chat-provider-id>.descriptionmodel_icon- Optional frontend icon id, for exampleiconoir-*,simpleicons-*,builtin-chatgpt, or/path/to/icon.svgmodel_name_langfuse- Optional model name for Langfuse reporting (falls back tomodel_name)api_key- Optional API key for authenticationbase_url- Optional custom base URL for the provider APIsystem_prompt- Optional prompt source specification (string or object; seeprompt)system_prompt_langfuse- Optional Langfuse prompt configuration (deprecated, mutually exclusive withsystem_prompt)additional_request_parameters- Optional array of additional request parametersadditional_request_headers- Optional array of additional request headershallucination_suppression- Optional settings to suppress non-substantive outputsguardrails- Optional provider-level prompt-injection filtering settingsmodel_capabilities- Optional configuration for model capabilities and limitationsmodel_settings- Optional configuration for model behavior and generation settings
chat_providers.providers.<provider-id>.provider_kind
The type of chat provider to use.
Type: string
Supported values: "openai", "azure_openai"
Example: "openai"
chat_providers.providers.<provider-id>.model_name
The name of the model to use with the chat provider.
Type: string
Example: "gpt-4o"
chat_providers.providers.<provider-id>.validate_availability
Whether to query the provider’s model listing endpoint before exposing this model in the UI. The request uses the configured base URL, request parameters, request headers, and API key. Header templates are rendered with the current user’s context.
Type: boolean
Default: false
Example: true
chat_providers.providers.<provider-id>.model_display_name
The display name for the model shown to users. If not provided, falls back to model_name.
Type: string | None
Example: "GPT-4 Omni (Production)"
chat_providers.providers.<provider-id>.model_description
An optional description shown below the model display name in the frontend model selector.
The frontend resolves this through a dynamic Lingui translation key using the chat provider id:
chat_models.<chat-provider-id>.description. If no translation exists, the configured value is used as the fallback text.
Type: string | None
Example: "Fast, lower-cost model for everyday questions"
chat_providers.providers.<provider-id>.model_icon
An optional icon identifier shown next to the model in the frontend model selector.
Supported formats:
iconoir-*for icons resolved from theiconoir-reactlibrarysimpleicons-*for icons resolved from thesimple-iconslibrary, for examplesimpleicons-anthropicbuiltin-chatgptfor the built-in ChatGPT/OpenAI mark/path/to/icon.svgfor a custom SVG asset served by the frontend
Type: string | None
Example: "builtin-chatgpt"
chat_providers.providers.<provider-id>.model_name_langfuse
The model name to report to Langfuse for observability and tracing. If not provided, falls back to model_name.
This is useful when the provider’s model name differs from the standardized name used in Langfuse. For example, Azure OpenAI uses deployment names that may differ from the underlying model name, or you may want to use a consistent naming convention across different providers for better analytics in Langfuse.
Type: string | None
Example:
[chat_providers.providers.azure-gpt4]
provider_kind = "azure_openai"
model_name = "gpt-4-deployment-prod-v1" # Azure deployment name
model_name_langfuse = "gpt-4" # Standardized name for Langfusechat_providers.providers.<provider-id>.base_url
The base URL for the chat provider API. If not provided, will use the default for the provider.
Type: string | None
Example: "https://api.openai.com/v1/", "http://localhost:11434/v1/"
chat_providers.providers.<provider-id>.api_key
The API key for the chat provider.
Type: string | None
Example: "sk-..."
chat_providers.providers.<provider-id>.system_prompt
A system prompt specification to use with the chat provider. This sets the behavior and personality of the AI assistant.
Type: string | object | None
A system prompt specification to use with the chat provider. This sets the behavior and personality of the AI assistant. For the shared prompt format, see prompt.
This option is mutually exclusive with system_prompt_langfuse (deprecated). Requires the Langfuse integration to be enabled when using source = "langfuse".
Example:
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4"
system_prompt = "You are a helpful assistant that provides concise and accurate answers."Using Langfuse:
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4"
system_prompt = { source = "langfuse", prompt_name = "assistant-prompt-v1", label = "production", fallback = "You are a helpful assistant." }Using a static object:
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4"
system_prompt = { source = "static", prompt = "You are a helpful assistant that provides concise and accurate answers." }chat_providers.providers.<provider-id>.system_prompt_langfuse
Configuration for using a system prompt from Langfuse prompt management instead of a static prompt.
Deprecated: Use chat_providers.providers.<provider-id>.system_prompt with a prompt source specification instead.
Note: This option is mutually exclusive with system_prompt. Requires the Langfuse integration to be enabled.
chat_providers.providers.<provider-id>.hallucination_suppression.enabled
Controls whether hallucination suppression is enabled for this provider.
When enabled, the model output is monitored for suspicious whitespace-only churn that can indicate degraded generation behavior.
Type: boolean
Default value: false
Example: true
chat_providers.providers.<provider-id>.hallucination_suppression.whitespace_delta_threshold
The number of successive whitespace-only text deltas after which generation is aborted to avoid low-quality or stalled output.
Lower values are more aggressive and may truncate responses earlier; higher values allow more aggressive streaming before suppression triggers.
Type: number
Default value: 20
Example: 15
chat_providers.providers.<provider-id>.guardrails.filter_input_prompt_injection
Provider-specific prompt-injection filtering settings.
When this block is configured for a provider, it completely overrides chat_providers.all_providers.guardrails for that provider. Matching runs after prompt composition and before each request turn is sent to the model. Text content parts are scanned, and tool-call outputs are scanned field-by-field when the tool output is JSON.
If a match is found, generation is aborted with a content-filter-style error that includes the offending pattern ID and the matched text in structured error details.
Type: object | None
Fields:
enabled(default: false) - Enables prompt-injection filtering for this provider.filter_pattern_ids(default: []) - Explicit pattern IDs fromguardrails.prompt_patternsto apply.filter_pattern_tags(default: []) - Pattern tags to apply.exclude_pattern_ids(default: []) - Explicit pattern IDs to skip after applyingfilter_pattern_idsandfilter_pattern_tags. Use this to opt out of individual patterns from a broader tag-based rule set.
Example:
[guardrails.prompt_patterns.ignore_previous_instructions]
type = "fixed"
pattern = "ignore all previous instructions"
language = "en"
tags = ["input", "prompt_injection"]
[chat_providers.providers.main.guardrails.filter_input_prompt_injection]
enabled = true
filter_pattern_ids = ["ignore_previous_instructions"]
filter_pattern_tags = []
exclude_pattern_ids = []chat_providers.providers.<provider-id>.model_capabilities
The model_capabilities field allows you to configure the capabilities and limitations of each chat provider’s model. This information is used for token usage estimation, cost calculation, and determining which features are available.
Type: object | None
Example:
[chat_providers.providers.main.model_capabilities]
context_size_tokens = 128000
supports_image_understanding = true
supports_reasoning = false
supports_reasoning_summary = true
supports_encrypted_reasoning_content = true
supports_audio_input = false
supports_verbosity = false
supports_strict_tool_calling = false
cost_input_tokens_per_1m = 5.0
cost_output_tokens_per_1m = 15.0Fields:
context_size_tokens(default: 1000000) - Maximum number of tokens that may be provided to the model including input messages, system prompt, and filessupports_image_understanding(default: false) - Whether the model supports being provided with images for understandingsupports_audio_input(default: false) - Whether the model supports binary audio input for the audio transcription, dictation, and conversational audio modessupports_reasoning(default: false) - Whether the model supports reasoning mode (e.g., OpenAI o1 family)supports_reasoning_summary(default: true) - Whether the model supports returning reasoning summaries when reasoning is enabledsupports_encrypted_reasoning_content(default: true) - Whether the model supports requesting encrypted reasoning content for stateless reasoning replaysupports_verbosity(default: false) - Whether the model supports providing a verbosity parameter (for future support of advanced models)supports_strict_tool_calling(default: false) - Opt this model/endpoint into native strict arguments for configured client submissions. Currently supported by the OpenAI-compatible Chat Completions and Responses adapters. Other adapters retain local schema validation and bounded corrections. Set this only after verifying the endpoint’s support; it is not inferred from the model name.cost_input_tokens_per_1m(default: 0.0) - Price per 1 million input tokens (unit-less, for cost estimation)cost_output_tokens_per_1m(default: 0.0) - Price per 1 million output tokens (unit-less, for cost estimation)
If not explicitly configured, all capabilities default to conservative values (large context window, no special features, no cost tracking).
chat_providers.providers.<provider-id>.model_settings
The model_settings field allows you to configure how the model should behave during generation. While model_capabilities describes what a model can do, model_settings controls what the model should do.
Type: object | None
Example:
[chat_providers.providers.main.model_settings]
temperature = 0.2
top_p = 0.9
reasoning_effort = "minimal"
verbosity = "high"
compat_omit_strict = false
compat_no_replay_summary = falseFields:
compat_omit_strict(default: false) - When set totrue, requests will omit explicit tool schemastrictfields for MCP tools. This can help when provider gateways reject strict-mode tool metadata.
compat_no_replay_summary(default: false) - When set totrue, plain reasoning summaries from prior turns are not replayed as reasoning content. Use this for provider gateways that require signed or encrypted reasoning replay data and reject unsigned summary-only reasoning blocks.temperature(default: None) - Optional sampling temperature for generation (higher values increase randomness).top_p(default: None) - Optional nucleus sampling parameter to control diversity.reasoning_effort(default: None) - Optional reasoning effort for supported models. Values:"none","minimal","low","medium","high".verbosity(default: None) - Optional verbosity for supported models. Values:"low","medium","high".
chat_providers.summary
Configuration for chat summary generation. This allows you to specify which provider and settings to use when generating chat summaries.
Type: object | None
Example:
[chat_providers.summary]
summary_chat_provider_id = "summarizer"
max_tokens = 150
system_prompt = "Generate a summary for the topic of the following chat, based on the first message to the chat. The summary should be a short single sentence description."chat_providers.summary.summary_chat_provider_id
The ID of the chat provider to use specifically for generating chat summaries. This provider must exist in the chat_providers.providers map.
If not specified, Erato will use the highest priority provider from priority_order for summary generation.
Type: string | None
Default value: None (uses highest priority provider)
Example: "summarizer"
chat_providers.summary.system_prompt
Optional prompt source specification for summary generation.
This prompt is used as the instruction prompt for generating chat summaries and supports the same prompt format as prompt.
In practice, the chat_providers.summary.system_prompt structure matches chat_providers.providers.<provider-id>.system_prompt, including all of its nested fields.
Type: string | object | None
Example (string):
system_prompt = "Generate a short, one-sentence summary for the topic of the following chat, based on the first message."Example (Langfuse):
system_prompt = { source = "langfuse", prompt_name = "summary-instructions", label = "production", fallback = "Generate a short, one-sentence summary..." }Default value: Built-in hard-coded summary instruction from previous behavior.
chat_providers.summary.max_tokens
The maximum number of output tokens to allow when generating chat summaries. This helps control the length and cost of summary generation.
Type: number | None
Default value: 300
Example: 150
Complete Example:
[chat_providers]
priority_order = ["main"]
[chat_providers.summary]
summary_chat_provider_id = "summarizer"
max_tokens = 150
system_prompt = "Generate a short, one-sentence summary for the topic of the following chat, based on the first message to the chat."
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4"
api_key = "sk-main-key"
[chat_providers.providers.summarizer]
provider_kind = "openai"
model_name = "gpt-4o-mini"
model_display_name = "GPT-4o Mini (Summarizer)"
api_key = "sk-summarizer-key"In this example:
- Regular chat interactions use the “main” provider (GPT-4)
- Chat summaries are generated using the “summarizer” provider (GPT-4o Mini)
- Summary generation is limited to 150 output tokens
- Summary prompts can be customized with
chat_providers.summary.system_promptand rendered with template variables - This allows using a faster, cheaper model for summaries while keeping a more powerful model for main interactions
Migration from chat_provider
If you’re using the old chat_provider configuration, it will be automatically migrated to the new format:
- The old provider will be given the ID
"default" - The priority order will be set to
["default"] - A deprecation warning will be logged
Before (deprecated):
[chat_provider]
provider_kind = "openai"
model_name = "gpt-4"
api_key = "sk-key"After (recommended):
[chat_providers]
priority_order = ["main"]
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4"
api_key = "sk-key"file_storage_providers
Configuration for file storage providers that handle uploaded files and documents in Erato. You can configure multiple storage providers with different backends (S3-compatible or Azure Blob Storage) and designate one as the default.
Type: object<string, FileStorageProviderConfig>
Required: At least one file storage provider must be configured.
Example:
[file_storage_providers.primary]
provider_kind = "s3"
display_name = "Primary Storage"
max_upload_size_kb = 102400 # 100 MB
config = { bucket = "erato-files", region = "us-east-1", access_key_id = "AKIAIOSFODNN7EXAMPLE", secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }
# Set which provider to use as default
default_file_storage_provider = "primary"file_storage_providers.<provider-id>.provider_kind
The type of file storage backend to use.
Type: string
Required: Yes
Supported values:
"s3"- Amazon S3 or services that expose an S3-compatible API (e.g., MinIO, DigitalOcean Spaces, Cloudflare R2)"azblob"- Azure Blob Storage
Example: "s3" or "azblob"
file_storage_providers.<provider-id>.display_name
A human-readable name to display in the UI for this storage provider.
Type: string | None
Default: Falls back to the provider ID if not specified
Example: "Primary Storage" or "Azure Backup Storage"
file_storage_providers.<provider-id>.max_upload_size_kb
The maximum individual file size that may be uploaded in kilobytes. This limit is enforced by the backend. When multiple storage providers configure this value, the effective global limit is the maximum configured value across providers.
Type: number | None
Default: 51200 KB (50 MB) when no provider configures a limit
Example: 102400 (100 MB), 10240 (10 MB)
file_storage_providers.<provider-id>.config
Provider-specific configuration object. The available fields depend on the provider_kind.
Type: object
Required: Yes
S3 Provider Configuration
When provider_kind = "s3", the following configuration fields are available:
bucket (required)
The name of the S3 bucket to store files in.
Type: string
Example: "erato-files", "my-company-documents"
endpoint (optional)
The S3-compatible endpoint URL. Use this when connecting to S3-compatible services other than AWS S3 (e.g., MinIO, DigitalOcean Spaces).
If not specified, defaults to AWS S3.
Type: string | None
Example: "https://nyc3.digitaloceanspaces.com", "http://localhost:9000"
region (optional)
The AWS region where the bucket is located.
Type: string | None
Example: "us-east-1", "eu-central-1"
root (optional)
A prefix path within the bucket where files should be stored. Useful for organizing files or sharing buckets across multiple applications.
Type: string | None
Example: "erato-uploads/", "production/files/"
access_key_id (optional)
The AWS access key ID for authentication. If not provided, the SDK will attempt to use environment variables or instance credentials.
Type: string | None
Example: "AKIAIOSFODNN7EXAMPLE"
secret_access_key (optional)
The AWS secret access key for authentication. If not provided, the SDK will attempt to use environment variables or instance credentials.
Type: string | None
Example: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
Complete S3 Example:
[file_storage_providers.s3_primary]
provider_kind = "s3"
display_name = "AWS S3 Primary"
max_upload_size_kb = 204800 # 200 MB
config = { bucket = "erato-production-files", region = "us-west-2", root = "uploads/", access_key_id = "AKIAIOSFODNN7EXAMPLE", secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }
default_file_storage_provider = "s3_primary"MinIO Example:
[file_storage_providers.minio]
provider_kind = "s3"
display_name = "MinIO Local Storage"
max_upload_size_kb = 102400 # 100 MB
config = { bucket = "erato-storage", endpoint = "http://localhost:9000", access_key_id = "minioadmin", secret_access_key = "minioadmin" }
default_file_storage_provider = "minio"Azure Blob Storage Configuration
When provider_kind = "azblob", the following configuration fields are available:
container (required)
The name of the Azure Blob Storage container to store files in.
Type: string
Example: "erato-files", "documents"
endpoint (required)
The Azure Blob Storage endpoint URL for your storage account.
Type: string
Example: "https://mystorageaccount.blob.core.windows.net"
root (optional)
A prefix path within the container where files should be stored. Useful for organizing files or sharing containers across multiple applications.
Type: string | None
Example: "erato-uploads/", "production/files/"
account_name (optional)
The Azure storage account name for authentication. If not provided, the SDK will attempt to use environment variables or managed identity.
Type: string | None
Example: "mystorageaccount"
account_key (optional)
The Azure storage account key for authentication. If not provided, the SDK will attempt to use environment variables or managed identity.
Type: string | None
Example: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
Complete Azure Blob Storage Example:
[file_storage_providers.azure]
provider_kind = "azblob"
display_name = "Azure Blob Storage"
max_upload_size_kb = 153600 # 150 MB
config = { endpoint = "https://mycompany.blob.core.windows.net", container = "erato-files", root = "uploads/", account_name = "mycompany", account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" }
default_file_storage_provider = "azure"Azurite (Azure Blob Storage Emulator) Example:
[file_storage_providers.azurite]
provider_kind = "azblob"
display_name = "Azurite Local Storage"
max_upload_size_kb = 102400 # 100 MB
config = { endpoint = "http://127.0.0.1:10000/devstoreaccount1", container = "erato-storage", account_name = "devstoreaccount1", account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" }
default_file_storage_provider = "azurite"default_file_storage_provider
Specifies which file storage provider to use as the default for new file uploads. This must match one of the keys in the file_storage_providers map.
When multiple file storage providers are configured, this setting is required and determines where new files will be written. All configured providers remain available for reading existing files, which is useful for migration scenarios.
Type: string | None
Required when: Multiple file storage providers are configured
Example: "primary", "s3_main", "azure"
Note: This is a top-level configuration key, not nested under file_storage_providers.
Configuration Example:
# Define multiple providers
[file_storage_providers.primary]
provider_kind = "s3"
display_name = "Primary Storage"
max_upload_size_kb = 102400
config = { bucket = "erato-files", region = "us-east-1" }
[file_storage_providers.backup]
provider_kind = "azblob"
display_name = "Backup Storage"
max_upload_size_kb = 102400
config = { endpoint = "https://backup.blob.core.windows.net", container = "erato-backup" }
# Specify which one is default (top-level key)
default_file_storage_provider = "primary"In this example:
- New files will be uploaded to the “primary” S3 provider
- Existing files can still be read from both “primary” and “backup” providers
- This allows migrating from one storage provider to another without data loss
Multiple Providers Example
You can configure multiple file storage providers. This is currently mainly useful for migration scenarios, where new file uploads should be read + written from a new file storage provider, but past uploaded files should still be available to be read from an older file storage provider.
# Primary S3 storage
[file_storage_providers.s3_primary]
provider_kind = "s3"
display_name = "S3 Primary (US)"
max_upload_size_kb = 204800 # 200 MB
config = { bucket = "erato-us-files", region = "us-east-1", access_key_id = "AKIAIOSFODNN7EXAMPLE", secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }
# Backup Azure storage
[file_storage_providers.azure_backup]
provider_kind = "azblob"
display_name = "Azure Backup (EU)"
max_upload_size_kb = 204800 # 200 MB
config = { endpoint = "https://eratoeu.blob.core.windows.net", container = "erato-eu-files", account_name = "eratoeu", account_key = "..." }
# Set default provider
default_file_storage_provider = "s3_primary"file_type_detection
Controls how Erato decides whether an uploaded file is an image or should be sent to the configured text extractor.
file_type_detection.mode
Default: "naive"
Supported values:
"naive"preserves the existing behavior: known image filename extensions are routed as images; other files use the storage-provided MIME type, with byte-signature inference in the file processor when no MIME type is available..emlfiles remain explicitly treated asmessage/rfc822."magika"loads Magika at startup, detects the type from the file bytes, and uses its MIME type for image routing and text extraction. Detection failures fall back tonaive.
Magika inference runs on a blocking worker, uses one ONNX inter-op and intra-op thread, and caches results for unchanged files. This limits impact on async request handling and avoids repeating inference for cache hits.
The equivalent environment variable is FILE_TYPE_DETECTION__MODE.
[file_type_detection]
mode = "magika"file_uploads
Controls which files users may upload or link, based on the operations Erato can perform on each file type. This policy applies to local uploads and external file links.
file_uploads.upload_allowed_if_supports
Default: ["supported_operations"]
An array of operation selectors. A file is allowed when at least one selector matches one of its available operations:
"extract_text"allows files with text extraction support."analyze_image"allows images when image analysis is available to the user."supported_operations"expands to all operations currently supported by Erato. This is the default and preserves the standard document, image and audio upload behavior."any"allows every file type, including files with no Erato processing operation. If present, it allows all types regardless of the other entries.
An empty array disables new uploads. It does not delete files already attached to chats or assistants.
[file_uploads]
upload_allowed_if_supports = ["supported_operations"]
# Allow all files, including formats Erato cannot read itself:
# upload_allowed_if_supports = ["any"]
# Only allow files Erato can extract text from:
# upload_allowed_if_supports = ["extract_text"]file_processor
Configuration for the file processing engine that extracts text content from uploaded documents. Erato supports multiple file processor backends with different capabilities.
Type: object | None
Default behavior: If not configured, Erato uses the kreuzberg processor by default.
file_processor.processor
The file processor backend to use for extracting text from uploaded files.
Type: string
Default: "kreuzberg"
Supported values:
Since the removal of the
parser-corefile processor, there is only a single optionkreuzberg
"kreuzberg"- Advanced file processor with page-aware markdown extraction. Extracts content with page boundary markers, making it easy to identify which page content comes from.
Example:
[file_processor]
processor = "kreuzberg"Processor Comparison
| Feature | kreuzberg |
|---|---|
| Text extraction | ✅ |
| Page awareness | ✅ |
| Page markers | ✅ (<page number="N">) |
| Output format | Markdown |
| Supported formats | Word, PDF, Excel, PowerPoint, text files |
Page-Aware Extraction
When using kreuzberg, extracted content includes XML-style page markers that clearly indicate page boundaries:
<page number="1">
First page content here...
<page number="2">
Second page content here...This is particularly useful when working with:
- Multi-page legal documents where page references matter
- Academic papers with citations to specific pages
- Technical documentation with page-specific diagrams
- Any scenario where knowing the source page of content is important
Example configuration:
[file_processor]
# Use kreuzberg for page-aware extraction
processor = "kreuzberg"Testing page-aware extraction:
You can generate test PDFs with distinct content per page using the provided script:
cd e2e-tests
./scripts/generate_multipage_test_pdf.py test.pdf 20This creates a PDF with 20 pages, each containing unique identifiers like PAGE-001, PAGE-002, etc.
Audio modes
Erato has three independently configurable audio modes:
audio_dictationenables microphone dictation that writes transcribed text into the chat input without creating an attachment.audio_transcriptionenables recording or uploading audio as a file attachment with transcription metadata.audio_conversationalenables hands-free conversational audio mode with voice activity detection, auto-send, and restart behavior.
Each enabled audio mode needs an audio-capable chat provider. Either set chat_provider_id on the audio mode, or omit it to let Erato pick the first provider in chat_providers.priority_order with model_capabilities.supports_audio_input = true. Provider kinds using the OpenAI Responses adapters do not currently support binary audio input for these modes.
audio_dictation
Controls the microphone dictation flow that populates the chat input without creating an attachment.
Type: object | None
Default behavior: audio_dictation is disabled.
audio_dictation.enabled
Whether this audio mode is available.
Type: bool
Default: false
audio_dictation.chat_provider_id
The provider ID to use for this audio mode. If omitted, Erato uses the first compatible audio-capable provider from the configured provider priority order.
Type: string | None
Default: None
The corresponding provider needs to have the supports_audio_input capability:
Provider example:
[chat_providers.providers.audio-model]
provider_kind = "openai"
model_name = "gpt-4o-audio-preview"
api_key = "sk-..."
[chat_providers.providers.audio-model.model_capabilities]
supports_audio_input = trueaudio_dictation.max_recording_duration_seconds
Maximum recording duration in seconds.
Type: number
Default: 1200
audio_dictation.chunk_duration_seconds
Audio chunk duration in seconds. Must be greater than 0 and no larger than max_recording_duration_seconds.
Type: number
Default: 30
audio_dictation.max_attempts
Maximum provider attempts per chunk, including retries for loop-detection.
Type: number
Default: 3
audio_dictation.initial_backoff_ms
Initial retry backoff in milliseconds.
Type: number
Default: 250
audio_dictation.max_backoff_ms
Maximum retry backoff in milliseconds.
Type: number
Default: 4000
audio_dictation.min_words_for_loop_check
Minimum transcript word count before loop detection runs.
Type: number
Default: 80
audio_dictation.min_unique_word_ratio
Minimum unique-word ratio accepted for non-loop transcripts.
Type: number
Default: 0.25
Constraint: Must be between 0.0 and 1.0.
audio_dictation.max_words_per_minute
Assumed maximum speech rate used to derive per-chunk output token budgets.
Type: number
Default: 200
audio_dictation.tokens_per_word
Token-per-word multiplier used to derive per-chunk output token budgets.
Type: number
Default: 1.5
audio_dictation.output_token_buffer_factor
Safety multiplier used to derive per-chunk output token budgets.
Type: number
Default: 2.0
audio_dictation.fixed_output_token_budget
Fixed output token budget added to the per-chunk heuristic.
Type: number
Default: 500
audio_dictation.min_output_tokens
Optional minimum output token budget for transcription chunks.
Type: number | None
audio_dictation.max_output_tokens
Optional maximum output token cap for transcription chunks.
Type: number | None
If both minimum and maximum are configured, min_output_tokens must be less than or equal to max_output_tokens.
audio_dictation example
[audio_dictation]
enabled = true
chat_provider_id = "audio-model"
max_recording_duration_seconds = 1200
chunk_duration_seconds = 30
max_attempts = 3
initial_backoff_ms = 250
max_backoff_ms = 4000
min_words_for_loop_check = 80
min_unique_word_ratio = 0.25
max_words_per_minute = 200
tokens_per_word = 1.5
output_token_buffer_factor = 2.0
fixed_output_token_budget = 500
min_output_tokens = 32
max_output_tokens = 2048audio_transcription
Controls the recording or upload-to-attachment transcription flow.
All subkeys share the same structure and meaning as audio_dictation:
[audio_transcription]
enabled = true
chat_provider_id = "audio-model"
max_recording_duration_seconds = 1200
chunk_duration_seconds = 30
max_attempts = 3
initial_backoff_ms = 250
max_backoff_ms = 4000
min_words_for_loop_check = 80
min_unique_word_ratio = 0.25
max_words_per_minute = 200
tokens_per_word = 1.5
output_token_buffer_factor = 2.0
fixed_output_token_budget = 500
min_output_tokens = 32
max_output_tokens = 2048audio_conversational
Controls hands-free conversational dictation with voice activity detection, auto-send, and automatic restart after responses.
All subkeys share the same structure and meaning as audio_dictation:
[audio_conversational]
enabled = true
chat_provider_id = "audio-model"
max_recording_duration_seconds = 1200
chunk_duration_seconds = 30
max_attempts = 3
initial_backoff_ms = 250
max_backoff_ms = 4000
min_words_for_loop_check = 80
min_unique_word_ratio = 0.25
max_words_per_minute = 200
tokens_per_word = 1.5
output_token_buffer_factor = 2.0
fixed_output_token_budget = 500
min_output_tokens = 32
max_output_tokens = 2048model_permissions
Configuration for controlling user access to chat providers based on user attributes like group membership. This allows you to implement fine-grained access control, such as restricting premium models to certain user groups or providing different models to different teams.
Type: object | None
Default behavior: If no model permissions are configured, all users have access to all configured chat providers.
model_permissions.rules
A map of permission rules, where each key is a unique rule name and the value is a rule configuration object. Rules are evaluated independently, and if any rule grants access to a chat provider, the user is allowed to use it.
Type: object<string, ModelPermissionRule>
Example:
[model_permissions.rules.basic_access]
rule_type = "allow-all"
chat_provider_ids = ["gpt-4o-mini"]
[model_permissions.rules.premium_access]
rule_type = "allow-for-group-members"
chat_provider_ids = ["gpt-4", "claude-3-opus"]
groups = ["premium-users", "administrators"]
[model_permissions.rules.admin_only]
rule_type = "allow-for-group-members"
chat_provider_ids = ["gpt-4-turbo", "experimental-model"]
groups = ["administrators"]Rule Types
There are two types of permission rules:
allow-all
Grants access to specified chat providers for all users, regardless of their group membership.
Fields:
rule_type- Must be"allow-all"chat_provider_ids- Array of chat provider IDs that this rule grants access to
Example:
[model_permissions.rules.public_models]
rule_type = "allow-all"
chat_provider_ids = ["gpt-4o-mini", "claude-3-haiku"]allow-for-group-members
Grants access to specified chat providers only for users who belong to at least one of the specified groups. User groups are derived from the groups claim in the user’s ID token (see SSO/OIDC configuration).
Fields:
rule_type- Must be"allow-for-group-members"chat_provider_ids- Array of chat provider IDs that this rule grants access togroups- Array of group names/identifiers that are allowed access
Example:
[model_permissions.rules.premium_models]
rule_type = "allow-for-group-members"
chat_provider_ids = ["gpt-4", "claude-3-opus"]
groups = ["premium-subscribers", "enterprise-users"]How Permission Evaluation Works
- No rules configured: All users have access to all chat providers
- Rules configured: For each chat provider, check if any rule grants access:
allow-allrules always grant accessallow-for-group-membersrules grant access only if the user belongs to at least one specified group
- Access granted: If any rule grants access to a chat provider, the user can use it
- Access denied: If no rules grant access to a chat provider, it’s hidden from the user
Complete Example
# Chat provider configuration
[chat_providers]
priority_order = ["basic", "premium", "experimental"]
[chat_providers.providers.basic]
provider_kind = "openai"
model_name = "gpt-4o-mini"
model_display_name = "GPT-4o Mini (Basic)"
api_key = "sk-basic-key"
[chat_providers.providers.premium]
provider_kind = "openai"
model_name = "gpt-4"
model_display_name = "GPT-4 (Premium)"
api_key = "sk-premium-key"
[chat_providers.providers.experimental]
provider_kind = "openai"
model_name = "gpt-4-turbo"
model_display_name = "GPT-4 Turbo (Experimental)"
api_key = "sk-experimental-key"
# Model permissions configuration
[model_permissions.rules.basic_access]
rule_type = "allow-all"
chat_provider_ids = ["basic"]
[model_permissions.rules.premium_access]
rule_type = "allow-for-group-members"
chat_provider_ids = ["premium"]
groups = ["premium-users", "enterprise"]
[model_permissions.rules.experimental_access]
rule_type = "allow-for-group-members"
chat_provider_ids = ["experimental"]
groups = ["beta-testers", "administrators"]In this example:
- All users can access the “basic” model (GPT-4o Mini)
- Premium users and enterprise users can access both “basic” and “premium” models
- Beta testers and administrators can access “basic”, “premium” (if they’re also in premium groups), and “experimental” models
- Regular users (not in any special groups) can only access the “basic” model
Integration with SSO/OIDC
Model permissions rely on group information from the user’s ID token. The groups claim in the JWT token is parsed and used for permission evaluation. See the SSO/OIDC documentation for information on how to configure your identity provider to include group information in ID tokens.
Common group claim formats:
- Array of strings:
["group1", "group2", "group3"] - Single string:
"group1"(automatically converted to array)
If no groups claim is present in the ID token, the user is treated as having no group memberships, and only allow-all rules will grant them access.
mcp_server_permissions
Configuration for controlling user access to configured MCP servers based on group membership. This is evaluated through the same policy engine as model_permissions, and affects which MCP servers can be attached to assistants or used during generation.
Type: object | None
Default behavior: If no MCP server permissions are configured, all users have access to all configured MCP servers.
mcp_server_permissions.rules
A map of permission rules, where each key is a unique rule name and the value is a rule configuration object. Rules are evaluated independently, and if any rule grants access to an MCP server, the user is allowed to use it.
Type: object<string, McpServerPermissionRule>
Example:
[mcp_server_permissions.rules.public_servers]
rule_type = "allow-all"
mcp_server_ids = ["filesystem", "company-search"]
[mcp_server_permissions.rules.premium_servers]
rule_type = "allow-for-group-members"
mcp_server_ids = ["browser-automation", "code-execution"]
groups = ["premium-users", "administrators"]Rule Types
mcp_server_permissions supports the same rule types as model_permissions:
allow-allallow-for-group-members
The only difference is the resource field name:
mcp_server_idsinstead ofchat_provider_ids
Example:
[mcp_server_permissions.rules.restricted_server]
rule_type = "allow-for-group-members"
mcp_server_ids = ["browser-automation"]
groups = ["security-reviewed-users"]facet_permissions
Configuration for controlling which facets are visible and usable for different users. This is evaluated through the same policy engine as model_permissions, and affects facet visibility in /me/facets, starter prompts, and message generation.
Type: object | None
Default behavior: If no facet permissions are configured, all users have access to all configured facets.
facet_permissions.rules
A map of permission rules, where each key is a unique rule name and the value is a rule configuration object. Rules are evaluated independently, and if any rule grants access to a facet, the user is allowed to use it.
Type: object<string, FacetPermissionRule>
Example:
[facet_permissions.rules.default_facets]
rule_type = "allow-all"
facet_ids = ["extended_thinking"]
[facet_permissions.rules.web_search]
rule_type = "allow-for-group-members"
facet_ids = ["web_search"]
groups = ["premium-users", "research-team"]Rule Types
facet_permissions supports the same rule types as model_permissions:
allow-allallow-for-group-members
The only difference is the resource field name:
facet_idsinstead ofchat_provider_ids
Example:
[facet_permissions.rules.image_generation]
rule_type = "allow-for-group-members"
facet_ids = ["image_generation"]
groups = ["design-team"]Integration with SSO/OIDC
Like model_permissions, both mcp_server_permissions and facet_permissions use the groups claim from the user’s ID token for allow-for-group-members rules. If no groups claim is present, only allow-all rules grant access.
integrations
Configuration for external service integrations.
integrations.langfuse
Configuration for the Langfuse observability and prompt management integration.
integrations.langfuse.enabled
Whether the Langfuse integration is enabled.
Default value: false
Type: boolean
integrations.langfuse.base_url
The base URL for your Langfuse instance. Use https://cloud.langfuse.com for Langfuse Cloud or your self-hosted URL.
Required when enabled: Yes
Type: string
Example: "https://cloud.langfuse.com" or "https://langfuse.yourcompany.com"
integrations.langfuse.public_key
Your Langfuse project’s public key. You can find this in your Langfuse project settings.
Required when enabled: Yes
Type: string
Example: "pk-lf-1234567890abcdef"
integrations.langfuse.secret_key
Your Langfuse project’s secret key. You can find this in your Langfuse project settings.
Required when enabled: Yes
Type: string
Example: "sk-lf-abcdef1234567890"
integrations.langfuse.tracing_enabled
Whether to enable detailed tracing of LLM interactions. When enabled, all chat completions will be logged to Langfuse.
Default value: false
Type: boolean
integrations.langfuse.summary_tracing_enabled
Whether to enable tracing of generated chat summaries. Set to false to skip Langfuse traces for summaries while keeping normal chat completion tracing unchanged.
Default value: true
Type: boolean
integrations.langfuse.enable_feedback
Whether to forward user feedback (thumbs up/down with optional comments) to Langfuse as scores. When enabled, feedback submitted by users will be sent to Langfuse and associated with the corresponding message trace for observability and quality tracking.
Default value: false
Type: boolean
Note: This requires frontend.enable_message_feedback to be enabled for users to submit feedback through the UI.
integrations.langfuse.use_otel
Whether to send Langfuse tracing data via Langfuse’s OpenTelemetry ingestion endpoint instead of the legacy ingestion endpoint. When enabled, Erato sends trace and observation data to ${base_url}/api/public/otel/v1/traces using OTLP over HTTP.
Default value: false
Type: boolean
Note: User feedback forwarding still uses Langfuse score ingestion and is controlled separately by integrations.langfuse.enable_feedback.
See the Langfuse Integration documentation for detailed usage instructions and feature descriptions.
integrations.sentry
Configuration for the Sentry error reporting and performance monitoring integration.
integrations.sentry.sentry_dsn
The Sentry DSN (Data Source Name) for your Sentry project. This enables error reporting and performance monitoring.
Default value: None
Type: string | None
Example:
[integrations.sentry]
sentry_dsn = "https://public@sentry.example.com/1"See the Sentry Integration documentation for detailed setup instructions.
integrations.otel
Configuration for OpenTelemetry tracing.
integrations.otel.enabled
Whether OpenTelemetry tracing is enabled.
Default value: false
Type: boolean
integrations.otel.endpoint
The endpoint URL of the OpenTelemetry collector.
Default value: "http://localhost:4318"
Type: string
Example: "http://localhost:4318" or "http://otel-collector:4317"
integrations.otel.protocol
The protocol to use for exporting traces.
Default value: "http"
Type: string
Supported values: "http", "grpc"
integrations.otel.service_name
The service name to identify this application in traces.
Default value: "erato-backend"
Type: string
integrations.ms_office
Configuration for Microsoft Office integrations.
integrations.ms_office.ews_api_endpoint
Optional Exchange EWS API endpoint used by the backend EWS proxy.
When configured, Erato exposes POST /api/v1beta/integrations/ms-office/ews and forwards the incoming Authorization header to this endpoint.
Type: string | None
integrations.ms_office.ews_skip_tls_validation
Whether the backend EWS proxy skips TLS certificate validation when calling the configured EWS endpoint.
This is unsafe and should only be enabled for development or controlled internal environments with non-compliant certificates.
Default value: false
Type: boolean
integrations.ms_office.addin.enabled
Whether the Office add-in frontend and manifest endpoint are enabled.
When enabled, Erato serves the add-in frontend bundle and exposes GET /office-addin/manifest.xml.
Default value: false
Type: boolean
integrations.ms_office.addin.msal_client_id
The Microsoft identity platform client (application) ID used by the Office add-in for OAuth-based authentication.
Required for add-in authentication flows: Yes
Type: string | None
integrations.ms_office.addin.addin_id
The Office add-in manifest ID used when rendering the add-in manifest.
Override this ID to install multiple add-in instances in the same tenant.
Default value: "ee94d041-bd77-446c-8854-421648f50e7c"
Type: string
integrations.ms_office.addin.document.addin_id
The Word add-in manifest ID, served by /office-addin/manifest-document.xml.
It must be nonempty and differ from the Outlook ID in integrations.ms_office.addin.addin_id so both add-ins can be installed in the same catalog.
Default value: "15a5d0e8-e96a-4e26-b6a1-bd8f429468c2"
Type: string
integrations.ms_office.teams.enabled
Whether to enable standalone Teams app distribution. The Teams app reuses the Office add-in frontend bundle and Entra client ID, so integrations.ms_office.addin.enabled and integrations.ms_office.addin.msal_client_id must also be configured.
When enabled, the backend serves a rendered manifest preview at /office-addin/teams/manifest.json and an installable Teams app ZIP at /office-addin/teams/app-package.zip.
Default value: false
Type: boolean
integrations.ms_office.teams.app_id
Stable GUID identifying the standalone Teams app. It must differ from the Outlook and Word add-in IDs.
Default value: "c88180bf-f50d-42dc-8b5e-c27280b6035e"
Type: string
integrations.ms_office.teams.manifest
Branding strings and package icon paths for the Teams app. Icon paths must be safe paths relative to the Office add-in bundle. The website, privacy, and terms URLs must use HTTPS.
Set version to a three-part numeric package version such as 1.0.1 when updating an installed Teams app’s branding or capabilities. Keep integrations.ms_office.teams.app_id unchanged and increase the version for each update. Each component must be between 0 and 65535, without leading zeros. When omitted, the package uses the Erato application release version.
Default values: Erato branding and the color, outline, and small color icons shipped in the Office add-in bundle; no version override.
integrations.ms_office.teams.bot
Conversational Teams bot for personal chats, group chats and channels. When enabled, the backend accepts Bot Framework activities at /api/integrations/ms_teams/messages; point the Azure Bot resource’s messaging endpoint there and route that exact path to the backend around oauth2-proxy (Helm chart: msTeamsBot.directRoute). A skip_auth_regex exemption does not work, because oauth2-proxy strips the Authorization header on every route. The endpoint verifies the Bot Connector’s signed token itself and only accepts activities from tenant_id.
app_id,app_password: the bot’s Entra application (single tenant) and its client secret.tenant_id: the deployment’s Entra tenant.oauth_connection_name: the Azure Bot OAuth connection that yields the user’s delegated Microsoft Graph token. Give it the Graph scopes the web login uses, plus:User.ReadandGroupMember.Read.All(identity and group-based policies),Files.Read.All/Sites.Read.All(files shared in group chats and channels),Chat.ReadandChannelMessage.Read.All(conversation context).token_service_url: the Bot Framework token service; regional bots use for examplehttps://europe.token.botframework.com.
Users are recognized by their Entra object ID, which Erato records on every web or Teams tab login; people who have never opened Erato are asked to do so once. Chats started in Teams are regular Erato chats, visible in the web app.
When integrations.ms_office.teams.enabled is also set, the rendered Teams app contains the bot next to the tab. See the Microsoft Teams bot guide for the Azure setup.
Default values: disabled; token_service_url is "https://token.botframework.com".
integrations.ms_office.teams.bot (optional behavior)
sso_app_id,sso_resource: set both to sign users in silently. The rendered manifest’swebApplicationInfothen points at this app and resource, which must equal the OAuth connection’s token exchange URL (typicallyapi://botid-<app_id>). Without them, users confirm sign-in once with a button. Verify that the Teams tab still signs in when overriding them.public_base_url: HTTPS origin used for links from Teams back to Erato chats.assistant_id: assistant that new Teams chats are bound to.context_message_count: preceding group chat or channel thread messages attached as context when the bot is mentioned (0disables). Default20.streaming: stream answers in personal chats. Defaulttrue.security_groups_only: resolve only security groups for the policy engine; match thegroupsclaim configuration of the web login. Defaulttrue.
Type: object
integrations.ms_office.addin.document.manifest.display_name
The nonempty display name shown for the Word add-in. Other branding, such as the provider name and icons, inherits from integrations.ms_office.addin.manifest.
Default value: "Erato for Documents"
Type: string
integrations.ms_office.addin.document.manifest.description
The nonempty description shown for the Word add-in.
Default value: "Erato AI assistant for Word documents"
Type: string
integrations.ms_office.addin.msal_authority
The Microsoft identity authority URL used for MSAL authentication.
Default value: "https://login.microsoftonline.com/common"
Type: string
integrations.ms_office.addin.serve_bundle_legacy_path
Whether to also serve the Office add-in bundle under /office-addin in addition to /public/platform-office-addin.
This is useful for deployments that still reference legacy asset URLs.
Default value: true
Type: boolean
integrations.ms_office.addin.frontend_bundle_path
Filesystem path to the Office add-in frontend bundle.
The path must contain the Office add-in frontend artifacts including manifest.xml.
Default value: "./public/platform-office-addin"
Type: string
integrations.ms_office.addin.default_settings
Default switching behavior for the Office add-in. These values apply only until a user chooses their own preferences in the add-in.
mode controls what happens when switching to another Outlook item. Supported
values are resume, ask, and new. compose_inherits_from_read controls
whether replies and forwards inherit the current read-mail chat.
Default value: { mode = "resume", compose_inherits_from_read = true }
Type: object
integrations.ms_office.addin.manifest
Branding values used when rendering the Office add-in manifest.
The string fields control the Office provider name, add-in display name, add-in description, support URL, ribbon group label, ribbon button label, and ribbon button description. Icon path fields control the top-level manifest icons and the 16px, 32px, and 80px ribbon icons.
Icon paths can be absolute http/https URLs, deployment-root-relative paths such as /public/common/custom-theme/contoso/icon.png, or paths relative to the Office add-in bundle such as assets/color-icon-192x192.png.
Type: object
Example:
[integrations.ms_office]
ews_api_endpoint = "https://outlook.office365.com/EWS/Exchange.asmx"
ews_skip_tls_validation = false
[integrations.ms_office.addin]
enabled = true
msal_client_id = "00000000-0000-0000-0000-000000000000"
addin_id = "11111111-2222-3333-4444-555555555555"
msal_authority = "https://login.microsoftonline.com/consumers"
serve_bundle_legacy_path = true
frontend_bundle_path = "./public/platform-office-addin"
[integrations.ms_office.addin.manifest]
provider_name = "Contoso"
display_name = "Contoso Office Extension"
description = "Contoso AI assistant for Outlook"
support_url = "https://contoso.example.com/support"
group_label = "Contoso"
button_label = "Open Contoso"
button_description = "Open the Contoso AI assistant in a task pane"
icon_path = "/public/common/custom-theme/contoso/color-icon.png"
high_resolution_icon_path = "/public/common/custom-theme/contoso/color-icon-hires.png"
icon_16_path = "assets/contoso-outline-16.png"
icon_32_path = "assets/contoso-outline-32.png"
icon_80_path = "https://cdn.example.com/contoso/icon-80.png"integrations.ms_office.addin.launch_events
Outlook launch events the add-in subscribes to. Launch events let the add-in run a handler when Outlook raises the event, without the user opening the task pane.
Each [[integrations.ms_office.addin.launch_events]] entry declares an event and the function_name that the runtime registers for it. The supported event is OnNewMessageCompose. Function names must be JavaScript identifiers, matching [A-Za-z_$][A-Za-z0-9_$]*. The events are rendered into the add-in manifest in the configured order.
[integrations.ms_office.addin.launch_event_runtime] declares where the handlers live and is required as soon as at least one launch event is configured. Declaring the runtime without any launch events is rejected as well; the two settings are only valid together. page_path is the HTML runtime page used by Outlook on the web, new Outlook, and Outlook on Mac; script_path is the JavaScript-only override used by classic Outlook on Windows. Both paths are resolved against the deployment base URL, so they have to start with / and must not contain ...
The runtime files are served by the deployment; Erato does not ship them. The example below assumes a component kit named example that provides both files — without them the manifest still renders, but Outlook fails to load the handlers. Launch events are only rendered into the default manifest.xml; the Exchange Server manifest variant cannot express them.
Type: object
Example:
[integrations.ms_office.addin.launch_event_runtime]
page_path = "/public/component-kits/example/commands.html"
script_path = "/public/component-kits/example/launchevent.js"
[[integrations.ms_office.addin.launch_events]]
event = "OnNewMessageCompose"
function_name = "onNewMessageComposeHandler"integrations.prometheus
Configuration for Prometheus metrics export.
integrations.prometheus.enabled
Whether the Prometheus metrics endpoint is enabled.
Default value: false
Type: boolean
integrations.prometheus.host
Host interface for the Prometheus metrics listener.
Default value: "127.0.0.1"
Type: string
integrations.prometheus.port
Port for the Prometheus metrics listener. This must be different from http_port.
Default value: 3131
Type: integer
integrations.sharepoint
Configuration for the SharePoint/OneDrive integration. This allows users to attach files from their Microsoft cloud storage directly to chats.
Deprecated compatibility: The previous [integrations.experimental_sharepoint] section is still accepted, but it is deprecated. Use [integrations.sharepoint] instead. Environment variables also accept both names; migrate to INTEGRATIONS__SHAREPOINT__.... Use only one name for each section across configuration files and environment variables; combining the old and new names is rejected as a duplicate field.
integrations.sharepoint.enabled
Whether the SharePoint/OneDrive integration is enabled.
Default value: false
Type: boolean
integrations.sharepoint.file_upload_enabled
Whether file linking from SharePoint is enabled. When disabled, users can browse their drives but cannot link files to chats.
Default value: true
Type: boolean
integrations.sharepoint.auth_via_access_token
Whether to use the user’s forwarded access token for Microsoft Graph API authentication. Currently, this is the only supported authentication method.
Default value: true
Type: boolean
integrations.sharepoint.show_disclaimer
Whether to show a disclaimer in the SharePoint/OneDrive file picker explaining that users may see directories they do not expect because a SharePoint site or OneDrive directory may have been shared more broadly, such as with the whole organization.
Default value: false
Type: boolean
integrations.sharepoint.all_drives_sources
Which Microsoft Graph discovery surfaces Erato should query for /integrations/sharepoint/all-drives.
If omitted or set to an empty list, Erato queries all supported sources.
Supported values:
me_driveme_drivesjoined_teamsgroup_drivessite_searchsite_drivesshared_with_meshared_drive_details
Default value: [] (interpreted as all sources)
Type: array[string]
Example:
[integrations.sharepoint]
enabled = true
file_upload_enabled = true
auth_via_access_token = true
all_drives_sources = ["me_drive", "me_drives", "shared_with_me", "shared_drive_details"]Prerequisites:
- Your OIDC provider (e.g., Microsoft Entra ID) must issue access tokens with Microsoft Graph API permissions (
Files.ReadorFiles.ReadWrite) - Your authentication proxy (e.g., oauth2-proxy) must forward the access token via the
X-Forwarded-Access-Tokenheader
See the SharePoint/OneDrive Integration documentation for detailed setup instructions.
integrations.entra_id
Configuration for the Entra ID integration. This allows exposing organization users and groups to the frontend for sharing features.
Deprecated compatibility: The previous [integrations.experimental_entra_id] section is still accepted, but it is deprecated. Use [integrations.entra_id] instead. Environment variables also accept both names; migrate to INTEGRATIONS__ENTRA_ID__.... Use only one name for each section across configuration files and environment variables; combining the old and new names is rejected as a duplicate field.
integrations.entra_id.enabled
Whether the Entra ID integration is enabled. When enabled, the /me/organization/users and /me/organization/groups endpoints will query the Microsoft Graph API to list organization users and groups. When disabled, these endpoints will return empty lists.
Default value: false
Type: boolean
integrations.entra_id.auth_via_access_token
Whether to use the user’s forwarded access token for Microsoft Graph API authentication. Currently, this is the only supported authentication method.
Default value: true
Type: boolean
Example:
[integrations.entra_id]
enabled = true
auth_via_access_token = truePrerequisites:
- Your OIDC provider (e.g., Microsoft Entra ID) must issue access tokens with Microsoft Graph API permissions (
User.Read.AllandGroup.Read.All) - Your authentication proxy (e.g., oauth2-proxy) must forward the access token via the
X-Forwarded-Access-Tokenheader - The ID token must include the
oidclaim for organization user IDs and thegroupsclaim for organization group memberships
Related Features:
When the Entra ID integration is enabled, the user profile will include additional fields:
organization_user_id- Extracted from theoidclaim (Entra ID specific)organization_group_ids- Extracted from thegroupsclaim
These fields enable sharing assistants with organization groups via share grants with subject_type = "organization_group" and subject_id_type = "organization_group_id". Assistants can also be shared with every logged-in user in the organization using subject_type = "organization", subject_id_type = "organization_id", and subject_id = "__organization__".
logging
Configuration for backend log output formatting.
logging.format
Controls how application logs are formatted on stdout/stderr.
Use "plain" for human-readable local development logs, or "json" for structured logs that are easier to ingest in production log pipelines.
When "json" is enabled, startup messages that are emitted before the tracing subscriber is initialized are also re-emitted as structured log events once logging is ready.
Default value: "plain"
Type: string
Supported values: "plain", "json"
Example:
[logging]
format = "json"mcp_servers
Configuration for Model Context Protocol (MCP) servers that extend Erato’s capabilities through custom tools and integrations.
Type: object<string, McpServerConfig>
mcp_servers_global
Global configuration for MCP server behavior across all configured servers.
Type: McpServersGlobalConfig
mcp_servers_global.approval
Global policy for pausing MCP tool calls until a user approves them.
Type: McpToolApprovalConfig
Default value: disabled
mcp_servers_global.approval.enabled
Whether MCP tool calls can require approval. Defaults to false for backwards compatibility.
Type: boolean
Default value: false
mcp_servers_global.approval.preset
Annotation policy: permissive permits explicitly non-destructive tools; restrictive also requires explicitly read-only and closed-world tools.
Type: "permissive" | "restrictive"
Default value: "permissive"
mcp_servers_global.approval.allow_always
Whether users may save an Always allow setting for a user + MCP server + tool. Defaults to false.
Type: boolean
Default value: false
mcp_servers_global.max_session_idle_seconds
Global default maximum idle time for MCP sessions, in seconds. If a session is idle longer than this threshold, it is evicted by the backend cleanup task.
Server-specific settings can override this value via mcp_servers.<server-id>.max_session_idle_seconds.
Type: integer | None
Default value: 3600 (1 hour)
Example: 900
Operational note: For MCP servers where each session allocates substantial resources (e.g. memory-heavy tools, expensive backend handles), use a lower idle timeout to reclaim server capacity faster.
mcp_servers_global.enable_wait
Whether to offer Erato’s synthetic wait tool alongside all discovered MCP tools. The wait tool pauses the current generation for a requested number of seconds, which is useful when an MCP tool starts deferred work or returns a result that should be polled later.
When disabled, individual MCP servers can opt in selected tools with mcp_servers.<server-id>.wait_tools.
Type: boolean
Default value: false
Example:
[mcp_servers_global]
enable_wait = truemcp_servers_global.max_wait_seconds
Maximum number of seconds that the synthetic wait tool may pause a generation in one call. The value must be greater than zero.
Type: integer (greater than 0)
Default value: 60
Example:
[mcp_servers_global]
max_wait_seconds = 120mcp_servers_global.show_frontend_tab
Whether the Servers & Tools tab is shown in the frontend preferences dialog. The chat input and assistant editor inherit this value unless their separate settings below are configured.
These settings only control frontend visibility. They do not change backend MCP availability, per-user authorization, or permissions. Hidden preferences also hide shortcuts to manage or connect servers in that tab.
Type: boolean
Default value: false
mcp_servers_global.show_in_chat_input
Whether MCP connector controls appear in the chat input/upload dropdown. Server discovery for this surface is independent of preferences-tab visibility. Controls remain hidden if no servers are available to the user.
Type: optional boolean
Default value: inherits show_frontend_tab when omitted. Explicit true or false overrides it independently.
mcp_servers_global.show_in_assistant_editor
Whether the MCP server selector appears on assistant create and edit pages. Server discovery for this surface is independent of preferences-tab visibility. The selector remains hidden if no servers are available to the user.
Type: optional boolean
Default value: inherits show_frontend_tab when omitted. Explicit true or false overrides it independently of the chat input setting.
These settings apply to both the web frontend and Office add-in consumers. Existing configurations that omit both settings retain their previous behavior.
Example: show preferences and the assistant selector, but hide chat connector controls:
[mcp_servers_global]
show_frontend_tab = true
show_in_chat_input = false
show_in_assistant_editor = trueTo show chat connectors while hiding preferences, set show_frontend_tab = false and show_in_chat_input = true. An omitted show_in_assistant_editor still inherits false.
mcp_servers.<server-id>.transport_type
The type of transport protocol used to communicate with the MCP server.
Type: string
Supported values:
"sse"(Server-Sent Events)"streamable_http"(Streamable HTTP)
Example: "sse" or "streamable_http"
mcp_servers.<server-id>.url
The URL endpoint for the MCP server.
- For
transport_type = "sse", this conventionally ends with/sse - For
transport_type = "streamable_http", this should be the base HTTP endpoint
Type: string
Example: "http://127.0.0.1:63490/sse", "https://my-mcp-server.example.com/sse", "https://api.example.com/mcp"
mcp_servers.<server-id>.http_headers
Optional static HTTP headers to include with every request to the MCP server. Use this for non-authentication headers. Prefer mcp_servers.<server-id>.authentication for credentials.
Type: object<string, string> | None
Example:
[mcp_servers.custom_headers_server]
transport_type = "sse"
url = "https://mcp-server.example.com/sse"
http_headers = { "X-Tenant-ID" = "tenant-123", "X-Environment" = "production" }mcp_servers.<server-id>.allow_tools
Optional list of wildcard patterns selecting which tools from this MCP server Erato exposes. Patterns match the unqualified MCP tool name, and * matches any sequence of characters.
If this option is omitted, all tools are allowed, which is equivalent to allow_tools = ["*"]. An explicitly empty list allows no tools.
Type: array<string> | None
Examples:
# Expose only the exact get_page tool and tools beginning with "list_".
allow_tools = ["get_page", "list_*"]mcp_servers.<server-id>.exclude_tools
List of wildcard patterns selecting tools to remove from the tools permitted by allow_tools. Exclusions are applied after the allowlist, and both filters are applied before request-level assistant, facet, and permission filtering.
Type: array<string>
Default value: []
Example:
allow_tools = ["get_*"]
exclude_tools = ["get_secret_*"]mcp_servers.<server-id>.wait_tools
Optional list of wildcard patterns selecting the MCP tools that may use the synthetic wait tool for deferred results. Patterns match the unqualified MCP tool name, and * matches any sequence of characters.
An empty list disables waiting for this server unless mcp_servers_global.enable_wait is enabled. When the global setting is enabled, the wait tool is available for all discovered MCP tools and this list is not needed.
Type: array<string>
Default value: []
Example:
[mcp_servers.research]
transport_type = "streamable_http"
url = "https://mcp.example.com/mcp"
wait_tools = ["dispatch_*", "poll_*"]mcp_servers.<server-id>.authentication
Authentication settings for the MCP server.
Type: McpServerAuthenticationConfig
Supported values:
mode = "none": No credentials are attached to MCP requests.mode = "forwarded": Forward credentials from the current user session.mode = "fixed": Attach a configured fixed API key using the configured header and prefix.mode = "oauth2": Use an OAuth 2.0 authorization flow managed by Erato and persist credentials per user.
Examples:
[mcp_servers.public_tools.authentication]
mode = "none"
[mcp_servers.intranet.authentication]
mode = "forwarded"
[mcp_servers.intranet.authentication.forwarded]
credential = "oidc_id_token"
[mcp_servers.partner_api.authentication]
mode = "fixed"
[mcp_servers.partner_api.authentication.fixed]
api_key = "your-api-key"
header_name = "Authorization"
prefix = "Bearer "
[mcp_servers.jira.authentication]
mode = "oauth2"
[mcp_servers.jira.authentication.oauth2]
client_name = "Erato Jira MCP"
scopes = ["read:jira-work"]mcp_servers.<server-id>.authentication.forwarded.credential
Selects which user credential to forward when authentication.mode = "forwarded".
Type: string
Supported values:
"access_token""oidc_id_token"
mcp_servers.<server-id>.authentication.fixed.api_key
The fixed API key to attach when authentication.mode = "fixed".
Type: string
mcp_servers.<server-id>.authentication.fixed.header_name
The HTTP header to use when authentication.mode = "fixed".
Type: string
Default value: "Authorization"
mcp_servers.<server-id>.authentication.fixed.prefix
The prefix prepended to authentication.fixed.api_key when authentication.mode = "fixed".
This defaults to bearer-token formatting, so the default fixed auth header is Authorization: Bearer <api_key>.
Type: string
Default value: "Bearer "
mcp_servers.<server-id>.authentication.oauth2.client_id
Optional statically configured OAuth client ID for authentication.mode = "oauth2".
If omitted, Erato attempts dynamic client registration and persists the returned client identity for later reuse.
Type: string | None
mcp_servers.<server-id>.authentication.oauth2.client_secret
Optional statically configured OAuth client secret for authentication.mode = "oauth2".
Use this for confidential clients that are pre-registered with the MCP server’s authorization server.
Type: string | None
mcp_servers.<server-id>.authentication.oauth2.client_name
Optional display name used during dynamic client registration when authentication.mode = "oauth2" and no static client_id is configured.
Type: string | None
Default behavior: Falls back to "Erato MCP OAuth Client"
mcp_servers.<server-id>.authentication.oauth2.resource
Selects the OAuth resource parameter for authorization, code exchange, and refresh.
- Unset: use the resource identifier advertised by Protected Resource Metadata (PRM). If PRM or its resource field is absent, fall back to the MCP endpoint URL with its query string and fragment removed.
- A nonempty string: override PRM with this exact absolute URI (no fragments).
- An empty string (
resource = ""): omit the parameter, taking precedence over PRM.
PRM and explicit resource identifiers preserve their trailing slash and query string. The resource can have a different host or path than the MCP endpoint. Invalid resource metadata fails automatic selection; override/omission bypasses that value. Scope selection and authorization-server discovery still apply.
Type: string | None
Example: resource = "https://api.example.com" with scope
https://api.example.com/Mcp.Read for an Entra-protected API.
See OAuth resource selection for precedence, compatibility, and a complete configuration example.
mcp_servers.<server-id>.authentication.oauth2.scopes
Optional list of OAuth scopes requested during authorization for authentication.mode = "oauth2".
If omitted, Erato relies on the scope guidance advertised by the MCP server and its authorization metadata.
Type: array<string>
Example:
[mcp_servers.jira.authentication]
mode = "oauth2"
[mcp_servers.jira.authentication.oauth2]
client_id = "erato-jira"
client_secret = "super-secret"
scopes = ["read:jira-work", "offline_access"]Operational note: oauth2 authentication requires server.encryption_key to be configured because Erato stores registered client secrets, authorization state, and user credentials encrypted at rest.
mcp_servers.<server-id>.max_session_idle_seconds
Optional per-server override for maximum MCP session idle time, in seconds.
If set, this value takes precedence over mcp_servers_global.max_session_idle_seconds for sessions created against this server.
Type: integer | None
Default behavior: Uses mcp_servers_global.max_session_idle_seconds (or 3600 when not set)
Example: 300
Operational note: Prefer low values for resource-intensive MCP servers where long-lived idle sessions can consume significant server resources.
See the MCP Servers documentation for more information about Model Context Protocol integration.
prompt_optimizer
Configuration for the prompt optimizer. When enabled, the backend exposes the POST /prompt-optimizer endpoint to optimize user prompts using a dedicated chat provider and system prompt.
prompt_optimizer.enabled
Whether to enable the prompt optimizer feature.
Default value: false
Type: boolean
prompt_optimizer.chat_provider_id
The chat provider ID to use for prompt optimization. Must match a provider configured in chat_providers.
Type: string
prompt_optimizer.prompt
System prompt used by the prompt optimizer. If omitted, a built-in non-interactive default prompt is used. Uses the common prompt format.
Type: string | object | None
Example:
[prompt_optimizer]
enabled = true
chat_provider_id = "gpt-4o-mini"
# Optional override:
# prompt = "Rewrite the user's prompt to be clearer, more specific, and action-oriented. Return only the improved prompt."Using Langfuse:
[prompt_optimizer]
enabled = true
chat_provider_id = "gpt-4o-mini"
prompt = { source = "langfuse", prompt_name = "prompt-optimizer", label = "production", fallback = "Rewrite the user's prompt to be clearer, more specific, and action-oriented. Return only the improved prompt." }Using a static object:
[prompt_optimizer]
enabled = true
chat_provider_id = "gpt-4o-mini"
prompt = { source = "static", prompt = "Rewrite the user's prompt to be clearer, more specific, and action-oriented. Return only the improved prompt." }user_preferences
Configuration for the user preferences feature.
user_preferences.enabled
Whether to enable the user preferences feature.
When disabled, the frontend hides the Personalization tab in the preferences dialog. Other preferences tabs can still be shown.
Default value: true
Type: boolean
Example:
[user_preferences]
enabled = falseuser_preferences.data_tab_enabled
Whether the Data tab should be shown in the frontend preferences dialog.
This only controls frontend visibility of the tab. It does not change backend availability of account data actions.
Default value: true
Type: boolean
Example:
[user_preferences]
data_tab_enabled = falseassistants
Configuration for the assistants feature. This allows enabling the assistants functionality in the frontend, which provides users with pre-configured chat assistants tuned for specific use-cases.
Deprecated compatibility: The previous [experimental_assistants] section is still accepted when parsing configuration, but it is deprecated. Use [assistants] instead. The deprecated section is planned to be removed in Erato 0.7.0.
assistants.enabled
Whether to enable the assistants feature in the frontend. When enabled, the frontend will expose assistant-related functionality and UI components to users.
Default value: false
Type: boolean
Example:
[assistants]
enabled = trueassistants.usage_view_enabled
Enable the assistants usage view for assistant owners and editors. Requires assistants.enabled. Counts persisted generated assistant responses (including regenerations and delegated calls) across the original assistant and all Hub versions. Archived chats remain included. Unique users are distinct chat owners, counted separately for the full timeframe and each bucket. Timeframes include today and use UTC calendar boundaries; weekly buckets start at the beginning of the selected range.
Default value: false
Type: boolean
assistants.enable_edit_sharing
Whether assistants can be shared with edit access. When enabled, assistant owners can grant editor access to other users or groups.
Default value: true
Type: boolean
Example:
[assistants]
enable_edit_sharing = trueassistants.show_recent_items
Whether to show recent assistant items in the chat sidebar.
Default value: false
Type: boolean
Example:
[assistants]
show_recent_items = trueassistants.show_recent_items_collapsible
Whether the recent assistants sidebar section is rendered as a collapsible section (header + chevron) like the recent chats section.
Default value: false
Type: boolean
Example:
[assistants]
show_recent_items_collapsible = trueassistants.context_warning_threshold
Threshold between 0.0 and 1.0 at or above which the assistant editor shows the overall context warning block.
Default value: 0.5
Type: number
Example:
[assistants]
context_warning_threshold = 0.6assistants.context_file_contributor_threshold
Threshold between 0.0 and 1.0 at or above which a file is included in the assistant editor’s “largest file context contributors” list.
Default value: 0.05
Type: number
Example:
[assistants]
context_file_contributor_threshold = 0.01assistants.max_system_prompt_length
Maximum number of characters allowed in an assistant system prompt. When not set, no server-side length limit is enforced and the frontend falls back to its built-in default for UI validation.
Default value: unset
Type: integer
Example:
[assistants]
max_system_prompt_length = 5000assistants.max_files
Maximum number of files that can be associated with an assistant. The backend enforces this limit for assistant create/update requests and standalone multipart uploads; the frontend uses it to limit file selection.
Default value: 10
Type: integer (greater than 0)
Example:
[assistants]
max_files = 10Deprecated compatibility
The [assistants.delegation] table was replaced by the top-level delegation section. It is still read until version 0.8.0: a configuration that only sets [assistants.delegation] boots with a deprecation warning and identical runtime behavior. Setting both [assistants.delegation] and [delegation] is rejected at startup — move the keys rather than duplicating them.
| Deprecated key | Replacement |
|---|---|
assistants.delegation.enabled | delegation.assistants.enabled |
assistants.delegation.max_mentions_per_message | delegation.assistants.max_mentions_per_message |
assistants.delegation.allow_background | delegation.allow_background |
assistants.delegation.max_concurrent_background_runs | delegation.max_concurrent_background_runs |
assistants.delegation.run_timeout_seconds | delegation.run_timeout_seconds |
assistants.delegation.result_max_chars | delegation.result_max_chars |
assistants.delegation.auto_archive_after_days | delegation.auto_archive_after_days |
assistants.delegation.preamble | delegation.preamble |
delegation
Configuration for delegated runs — child chats that a conversation spawns to do a piece of work. Two offer routes share these settings: delegation.assistants for in-chat delegation to @-mentioned assistants, and delegation.tasks for tasks the model plans itself. Both are disabled by default.
This section replaces the deprecated [assistants.delegation] table.
delegation.run_timeout_seconds
Maximum wall-clock duration in seconds a delegated child run may take before it is aborted. An awaited run is reported to the origin chat as timed out; a background run just stops, keeping whatever partial answer it produced in its own chat.
Default value: 600
Type: integer (greater than 0)
Example:
[delegation]
run_timeout_seconds = 600delegation.result_max_chars
Maximum number of characters of a delegate’s final message that are returned to the origin chat; longer results are truncated.
Default value: 16000
Type: integer (greater than 0)
Example:
[delegation]
result_max_chars = 16000delegation.allow_background
Whether users may launch delegated runs in background mode, where the delegation tool returns at launch and the child run’s result never flows back into the origin turn. When disabled, a request asking for background mode is downgraded to a normally awaited run rather than rejected.
Default value: false
Type: boolean
Example:
[delegation]
allow_background = truedelegation.max_concurrent_background_runs
Maximum number of one user’s delegated background runs that may be in flight at once; further background launches are refused until a run finishes. A single message is also capped at this many background launches. Counted over live generation leases without locking, so concurrent dispatches can briefly overshoot it — the cap exists to stop runaway fan-out, not to be exact. 0 disables the cap.
Default value: 5
Type: integer
Example:
[delegation]
max_concurrent_background_runs = 5delegation.auto_archive_after_days
Number of days without activity after which a finished delegated run is archived, whether or not the chat that spawned it was ever archived. Archived runs are deleted by the cleanup worker after cleanup_archived_max_age_days, so this only has an effect if cleanup_enabled is true. 0 never archives a run automatically, and neither does a pin, an enabled share link, or a turn the user wrote into the run themselves.
Default value: 7
Type: integer
Example:
[delegation]
auto_archive_after_days = 7delegation.preamble
Directive rendered into the delegated chat’s first turn, telling the delegate it runs as a delegated worker and where its final message ends up. The delegate’s own system prompt is untouched; the directive is additive. The placeholder {{result_disposition}} is replaced with a sentence matching the run’s mode — for an awaited run, that the final message is returned to the origin chat as the task result; for a background run, that the origin chat does not receive it and the user reads it in the run itself; for an async run, that the final message is delivered back into the origin conversation when the run finishes and must therefore stand on its own. The placeholders {{expected_output_section}} and {{constraints_section}} are replaced with formatted blocks when the origin model supplies the corresponding tool arguments, and with empty strings otherwise. A template that omits a placeholder simply renders without that part.
Default value: the built-in directive shown in the erato.template.toml reference block
Type: string (non-empty)
Example:
[delegation]
preamble = """
You act on a delegated task from another conversation. Your final message is returned as the result.
{{expected_output_section}}{{constraints_section}}"""delegation.assistants
In-chat delegation to @-mentioned assistants.
delegation.assistants.enabled
Whether in-chat delegation to @-mentioned assistants is enabled. When enabled, users can mention other assistants in a chat message, and the model can delegate a task to the mentioned assistant as an awaited child chat run. Requires assistants.enabled.
Default value: false
Type: boolean
Example:
[delegation.assistants]
enabled = truedelegation.assistants.max_mentions_per_message
Maximum number of assistants that can be mentioned in a single message.
Default value: 3
Type: integer (at least 1)
Example:
[delegation.assistants]
max_mentions_per_message = 3delegation.tasks
Model-planned delegated tasks. Where the assistants route is driven by an explicit @-mention from the user, this route lets the model itself decide to split work off into a child run, using the reserved erato/delegate_task tool.
Unlike the assistants route, this route has no dependency on assistants.enabled — a deployment can offer model-planned tasks without using assistants at all.
Enabling the feature is not sufficient on its own: the tool is only offered when a selected facet’s tool_call_allowlist also selects it, which keeps the capability scoped to the facets where planning makes sense.
Always pair a reserved erato/* pattern with a real MCP pattern on the same facet. An allowlist naming only erato/* patterns narrows the derived MCP server filter to nothing, so the parent turn loses every MCP tool while that facet is selected.
delegation.tasks.enabled
Whether the model may plan and dispatch its own delegated tasks with the reserved erato/delegate_task tool.
Default value: false
Type: boolean
Example:
[delegation.tasks]
enabled = truedelegation.tasks.max_tasks_per_turn
Maximum number of tasks one origin turn may dispatch in total. A call beyond the budget is refused with a tool result rather than ending the turn, so the model can carry on with what it already has.
Default value: 5
Type: integer (at least 1)
Example:
[delegation.tasks]
max_tasks_per_turn = 5delegation.tasks.max_parallel
How many task runs of one turn may be in flight at the same time. A batch larger than this starts the first max_parallel runs and queues the rest, launching each as a slot frees up — so ten tasks with max_parallel = 4 run four, then four, then two, and never five at once.
This bounds concurrent load; max_tasks_per_turn bounds the total. A queued run is already visible in the conversation, marked as waiting, before it starts.
Default value: 3
Type: integer (at least 1)
A value larger than max_tasks_per_turn is capped to it — a turn cannot run more tasks at once than it may run at all. Writing one explicitly is rejected at startup as a contradiction; inheriting the default under a lower total is simply capped, so lowering max_tasks_per_turn alone never stops a deployment booting. The same cap applies to a per-facet override.
Example:
[delegation.tasks]
max_tasks_per_turn = 10
max_parallel = 4Tool budgets for a task run
A task run carries two independent tool budgets, one per execution class. Neither constrains the other and they are never summed into a shared total, so there is no third number to keep consistent: each says exactly what it says.
The split exists because the two classes are not remotely alike in cost. A server-executed call is an MCP call — the backend holds the session, the outbound connection and a slot in the sequential tool loop — so it consumes shared capacity that scales slowly. A client-executed call runs on the user’s own device, including work the client forwards to the desktop sidecar, so a deployment can afford to be far more generous with it.
The built-in delegation tools count against neither budget. They have their own bound in max_tasks_per_turn.
Client-executed calls are cheap to run but not free: each one still costs a model round trip and holds the run’s generation open while the client works. The client budget should be generous, not unbounded.
delegation.tasks.max_server_tool_calls_per_task
How many server-executed (MCP) tool calls one task run may make. A task child gets its own budget, independent of the origin turn’s. Set it to 0 to forbid server-executed tools in task runs entirely.
Default value: 5
Type: integer
Example:
[delegation.tasks]
max_server_tool_calls_per_task = 5delegation.tasks.max_client_tool_calls_per_task
How many client-executed tool calls one task run may make, including work a client forwards to the desktop sidecar. Set it to 0 to forbid client-executed tools in task runs entirely.
Default value: 30
Type: integer
Example:
[delegation.tasks]
max_client_tool_calls_per_task = 30delegation.tasks.persona
Whether a task child runs with the origin chat’s assistant persona ("inherit") or on the bare model ("bare"). With "inherit", a child spawned from a chat bound to an assistant uses that assistant’s system prompt and model settings; a chat with no assistant bound runs bare either way.
Default value: "inherit"
Type: string (one of "inherit", "bare")
Example:
[delegation.tasks]
persona = "inherit"delegation.tasks.child_facet_ids
Facets composed into every task child. Each entry must name a facet configured under facets.facets; an unknown id is rejected at startup. Use this to give task children a capability the origin turn does not need to carry itself, such as web search.
Default value: []
Type: list<string>
Example:
[delegation.tasks]
child_facet_ids = ["web_search"]delegation.tasks.result_template
How a delivered task result is presented to the model that has to react to it, once an async task’s answer re-enters the conversation it was started from.
This is presentation only. {{result}} is required — a template without it is rejected at startup, because it would render a task result with the answer silently missing rather than failing. The child’s answer is substituted inside an untrusted-data frame, and the run’s status, reason and identifiers are stated ahead of this text, where editing the template cannot remove them. The safety guidance that tells the model not to follow instructions found in a child’s answer is likewise fixed.
{{truncated_note}} expands to a note when the answer was shortened to delegation.result_max_chars, and to nothing otherwise.
Note that the template is applied when the conversation is composed, not when the result is stored, so editing it also changes how results already in a chat’s history are presented on the next turn.
Default value:
A task you delegated earlier has finished and its result is below.{{truncated_note}}
{{result}}Type: string
Example:
[delegation.tasks]
result_template = """
Background task finished.{{truncated_note}}
{{result}}"""delegation.tasks.multitask_strategy
What happens to a message, edit or regenerate that arrives while the chat’s generation is still running. reject answers 409 generation_running and leaves the running turn untouched; the client holds the draft and sends it once the turn ends. Before this key existed, a second write silently replaced the running generation’s lease.
enqueue and interrupt are reserved spellings with no implementation. Writing either is rejected at startup rather than silently treated as reject, because both names promise behaviour the deployment would not get.
This only takes effect when delegation.tasks.enabled is true.
Default value: "reject"
Type: string (one of reject, enqueue, interrupt)
Example:
[delegation.tasks]
multitask_strategy = "reject"delegation.tasks.run_modes
Which run modes the erato/delegate_task tool offers the model. wait returns the sub-task’s answer as the result of the call, in the same turn. async settles the call at launch and the answer is delivered into the conversation later, as its own message the model then reacts to — use it for work that would otherwise keep the user waiting.
Only the modes listed here appear in the tool schema, and a call naming a mode that is not offered is refused rather than quietly downgraded. wait is always available even if a facet override omits it: the offer would otherwise have an empty choice.
The list must be non-empty and free of duplicates; a repeated entry is rejected at startup, because the list becomes an enum the model reads and a repetition reads as emphasis.
This only takes effect when delegation.tasks.enabled is true.
Default value: ["wait"]
Type: list<string> (each one of wait, async)
Example:
[delegation.tasks]
run_modes = ["wait", "async"]delegation.tasks.scheduling
What happens when an async task’s result is delivered back into the conversation that started it. when_idle runs a reaction turn as soon as that chat is free, so the answer is followed by the model’s own response to it. silent stores the result without replying; the next message the user sends composes it into history.
The model may ask for either per call through the tool’s scheduling argument; this key is the default when it does not.
interrupt is a reserved spelling with no implementation. Writing it is rejected at startup rather than silently treated as when_idle, because the name promises behaviour the deployment would not get.
This only takes effect when delegation.tasks.enabled is true and async is in delegation.tasks.run_modes.
Default value: "when_idle"
Type: string (one of when_idle, silent, interrupt)
Example:
[delegation.tasks]
scheduling = "when_idle"delegation.tasks.propagate_child_mcp_approvals
What a task child does when it reaches an MCP tool call that needs the user’s approval. With this on the child parks and asks: the user answers, the child resumes where it stopped, and a denial reaches it as an ordinary refused tool call it finishes around.
Where the question appears depends on the run mode. A wait child is asked about on the turn that dispatched it, which is still waiting for it. An async child stops on its own card in its own chat, and the conversation that started the task is told by a result with status input_required linking to it; answering there delivers a second result carrying the run’s real answer.
Turning it off refuses the gated call inside the child instead. The child still finishes, but without whatever the call was for, and nobody is asked — which is why the default is on. Leave it off only where an approval prompt raised by work the user did not explicitly start is unacceptable.
An @-mentioned assistant’s run is not covered either way: it has neither a task slot on the turn waiting for it nor a card of its own, so its gated calls are refused whatever this says.
This only takes effect when delegation.tasks.enabled is true.
Default value: true
Type: boolean
Example:
[delegation.tasks]
propagate_child_mcp_approvals = truedelegation.tasks.approval.mode
When the user is asked before the tasks the model planned are dispatched. The whole batch is shown as a single card carrying one item per task, so a plan can be approved in part: the tasks the user keeps are dispatched in the order the model asked for them, and each one it drops reaches the model as a refused call it answers around. Nothing is created before the decision — no child chat, no run, no placeholder.
neverdispatches every planned task without asking.alwaysasks about every batch that contains a task call.planasks only once a batch reachesplan_min_taskstask calls, so a single lookup goes through unprompted while a multi-step plan is reviewed.async_onlyasks about the task calls that detach and leaves awaited ones alone.
The default is async_only, written as that literal rather than derived from run_modes: an awaited task reports back into the turn that asked for it, so the user sees what it did, while a detached one runs on its own after the turn has ended. Until async is in delegation.tasks.run_modes there is nothing for it to ask about, so it behaves exactly as never — and a deployment that later offers async gets the prompt that implies without having to remember to ask for it.
A planning facet may run under a stricter policy of its own; see facets.facets.<facet-id>.delegation.
This only takes effect when delegation.tasks.enabled is true.
Default value: "async_only"
Type: string (one of never, always, plan, async_only)
Example:
[delegation.tasks.approval]
mode = "async_only"delegation.tasks.approval.plan_min_tasks
From how many task calls in one batch mode = "plan" asks. Read by that mode only.
Must be at least 2. Below two there is no plan to review, only a single call with nothing to choose between — which is what mode = "always" is for, and writing it as a threshold of 1 would be that behaviour under a name that promises a review.
Default value: 2
Type: integer
Example:
[delegation.tasks.approval]
mode = "plan"
plan_min_tasks = 3assistant_hub
Configuration for the optional Assistant Hub. The Assistant Hub lets users submit immutable assistant snapshots for an organization-internal review process. Accepted versions can then be published to a configured audience without exposing the creator’s editable draft assistant.
The feature is disabled by default. It also depends on the regular assistants feature, so enable assistants.enabled when enabling the hub.
assistant_hub.enabled
Whether to enable Assistant Hub backend APIs and frontend surfaces.
Default value: false
Type: boolean
Example:
[assistants]
enabled = true
[assistant_hub]
enabled = trueassistant_hub.enable_categories
Whether to show categories in the Assistant Hub and allow creators to assign them to submitted assistant versions.
Default value: true
Type: boolean
assistant_hub.default_share_with_whole_organization
Whether to pre-select the whole organization as the audience when submitting a new assistant to the Assistant Hub. Existing assistant versions retain their previous share grants.
Default value: true
Type: boolean
assistant_hub.categories
A map of categories that creators can assign to submitted assistant versions. The map key is the stable category ID used by the backend. Categories are shown in the hub for browsing and filtering.
Each category has:
display_name- User-facing category label.
icon- Icon identifier from the existing frontend icon system.
Type: object<string, AssistantHubCategoryConfig>
Example:
[assistant_hub.categories.productivity]
display_name = "Productivity"
icon = "Sparkles"
[assistant_hub.categories.engineering]
display_name = "Engineering"
icon = "Code"assistant_hub.reviewers.rules
A map of reviewer rules. Reviewer access is based on the same group-membership model used by model_permissions, mcp_server_permissions, and facet_permissions.
Assistant Hub reviewer rules currently support only allow-for-group-members.
rule_type- Must be"allow-for-group-members".
groups- Group names or identifiers whose members can review assistant hub submissions.
Type: object<string, AssistantHubReviewerRule>
Example:
[assistant_hub.reviewers.rules.default_reviewers]
rule_type = "allow-for-group-members"
groups = ["assistant-hub-reviewers", "ai-governance"]Reviewer group membership comes from the authenticated user’s group information, for example through the OIDC groups claim. If no reviewer rule matches, the user can still browse published hub assistants they can access, but cannot accept, decline, feature, or review submissions.
starter_prompts
Configuration for welcome-screen starter prompts. Starter prompts prefill the chat input, optional facet selections, and an optional chat provider when clicked, but do not submit the message automatically.
Starter prompt titles and subtitles can be translated in the frontend with Lingui message IDs derived from the starter prompt id:
starter_prompts.<starter-prompt-id>.titlestarter_prompts.<starter-prompt-id>.subtitle
If no translation exists for a given starter prompt id, the frontend falls back to the configured title and subtitle.
starter_prompts.enabled
Whether to enable starter prompts globally.
Default value: false
Type: boolean
Example:
[starter_prompts]
enabled = truestarter_prompts.prompts
Map of starter prompt configurations, keyed by starter prompt id.
Type: object
Example:
[starter_prompts.prompts.research_topic]
title = "Research a topic"
subtitle = "Prefill a research request with web search selected"
icon = "iconoir-globe"
prompt = "Research this topic and summarize the most important findings."
localized_prompts = { de = { source = "static", prompt = "Recherchiere dieses Thema und fasse die wichtigsten Erkenntnisse zusammen." } }
selected_facets = ["web_search"]
chat_provider = "mock-llm"
[starter_prompts.prompts.draft_email]
title = "Draft an email"
subtitle = "Write a concise and professional reply"
icon = "iconoir-mail"
prompt = { source = "static", prompt = "Draft a concise and professional reply to this email." }Using Langfuse:
[starter_prompts.prompts.summarize_notes]
title = "Summarize notes"
subtitle = "Turn rough notes into a structured summary"
icon = "iconoir-page"
prompt = { source = "langfuse", prompt_name = "starter-summarize-notes", label = "production", fallback = "Summarize these notes into clear action items and decisions." }Each starter prompt supports these fields:
title- Fallback title shown when no frontend translation existssubtitle- Fallback subtitle shown when no frontend translation existsicon- Optional icon idprompt- Prompt source specification (uses commonpromptformat)localized_prompts- Optional map of locale code to prompt source specification (each value uses the commonpromptformat)selected_facets- Optional list of facet ids to preselectchat_provider- Optional chat provider id to preselect
localized_prompts is resolved in the backend against the authenticated user’s profile locale. Resolution order is:
- Exact locale match, for example
de-DE - Base language match, for example
de - Fallback to
prompt
starter_prompts.priority_order
List of starter prompt IDs in the order they should be displayed.
Type: list<string>
Default value: []
Example:
[starter_prompts]
priority_order = ["research_topic", "draft_email", "summarize_notes"]Optional desktop-sidecar client tools
The web app and Office/Teams add-ins register get_sidecar_search_fields,
search_sidecar_index, and read_sidecar_conversation when the configured desktop
sidecar supports each capability. Tool definitions, input schemas, and facet
prompts are maintained in the subscription configuration package. Load that
package through the backend’s normal TOML configuration; the public runtime
provides the shared executors. Configuration still selects the tools through
ordinary [client_tools.tools.<id>] entries and facet tool allowlists.
Configure the sidecar endpoint and allowed origins separately as usual. Deploy
the backend, frontend, and rebuilt add-in together before enabling these tools;
older backends do not gate optional tools or resolve their attachment references.
Client-tool executors can return uploaded files alongside their JSON result:
return { ok: true, result: { summary: "Files retrieved" }, fileUploadIds: [file.id] };Upload files through the ordinary files API first. The client sends these IDs in
the dedicated file_upload_ids field of POST /api/v1beta/me/messages/clienttoolresult.
The backend checks access, extracts file text for the model, and attaches authorized
files to the assistant message. Documents use rich file previews; images appear
inline. Attachments persist when the chat is reloaded. Duplicate IDs are ignored,
and at most min(frontend.max_files, 20) unique files are considered per result.
Files remain attachable if text extraction fails or reaches its text limit; the
tool result reports unavailable or truncated contents. Error and rejected
validation results do not attach files. IDs nested inside arbitrary result JSON
are not treated as attachments.
client_tools.durable_operations_enabled
Default: false. Enables durable escalation for explicitly registered client
operation kinds. Each kind must provide server-side input, binding and result
validation. Enabling this setting does not register an executor or make arbitrary
client tools durable. Native local evidence is registered only when
desktop_sidecar.local_delegation.enabled is also enabled and its signing policy
is valid. Its scoped child offer additionally requires an authenticated native
context and matching executor capability from the submitting frontend.
Eligible consent: none calls retain the ordinary in-memory fast path. Native
review operations enter the durable inbox immediately. On disconnect or timeout,
they may park in the logged operation inbox and later continue under a fresh
authenticated request. Existing generation leases coordinate backend replicas;
no backend credentials are stored with an operation. Kinds may impose narrower
rollout restrictions, including single-replica execution.
client_tools.tools.<key>.requires_client_registration
Type: boolean — Default: false
Set this to true for optional client tools. They are offered only if their name
appears in the requesting client’s X-Erato-Client-Tools availability header.
Configuration, facet allowlists, name collision handling, and the exclusion of
client tools from delegated runs still apply. The header cannot enable an
unconfigured or disallowed tool. Older clients and absent sidecars cannot offer
these tools. Existing tools keep their prior behavior with the default false.
Availability is captured for each new generation; a device that disconnects
mid-turn returns an ordinary tool error. Discovery retries when the sidecar is
unavailable at startup. Explicit task-result reactions also capture current
availability. Reconnecting to a stream retains its original generation context;
the existing MCP approval-continuation flow offers MCP tools only. There is no
separate sharing-consent workflow.
| Capability | Web and add-in behavior |
|---|---|
| Search field discovery | get_sidecar_search_fields invokes search.metadata_fields.v1 with {}. Returns field names, value types, operators, descriptions, and applicable document kinds. This describes filter support, not the distinct values stored in an index. |
| Index search | Emails, attachment files, and Teams messages; optional kind, source, sender, mailbox, date, and file-type filters, plus metadata_filters predicates. Returns metadata and source references, not excerpts or bodies. Only indexed local data is covered. |
| Outlook conversation read | A search email’s readConversation reference supplies the mailbox and Message-ID. Bodies are converted to readable text. Attachment metadata is always returned. |
| Attachment contents | includeAttachments=true uploads selected available bytes using the normal authenticated file API. Server authorization and the existing file processor apply before attachment text enters the tool result. attachmentNames selects exact names; otherwise inline files are skipped. |
| Missing or partial content | Explicit statuses for unavailable bytes, disabled uploads, limits, upload failures, unparseable files, and truncated bodies/text. Images can be downloaded, but this tool returns text; image understanding uses normal message attachments. |
Each metadata_filters entry has field, operator, and value, as defined by
the pinned SearchQueryV1Params contract. Metadata predicates and the existing
filters are combined with AND. The runtime preserves the sidecar’s descriptors
and source-specific field names. Compact mailbox IDs are normalized for the
existing filters.mailboxId and metadata mailbox_id equality, inequality, and
membership predicates. Other metadata values are forwarded unchanged.
An older sidecar without field discovery can still provide its existing search
and conversation capabilities; each executor advertises availability separately.
Conversation reads default to 20 messages (maximum 50), with up to 20,000 body characters per message and 80,000 across the result. Attachment count respects the configured chat limit, capped at 20, and combined bytes respect the configured upload-size limit. Extracted attachment text has a separate 80,000-character budget. An attachment search match without an email Message-ID cannot be read directly; search for its parent email first. Teams results provide metadata and links because the sidecar currently exposes no Teams body-read method.
Client-tool file references use the requesting user’s SharePoint access token when they refer to linked SharePoint files. Completed audio transcripts are returned as text within the same character budget; incomplete or failed transcriptions are reported explicitly. These tools do not start or wait for an audio transcription job.
These tools are an additional local-data source. Outlook Exchange Online retains its Graph implementation and delegated/shared-mailbox scoping. Exchange SE and other on-premises Outlook installations retain their item-scoped EWS proxy and Office-host EWS conversation path; the optional sidecar conversation adapter still falls back to EWS on failure. On-premises shared/delegated mailboxes keep their existing restrictions: the adapter never substitutes the user’s own mailbox. Teams Graph message, transcript, and file flows remain independent of the sidecar. The web app does not acquire Office.js or Graph host dependencies.
This integration adds no source/folder browser, global indexing controls, restart action, or benchmark UI.
facets
Configuration for the facets feature. Facets allow users to select tool-focused modes (e.g. web search) with optional tool allowlists, model setting overrides, and system prompt additions.
Deprecated compatibility: The previous [experimental_facets] section is still accepted, but it is deprecated. Use [facets] instead. Environment variables also accept both names; migrate to FACETS__.... Use only one name for each section across configuration files and environment variables; combining the old and new names is rejected as a duplicate field.
facets.facets
Map of facet configurations, keyed by facet id. Each facet defines display properties and optional behavior.
Type: object
Example:
[facets.facets.extended_thinking]
display_name = "Extended thinking"
icon = "iconoir-lightbulb"
disable_facet_prompt_template = true
model_settings = { reasoning_effort = "high" }
[facets.facets.web_search]
display_name = "Web search"
icon = "iconoir-globe"
tool_call_allowlist = ["web-search-mcp/*", "web-access-mcp/*"]
additional_system_prompt = "Please execute one or multiple web searches to answer the user's question."Using Langfuse:
[facets.facets.web_search]
display_name = "Web search"
icon = "iconoir-globe"
tool_call_allowlist = ["web-search-mcp/*", "web-access-mcp/*"]
additional_system_prompt = { source = "langfuse", prompt_name = "facet-web-search", label = "production", fallback = "Please execute one or multiple web searches to answer the user's question." }Using a static object:
[facets.facets.web_search]
display_name = "Web search"
icon = "iconoir-globe"
tool_call_allowlist = ["web-search-mcp/*", "web-access-mcp/*"]
additional_system_prompt = { source = "static", prompt = "Please execute one or multiple web searches to answer the user's question." }Hidden baseline facets
A facet with hidden = true is a platform-baseline: it never appears in the facet pool and cannot be selected or removed by the user. Instead of being toggled, it is injected as an always-on system prompt once at the start of a conversation — under the same rule as the base system prompt — and then persists in history for the rest of the conversation. Because it is an ordinary facet, its model_settings and tool_call_allowlist still apply (per request) like any active facet.
Scope activation to a platform with hidden_always_active_for_platform, matched against the request’s X-Erato-Platform header. Omit it to apply on all platforms.
Use a hidden facet for general platform-baseline behavior that should always be present (e.g. “you are running inside the Outlook add-in”). For per-request, task-specific instructions that should not persist across turns, use an action facet instead.
[facets.facets.outlook_baseline]
display_name = "Outlook baseline"
hidden = true
hidden_always_active_for_platform = "outlook"
additional_system_prompt = "You are assisting inside the Outlook add-in. Keep responses concise and email-appropriate."hidden- Whentrue, the facet is a hidden baseline: never shown in the pool, not user-selectable, and injected as an always-on system prompt. Defaults tofalse.hidden_always_active_for_platform- Optional platform identifier (e.g."outlook") restricting where a hidden facet auto-activates. Omit to apply on all platforms. Only meaningful together withhidden = true.
Per-facet delegation overrides
A facet may override the delegation.tasks runtime keys while it is selected, so a dedicated planning facet can budget differently from the deployment default. Every key is optional and falls back to the global value.
[facets.facets.plan]
display_name = "Plan & delegate"
tool_call_allowlist = ["erato/delegate_task", "web-search-mcp/*"]
[facets.facets.plan.delegation]
max_tasks_per_turn = 3
max_parallel = 2
# Generous with the user's own device, stingy with shared MCP capacity.
max_client_tool_calls_per_task = 40
max_server_tool_calls_per_task = 4
child_facet_ids = ["web_search"]
[facets.facets.plan.delegation.approval]
mode = "plan"
plan_min_tasks = 3max_tasks_per_turn- Overridesdelegation.tasks.max_tasks_per_turn.max_server_tool_calls_per_task- Overridesdelegation.tasks.max_server_tool_calls_per_task.max_client_tool_calls_per_task- Overridesdelegation.tasks.max_client_tool_calls_per_task. Raising this while leaving the server budget alone is the usual shape for a planning facet.persona- Overridesdelegation.tasks.persona.child_facet_ids- Overridesdelegation.tasks.child_facet_ids. Each entry must name a configured facet.run_modes- Overridesdelegation.tasks.run_modes. Unioned with the global list rather than replacing it, because a run mode is a capability the facet’s author meant to grant;waitis always kept available.scheduling- Overridesdelegation.tasks.scheduling. The first selected facet that states one wins.approval.modeandapproval.plan_min_tasks- Overridedelegation.tasks.approval.modeanddelegation.tasks.approval.plan_min_tasks. Merged like a cap rather than replacing: the strictestmode(alwaysbeatsplanbeatsasync_onlybeatsnever) and the lowestplan_min_taskswin, so selecting a second planning facet can only ever make a turn ask more.
facets.priority_order
List of facet IDs in the order they should be displayed.
Type: list<string>
Default value: []
Example:
[facets]
priority_order = ["extended_thinking", "web_search"]facets.tool_call_allowlist
Global tool allowlist that applies regardless of selected facets.
Type: list<string>
Default value: []
Example:
[facets]
tool_call_allowlist = ["web-search-mcp/*"]Selecting built-in erato/* tools
The erato namespace is reserved for tools the backend exposes itself, and no MCP server or client tool may claim it. Built-ins are selected the same way as any other tool — by an allowlist pattern — so erato/delegate_task, erato/*, a bare erato, or * all select the task-delegation tool where it is available.
Selection is opt-in: unlike the MCP allowlist, where leaving it unset means “all tools allowed”, an empty tool allowlist selects no built-in at all. A built-in also stands down if a real MCP tool claims its name, matching the precedence the other synthetic tools already follow.
Always pair a reserved pattern with a real MCP pattern on the same facet. Adding any pattern to a facet turns the derived MCP server filter from “no restriction” into a concrete set; if the only patterns are erato/* ones, that set is empty and the turn loses every MCP tool while the facet is selected. Write ["erato/delegate_task", "web-search-mcp/*"], not ["erato/delegate_task"].
Note that the delegate_to_assistant tool of the delegation.assistants route is not allowlist-gated: it is offered whenever the user’s message carries validated assistant mentions. Gating it would silently disable in-chat delegation in every deployment whose facets already carry narrow allowlists.
facets.facet_prompt_template
Optional global prompt template injected when facets are selected. Set to an empty string to disable. Uses the common prompt format when set to an object.
Type: string | object | None
Default value: The user has requested the use of the "{{facet_display_name}}" feature.\n\nPrioritize the use of the following tools:\n{{facet_tools_list}}
Example:
[facets]
facet_prompt_template = ""Using Langfuse:
[facets]
facet_prompt_template = { source = "langfuse", prompt_name = "facet-template", label = "production", fallback = "The user has requested the use of the \"{{facet_display_name}}\" feature.\n\nPrioritize the use of the following tools:\n{{facet_tools_list}}" }Using a static object:
[facets]
facet_prompt_template = { source = "static", prompt = "The user has requested the use of the \"{{facet_display_name}}\" feature.\n\nPrioritize the use of the following tools:\n{{facet_tools_list}}" }facets.only_single_facet
Whether only a single facet can be selected at the same time.
Type: boolean
Default value: false
facets.show_facet_indicator_with_display_name
Whether the chat box indicator should include the facet display name.
Type: boolean
Default value: false
facets.default_selected_facets
Facets that should be selected by default in the frontend (no backend logic is applied).
Type: list<string>
Default value: []
Example:
[facets]
default_selected_facets = ["web_search"]action_facets
Configuration for action facets. Action facets are non-user-visible, parameterized extensions of the facets system that inject context-aware additional system prompts based on client-supplied action ID and arguments. Unlike regular facets, action facets are not selectable by the user — they are resolved from client context at request time.
Clients send an action_facet object (containing id and args) alongside the user’s message on submit, edit, and regenerate requests. The backend resolves the configured prompt template, validates the arguments against the allowed list, and injects the rendered prompt as an additional system prompt before generation.
Note: Action facet prompts are per-request and ephemeral. They do not replace or suppress any other prompt in the composition pipeline — the base system prompt is always preserved regardless of whether an action facet is active.
action_facets.enable_builtin_ms_office_addin
Whether to inject the builtin Outlook action facets used by the Microsoft Office add-in.
Type: boolean
Default value: false
When action_facets.enable_builtin_ms_office_addin = true, the backend injects these builtin Outlook action facets unless the same IDs are explicitly configured under action_facets.facets:
outlook_rewrite_selectionoutlook_review_draft
action_facets.facets.<id>
Map of action facet configurations, keyed by action facet id. Each action facet defines a prompt template with declared argument variables.
Type: object
Example:
[action_facets.facets.excel_rewrite_selection]
display_name = "Excel Rewrite Selection"
platform = "excel"
template = """
The user is working in Excel on sheet "{{sheet_name}}".
The current selection type is "{{selection_type}}".
Rewrite the following selected content:
{{selected_text}}
"""
allowed_args = ["sheet_name", "selection_type", "selected_text"]Each action facet supports these fields:
display_name- Human-readable name for the action facetplatform- Optional platform identifier (e.g."excel","outlook")template- Prompt template with{{variable}}placeholdersallowed_args- List of allowed argument names for the template
Template Rendering
Templates use {{variable}} placeholders that are replaced with argument values supplied in the request. Rendering uses single-pass left-to-right scanning — substituted values are never re-scanned for further {{…}} patterns, preventing injection via argument values.
Validation
The backend validates each request against the action facet configuration:
- The
action_facet.idmust match a configured action facet — unknown IDs are rejected - All keys in
argsmust be present inallowed_args— unexpected keys are rejected - Argument values are subject to a maximum size limit (10 KB per value)
Request Shape
The action_facet field is accepted on submit, edit, and regenerate endpoints. It is not accepted on resume, since the prompt was already composed when the generation started.
{
"action_facet": {
"id": "excel_rewrite_selection",
"args": {
"selection_type": "cell_text",
"sheet_name": "Q1 Plan",
"selected_text": "badly phrased content here"
}
}
}Differences from Regular Facets
| Aspect | Regular Facets | Action Facets |
|---|---|---|
| User-visible | Yes, selectable in UI | No, resolved from client context |
| Parameterized | No, behavior is config-time only | Yes, runtime args from request |
| Request field | selected_facet_ids | action_facet |
| Prompt lifecycle | Toggle-state based, persistent across messages | Per-request, ephemeral |
| Base system prompt | May suppress historical replay on newly-enabled turn | Never suppresses — always additive |
Validated client submissions
Client tools normally return a result to the model and continue generation. Add a
submission table to a client tool to validate its arguments against its configured
JSON Schema before dispatch, then finish generation immediately when the client
accepts the draft. The client executor must only validate and stage the draft;
applying it remains a separate action requiring user confirmation.
[client_tools.tools.submit_draft]
name = "submit_draft"
namespace = "editor"
description = "Validate and stage a draft for review. Call alone after reading."
parameters = '''{"type":"object","properties":{"title":{"type":"string"}},"required":["title"],"additionalProperties":false}'''
[client_tools.tools.submit_draft.submission]
max_attempts = 3
native_schema = "auto"Select the tool using editor/submit_draft in a global, regular-facet or
action-facet tool_call_allowlist. Existing tools are unchanged when submission
is absent. Schemas support local references, and never fetch external schemas.
max_attempts(default: 3, range: 1–10) - Total attempts per tool and generation, including the initial call. Schema errors and client failures return bounded feedback for correction; the final failed attempt ends generation. A timeout ends generation without retrying a possibly staged draft.native_schema(default:"auto") -autouses native enforcement when the adapter, explicit model capability and schema are compatible; otherwise it uses local validation and feedback.offalways uses local validation.requiredrejects request preparation if native enforcement is unavailable, including whencompat_omit_strictis enabled.
Native enforcement does not replace the host’s parser or domain checks. A schema
must already fit the provider subset: object properties are all required and
additionalProperties is false. Optional fields are never silently made required
or rewritten. More general schemas continue to work through local validation.
Gemini/Vertex and Ollama currently use that local path.
The shared frontend executor may return ok: false, an error string and
validationErrors: [{ path, code, message }]. Paths are JSON Pointers into the
arguments. The backend passes at most 16 bounded diagnostics to the model, with
the remaining attempt count. A successful executor returns a small receipt in
result; the accepted arguments and receipt are persisted without an additional
model call. The host’s review UI can restore the draft from those records.
Compact replay of earlier client tool calls
By default, every client tool call from earlier in a chat is sent to the model
again on each later request, with its full arguments and result. Add a replay
table to replace calls from earlier user turns with a small receipt instead.
Use it for tools whose results expire at the next user message, such as reads of
a document capture. Those results cost context without helping later turns.
[client_tools.tools.read_pages]
name = "read_pages"
namespace = "editor"
description = "Read the captured document page by page."
parameters = '''{"type":"object","properties":{"snapshot":{"type":"string"},"cursor":{"type":["string","null"]}},"required":["snapshot"]}'''
[client_tools.tools.read_pages.replay]
mode = "receipt"
keep_input_fields = ["$.snapshot"]
keep_output_fields = ["$.status", "$.error", "$.result.snapshot", "$.result.complete"]mode(default:"full") -fullreplays arguments and results unchanged.receiptreplaces both with a receipt for calls from earlier user turns.keep_input_fields(default:[]) - Argument fields to keep in the receipt, as$.key.keypaths. Only object keys are supported, not array indices or wildcards. At most 32 paths.keep_output_fields(default:[]) - Result fields to keep, using the same syntax. Paths start at the stored tool output, so client results are under$.result, and$.status,$.errorand$.submission.statusare also available.
A receipt keeps the tool name and call ID, the call status, the listed fields at
their original nesting, and a fixed note. The note tells the model the body was
omitted and that it should call the tool again for current data. A listed field
whose JSON is larger than 512 bytes is replaced by a size marker. Missing fields
are skipped. A call that never returned a result keeps replaying without one. The
fields may only be set when mode = "receipt". Client tools that share a
model-facing name across namespaces must use the same replay settings.
A call is compacted only when the generation that made it recorded offering a client tool with that name. When an MCP tool has the same name, the MCP tool is offered instead, so its calls always replay in full. Calls from generations without that record, including all calls stored before it existed, also replay in full.
A user turn is one user message and every model call, tool call, correction and
approval continuation that answers it. Calls from the current user turn are always
replayed in full, so paging and corrections still see their own results. Only the
model’s context changes. Stored messages, chat history, previews and traces keep
the full arguments and results. A call is compacted when the next user message is
composed. History that an earlier request already stored in full stays full. Each request’s stored input records the
receipts the model received, so switching mode back to full restores only calls
from the latest earlier turn. Older calls in that chat stay compacted.
budget
Configuration for budget tracking and display functionality. This feature allows you to track and display per-user spending based on token usage and model pricing.
Type: object
Default behavior: Budget tracking is disabled by default.
Example:
[budget]
enabled = true
max_budget = 100.0
budget_currency = "USD"
warn_threshold = 0.8
budget_period_days = 30budget.enabled
Whether budget tracking and display is enabled. When disabled, no budget information will be calculated or displayed to users.
Type: boolean
Default value: false
Example: true
budget.max_budget
The maximum budget amount per budget period (unit-less). This value is used for calculating budget usage percentages and determining when warning thresholds are exceeded.
The budget amount is treated as unit-less for flexibility - the actual monetary value depends on your model pricing configuration and the configured display currency.
Type: number | None
Required when: budget.enabled = true
Example: 100.0
budget.budget_currency
The currency to use for display purposes in the frontend. This is purely cosmetic and doesn’t affect calculations - all budget amounts and costs are treated as unit-less values.
Type: string
Supported values: "USD", "EUR"
Default value: "USD"
Example: "EUR"
budget.warn_threshold
The threshold (between 0.0 and 1.0) at which to warn users about budget usage. When a user’s spending exceeds this percentage of their budget limit, they will receive warnings in the UI.
For example, a value of 0.7 means users will be warned when they’ve used 70% of their budget.
Type: number
Default value: 0.7
Valid range: 0.0 to 1.0
Example: 0.8
budget.budget_period_days
Number of days that counts as one budget period. Budget usage is calculated and reset based on this period length.
Type: number
Default value: 30
Example: 7 (for weekly budgets), 30 (for monthly budgets)
Complete Budget Configuration Example
[budget]
enabled = true
max_budget = 50.0
budget_currency = "EUR"
warn_threshold = 0.75
budget_period_days = 7
# You also need to configure model pricing for accurate cost calculations
[chat_providers]
priority_order = ["main"]
[chat_providers.providers.main]
provider_kind = "openai"
model_name = "gpt-4o"
api_key = "sk-your-key"
[chat_providers.providers.main.model_capabilities]
cost_input_tokens_per_1m = 5.0
cost_output_tokens_per_1m = 15.0Note: Budget tracking requires that you configure accurate pricing information in your chat provider’s model_capabilities section. The budget calculations are based on actual token usage multiplied by the configured token prices.
caches
Configuration for in-memory caches that improve performance by reducing redundant file processing and token counting operations. Caches use memory-based size limits (in megabytes) rather than entry counts.
Type: object
Default behavior: file_contents_cache_mb, file_bytes_cache_mb, and token_count_cache_mb default to 100MB each. file_processing_parallelism defaults to 4.
Example:
[caches]
file_contents_cache_mb = 100
file_bytes_cache_mb = 100
token_count_cache_mb = 100
file_processing_parallelism = 4caches.file_contents_cache_mb
Maximum memory size in megabytes for the file contents cache. This cache stores parsed file contents indexed by file ID to avoid repeatedly reading and parsing the same files.
When the cache reaches this size limit, the least recently used entries will be automatically evicted to make room for new entries.
Type: number
Default value: 100
Example: 200 (for 200MB cache), 50 (for 50MB cache)
caches.file_bytes_cache_mb
Maximum memory size in megabytes for the raw file bytes cache. This cache stores original file bytes (text and images) before extraction and parsing.
This cache helps avoid repeated file reads for frequently reused files and can be useful when documents are large or re-opened often.
Type: number
Default value: 100
Example: 300 (for 300MB cache), 75 (for 75MB cache)
caches.token_count_cache_mb
Maximum memory size in megabytes for the token count cache. This cache stores token counts indexed by file content to avoid repeatedly tokenizing the same content.
When the cache reaches this size limit, the least recently used entries will be automatically evicted to make room for new entries.
Type: number
Default value: 100
Example: 200 (for 200MB cache), 50 (for 50MB cache)
caches.file_processing_parallelism
Maximum number of file processing tasks that may run in parallel when cache misses require extraction, token counting, or related preprocessing.
Higher values can improve throughput for concurrent requests that trigger expensive file processing, while lower values reduce CPU and memory pressure.
Type: number
Default value: 4
Example: 8 for higher throughput, 2 for more conservative resource usage
Complete Cache Configuration Example
# Configure caches for optimal performance
[caches]
# Allocate 200MB for file contents caching
file_contents_cache_mb = 200
# Allocate 150MB for raw file bytes caching
file_bytes_cache_mb = 150
# Allocate 100MB for token count caching
token_count_cache_mb = 100
# Allow up to 6 parallel file processing tasks
file_processing_parallelism = 6Performance Considerations:
- Increase cache sizes if you have many users frequently accessing the same files
- Decrease cache sizes in memory-constrained environments
- The file contents cache is typically more beneficial than the token count cache
- Cache efficiency depends on file reuse patterns in your workflow
generation_status
Configuration for the persisted per-chat generation status that powers the chat list’s running/finished/error indicators. While a generation runs, the backend periodically refreshes a heartbeat on the chat; generations whose heartbeat goes stale (for example because the process was killed mid-generation) are marked as errored.
Type: object
Default behavior: heartbeat_interval_secs defaults to 10, stale_after_secs to 30, terminal_retention_secs to 60, shutdown_drain_secs to 25, and provider_idle_timeout_secs to 600.
Example:
[generation_status]
heartbeat_interval_secs = 10
stale_after_secs = 30
terminal_retention_secs = 60
shutdown_drain_secs = 25
provider_idle_timeout_secs = 600generation_status.heartbeat_interval_secs
Interval in seconds at which in-flight generations refresh their heartbeat on the chat.
Type: number
Default value: 10
generation_status.stale_after_secs
Seconds after the last heartbeat before a running generation is considered stale. Stale generations are marked as errored and no longer reported as running.
Type: number
Default value: 30
generation_status.terminal_retention_secs
Seconds a completed or errored generation remains visible in the generating-chats endpoint after it ended. Clients use this window to observe a chat leaving the running state.
Type: number
Default value: 60
generation_status.shutdown_drain_secs
Upper bound in seconds on how long the process keeps running after a shutdown signal (SIGTERM or Ctrl-C): one deadline shared by the connection drain and the generation drain, within which aborted in-flight generations persist their terminal outcome before exiting. Without this window, a deploy would kill generations mid-stream and their chats would only be marked as errored once the stale-heartbeat reaper notices. Set to 0 to skip the wait entirely.
The value should stay below the deployment’s termination grace period (Kubernetes defaults to 30 seconds), or the process gets killed before the drain finishes.
Type: number
Default value: 25
generation_status.provider_idle_timeout_secs
Seconds a turn may wait on its chat provider without receiving any content before the generation is failed and reported as a provider error.
This is an idle budget, not a total one: a long answer that keeps streaming never trips it, however long it runs. It exists because the heartbeat above proves that the backend process is alive, not that the generation is making progress. A provider connection that stalls without closing would otherwise hold the chat in the running state indefinitely, where neither the stale-heartbeat reaper nor the user can clear it.
The budget is measured on content, not on traffic. Keep-alive pings, empty deltas and SSE comments are dropped by the provider adapter and never reach the turn, so a provider that holds the connection open without producing anything still counts as idle. That is deliberate — a live socket is exactly what a wedged upstream and a healthy slow one have in common — but it means the value has to be sized against a reasoning model’s think time before its first token, not against a transport timeout.
Set to 0 to wait on the provider indefinitely, which is how earlier releases behaved.
Type: number
Default value: 600