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.
Configuration lifecycle
Each documented key shows its stability and the Erato release in which that exact key first appeared:
| Stability | Meaning |
|---|---|
stable | Suitable for general adoption. |
alpha | Available to eager testers; may be rough around the edges. |
beta | Available to eager testers; expected to be ready for stabilization in the next release. |
unstable | Not ready for adoption. May be intended only for development and never stabilized. |
deprecated | Should no longer be adopted; follow the replacement guidance where provided. |
Stability labels are initially conservative: existing deprecated keys remain
deprecated, and other keys start as unstable pending review. These labels
communicate maturity; they do not change which configuration values are accepted.
Introduced in identifies the first release containing the key in the generated
configuration reference. Keys that predate that reference use 0.6.0, the first
release containing it. unreleased means the key has not appeared in a release;
release preparation replaces this marker with the release version. Renamed keys
and their deprecated aliases can have different introduction versions.
Both metadata properties can be declared on a parent section. Children inherit each property independently unless they specify their own value. Labels beside individual keys show the resolved values; a section may contain keys with different stability levels or introduction versions.
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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.logo_path, frontend.logo_dark_path, frontend.assistant_avatar_path
frontend.logo_path — Stability: unstable · Introduced in: 0.7.0
frontend.logo_dark_path — Stability: unstable · Introduced in: 0.7.0
frontend.assistant_avatar_path — Stability: unstable · Introduced in: 0.7.0
Optional browser URLs for the light/dark application logos and assistant avatar, shared by web, Office and Teams frontends. These typed settings override theme declarations and legacy THEME_LOGO_PATH, THEME_LOGO_DARK_PATH and THEME_ASSISTANT_AVATAR_PATH environment keys. When unset, declarations and conventional theme files apply. Light and dark logo overrides are independent. See shared branding for a complete example and precedence.
frontend.favicon_path
Stability: unstable · Introduced in: 0.7.0
Browser URL for the favicon. Use a deployment-root-relative path to a served image, such as an SVG, ICO, or PNG. Chat activity badges use the same image.
Default value: /favicon.svg (uses the theme favicon or the bundled default, with the existing ICO fallback)
Type: string
Example
[frontend]
favicon_path = "/public/common/custom-theme/my-company/icon.png"For local frontend development, set VITE_FAVICON_PATH to override this value.
frontend.translation_po_compilation_mode
Stability: unstable · Introduced in: 0.6.2
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 until the modification time or size of a messages.po it was built from (including the default-language catalog used for missing translations) changes.
Default value: precompiled
Type: precompiled | just_in_time
Example
[frontend]
translation_po_compilation_mode = "just_in_time"frontend.disable_upload
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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.keep_tools_dropdown_open
Stability: beta · Introduced in: unreleased
Whether the Tools dropdown stays open after selecting or deselecting a facet or toggling a connector. This applies to both the chat input and the assistant editor’s Tools dropdown. Enable this to select multiple facets without reopening the dropdown when facets.only_single_facet is false. Single-facet selection rules still apply when that setting is true.
The menu can still be dismissed with Escape, a click outside, or the trigger button. Actions such as browsing tools or assistants still close it. The compact ”+” menu already stays open for tool toggles independently of this setting.
Default value: false
Type: boolean
Example:
[frontend]
keep_tools_dropdown_open = truefrontend.disable_logout
Stability: unstable · Introduced in: 0.6.0
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_editing
Stability: unstable · Introduced in: 0.7.0
Whether to show the edit action on user messages. Set to false for customer chat interfaces that should not offer message editing. Existing chat permissions still apply. This presentation setting does not disable the editing API.
Default value: true
Type: boolean
frontend.enable_message_regeneration
Stability: unstable · Introduced in: 0.7.0
Whether to show the regenerate action on assistant responses. Existing chat permissions still apply. This presentation setting does not disable the regeneration API. Copying and message feedback remain available independently.
Default value: true
Type: boolean
frontend.enable_chat_history_filters
Stability: unstable · Introduced in: 0.7.0
Whether to offer filtering and grouping in the chat history of the web sidebar and the Office add-in. When false, the history shows an ungrouped Recent list of active chats, without delegated runs, and search lists active chats only. Saved browser filters are ignored for both display and API requests; they are retained for use if filtering is enabled again. Archived chats are then not listed anywhere, so archiving a chat hides it from the interface.
Default value: true
Type: boolean
Example: reduced customer interface
[frontend]
enable_message_editing = false
enable_message_regeneration = false
enable_chat_history_filters = falsefrontend.enable_message_feedback
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.2
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 the hosts that embed it:
- Outlook:
https://outlook.office.comandhttps://outlook.cloud.microsoft. - Word on the web:
https://*.officeapps.live.comandhttps://*.sharepoint.com. - The Teams personal tab:
https://teams.microsoft.com,https://*.teams.microsoft.com,https://*.microsoft365.com,https://*.office.com,https://*.cloud.microsoft,https://outlook.office365.comandhttps://outlook-sdf.office365.com.
Default value: []
Type: array<string>
Example
[frontend]
extra_frame_ancestors = ["https://outlook.cloud.microsoft"]frontend.allow_any_frame_ancestor
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
desktop_sidecar.local_delegation.enabled — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.local_delegation.backend_origin — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.local_delegation.signing_key_id — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.local_delegation.signing_private_key_pem — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.local_delegation.verification_keys.<key> — Stability: unstable · Introduced in: 0.7.0
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 issuer origin, for example
https://erato.example.com. Use https in production; a plaintext http
origin is accepted for development setups and logs a startup warning. The value
must be exactly https://host[:port] or http://host[:port] with no path, query
or trailing slash; other schemes are rejected.
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
desktop_sidecar.port — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.directory_suffix — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.launch_suffix — Stability: unstable · Introduced in: unreleased
desktop_sidecar.icon — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.display_name — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.product_code — Stability: unstable · Introduced in: unreleased
desktop_sidecar.upgrade_code — Stability: unstable · Introduced in: unreleased
Configure a distinct port, directory suffix and launch suffix for each installation:
[desktop_sidecar]
port = 23124
directory_suffix = "-staging"
launch_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, and to the Windows MSI installation directory (for example,
C:\Program Files\Erato Desktop Sidecar-staging), 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 524262-byte payload capacity
of the 512 KiB slot; use a small, optimized PNG or an installer when more space
is needed.
Windows MSI and Intune downloads also isolate the Windows Installer product, upgrade family, and component GUIDs by directory suffix. Suffix comparisons for Windows identities are case-insensitive, matching Windows paths. Each environment must use a distinct directory suffix, launch suffix, and port. Keep the suffixes stable across upgrades. The default, empty directory suffix preserves the template’s product and upgrade codes and installation path.
desktop_sidecar.product_code and desktop_sidecar.upgrade_code optionally
override the MSI’s ProductCode and UpgradeCode. Each accepts a non-nil UUID,
with or without braces; MSI output uses uppercase, braced GUIDs. These settings
affect both MSI and .intunewin downloads.
[desktop_sidecar]
directory_suffix = "-staging"
launch_suffix = "-staging"
port = 23124
display_name = "Erato Staging"
# Optional explicit MSI identities:
product_code = "{11111111-2222-4333-8444-555555555555}"
upgrade_code = "{AAAAAAAA-BBBB-4CCC-8DDD-EEEEEEEEEEEE}"When omitted, each code is derived deterministically from the corresponding template code and the directory suffix. Repeated downloads therefore retain the same ProductCode, while a new template ProductCode yields a new environment-specific ProductCode. The UpgradeCode remains stable across releases as long as the template upgrade family and suffix remain unchanged.
When setting codes explicitly, use a distinct ProductCode for each environment and major upgrade, and keep each environment’s UpgradeCode stable across its releases. Changing an UpgradeCode breaks that environment’s existing upgrade chain. Do not reuse these example GUIDs for production deployments. Every personalized MSI receives a fresh PackageCode because its contents differ from the template. Intune detection metadata is read from the finalized MSI, including all overridden or derived codes.
desktop_sidecar.launch_suffix independently controls the Windows launch URI scheme.
It is appended as plain text, with no separator added: "-staging" produces
erato-launch-staging://launch. URI schemes are case-insensitive and normalized to
lowercase. Up to 64 ASCII letters, digits, hyphens, dots and plus signs are allowed;
underscores are not valid in URI schemes. Omitted or empty values keep erato-launch,
regardless of directory_suffix. Keep each environment’s launch suffix stable.
Deploy a sidecar build
with environment-specific protocol registration support together with this backend;
older sidecar executables still register the shared erato-launch handler at startup.
desktop_sidecar.tls
desktop_sidecar.tls.certificate_pem — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.tls.private_key_pem — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.tls.intermediate_certificate_pem — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.tls.intermediate_private_key_pem — Stability: unstable · Introduced in: 0.7.0
desktop_sidecar.tls.validity_days — Stability: unstable · Introduced in: 0.7.0
TLS identity added to the immutable bootstrap data of personalized Windows
EXE/MSI/.intunewin 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
524262-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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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"For each Windows MSI installer, Erato also offers a Microsoft Intune package
(.intunewin) on the desktop-sidecar download page. This package is generated
on demand from the MSI after injecting the organization bootstrap, including
configured origins, installation identity, and TLS settings. No extra file is
required in the distribution directory. The derived artifact’s API file ID is
the MSI file ID followed by .intunewin (for example, installer.intunewin);
this ID must not already be used in the manifest. The default download remains
the manifest’s default file.
Upload the downloaded .intunewin as a Windows app (Win32) in Microsoft Intune.
The package contains MSI metadata for automatic product-code detection and
install/uninstall command setup. For an MSI named
erato-desktop-sidecar-windows-x86_64.msi, the silent install command is:
msiexec /i "erato-desktop-sidecar-windows-x86_64.msi" /qnUse msiexec /x "{ProductCode}" /qn for uninstall, replacing {ProductCode}
with the code detected from the uploaded MSI. Product codes belong to the
packaged MSI; do not reuse a code from an older release. The download response
reports the actual package size; sizes in the artifact listing are template
estimates.
desktop_sidecar.organization_configuration.show_tray_icon
Stability: unstable · Introduced in: 0.7.0
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 = falsedesktop_sidecar.organization_configuration.outlook_addin_autopin
Stability: unstable · Introduced in: unreleased
Controls whether the desktop sidecar pins the Erato Outlook add-in in Outlook on macOS and Windows, so users find its buttons without pinning them themselves. The sidecar pins while Outlook is closed, and never pins again what a user unpinned.
When this value is omitted, a user-specific sidecar setting may take
precedence; if neither layer provides a value, the sidecar does not pin, so a
deployment opts in. Setting it to false or removing it stops further pinning
and leaves existing pins in place. In classic Outlook for Windows, the sidecar
needs the add-in’s id; see
outlook_addin_id.
Default value: None
Type: boolean | None
Example
[desktop_sidecar.organization_configuration]
outlook_addin_autopin = truedesktop_sidecar.organization_configuration.outlook_addin_autopin_panes
Stability: unstable · Introduced in: unreleased
Controls whether outlook_addin_autopin also pins the add-in’s task panes in
classic Outlook for Windows, which then open by themselves when an item is
selected. Set it to false to pin only the add-in’s buttons. When this value
is omitted, a user-specific sidecar setting may take precedence; if neither
layer provides a value, the sidecar pins the task panes too.
Default value: None
Type: boolean | None
Example
[desktop_sidecar.organization_configuration]
outlook_addin_autopin = true
outlook_addin_autopin_panes = falsedesktop_sidecar.organization_configuration.outlook_addin_id
Stability: unstable · Introduced in: unreleased
The manifest id of the Erato Outlook add-in, which the desktop sidecar needs to
find the add-in in classic Outlook for Windows. Normally left unset: while
integrations.ms_office.addin.enabled is
true, the backend sends
integrations.ms_office.addin.addin_id.
Set it only if the add-in deployed to Outlook uses a different id.
Default value: None
Type: string | None
i18n
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
Stability: unstable · Introduced in: 0.6.0
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>
guardrails.prompt_patterns.<pattern-id>.type — Stability: unstable · Introduced in: 0.6.1
guardrails.prompt_patterns.<pattern-id>.pattern — Stability: unstable · Introduced in: 0.6.1
guardrails.prompt_patterns.<pattern-id>.language — Stability: unstable · Introduced in: 0.6.1
guardrails.prompt_patterns.<pattern-id>.tags.[] — Stability: unstable · Introduced in: 0.6.1
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
Stability: unstable · Introduced in: 0.6.0
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"]
Provider replacements and archives
chat_providers.replacement_providers.[].old — Stability: unstable · Introduced in: unreleased
chat_providers.replacement_providers.[].new — Stability: unstable · Introduced in: unreleased
chat_providers.providers.<provider-id>.archived — Stability: unstable · Introduced in: unreleased
speech_providers.replacement_providers.[].old — Stability: unstable · Introduced in: unreleased
speech_providers.replacement_providers.[].new — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.archived — Stability: unstable · Introduced in: unreleased
decision_providers.replacement_providers.[].old — Stability: unstable · Introduced in: unreleased
decision_providers.replacement_providers.[].new — Stability: unstable · Introduced in: unreleased
decision_providers.provider.<provider-id>.archived — Stability: unstable · Introduced in: unreleased
Set archived = true to retain a provider’s historical metadata without making it selectable or requiring inference credentials. Configure replacement_providers as a list of { old, new } ID pairs under chat_providers, speech_providers, or decision_providers. An old ID can be archived or absent from the configuration; chains must end at an active provider. Chat model permission rules for an old ID also grant access to its replacement. Keep archived chat entries and their original token prices to calculate historical spending correctly; an absent old entry cannot supply its historical price or display name. File storage providers are not replaced by this mechanism.
[chat_providers]
priority_order = ["new"]
replacement_providers = [{ old = "old", new = "new" }]
[chat_providers.providers.old]
archived = true
model_name = "old-model"
[chat_providers.providers.old.model_capabilities]
cost_input_tokens_per_1m = 1.0
cost_output_tokens_per_1m = 2.0
[chat_providers.providers.new]
provider_kind = "openai"
model_name = "new-model"For speech synthesis, use the same mapping under speech_providers and update
the configured audio_conversational.speech_provider only if desired:
[speech_providers]
replacement_providers = [{ old = "old-voice", new = "new-voice" }]
[speech_providers.provider.old-voice]
archived = true
[speech_providers.provider.new-voice]
provider_kind = "openai"
model_name = "tts-model"
voice = "alloy"Embedded PDF image retrieval
chat_providers.all_providers.enable_embedded_image_retrieval_tool — Stability: unstable · Introduced in: 0.7.0
chat_providers.providers.<provider-id>.enable_embedded_image_retrieval_tool — Stability: unstable · Introduced in: 0.7.0
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
chat_providers.all_providers.guardrails.filter_input_prompt_injection.enabled — Stability: unstable · Introduced in: 0.6.1
chat_providers.all_providers.guardrails.filter_input_prompt_injection.filter_pattern_ids.[] — Stability: unstable · Introduced in: 0.6.1
chat_providers.all_providers.guardrails.filter_input_prompt_injection.filter_pattern_tags.[] — Stability: unstable · Introduced in: 0.6.1
chat_providers.all_providers.guardrails.filter_input_prompt_injection.exclude_pattern_ids.[] — Stability: unstable · Introduced in: 0.7.0
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
Provider additional_request_headers entries use Header-Name=value syntax. Values
support Handlebars templates with these variables:
{{erato_user.id}}: the authenticated Erato user ID.{{id_token.claims.<claim>}}: a claim from the user’s ID token. Missing claims render as empty strings.{{chat.id}}: the existing Erato conversation UUID. It stays the same across turns, retries, tool-call iterations, and resumed generation. Summary and history compaction requests use the conversation’s ID; delegated runs use their own chat ID.
When no conversation exists, such as during model availability checks or standalone
transcription, headers referencing chat are omitted entirely. No session ID is
invented, and no empty session header is sent. Other headers retain their existing behavior.
For example, configure Bifrost’s session header using the conversation UUID:
[chat_providers.providers.bifrost]
additional_request_headers = ["x-bf-session-id={{chat.id}}"]Session affinity also requires the appropriate configuration in Bifrost.
chat_providers.providers.<provider-id>.provider_kind
Stability: unstable · Introduced in: 0.6.0
The type of chat provider to use.
Type: string
Supported values: "openai", "openai_responses", "azure_openai", "azure_openai_responses", "ollama", "gemini", "vertex_ai", "openai_audio_transcriptions"
"openai_audio_transcriptions" uses the OpenAI-compatible /v1/audio/transcriptions API for audio modes. It is configured like other providers, but is excluded from chat model selection.
Example: "openai"
chat_providers.providers.<provider-id>.model_name
Stability: unstable · Introduced in: 0.6.0
The name of the model to use with the chat provider.
Type: string
Example: "gpt-4o"
chat_providers.providers.<provider-id>.validate_availability
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
The API key for the chat provider.
Type: string | None
Example: "sk-..."
chat_providers.providers.<provider-id>.system_prompt
chat_providers.providers.<provider-id>.system_prompt — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.system_prompt.fallback — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.system_prompt.label — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.system_prompt.prompt — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.system_prompt.prompt_name — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.system_prompt.source — Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
chat_providers.providers.<provider-id>.guardrails.filter_input_prompt_injection.enabled — Stability: unstable · Introduced in: 0.6.1
chat_providers.providers.<provider-id>.guardrails.filter_input_prompt_injection.filter_pattern_ids.[] — Stability: unstable · Introduced in: 0.6.1
chat_providers.providers.<provider-id>.guardrails.filter_input_prompt_injection.filter_pattern_tags.[] — Stability: unstable · Introduced in: 0.6.1
chat_providers.providers.<provider-id>.guardrails.filter_input_prompt_injection.exclude_pattern_ids.[] — Stability: unstable · Introduced in: 0.7.0
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:
chat_providers.providers.<provider-id>.model_capabilities.context_size_tokens — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_image_understanding — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_audio_input — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_reasoning — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_reasoning_summary — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_encrypted_reasoning_content — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_verbosity — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.supports_strict_tool_calling — Stability: unstable · Introduced in: 0.7.0
chat_providers.providers.<provider-id>.model_capabilities.cost_input_tokens_per_1m — Stability: unstable · Introduced in: 0.6.0
chat_providers.providers.<provider-id>.model_capabilities.cost_output_tokens_per_1m — Stability: unstable · Introduced in: 0.6.0
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.
chat_providers.providers.<provider-id>.model_settings.compat_no_replay_summary — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.model_settings.compat_no_replay_summary — Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.1
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.
chat_providers.summary.system_prompt.fallback — Stability: unstable · Introduced in: 0.6.1
chat_providers.summary.system_prompt.label — Stability: unstable · Introduced in: 0.6.1
chat_providers.summary.system_prompt.prompt — Stability: unstable · Introduced in: 0.6.1
chat_providers.summary.system_prompt.prompt_name — Stability: unstable · Introduced in: 0.6.1
chat_providers.summary.system_prompt.source — Stability: unstable · Introduced in: 0.6.1
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
Default: "naive"
Supported values:
"naive"uses filename extensions first: known image filename extensions are routed as images; known extractable extensions 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.
In naive mode, extensionless files first use a supported storage MIME type for image routing or extraction (including PDF, DOCX, HTML, and JSON). MIME matching ignores case, surrounding whitespace, and parameters. Failed extraction retains a file reference; it does not retry a document as plain text. Filenames such as png or pdf without a dot are extensionless.
For extensionless files without a supported MIME route, and files with unsupported extensions, processing falls back to text/plain if either the stored MIME is text/* or the first up to 8 KiB are valid UTF-8 without NUL bytes. Characters crossing that boundary are checked for completeness. This is only a prefix heuristic, and text MIME metadata is trusted even when the bytes fail it. Rejected binaries and failed fallback extraction remain available as file references. Known-extension/MIME conflicts are unchanged.
The prefix heuristic reads and caches the entire file, including previously reference-only binaries. The storage range helper currently also reads the whole file and does not support SharePoint, so processing uses the existing authenticated byte cache to avoid an additional download for extraction. Existing upload size limits, cache limits, and processing concurrency controls still apply; the 8 KiB inspection is not a download or memory limit. This applies to MCP outputs and already-admitted uploads and does not broaden upload admission: the default supported_operations policy still rejects unsupported extensions.
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
Stability: unstable · Introduced in: 0.7.0
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_context
file_context.retrieve_file_contents_enabled — Stability: unstable · Introduced in: 0.7.0
file_context.native_file_formats_bypass_limit — Stability: unstable · Introduced in: 0.7.0
file_context.max_inline_tokens_per_file — Stability: unstable · Introduced in: 0.7.0
file_context.max_total_attachment_tokens — Stability: unstable · Introduced in: 0.7.0
file_context.max_context_fraction — Stability: unstable · Introduced in: 0.7.0
file_context.max_preview_tokens_per_file — Stability: unstable · Introduced in: 0.7.0
file_context.preview.csv_max_sample_rows — Stability: unstable · Introduced in: 0.7.0
file_context.preview.csv_max_columns — Stability: unstable · Introduced in: 0.7.0
file_context.preview.csv_max_field_chars — Stability: unstable · Introduced in: 0.7.0
file_context.preview.text_max_chars — Stability: unstable · Introduced in: 0.7.0
The integrated retrieve_file_contents tool is enabled by default and offered when
the conversation has file references. Set retrieve_file_contents_enabled = false
to disable it. Restrictive facet tool allowlists must permit
erato/retrieve_file_contents; a conflicting MCP or client tool name takes precedence.
The model chooses when to request an omitted document. Small files still go directly
into context without a tool call.
Every preview/reference indicates whether integrated full-text retrieval is currently available. Availability means the tool is offered and the file is eligible; it does not mean extraction has already succeeded. Missing permissions, unavailable storage, unsupported formats and known extraction restrictions make retrieval unavailable. This indicator and its text count toward attachment budgets. It says nothing about whether an MCP tool can analyze the original bytes.
The tool accepts one file_reference such as erato-file://<file_upload_id>, checks
file access again and uses the configured extractor and complete extraction cache.
It never invokes MCP or an additional model. Uploads, configured assistant files,
and external files admitted through the existing MCP/client-tool approval flow are
eligible for this read. Arbitrary file IDs in tool output are not admitted references.
Retrieval itself requires no additional tool-call approval.
Tool results store only the reference, filename and status metadata. Extracted text
is injected separately after the result. A persisted requested_in_full marker keeps
this explicit read in full when conversation history is reconstructed, independently
of extraction completeness. Automatic per-file and aggregate inclusion limits do not
reduce it back to a preview. Extraction byte/character/time limits and model/provider
limits still apply; incomplete extraction produces an error, and sufficiently large
text can exceed the provider’s context window. “Full” means complete textual extraction,
not original binary bytes or preservation of every visual element. Use an appropriate
MCP tool when original-byte analysis is needed.
Attachment inclusion is independent of tool availability. By default, files passed
in their native format to the LLM (currently images) are included in full and bypass
all attachment inclusion budgets, including their metadata. They do not consume
the aggregate budget for other files and remain included when other files fall
back to previews or references. Their token estimates still count toward overall
request usage; model context limits still apply. Set
native_file_formats_bypass_limit = false to apply attachment budgets to these
files as well. Files without available native content do not receive this exemption.
For files subject to budgets, files above the per-file full-inclusion limit become previews (or references when no preview is available). If the combined selected representations fit the attachment budget, they are retained. Otherwise every non-exempt attachment takes the preview path; if those representations still exceed the budget, every non-exempt attachment becomes a reference. References that exceed the budget produce an explicit attachment-budget error. The planner preserves conversation order and never selects a subset to fill space.
The aggregate budget is the minimum of the enabled absolute limit and the enabled fraction of the configured model context window. Conversation text, system instructions, tool definitions and output reserves are not subtracted from this budget. Overall request estimates still count conversation and tool definitions. Changing non-file history alone does not change attachment inclusion.
| Key | Default | Meaning |
|---|---|---|
native_file_formats_bypass_limit | true | Include native LLM attachments in full and exclude them from per-file and aggregate attachment budget checks. |
max_inline_tokens_per_file | 16000 | Full rendered cost per attachment, including metadata. |
max_total_attachment_tokens | 0 | Absolute aggregate attachment budget. |
max_context_fraction | 0.25 | Fraction of the model context window; finite number between 0 and 1. |
max_preview_tokens_per_file | 1000 | Per-preview cost including metadata. |
preview.csv_max_sample_rows | 5 | Complete sample records after the header. |
preview.csv_max_columns | 50 | Displayed columns; omitted columns are disclosed. |
preview.csv_max_field_chars | 256 | Characters per displayed CSV field; max_field_chars is an alias. |
preview.text_max_chars | 0 | Independent leading-text character cap. |
Every numeric zero disables only that limit. Omitted keys use defaults. These initial defaults are tuning starting points, not measured workload optima. Preview limits never truncate a selected full representation. CSV previews use headers and complete records, including quoted multiline fields. Document excerpts retain available page/section markers. Previews do not make a summarization call or claim complete row counts. Original bytes remain available through file URIs; transient virtual files have no usable URI.
Extraction guards
file_processor.limits.max_input_bytes — Stability: unstable · Introduced in: 0.7.0
file_processor.limits.max_extracted_chars — Stability: unstable · Introduced in: 0.7.0
file_processor.limits.timeout_ms — Stability: unstable · Introduced in: 0.7.0
file_processor.limits.bounded_text_preview — Stability: unstable · Introduced in: 0.7.0
Key under file_processor.limits | Default | Guarantee |
|---|---|---|
max_input_bytes | 0 | Refuse eager extraction above this byte size. Storage reads and original-byte caches remain subject to existing infrastructure limits. |
max_extracted_chars | 0 | Limit retained request text characters; not a bound on the document parser’s peak allocations. |
timeout_ms | 0 | Limit extraction waiting time. Blocking document extraction may continue after the request stops waiting; this does not cancel parser CPU work. |
bounded_text_preview | true | Allow CSV/text early stopping when enabled inclusion limits establish that full inclusion is impossible. |
All numeric extraction guards are initially disabled. Configure them explicitly
for your deployment. Partial extraction is marked separately from extraction
failure and is never labeled full. Request-local partial artifacts never populate
the complete extraction cache. Existing cache sizes and
caches.file_processing_parallelism remain applicable. Upload acceptance and
upload size restrictions are separate from these settings.
Disable every introduced limit
[file_context]
max_inline_tokens_per_file = 0
max_total_attachment_tokens = 0
max_context_fraction = 0
max_preview_tokens_per_file = 0
[file_context.preview]
csv_max_sample_rows = 0
csv_max_columns = 0
csv_max_field_chars = 0
text_max_chars = 0
[file_processor.limits]
max_input_bytes = 0
max_extracted_chars = 0
timeout_ms = 0
bounded_text_preview = falseThis combination fully extracts and inlines successful extractions, counts their full content, and preserves ordinary tool-result handling. Large requests can still overflow the model context as before. It introduces no final context rejection or special legacy-mode branch. Authorization and existing upload and infrastructure limits still apply.
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
Stability: unstable · Introduced in: 0.6.0
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.
Decision providers
decision_providers.provider.<provider-id>.provider_kind — Stability: unstable · Introduced in: unreleased
decision_providers.provider.<provider-id>.model_name — Stability: unstable · Introduced in: unreleased
decision_providers.provider.<provider-id>.base_url — Stability: unstable · Introduced in: unreleased
decision_providers.provider.<provider-id>.api_key — Stability: unstable · Introduced in: unreleased
Configure named decision providers independently of chat and speech providers. The registry does not enable a feature by itself; a backend consumer must select a provider ID explicitly and resolve it through the replacement registry. Archived entries may omit provider settings; a replacement chain must end at an active provider. For example:
[decision_providers]
replacement_providers = [{ old = "previous", new = "classifier" }]
[decision_providers.provider.previous]
archived = true
[decision_providers.provider.classifier]
provider_kind = "openai"
model_name = "gpt-6-luna"
api_key = "your-key"
# base_url = "https://api.openai.com/v1"
[decision_providers.provider.local_classifier]
provider_kind = "llama_cpp"
base_url = "http://localhost:8080/v1"
# api_key = "optional-server-key"OpenAI requires model_name and defaults to https://api.openai.com/v1;
the Decisions API currently supports gpt-6-luna. llama.cpp requires the
server’s /v1 base URL and a native decision model; its optional API key is a
bearer token. Choice limits and input limits depend on the loaded model.
The shared interface accepts text or JSON, passing JSON as serialized text to
OpenAI and structured state to llama.cpp (JSON scalars are serialized as text).
It returns estimated probabilities,
which are not necessarily calibrated; consumers determine their own thresholds.
Credentials stay on the backend.
Policy guides
policy_guides.policies.<policy-id>.enabled — Stability: unstable · Introduced in: unreleased
policy_guides.policies.<policy-id>.question — Stability: unstable · Introduced in: unreleased
policy_guides.policies.<policy-id>.choice_refuse — Stability: unstable · Introduced in: unreleased
policy_guides.policies.<policy-id>.choice_allow — Stability: unstable · Introduced in: unreleased
policy_guides.policies.<policy-id>.decision_providers.[] — Stability: unstable · Introduced in: unreleased
policy_guides.policies.<policy-id>.metadata — Stability: unstable · Introduced in: unreleased
Policy guides define reusable predicates evaluated through a named decision provider. The section defaults to an empty policy map. Policies are disabled by default; an enabled policy requires a nonblank question, both descriptions and at least one configured provider reference. For example, using the providers defined above:
[policy_guides.policies.confidential]
enabled = true
question = "Does the supplied content contain confidential information?"
choice_refuse = "The content contains confidential business information."
choice_allow = "The content contains only public information."
decision_providers = ["previous", "local_classifier"]
[policy_guides.policies.confidential.metadata]
label = "Confidential information"
tags = ["upload", "review"]
display = { severity = "warning", visible = true }Provider order defines priority: only the first entry is used, after resolving
decision-provider replacement chains (here previous resolves to classifier).
There is no retry or fallback to later entries on errors or provider refusal.
All provider references on enabled policies must resolve to active providers;
disabled policies may retain references to unavailable providers.
The internal evaluator accepts caller-supplied text or JSON. It combines the
question and both descriptions into a predicate: true means choice_refuse,
false means choice_allow. The result is the estimated refusal probability,
without a threshold or conversion to allow/refuse. A provider declining to
evaluate is a separate outcome, and transport or malformed-response errors
remain errors. Enabling a guide does not enforce upload or tool-call policies.
Content selection, interpretation of the probability, public API endpoints and
concrete enforcement attachment points are future work.
Metadata supports arbitrary JSON values, like frontend.additional_environment,
including nested objects, arrays, numbers and booleans. It is forwarded verbatim
and is not included in model instructions. Policy definitions and metadata are
visible to authenticated frontend clients and receiving sidecars; put no secrets
there. Decision-provider credentials and definitions remain on the backend.
The top-level section is the only policy-guide configuration source. Its complete
map, including disabled entries, metadata and ordered provider IDs, is included
in organization_configuration.policy_guides sent through sidecar.configure.v1.
An empty map clears previously accepted definitions. User-layer policy guides do
not override organization definitions. Clients send configuration independently
of localDelegation; the sidecar decides whether to trust and apply it. A sidecar
may retain policy definitions without executing them, reject a call or ignore
untrusted settings. Clients preserve saved user settings and respect rejection.
These are startup settings: restart the backend after changes and let the frontend
refetch and synchronize configuration; there is no policy-guide hot reload.
Speech providers
speech_providers.provider.<provider-id>.provider_kind — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.model_name — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.voice — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.base_url — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.api_key — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.access_token — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.project_id — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.location — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.instructions — Stability: unstable · Introduced in: unreleased
speech_providers.provider.<provider-id>.sample_rate — Stability: unstable · Introduced in: unreleased
audio_conversational.speech_provider — Stability: unstable · Introduced in: unreleased
Configure speech output separately from chat and transcription. The registry uses
[speech_providers.provider.<id>]. Set audio_conversational.speech_provider to
that ID, select conversational mode, and enable Audio-back to hear responses.
The speech_provider setting is available only in audio_conversational.
Other input modes do not generate speech. Audio-back streams audio while text is generated, combining complete sentences
into passages of up to 2000 characters. Short passages wait up to 250 ms for
more context; sentences queued during synthesis are combined for the next request.
Single line breaks do not force a synthesis boundary. Completed audio is saved as a WAV file on the assistant
message, with the exact spoken input and passage segments in file metadata.
Replay after reload does not invoke the provider again.
[speech_providers.provider.voice]
provider_kind = "openai"
model_name = "gpt-4o-mini-tts"
voice = "alloy"
api_key = "your-key"
[audio_conversational]
enabled = true
speech_provider = "voice"
# Configure the transcription chat_provider_id as usual.Supported kinds are openai, openai_realtime, audio_cpp, gemini, and gemini_live. Each requires model_name
and voice. OpenAI uses api_key and an optional /v1 base_url. Use
openai_realtime with a Realtime model such as gpt-realtime-2; its base URL
may use HTTP(S) or WS(S) and defaults to https://api.openai.com/v1. Realtime
models are instructed to read the supplied text verbatim, but their output is
generative and may differ from the input. audio.cpp
requires base_url, a model configured in streaming mode, and sample_rate
matching its native mono PCM output (default 24000). Its authentication key is
optional. Gemini requires project_id for OAuth authentication; with an api_key,
omitting project_id uses the projectless Express endpoint. location defaults
to global. Gemini requires an explicit access_token (OAuth bearer) or
api_key where supported by the deployment. Credentials are not discovered
automatically.
Use gemini for Gemini TTS models via streamGenerateContent. Live models such
as gemini-3.8-live require gemini_live and the Live API WebSocket transport:
[speech_providers.provider.live_voice]
provider_kind = "gemini_live"
model_name = "gemini-3.8-live"
voice = "Kore"
api_key = "your-api-key" # Alternatively set access_token for OAuth
project_id = "your-project"
location = "eu"
[audio_conversational]
speech_provider = "live_voice"Live requires exactly one of api_key or OAuth access_token, plus project_id
and location. API keys are sent in the x-goog-api-key WebSocket header;
whether a key is accepted depends on the Google deployment and key configuration.
Express mode does not support Gemini Live models. Automatic credential discovery
is not supported, and access tokens must be refreshed externally. The model may also be a full
projects/<project>/locations/<location>/publishers/google/models/<model>
identifier matching those settings. location = "eu" routes to the EU
multi-region endpoint. A custom base_url overrides that endpoint and must
itself point to the intended deployment. Choose a voice and location supported
by the model. Like other generative speech models, Live is instructed to read
the text exactly but may deviate from it. One Live connection is retained for
the assistant response, so later passages retain the preceding session context.
Each passage waits for the previous turn to complete before submission; the
connection closes when the response ends. REST TTS requests remain independent,
but receive the larger passages.
An optional instructions string controls delivery on models that support it.
Credentials are backend-only. Provider/model-specific options must be supported
by the selected model; streamed output uses 16-bit little-endian mono PCM.
The frontend pauses conversational microphone restart until speech playback ends. Stopping playback does not discard the text or prevent the backend from finishing the replay file. Cancelling generation discards incomplete speech. A speech failure leaves the text response available. Code blocks and image descriptions are omitted from spoken output; Markdown links are read by their visible text. The synthesis response is limited to 64 MiB of PCM per assistant message. Speech queuing does not block text streaming. Speech input is limited to 1 MiB per assistant message; exceeding this limit stops speech synthesis and discards the incomplete replay while preserving the text response.
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.
For providers that implement the OpenAI audio transcription API , use provider_kind = "openai_audio_transcriptions":
[chat_providers.providers.transcriber]
provider_kind = "openai_audio_transcriptions"
model_name = "whisper-1"
api_key = "your-api-key"
# Optional for other OpenAI-compatible providers; defaults to https://api.openai.com/v1/
base_url = "https://api.openai.com/v1/"
[chat_providers.providers.transcriber.model_capabilities]
supports_audio_input = true
[audio_dictation]
enabled = true
chat_provider_id = "transcriber"
[audio_transcription]
enabled = true
chat_provider_id = "transcriber"
[audio_conversational]
enabled = true
chat_provider_id = "transcriber"Erato appends audio/transcriptions to base_url, uploads each WAV chunk as multipart form data, and requests JSON transcripts. api_key supplies bearer authentication; additional_request_headers (including user and token claim templates) and additional_request_parameters work as usual. An explicit Authorization header takes precedence over api_key. No API key is required for unauthenticated compatible servers.
The provider retains the audio modes’ chunking, retries, and hallucination checks. Chat-specific prompts, reasoning options, and output token budgets do not apply to this API. Keep an ordinary chat provider configured for conversation responses and chat summaries. A transcriber can be selected explicitly as above, or included in priority_order for automatic audio provider selection; it will not appear in the chat model picker.
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
Stability: unstable · Introduced in: 0.6.0
Whether this audio mode is available.
Type: bool
Default: false
audio_dictation.chat_provider_id
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
Maximum recording duration in seconds.
Type: number
Default: 1200
audio_dictation.chunk_duration_seconds
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
Maximum provider attempts per chunk, including retries for loop-detection.
Type: number
Default: 3
audio_dictation.initial_backoff_ms
Stability: unstable · Introduced in: 0.6.0
Initial retry backoff in milliseconds.
Type: number
Default: 250
audio_dictation.max_backoff_ms
Stability: unstable · Introduced in: 0.6.0
Maximum retry backoff in milliseconds.
Type: number
Default: 4000
audio_dictation.min_words_for_loop_check
Stability: unstable · Introduced in: 0.6.0
Minimum transcript word count before loop detection runs.
Type: number
Default: 80
audio_dictation.min_unique_word_ratio
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
Assumed maximum speech rate used to derive per-chunk output token budgets.
Type: number
Default: 200
audio_dictation.tokens_per_word
Stability: unstable · Introduced in: 0.6.0
Token-per-word multiplier used to derive per-chunk output token budgets.
Type: number
Default: 1.5
audio_dictation.output_token_buffer_factor
Stability: unstable · Introduced in: 0.6.0
Safety multiplier used to derive per-chunk output token budgets.
Type: number
Default: 2.0
audio_dictation.fixed_output_token_budget
Stability: unstable · Introduced in: 0.6.0
Fixed output token budget added to the per-chunk heuristic.
Type: number
Default: 500
audio_dictation.min_output_tokens
Stability: unstable · Introduced in: 0.6.0
Optional minimum output token budget for transcription chunks.
Type: number | None
audio_dictation.max_output_tokens
Stability: unstable · Introduced in: 0.6.0
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 — Stability: unstable · Introduced in: 0.6.0
audio_transcription.chat_provider_id — Stability: unstable · Introduced in: 0.6.0
audio_transcription.max_recording_duration_seconds — Stability: unstable · Introduced in: 0.6.0
audio_transcription.chunk_duration_seconds — Stability: unstable · Introduced in: 0.6.0
audio_transcription.max_attempts — Stability: unstable · Introduced in: 0.6.0
audio_transcription.initial_backoff_ms — Stability: unstable · Introduced in: 0.6.0
audio_transcription.max_backoff_ms — Stability: unstable · Introduced in: 0.6.0
audio_transcription.min_words_for_loop_check — Stability: unstable · Introduced in: 0.6.0
audio_transcription.min_unique_word_ratio — Stability: unstable · Introduced in: 0.6.0
audio_transcription.max_words_per_minute — Stability: unstable · Introduced in: 0.6.0
audio_transcription.tokens_per_word — Stability: unstable · Introduced in: 0.6.0
audio_transcription.output_token_buffer_factor — Stability: unstable · Introduced in: 0.6.0
audio_transcription.fixed_output_token_budget — Stability: unstable · Introduced in: 0.6.0
audio_transcription.min_output_tokens — Stability: unstable · Introduced in: 0.6.0
audio_transcription.max_output_tokens — Stability: unstable · Introduced in: 0.6.0
[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 — Stability: unstable · Introduced in: 0.6.0
audio_conversational.chat_provider_id — Stability: unstable · Introduced in: 0.6.0
audio_conversational.max_recording_duration_seconds — Stability: unstable · Introduced in: 0.6.0
audio_conversational.chunk_duration_seconds — Stability: unstable · Introduced in: 0.6.0
audio_conversational.max_attempts — Stability: unstable · Introduced in: 0.6.0
audio_conversational.initial_backoff_ms — Stability: unstable · Introduced in: 0.6.0
audio_conversational.max_backoff_ms — Stability: unstable · Introduced in: 0.6.0
audio_conversational.min_words_for_loop_check — Stability: unstable · Introduced in: 0.6.0
audio_conversational.min_unique_word_ratio — Stability: unstable · Introduced in: 0.6.0
audio_conversational.max_words_per_minute — Stability: unstable · Introduced in: 0.6.0
audio_conversational.tokens_per_word — Stability: unstable · Introduced in: 0.6.0
audio_conversational.output_token_buffer_factor — Stability: unstable · Introduced in: 0.6.0
audio_conversational.fixed_output_token_budget — Stability: unstable · Introduced in: 0.6.0
audio_conversational.min_output_tokens — Stability: unstable · Introduced in: 0.6.0
audio_conversational.max_output_tokens — Stability: unstable · Introduced in: 0.6.0
[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)
For Entra ID tokens that signal group overage, group-overage resolution retrieves memberships through Microsoft Graph by default. Otherwise, if no groups claim is present, 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. Entra ID group-overage tokens use memberships resolved through Microsoft Graph by default. If neither source supplies memberships, 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
Stability: unstable · Introduced in: 0.6.0
Whether the Langfuse integration is enabled.
Default value: false
Type: boolean
integrations.langfuse.base_url
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
Whether OpenTelemetry tracing is enabled.
Default value: false
Type: boolean
integrations.otel.endpoint
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
The protocol to use for exporting traces.
Default value: "http"
Type: string
Supported values: "http", "grpc"
integrations.otel.service_name
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.0
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.
The add-in in Outlook and Word on the web runs in a cross-site frame, so the oauth2-proxy must set cookie_samesite = "none" (see OAuth2 Proxy).
Default value: false
Type: boolean
integrations.ms_office.addin.msal_client_id
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.1
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
Stability: unstable · Introduced in: 0.7.0
The Word add-in manifest ID, served by /office-addin/manifest-document.xml.
For SharePoint app catalogs, use /office-addin/manifest-document.xml?distribution=sharepoint.
This variant omits VersionOverrides, including ribbon commands, to avoid Office’s unsupported-catalog-features warning.
The default endpoint (or ?distribution=integrated-apps) retains ribbon commands for Microsoft 365 admin center distribution.
Both variants use the same configured Word add-in ID and authentication settings.
The setup page at /office-addin/ provides a distribution-method selector, matching downloads and installation instructions.
When updating an existing SharePoint installation, retain its configured ID and upload the corrected manifest from a newer deployment version.
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
integrations.ms_office.teams.manifest.version — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.developer_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.website_url — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.privacy_url — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.terms_url — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.short_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.full_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.short_description — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.full_description — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.tab_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.color_icon — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.outline_icon — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.small_color_icon — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.color_icon_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.outline_icon_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.manifest.small_color_icon_source_path — Stability: unstable · Introduced in: 0.7.0
Optional color_icon_source_path, outline_icon_source_path and small_color_icon_source_path select local files using the Office source rules below. Source settings leave color_icon, outline_icon and small_color_icon package filenames and all manifest-facing URLs unchanged. Only ZIP bytes change. The color icon must be a 192×192 PNG; the outline and small color icons must be 32×32 PNGs. Remote downloading is unsupported. Unset sources search platform theme, shared theme, then the existing Office bundle file.
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
integrations.ms_office.teams.bot.enabled — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.app_id — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.app_password — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.tenant_id — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.oauth_connection_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.token_service_url — Stability: unstable · Introduced in: 0.7.0
Conversational Teams bot for personal chats, group chats and channels. When enabled, the backend accepts Bot Framework activities at /api/integrations/ms_teams/messages; route that exact path to the backend around oauth2-proxy (Helm chart: msTeamsBot.directRoute, or msTeamsBot.publicIngress for a separate public hostname). 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 before reading the request, accepts only the Teams channel, and only activities from tenant_id.
Enabling the bot needs no values that only exist after its Azure setup. The setup page’s Cloud Shell helper creates the Azure Bot and OAuth connection for the values Erato uses:
app_id: the bot’s Entra application (single tenant). Defaults tointegrations.ms_office.addin.msal_client_id, the app registration the add-ins and usually the web login already use.app_password: that application’s client secret. With the defaultapp_id, the secret oauth2-proxy already uses; the Helm chart passes it withmsTeamsBot.appPassword.existingSecret.tenant_id: the deployment’s Entra tenant. Defaults to the tenant in a tenant-specificintegrations.ms_office.addin.msal_authority; required otherwise.oauth_connection_name: the Azure Bot OAuth connection that yields the user’s delegated Microsoft Graph token. Defaults tograph-sso, which the helper creates with 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; app_id from the add-in’s msal_client_id; tenant_id from a tenant-specific msal_authority; oauth_connection_name is "graph-sso"; token_service_url is "https://token.botframework.com".
integrations.ms_office.teams.bot (optional behavior)
integrations.ms_office.teams.bot.sso_enabled — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.sso_app_id — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.sso_resource — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.public_base_url — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.messaging_endpoint — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.assistant_id — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.context_message_count — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.streaming — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.teams.bot.security_groups_only — Stability: unstable · Introduced in: 0.7.0
sso_enabled: Teams single sign-on, defaulttrue. Setfalseonly to keep an existing bot without single sign-on, and its Teams package, unchanged: the guided setup always configures it.sso_app_id,sso_resource: Teams single sign-on. The rendered manifest’swebApplicationInfopoints at this app and resource, which must equal the OAuth connection’s token exchange URL and end withbotid-<app_id>. They default to the add-in’smsal_client_idandapi://<host>/botid-<app_id>, with the host the Teams package is downloaded from, which is what the setup helper configures. Override them only for an existing setup with other values.public_base_url: address users open Erato at, used for links in bot messages (chat links, citations). An internal host is fine, because users open these links in their own browser.messaging_endpoint: public URL the Azure Bot sends messages to, when it differs from the address the setup page is opened at, for example a public hostname in front of an internal-only deployment. An HTTPS origin or the fullhttps://<host>/api/integrations/ms_teams/messagesURL; the setup page and its helper use it.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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
integrations.ms_office.addin.default_settings.mode — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.compose_inherits_from_read — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_include_document — Stability: unstable · Introduced in: 0.7.0
Default behavior for the Office add-in. These values apply only until a user
chooses their own preferences in the add-in. word_apply is different: see
below.
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.
word_include_document controls whether new Word chats include the open
document; users can still remove it from any chat.
Default value: { mode = "ask", compose_inherits_from_read = true, word_include_document = true }
Type: object
integrations.ms_office.addin.default_settings.word_apply
integrations.ms_office.addin.default_settings.word_apply.pc.in_place — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.pc.whole_document_import — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.pc.package_read — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.mac.in_place — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.mac.whole_document_import — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.mac.package_read — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.web.in_place — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.web.whole_document_import — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.word_apply.web.package_read — Stability: unstable · Introduced in: 0.7.0
How the Word add-in applies document changes on each Word host: pc (Word for
Windows), mac (Word for Mac) and web (Word for the web). These are
deployment switches that users cannot change. The per-device Compatibility mode
in the add-in only sends more rewrites to the whole-document import.
Each table takes:
in_place: write changes through Word’s object model where the add-in supports that change on the host, leaving the rest of the document untouched. On by default on every host; a change it can’t write this way, such as a section layout change or a moved passage, falls back to the whole-document import aswhole_document_importallows. Set it tofalseto send every rewrite through the import.whole_document_import: whether a change may replace the whole document by import.allowedimports without asking,confirmonly after the user confirms that change, andoffnever imports.package_read: read the complete.docxfile from Word. When off, the add-in reads only the document body, and in-place writing is unavailable because it keeps the complete file as its backup.
A key left out of a table keeps that host’s default. On Word for the web the
import cannot be undone with Word Undo, co-authors may lose concurrent edits,
comment dates and resolved flags may change, and Word rewrites styles, settings
and comment metadata on every import, so the add-in cannot verify the result.
The import is therefore off there by default: a change that needs it is
refused, with the proposed text to copy, and can be applied in Word for Windows
or Mac instead. In-place and scoped changes are not affected. confirm is an
explicit opt-in on the web; its result still can’t be verified there. Unless
the import is allowed, undoing an in-place change that did not verify never
falls back to it.
Default value: { pc = { in_place = true, whole_document_import = "allowed", package_read = true }, mac = { in_place = true, whole_document_import = "allowed", package_read = true }, web = { in_place = true, whole_document_import = "off", package_read = true } }
Type: object
Example:
[integrations.ms_office.addin.default_settings.word_apply.web]
package_read = falseintegrations.ms_office.addin.default_settings comment keys
integrations.ms_office.addin.default_settings.comment_label — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_author — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_author_enforced — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_assistant_author — Stability: unstable · Introduced in: 0.7.0
How Erato’s Word comments are attributed.
comment_label: the text Erato puts in front of every comment Word shows as written by the user: new comments in user mode and every reply. Plain text of at most 40 characters. An empty label is allowed, but users are then asked to confirm every comment Erato adds.comment_author: who Word shows as the author of new comments until the user chooses in the add-in’s Word settings:user(the user’s name, with the label) orassistant(comment_assistant_author, on the user’s behalf). Replies, resolves, edits and deletes always show the user.comment_author_enforced: whentrue, users cannot changecomment_author.comment_assistant_author: the author name for comments posted as Erato.{user}must appear exactly once and becomes the signed-in Erato user’s display name; at most 60 characters. A long name is shortened so the author Word shows stays within 120 characters. German deployments may use"Erato (KI) für {user}". Word for the web keeps user mode with the label for now.
Default value: { comment_label = "Erato AI:", comment_author = "user", comment_author_enforced = false, comment_assistant_author = "Erato (AI) for {user}" }
Type: object
integrations.ms_office.addin.default_settings.comment_writes
integrations.ms_office.addin.default_settings.comment_writes.add — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.respond — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.modify — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.assistant_author — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.max_rows.add — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.max_rows.respond — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.default_settings.comment_writes.max_rows.modify — Stability: unstable · Introduced in: 0.7.0
Deployment switches for Word comment actions, which users cannot change:
add(new comments),respond(replies, resolve and reopen) andmodify(edits and deletes):postwrites after the user confirms;copyturns the action off, and its card offers “Copy comment” and “Show passage” instead. The add-in also switches an action tocopyfor the rest of a session when Word refuses it on that host.assistant_author:falseposts every new comment under the user’s name with the label, whatevercomment_authorsays.max_rows: how many changes one card may hold per action, 1 to 50.
A key left out keeps the add-in’s default: every action posts, AI-author mode is available on Word for Windows and Mac, and cards hold up to 50 new comments and 20 other changes.
Default value: {}
Type: object
Example:
[integrations.ms_office.addin.default_settings.comment_writes]
modify = "copy"
assistant_author = falseintegrations.ms_office.addin.favicon_path
Stability: unstable · Introduced in: 0.7.0
Optional local image source for the Office favicon. Unset inherits frontend.favicon_path. With the default main favicon setting, conventional platform theme favicons take precedence over shared theme favicons, then the bundled main favicon. Office HTML references /public/platform-office-addin/favicon.svg; both SVG/ICO routes at that mount and the legacy /office-addin mount serve the selected image with its actual content type. Changing the source keeps these URLs stable.
Sources use the local path rules described below for manifest icons. Example: favicon_path = "/public/common/custom-theme/acme/office-favicon.png".
integrations.ms_office.addin.manifest
integrations.ms_office.addin.manifest.provider_name — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.display_name — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.description — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.support_url — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.group_label — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.button_label — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.button_description — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.icon_path — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.high_resolution_icon_path — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.icon_16_path — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.icon_32_path — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.icon_80_path — Stability: unstable · Introduced in: 0.6.2
integrations.ms_office.addin.manifest.icon_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.manifest.high_resolution_icon_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.manifest.icon_16_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.manifest.icon_32_source_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.manifest.icon_80_source_path — Stability: unstable · Introduced in: 0.7.0
Optional *_source_path settings select the local image served at the corresponding existing *_path URL, including canonical and legacy Office routes. They never change manifest URLs. Use these to update deployed branding without regenerating Office manifests. Existing icon URL settings continue to support shared public paths and HTTP(S) URLs.
Local sources accept ./icon.png or icon.png relative to the selected theme, /public/common/... relative to the main bundle, or /public/platform-office-addin/... (also /office-addin/...) relative to the Office bundle. They reject traversal, encoded paths, filesystem absolute paths, remote URLs and symlinks escaping those roots. Explicit missing sources fail rather than silently displaying a different brand. With no source override, icon files are searched in the platform theme, then shared theme (the bundle-relative filename or its basename), then the Office bundle. When multiple settings address the same URL, explicit Office sources take precedence over explicit Teams sources; explicit sources take precedence over all conventional files.
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
integrations.ms_office.addin.launch_events.[].event — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.launch_events.[].function_name — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.launch_event_runtime.page_path — Stability: unstable · Introduced in: 0.7.0
integrations.ms_office.addin.launch_event_runtime.script_path — Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
Whether the Prometheus metrics endpoint is enabled.
Default value: false
Type: boolean
integrations.prometheus.host
Stability: unstable · Introduced in: 0.6.0
Host interface for the Prometheus metrics listener.
Default value: "127.0.0.1"
Type: string
integrations.prometheus.port
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
Whether the SharePoint/OneDrive integration is enabled.
Default value: false
Type: boolean
integrations.sharepoint.file_upload_enabled
Stability: unstable · Introduced in: 0.7.0
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.file_search_enabled
Stability: unstable · Introduced in: unreleased
Enable cross-drive filename search in the SharePoint/OneDrive picker using Microsoft Search. Searches all indexed files the signed-in user can access, independently of all_drives_sources. Requires delegated Files.Read.All or Sites.Read.All permission.
When false, the picker uses the original drive/site-name search and the filename-search endpoint is disabled. Browsing and attaching files remain available.
Default value: true
Type: boolean
integrations.sharepoint.auth_via_access_token
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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 and resolving group overage for authorization.
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
Whether to use the user’s forwarded access token for Microsoft Graph API authentication. Currently, this is the only supported authentication method. This must be true when directory integration or group-overage resolution is enabled.
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:
The user profile includes organization identity fields when claims or resolved memberships supply them, independently of whether directory listing is enabled:
organization_user_id- Extracted from theoidclaim (Entra ID specific)organization_group_ids- Extracted from thegroupsclaim or group-overage resolution
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__".
integrations.entra_id.group_overage
Resolve memberships when an Entra ID token signals group overage through _claim_names.groups, or hasgroups: true without a groups claim. This works independently of integrations.entra_id.enabled. Tokens without an overage signal keep their existing claim groups and do not use Graph or the membership cache.
The proxy must forward a Microsoft Graph access token with delegated User.Read permission in X-Forwarded-Access-Token, and the ID token must contain oid. Resolution supports global Entra ID issuers (login.microsoftonline.com v2 and sts.windows.net v1) and uses graph.microsoft.com. Erato verifies that Graph’s /me identity matches oid before fetching all pages of /me/transitiveMemberOf. The membership set matches oauth2-proxy: nested memberships, groups, directory roles and administrative units, expressed as object IDs. It does not reproduce application-assigned group filtering or on-premises group names/SIDs.
Successful lookups are cached per replica for ten minutes; failed lookups have a thirty-second cooldown. Entries are bound to the issuer, user object ID and forwarded access token. Identity verification and all membership pages share a three-second timeout. Missing prerequisites or lookup failures leave both groups and organization_group_ids empty; normal group-based authorization then denies access. Failed lookups never return partial membership lists. The profile response retains the full resolved lists.
integrations.entra_id.group_overage.enabled
Stability: unstable · Introduced in: 0.7.0
Whether Entra group-overage resolution is enabled. Set this to false to disable Graph and cache access for overage tokens. Overage source identifiers are never interpreted as group IDs, including when disabled. integrations.entra_id.auth_via_access_token must be true while resolution is enabled.
Default value: true
Type: boolean
Example:
[integrations.entra_id.group_overage]
enabled = falselogging
Configuration for backend log output formatting.
logging.format
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
Whether MCP tool calls can require approval. Defaults to false for backwards compatibility.
Type: boolean
Default value: false
mcp_servers_global.approval.preset
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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>.protocol_mode
Stability: unstable · Introduced in: 0.7.0
Selects the MCP lifecycle independently of the HTTP transport.
Type: "auto" | "legacy" | "stateless"
Default value: "auto"
For Streamable HTTP, auto prefers stateless protocol version 2026-07-28 and
falls back to initialization for legacy servers. legacy forces the existing
initialization/session lifecycle. stateless requires the modern protocol and
retains no idle MCP services. The legacy SSE transport accepts auto or legacy.
Stateless tool lists default to 30 seconds of freshness when the server omits a TTL; explicit TTLs and public/private cache scope are honored. See protocol selection and caching.
mcp_servers.<server-id>.transport_type
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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 tool annotation defaults and overrides
Tool annotations can be configured globally, per server, or per exact tool. All settings come from erato.toml sources. The backend resolves each hint independently in this order (highest priority first):
- Tool override
- Server override
- Global override
- Server-supplied hint
- Tool default
- Server default
- Global default
- MCP protocol fallback
An explicit false participates in the merge. Omitted hints inherit from the next layer, including hints missing from partial, empty, or title-only server annotations. Server titles and unrelated metadata are preserved. Empty sections have no effect. Protocol fallbacks remain read_only_hint = false, destructive_hint = true, idempotent_hint = false, and open_world_hint = true.
The same effective hints drive tool-call approval, the chat’s write toggle, and tool enumeration. Annotation settings belong outside mcp_servers_global.approval because they also affect tools when approval is disabled. destructive_hint = false permits execution without an approval prompt under the permissive preset; it does not imply read-only behavior. read_only_hint = true permits a tool through the write-disabled filter, but restrictive approval still checks destructive and open-world hints separately. Idempotent hints do not change approval or retry behavior.
Operator hints are assertions about tool behavior. Broad defaults and overrides also apply to newly discovered tools; prefer exact-tool settings or pair broad settings with an allow_tools allowlist when a server may gain write tools. Server declarations supersede defaults; use overrides when operator hints must win over server metadata. Overrides are metadata, not mandatory approval rules: existing honored “Always allow” decisions still apply. Access control and user denials remain in effect.
Runtime reloads invalidate cached sessions for a changed server/tool, or across servers when global annotation hints change. Removed hints are resolved again from original server declarations. Deploy a supporting Erato build before relying on these sections: older builds can silently ignore unfamiliar keys.
Example:
[mcp_servers_global.default_tool_annotations]
open_world_hint = true
[mcp_servers.company_search]
transport_type = "streamable_http"
url = "https://mcp.example.com/mcp"
[mcp_servers.company_search.default_tool_annotations]
read_only_hint = true
destructive_hint = false
[mcp_servers.company_search.tools.rebuild_index.override_tool_annotations]
read_only_hint = false
destructive_hint = truemcp_servers.<server-id>.tools
Type: object<string, object>
Default: {}
Annotation settings keyed by exact, case-sensitive, original MCP tool name within that server. Wildcards are not supported for these entries. Quote TOML key segments when a name contains punctuation, for example [mcp_servers.files.tools."Read.File".default_tool_annotations]. Entries do not expose excluded tools or create new tools. A configured name may be absent from discovery, since tools can depend on authentication or change over time. Per-tool entries accept only default_tool_annotations and override_tool_annotations.
mcp_servers_global.default_tool_annotations
Type: object
Default: all hints omitted.
Optional fallback hints for missing server declarations. More specific defaults take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers_global.default_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers_global.default_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers_global.default_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers_global.default_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers_global.override_tool_annotations
Type: object
Default: all hints omitted.
Optional hints overriding server declarations and all defaults. More specific overrides take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers_global.override_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers_global.override_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers_global.override_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers_global.override_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers.<server-id>.default_tool_annotations
Type: object
Default: all hints omitted.
Optional fallback hints for missing server declarations. More specific defaults take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers.<server-id>.default_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers.<server-id>.default_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers.<server-id>.default_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers.<server-id>.default_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers.<server-id>.override_tool_annotations
Type: object
Default: all hints omitted.
Optional hints overriding server declarations and all defaults. More specific overrides take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers.<server-id>.override_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers.<server-id>.override_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers.<server-id>.override_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers.<server-id>.override_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.default_tool_annotations
Type: object
Default: all hints omitted.
Optional fallback hints for missing server declarations. More specific defaults take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers.<server-id>.tools.<tool-name>.default_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.default_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.default_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.default_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.override_tool_annotations
Type: object
Default: all hints omitted.
Optional hints overriding server declarations and all defaults. More specific overrides take precedence.
Only the following optional boolean keys are accepted. Unknown keys, camelCase spellings, strings, and numeric values are rejected at startup and runtime reload.
mcp_servers.<server-id>.tools.<tool-name>.override_tool_annotations.read_only_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool leaves its environment unmodified. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.override_tool_annotations.destructive_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can make destructive updates. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.override_tool_annotations.idempotent_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether repeated calls with identical arguments have no additional effect. Omitted values inherit.
mcp_servers.<server-id>.tools.<tool-name>.override_tool_annotations.open_world_hint
Stability: unstable · Introduced in: 0.7.0
Type: boolean | None
Whether the tool can interact with external entities beyond a closed domain. Omitted values inherit.
mcp_servers.<server-id>.wait_tools
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
The fixed API key to attach when authentication.mode = "fixed".
Type: string
mcp_servers.<server-id>.authentication.fixed.header_name
Stability: unstable · Introduced in: 0.6.0
The HTTP header to use when authentication.mode = "fixed".
Type: string
Default value: "Authorization"
mcp_servers.<server-id>.authentication.fixed.prefix
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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.authorization_params
Stability: unstable · Introduced in: 0.7.0
Additional query parameters sent only to the OAuth authorization URL. They are never added to discovery, client registration, code exchange, or token refresh requests. An omitted or empty map preserves the existing authorization behavior.
Values are literal strings: do not URL-encode them. Each configured parameter replaces all endpoint query pairs with the same case-sensitive name and appears once, form-encoded, after other parameters in ascending key order. Other pairs retain their values, order, and multiplicity.
Erato ignores empty names and the following protected names (ignoring ASCII
case), emitting a WARN message when authorization starts: response_type,
client_id, client_secret, redirect_uri, scope, state, code_challenge,
code_challenge_method, resource, response_mode, request, and request_uri.
Use the existing dedicated client, scopes, and resource settings instead.
The warning includes the parameter name, never its value.
Values must not contain secrets: the authorization URL is visible in the user’s browser and may appear in browser history or proxy logs. Discovery and callback issuer checks still use the authorization server’s metadata.
Like other configuration maps, this table merges by key across sources. An empty override table does not remove inherited entries; remove them from the source that defines them. Use TOML for case-sensitive parameter names, since environment variable names are lowercased. Reloading changed settings invalidates that server’s cached MCP sessions but does not delete stored OAuth grants. New values take effect on the next authorization; users with existing grants must reconnect to authorize with them.
Type: object<string, string>
Default: {}
Example:
[mcp_servers.tickets.authentication.oauth2.authorization_params]
audience = "api.atlassian.com"
prompt = "consent"These illustrate provider-specific settings; they do not establish complete Atlassian compatibility. The provider must also support Erato’s discovery, PKCE, token exchange, and refresh behavior.
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
Stability: unstable · Introduced in: 0.6.0
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.
chat_history_compaction
Chat history compaction lets users replace the context of a long conversation with a summary. The original transcript remains visible, with an expandable summary marker. Assistant knowledge files are always retained; other files are selected using bounded previews. Compaction runs only after a user clicks the action.
[chat_history_compaction]
enabled = true
chat_provider_id = "gpt-4o-mini"
mode = "summarize"
trigger_mode = "suggest_with_user_approval"
trigger_on_token_limit_threshold_percentage = 80
target_compaction_token_limit_percentage = 10chat_history_compaction.enabled
Stability: unstable · Introduced in: 0.7.0
Enable the manual action and context-limit suggestion. Default: false. Disabling the feature still honors checkpoints already stored in conversations.
chat_history_compaction.chat_provider_id
Stability: unstable · Introduced in: 0.7.0
Explicit provider used to summarize, required when enabled. It must identify a configured chat provider. The conversation’s selected model determines the target context size; this auxiliary provider handles summarization and its token usage.
chat_history_compaction.mode
Stability: unstable · Introduced in: 0.7.0
Default and supported value: "summarize".
chat_history_compaction.trigger_mode
Stability: unstable · Introduced in: 0.7.0
Default and supported value: "suggest_with_user_approval". Suggestions never trigger compaction automatically.
chat_history_compaction.trigger_on_token_limit_threshold_percentage
Stability: unstable · Introduced in: 0.7.0
Show the suggestion when estimated context usage reaches this inclusive percentage of the selected conversation model’s context limit. Default: 80. Range: 0–100.
chat_history_compaction.target_compaction_token_limit_percentage
Stability: unstable · Introduced in: 0.7.0
Soft target for the resulting context as a percentage of the selected conversation model’s context limit. Default: 10. Range: 0–100. Summaries are not cut to meet this target. Persistent instructions and retained files may keep the measured context above it. If the conversation and previews do not fit the summarizer’s context window, the operation fails without changing history.
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
Stability: unstable · Introduced in: 0.6.0
Whether to enable the prompt optimizer feature.
Default value: false
Type: boolean
prompt_optimizer.chat_provider_id
Stability: unstable · Introduced in: 0.6.0
The chat provider ID to use for prompt optimization. Must match a provider configured in chat_providers.
Type: string
prompt_optimizer.prompt
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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.tools_menu_description_display
Stability: unstable · Introduced in: 0.7.0
Controls assistant descriptions in the desktop Tools menu and the mobile add menu.
none: Hide descriptions.truncated: Show up to two lines, with an ellipsis when the description does not fit.full: Show the full description, wrapping onto multiple lines.
Default value: "truncated"
Type: "none" | "truncated" | "full"
Example:
[assistants]
tools_menu_description_display = "truncated"assistants.show_recent_items
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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, or the action facet the request runs under, 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, and so does every task it launches: a child is capped at the planning turn’s allowlist, so child_facet_ids cannot add an MCP server the planning turn does not allow.
delegation.tasks.enabled
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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 facets, but not its default model (see which model a task runs on); a chat with no assistant bound runs bare either way. A "bare" child still gets only the MCP servers that assistant is restricted to.
Default value: "inherit"
Type: string (one of "inherit", "bare")
Example:
[delegation.tasks]
persona = "inherit"delegation.tasks.child_facet_ids
Stability: unstable · Introduced in: 0.7.0
Facets a task may name in facet_ids beyond the ones selected on the planning turn. They widen the list the model picks from, and a task child gets one only when its task names it. Whatever its facets allow, a child is never offered an MCP tool outside the planning turn’s own allowlist. Each entry must name a facet configured under facets.facets; an unknown id is rejected at startup.
Default value: []
Type: list<string>
Example:
[delegation.tasks]
child_facet_ids = ["web_search"]delegation.tasks.result_template
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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.child_dispatch
Stability: unstable · Introduced in: unreleased
How a client task’s calls reach the device, for client tools that do not set
child_dispatch themselves.
livehands each call to the executor stream at once. Without a connected pane that takes it, the call ends ascontext_unavailable.durablelets the call wait for the device: seeclient_tools.tools.<key>.child_dispatch.
durable applies only to the tools it can apply to: child_access = "read", a
host_context whose client state outlives the pane session (outlook_mailbox
and desktop_sidecar, not word_document), a durable table, and
client_tools.durable_operations_enabled on. Every other tool stays live, so this
key never makes a configuration invalid.
Default value: "live"
Type: "live" | "durable"
Example:
[delegation.tasks]
child_dispatch = "live"delegation.tasks.approval.mode
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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:
Stability: unstable · Introduced in: 0.6.2
display_name- User-facing category label.
Stability: unstable · Introduced in: 0.6.2
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.
Stability: unstable · Introduced in: 0.6.2
rule_type- Must be"allow-for-group-members".
Stability: unstable · Introduced in: 0.6.2
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
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. Configured
client tools become durable only through their own
durable table.
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.
Turning the setting on also enables the generation lease identity guard and its compare-and-set path. After a durable park, a short SSE drop no longer ends the call as a timeout: the turn detaches and the frontend resumes it from the operation inbox.
client_tools.executor_stream_enabled
Stability: unstable · Introduced in: unreleased
Default: false. Lets a client open an executor stream,
GET /api/v1beta/me/client-operations/stream, for its X-Erato-Executor
binding. A device can have several panes for one binding, such as two Outlook
tabs on one mailbox: a new call goes to the newest pane that advertises its
tool, and a call one pane received never moves to another.
A live client tool call normally runs over the calling chat’s own stream, and
it still does while that stream is connected. When the calling chat has no
connected stream, the call goes to the stream that serves the call’s binding and
advertises the tool, whichever chat of the account made it. Without such a
stream, or once the pane that received the call is gone, the call ends as
context_unavailable after 15 seconds; a pane that connects or reconnects in
that time still gets it. The tool’s own timeout_ms still
applies. Calls with a durable operation and submission tools keep their usual
path.
With client_tools.durable_operations_enabled also on, the stream announces new
durable operations for its binding, so the client claims them at once instead of
at its next inbox poll.
With the setting off, the stream endpoint answers 404 and nothing else changes.
client_tools.tools.<key>.durable
client_tools.tools.<key>.durable.host_context — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.durable.result_schema — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.durable.attempt_expiry_secs — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.durable.claim_lease_secs — Stability: unstable · Introduced in: 0.7.0
Opts one configured client tool into durable operations. It takes effect only
while client_tools.durable_operations_enabled is on. A call that runs keeps the
in-memory fast path. If the client’s stream drops or the call reaches its
timeout_ms, the call parks in the operation inbox instead of ending as a
timeout. An executor with the same device and host context can then answer it,
for example after a reconnect or from a second tab, and the turn resumes.
[client_tools.tools.outlook_fetch_availability.durable]
host_context = "outlook_mailbox"
attempt_expiry_secs = 3600
claim_lease_secs = 120
result_schema = '''{"type":"object","required":["busy"],"properties":{"busy":{"type":"array"}}}'''host_context(required) - The host family whose executor binding the call needs.outlook_mailboxis the Outlook add-in’s signed-in mailbox;desktop_sidecaris the local index of the Erato desktop app on the user’s device. A call without a matching binding, inX-Erato-ExecutororX-Erato-Additional-Executors, keeps the in-memory path. This happens with older clients, Teams, or a binding of another host family.result_schema(optional) - A JSON Schema string with top-leveltype = "object"that a successful result must satisfy. A result that fails it, or any malformed result, reaches the model as fixed feedback, never verbatim. It applies only to results without files: a result that returned files reaches the model inside the server’s file wrapper and is not checked.attempt_expiry_secs(default: 3600, range: 60–86400) - How long a parked call stays claimable, counted from when the call was dispatched. It must cover the tool’stimeout_ms(default 60000) plusclaim_lease_secs. An expired call continues the turn asexpired.claim_lease_secs(default: 120, range: 60–900) - How long a claiming executor holds the call before another claim may replace it. It must not exceedattempt_expiry_secs.
Per-tool decisions still apply. ask shows the approval card before the call
runs. Once approved, the call parks like any other, and it does not prompt again
when it completes later on the same device. If an approval is answered from another
device or client, no call of an opted-in tool parks for the rest of that turn;
they keep the in-memory path. never_allow tools are neither offered nor run.
A decision changed to never_allow after a call parked withdraws it. Claims are
never allowed across devices. A tool with submission cannot be durable. Results
must be JSON; tools that return files must not opt in. Opting in also drops the
client’s per-field validation errors: a result the client rejects reaches the
model as fixed validation_failed text, on the live path too, so tools that rely
on correctable validation errors must not opt in. Every backend replica must load
the same durable tables.
client_tools.tools.<key>.child_access
client_tools.tools.<key>.child_access — Stability: unstable · Introduced in: unreleased
client_tools.tools.<key>.host_context — Stability: unstable · Introduced in: unreleased
client_tools.tools.<key>.child_context_arg — Stability: unstable · Introduced in: unreleased
client_tools.tools.<key>.child_context_input — Stability: unstable · Introduced in: unreleased
client_tools.tools.<key>.child_host_inputs.[] — Stability: unstable · Introduced in: unreleased
Lets delegated tasks read through a client tool on the device of the turn that planned them. Off by default.
[client_tools.tools.word_read_document_blocks]
# name, namespace, description and parameters as usual
child_access = "read"
host_context = "word_document"
child_context_arg = "document_snapshot"
child_context_input = "snapshot"
child_host_inputs = ["documentIdentity"]child_access(default:"none") -readlets a delegated task call the tool when the task names a facet with the samechild_host_context. Not allowed together withsubmission.host_context(required forread) - The host family whose client state the tool reads:word_document,outlook_mailboxordesktop_sidecar. It must equaldurable.host_contextwhen both are set.child_context_argandchild_context_input(optional, set together, requireread) -child_context_argnames the planning turn’s action-facet argument that holds the client’s context version, for example Word’sdocument_snapshot. The server writes its value into the input fieldchild_context_inputof every call a task makes, replacing what the model sent. Allreadtools of one host that set them must name the samechild_context_arg.child_host_inputs(optional, requiresread) - Input fields the host fills from the client context it holds, for example Word’sdocumentIdentity. The server drops them from every call a task makes.
A task’s model sees the tool’s parameters without child_context_input and
child_host_inputs, in every alternative the schema offers, so it is never
asked for values it cannot know.
When a client task starts, the server writes a grant onto it. The grant lists the
tools the planning turn was offered with child_access = "read" and the task’s
host, that the user has not set to never allow, and that the executor stream of
the planning turn’s binding accepts for delegated calls
(X-Erato-Child-Client-Tools). This needs client_tools.executor_stream_enabled.
When no tool qualifies, the task runs without client tools and its result says
why. The task is offered only the granted tools, and only while the planning turn
is still running and the task runs on the grant’s binding. A task the user took
over and a retried task get none. Its calls go live to the executor stream with
the grant’s lineage, unless the tool dispatches durably (see
child_dispatch). Without a stream that
takes a live call, or once its stream goes away, the call ends as
context_unavailable after 15 seconds.
client_tools.tools.<key>.child_dispatch
Stability: unstable · Introduced in: unreleased
How a client task’s calls of this tool reach the device. Unset, the tool follows
delegation.tasks.child_dispatch.
[client_tools.tools.outlook_fetch_availability]
# name, namespace, description and parameters as usual
child_access = "read"
host_context = "outlook_mailbox"
child_dispatch = "durable"
[client_tools.tools.outlook_fetch_availability.durable]
host_context = "outlook_mailbox"
attempt_expiry_secs = 86400livehands each call to the executor stream at once, as described underchild_access.durableturns each call into a durable operation for the grant’s binding. The task parks at once, and the turn that planned it waits, showing the task as waiting for the device. A client of the user that serves the binding and takes delegated calls picks the operation up: at once when one is connected, otherwise when one connects or next reads its inbox. It runs the call and the task goes on. An approval on the device may stay open until the operation’s attempt ends (durable.attempt_expiry_secs). The call ends asexpiredwhen no client ran it before then; the expiry is applied the next time one of the user’s Erato clients is open. It ends asdeclinedwhen the user declined it on the device, and aswithdrawnonce the planning turn moved on (the user wrote again or archived the chat) or the tool was set to never allow. The task then answers without the data and says what is missing. The planning turn goes on by itself once every task it waits for has finished.
Refused at load time: child_dispatch without child_access = "read", and
durable on a tool without a durable table or whose host_context does not
outlive the pane session. outlook_mailbox and desktop_sidecar outlive it;
word_document does not, so Word tools always stay live. A durable tool’s calls
are durable only while client_tools.durable_operations_enabled is on; otherwise
they go live.
Enable durable for a tool, and the desktop sidecar’s durable tables, only
once every replica runs a release that supports it (ERMAIN-1032 for durable task
calls, ERMAIN-1034 for desktop_sidecar and results with files), and do not
roll back below that release while such calls may be parked: an older replica
cannot continue them and ends the task or the chat with an error. A backend
older than ERMAIN-1034 refuses to load a configuration that names
desktop_sidecar.
The desktop sidecar’s search as a durable task read, with a child facet and a planning facet that may start such tasks:
[client_tools.tools.desktop_search_index]
# name = "search_sidecar_index", namespace = "desktop", description and parameters as usual
child_access = "read"
host_context = "desktop_sidecar"
child_dispatch = "durable"
[client_tools.tools.desktop_search_index.durable]
host_context = "desktop_sidecar"
attempt_expiry_secs = 86400
[facets.facets.desktop_delegated_search]
display_name = "Local search task"
child_host_context = "desktop_sidecar"
[facets.facets.desktop_local.delegation]
child_facet_ids = ["desktop_delegated_search"]
run_modes = ["wait"]A sidecar task gets the tools only when its turn was sent with the sidecar’s
binding in X-Erato-Additional-Executors: the web app sends it alone, an add-in
beside its own Office binding.
Its calls then go to the Erato desktop app on the device, and a file the read returns
(for example from get_sidecar_document) reaches the task’s chat.
Default value: unset (follows delegation.tasks.child_dispatch)
Type: "live" | "durable"
client_tools.tools.<key>.approval_scope
Stability: unstable · Introduced in: unreleased
What one approval of a client task’s call may cover. With call, every call the
user’s decision asks for needs its own approval. With context, when a task’s
call to this tool asks, the approval card in the planning chat also offers
“Allow for this document” (word_document) or “Allow for this response”
(outlook_mailbox). One such decision lets every task call of this tool in that
chat run without asking:
- on the same document in the same pane session, until the document changes outside the chat. The chat’s own edits do not count (another chat’s Apply in the same pane does), and neither do formatting-only edits in Word on Windows and Mac, turning Track Changes on or off, or Word’s proofing; in Word for the web a formatting edit counts. An edit the user makes while one of the chat’s own inserts or a whole-document Apply is still settling, or to a paragraph an in-place Apply just changed, is taken as the chat’s own. After an outside change the next call asks again, marked as changed. A document can only be allowed in the pane that has it open, and only while that pane checks for outside changes; elsewhere the card offers the usual choices.
- in the same response, until it is done; the next response asks again.
A reloaded pane or another device asks again, and unarchiving the chat ends what was allowed. The planning turn’s own calls are always asked one by one. Always allow and Never allow keep their meaning; setting a tool to Never allow ends what was allowed for it, so setting it back to Ask asks again. It is a tool approval only: the host’s own prompts and any release review still run.
[client_tools.tools.word_read_document_blocks]
# name, namespace, description and parameters as usual
child_access = "read"
host_context = "word_document"
approval_scope = "context"Refused at load time: context without child_access = "read", and on a host
context that does not support it (every host context other than word_document
and outlook_mailbox). Calls of desktop_sidecar tools are always asked one by
one: an approval there could cover only the server’s tool approval, never the
Erato desktop app’s own consent on the device, and checking a thread has to wait
until the app reports what changed in it. Needs
client_tools.executor_stream_enabled, like child_access.
Default value: "call"
Type: "call" | "context"
client_tools.tools.<key>.requires_client_registration
Stability: unstable · Introduced in: 0.7.0
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
facets.facets.<facet-id>.hidden — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.hidden_always_active_for_platform — Stability: unstable · Introduced in: 0.7.0
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.
Child facet model
Stability: unstable · Introduced in: unreleased
child_chat_provider names the chat provider a delegated task runs on when the task names this facet in its facet_ids, for example a cheaper model for simple lookups or a model your organisation runs itself for mail search. It applies only to delegated runs; selecting the facet in a normal conversation does not change the model. The id must be a configured chat provider.
[facets.facets.mail_search]
display_name = "Mail search"
tool_call_allowlist = ["mail-mcp/*"]
child_chat_provider = "in-house-model"When a task names several facets that set one, the first in the task’s facet_ids wins. A model the parent picks for the task comes first; see which model a task runs on.
Per-facet delegation overrides
facets.facets.<facet-id>.delegation.max_tasks_per_turn — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.max_server_tool_calls_per_task — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.max_client_tool_calls_per_task — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.max_parallel — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.persona — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.child_facet_ids.[] — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.child_chat_providers.[] — Stability: unstable · Introduced in: unreleased
facets.facets.<facet-id>.delegation.run_modes.[] — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.scheduling — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.approval.mode — Stability: unstable · Introduced in: 0.7.0
facets.facets.<facet-id>.delegation.approval.plan_min_tasks — Stability: unstable · Introduced in: 0.7.0
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"
# Lists every server the child facet needs: a task never gets an MCP tool
# outside the planning turn's allowlist.
tool_call_allowlist = ["erato/delegate_task", "web-search-mcp/*", "web-access-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- Added todelegation.tasks.child_facet_ids(union). Like the global list, it only widens what a task may name, and a child never gets an MCP tool outside the planning turn’s allowlist. Once you write into a task’s chat yourself, that chat is yours and this cap no longer applies. Each entry must name a configured facet.child_chat_providers- The chat providers the parent may pick for each task, offered as an optionalmodelargument ofdelegate_taskand described by their display names. Facet-only: there is nodelegation.tasksvalue, and without a list the argument is not offered. Unioned across the selected planning facets. Entries the user may not use are not offered, and a pick outside the offered list is refused. Each entry must be a configured chat provider.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.
Child facets for client tasks
Stability: unstable · Introduced in: unreleased
A facet with child_host_context (word_document, outlook_mailbox or desktop_sidecar) marks client tasks. A delegated task that names it in facet_ids always runs in wait mode: an async request runs as wait and the task result says so. The task gets a grant for the planning turn’s client tools of that host that set child_access = "read". The facet restricts no tools and only carries instructions, so it must not be hidden or have a tool_call_allowlist. List it in the planning facet’s child_facet_ids.
When the user set a granted tool to ask, the task’s call waits for a decision in the planning turn’s chat, next to the turn’s own approvals, as an MCP approval of a task does. With delegation.tasks.propagate_child_mcp_approvals off, the call is refused instead. A declined call ends as declined, and the task result counts it. The task result also carries the last freshness the client reported for the document or mailbox the task read, and the planning turn’s trace shows each read with the part it asked for.
[facets.facets.word_reader]
display_name = "Read the open document"
child_host_context = "word_document"
additional_system_prompt = "Read only the part of the document your task names."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, and so does every task it launches, whatever its child_facet_ids allow. 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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
Whether only a single facet can be selected at the same time.
Type: boolean
Default value: false
facets.show_facet_indicator_with_display_name
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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:
action_facets.facets.<facet-id>.display_name — Stability: unstable · Introduced in: 0.6.0
action_facets.facets.<facet-id>.platform — Stability: unstable · Introduced in: 0.6.0
action_facets.facets.<facet-id>.template — Stability: unstable · Introduced in: 0.6.0
action_facets.facets.<facet-id>.allowed_args.[] — Stability: unstable · Introduced in: 0.6.0
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
Action facet grant scope
Stability: unstable · Introduced in: 0.7.0
grant_scope sets, per client action the facet offers, how far a user’s “Always allow” reaches:
device (the default) keeps it on the device until the user changes it in settings;
session_document keeps it only while the add-in stays open on the document it was given for,
and ignores an “Always allow” stored on the device. Keys must be Word add-in actions (word.*)
listed in the facet’s client_actions.
[action_facets.facets.word_selection]
grant_scope = { "word.add_comments" = "session_document" }Action facet delegation
action_facets.facets.<facet-id>.delegation.max_tasks_per_turn — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.max_server_tool_calls_per_task — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.max_client_tool_calls_per_task — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.max_parallel — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.persona — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.child_facet_ids.[] — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.child_chat_providers.[] — Stability: unstable · Introduced in: unreleased
action_facets.facets.<facet-id>.delegation.run_modes.[] — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.scheduling — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.approval.mode — Stability: unstable · Introduced in: 0.7.0
action_facets.facets.<facet-id>.delegation.approval.plan_min_tasks — Stability: unstable · Introduced in: 0.7.0
Add erato/delegate_task to an action facet’s tool_call_allowlist to offer delegated tasks on its turns, for example a Word document turn that no selected facet plans for. The facet then counts as a planning facet: its optional delegation table takes the same keys as per-facet delegation overrides and merges with any selected planning facet by the same rules (caps take the minimum, child_facet_ids, child_chat_providers and run_modes the union, the strictest approval wins). Single-valued keys such as persona and scheduling come from a selected facet first.
[action_facets.facets.word_document_review]
tool_call_allowlist = ["erato/delegate_task"]
[action_facets.facets.word_document_review.delegation]
max_tasks_per_turn = 4
child_facet_ids = ["web_search"]Delegated tasks receive no client tools unless they name a child facet whose host matches client tools of the turn that set child_access = "read". Such a task can read the open document through those tools but never edit it; any other task cannot read it at all, so pass what it needs in the brief or as attached files. An action facet’s erato/* patterns do not change its MCP tool set.
Which model a task runs on
A delegated task runs on, in this order:
- the model the parent picked for the task from
child_chat_providers; - else the
child_chat_providerof the first facet in the task’sfacet_idsthat sets one; - else the model of the parent turn that started it.
A run delegated to an @-mentioned assistant uses that assistant’s default model in place of step 2. A task never runs on the default model of the assistant it inherits from the conversation.
A task always runs on a model the user may use. When a configured model is not available to the user, the task runs on the parent turn’s model instead, and its result says so, both to the parent model and on the task card. The model is stored with the task when it starts: later turns of the task and a retry keep it, and a retry falls back to the parent turn’s model in the same way when the user may no longer use it, or to the user’s default model when they have lost that one too.
[facets.facets.mail_search]
display_name = "Mail search"
child_chat_provider = "in-house-model"
[action_facets.facets.word_document_review.delegation]
child_facet_ids = ["mail_search"]
child_chat_providers = ["fast-model", "in-house-model"]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 = 5
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.
client_tools.tools.<key>.submission.max_attempts — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.submission.native_schema — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.submission.recovery_read_tools.[] — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.submission.max_recovery_reads — Stability: unstable · Introduced in: 0.7.0
max_attempts(default: 3, range: 1–10) - Total attempts per logical editing step, including the initial call and plain-text replies during correction. These attempts are reserved independently of ordinary tool budgets. Switching submission tools or draft identifiers does not reset the allowance. Schema errors and client failures return bounded feedback for correction; the final failed attempt ends generation with a persisted unresolved outcome. 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.recovery_read_tools(default:[]) - Qualified names of configured non-submission client tools permitted to read current context when the host returnssubmission_feedback.recovery = "read_current_context". These reads use their separate allowance without spending the ordinary tool budget. Configure only tools that read the current source context.max_recovery_reads(default: 32, range: 0–128) - Maximum recovery reads across the editing step, including approval continuations. Exhaustion ends with an unresolved outcome. The host can end recovery immediately withsubmission_feedback.recovery = "cannot_continue"when the source context is unavailable.
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"]client_tools.tools.<key>.replay.mode — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.replay.keep_input_fields.[] — Stability: unstable · Introduced in: 0.7.0
client_tools.tools.<key>.replay.keep_output_fields.[] — Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.6.0
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
Stability: unstable · Introduced in: 0.7.0
Interval in seconds at which in-flight generations refresh their heartbeat on the chat.
Type: number
Default value: 10
generation_status.stale_after_secs
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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
Stability: unstable · Introduced in: 0.7.0
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