Skip to Content
DocumentationConfiguration

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:

StabilityMeaning
stableSuitable for general adoption.
alphaAvailable to eager testers; may be rough around the edges.
betaAvailable to eager testers; expected to be ready for stabilization in the next release.
unstableNot ready for adoption. May be intended only for development and never stabilized.
deprecatedShould 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 when source = "static".
    • prompt_name: Langfuse prompt identifier used when source = "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 32

When 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 = true

generation

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 = 30

frontend

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 = true

frontend.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 = 10

frontend.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 = true

frontend.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 = true

frontend.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 = true

frontend.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 = false

frontend.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 = true

frontend.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 = true

frontend.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 = true

frontend.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 = 10

frontend.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 minutes

frontend.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 = true

frontend.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 = false

frontend.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:

  • environment
  • timestamp - the errored chat message created_at timestamp
  • chat_id
  • assistant_id
  • platform
  • facets_active
  • error

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.com and https://outlook.cloud.microsoft.
  • Word on the web: https://*.officeapps.live.com and https://*.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.com and https://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 = true

frontend.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 = false

desktop_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 = 365

Set 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" /qn

Use 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 = false

desktop_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 = true

desktop_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 = false

desktop_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_any
  • id_token_xms_pl
  • id_token_xms_tpl
  • browser_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 Rust regex crate 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 = false

chat_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 from guardrails.prompt_patterns to apply.
  • filter_pattern_tags (default: []) - Pattern tags to apply.
  • exclude_pattern_ids (default: []) - Explicit pattern IDs to skip after applying filter_pattern_ids and filter_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.0

Provider 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 provider
  • validate_availability - Optional; when true, query the provider’s model listing endpoint and only expose this model when it is returned
  • model_display_name - Optional display name for the model (falls back to model_name)
  • model_description - Optional frontend description shown below the model name; translatable via chat_models.<chat-provider-id>.description
  • model_icon - Optional frontend icon id, for example iconoir-*, simpleicons-*, builtin-chatgpt, or /path/to/icon.svg
  • model_name_langfuse - Optional model name for Langfuse reporting (falls back to model_name)
  • api_key - Optional API key for authentication
  • base_url - Optional custom base URL for the provider API
  • system_prompt - Optional prompt source specification (string or object; see prompt)
  • system_prompt_langfuse - Optional Langfuse prompt configuration (deprecated, mutually exclusive with system_prompt)
  • additional_request_parameters - Optional array of additional request parameters
  • additional_request_headers - Optional array of additional request headers
  • hallucination_suppression - Optional settings to suppress non-substantive outputs
  • guardrails - Optional provider-level prompt-injection filtering settings
  • model_capabilities - Optional configuration for model capabilities and limitations
  • model_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 the iconoir-react library
  • simpleicons-* for icons resolved from the simple-icons library, for example simpleicons-anthropic
  • builtin-chatgpt for the built-in ChatGPT/OpenAI mark
  • /path/to/icon.svg for 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 Langfuse
chat_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 from guardrails.prompt_patterns to apply.
  • filter_pattern_tags (default: []) - Pattern tags to apply.
  • exclude_pattern_ids (default: []) - Explicit pattern IDs to skip after applying filter_pattern_ids and filter_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.0

Fields:

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 files
  • supports_image_understanding (default: false) - Whether the model supports being provided with images for understanding
  • supports_audio_input (default: false) - Whether the model supports binary audio input for the audio transcription, dictation, and conversational audio modes
  • supports_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 enabled
  • supports_encrypted_reasoning_content (default: true) - Whether the model supports requesting encrypted reasoning content for stateless reasoning replay
  • supports_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 = false

Fields:

  • compat_omit_strict (default: false) - When set to true, requests will omit explicit tool schema strict fields 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 to true, 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_prompt and 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. .eml files remain explicitly treated as message/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 to naive.

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.

KeyDefaultMeaning
native_file_formats_bypass_limittrueInclude native LLM attachments in full and exclude them from per-file and aggregate attachment budget checks.
max_inline_tokens_per_file16000Full rendered cost per attachment, including metadata.
max_total_attachment_tokens0Absolute aggregate attachment budget.
max_context_fraction0.25Fraction of the model context window; finite number between 0 and 1.
max_preview_tokens_per_file1000Per-preview cost including metadata.
preview.csv_max_sample_rows5Complete sample records after the header.
preview.csv_max_columns50Displayed columns; omitted columns are disclosed.
preview.csv_max_field_chars256Characters per displayed CSV field; max_field_chars is an alias.
preview.text_max_chars0Independent 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.limitsDefaultGuarantee
max_input_bytes0Refuse eager extraction above this byte size. Storage reads and original-byte caches remain subject to existing infrastructure limits.
max_extracted_chars0Limit retained request text characters; not a bound on the document parser’s peak allocations.
timeout_ms0Limit extraction waiting time. Blocking document extraction may continue after the request stops waiting; this does not cancel parser CPU work.
bounded_text_previewtrueAllow 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 = false

This 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-core file processor, there is only a single option kreuzberg

  • "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

Featurekreuzberg
Text extraction✅
Page awareness✅
Page markers✅ (<page number="N">)
Output formatMarkdown
Supported formatsWord, 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 20

This 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_dictation enables microphone dictation that writes transcribed text into the chat input without creating an attachment.
  • audio_transcription enables recording or uploading audio as a file attachment with transcription metadata.
  • audio_conversational enables 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 = true

audio_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 = 2048

audio_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 = 2048

audio_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 = 2048

model_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 to
  • groups - 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

  1. No rules configured: All users have access to all chat providers
  2. Rules configured: For each chat provider, check if any rule grants access:
    • allow-all rules always grant access
    • allow-for-group-members rules grant access only if the user belongs to at least one specified group
  3. Access granted: If any rule grants access to a chat provider, the user can use it
  4. 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-all
  • allow-for-group-members

The only difference is the resource field name:

  • mcp_server_ids instead of chat_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-all
  • allow-for-group-members

The only difference is the resource field name:

  • facet_ids instead of chat_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 to integrations.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 default app_id, the secret oauth2-proxy already uses; the Helm chart passes it with msTeamsBot.appPassword.existingSecret.
  • tenant_id: the deployment’s Entra tenant. Defaults to the tenant in a tenant-specific integrations.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 to graph-sso, which the helper creates with the Graph scopes the web login uses, plus: User.Read and GroupMember.Read.All (identity and group-based policies), Files.Read.All/Sites.Read.All (files shared in group chats and channels), Chat.Read and ChannelMessage.Read.All (conversation context).
  • token_service_url: the Bot Framework token service; regional bots use for example https://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, default true. Set false only 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’s webApplicationInfo points at this app and resource, which must equal the OAuth connection’s token exchange URL and end with botid-<app_id>. They default to the add-in’s msal_client_id and api://<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 full https://<host>/api/integrations/ms_teams/messages URL; 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 (0 disables). Default 20.
  • streaming: stream answers in personal chats. Default true.
  • security_groups_only: resolve only security groups for the policy engine; match the groups claim configuration of the web login. Default true.

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 as whole_document_import allows. Set it to false to send every rewrite through the import.
  • whole_document_import: whether a change may replace the whole document by import. allowed imports without asking, confirm only after the user confirms that change, and off never imports.
  • package_read: read the complete .docx file 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 = false
integrations.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) or assistant (comment_assistant_author, on the user’s behalf). Replies, resolves, edits and deletes always show the user.
  • comment_author_enforced: when true, users cannot change comment_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) and modify (edits and deletes): post writes after the user confirms; copy turns the action off, and its card offers “Copy comment” and “Show passage” instead. The add-in also switches an action to copy for the rest of a session when Word refuses it on that host.
  • assistant_author: false posts every new comment under the user’s name with the label, whatever comment_author says.
  • 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 = false
integrations.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_drive
  • me_drives
  • joined_teams
  • group_drives
  • site_search
  • site_drives
  • shared_with_me
  • shared_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.Read or Files.ReadWrite)
  • Your authentication proxy (e.g., oauth2-proxy) must forward the access token via the X-Forwarded-Access-Token header

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 = true

Prerequisites:

  • Your OIDC provider (e.g., Microsoft Entra ID) must issue access tokens with Microsoft Graph API permissions (User.Read.All and Group.Read.All)
  • Your authentication proxy (e.g., oauth2-proxy) must forward the access token via the X-Forwarded-Access-Token header
  • The ID token must include the oid claim for organization user IDs and the groups claim 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 the oid claim (Entra ID specific)
  • organization_group_ids - Extracted from the groups claim 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 = false

logging

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 = true

mcp_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 = 120

mcp_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 = true

To 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):

  1. Tool override
  2. Server override
  3. Global override
  4. Server-supplied hint
  5. Tool default
  6. Server default
  7. Global default
  8. 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 = true

mcp_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 = 10

chat_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 = false

user_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 = false

assistants

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 = true

assistants.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 = true

assistants.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 = true

assistants.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 = true

assistants.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.6

assistants.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.01

assistants.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 = 5000

assistants.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 = 10

Deprecated 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 keyReplacement
assistants.delegation.enableddelegation.assistants.enabled
assistants.delegation.max_mentions_per_messagedelegation.assistants.max_mentions_per_message
assistants.delegation.allow_backgrounddelegation.allow_background
assistants.delegation.max_concurrent_background_runsdelegation.max_concurrent_background_runs
assistants.delegation.run_timeout_secondsdelegation.run_timeout_seconds
assistants.delegation.result_max_charsdelegation.result_max_chars
assistants.delegation.auto_archive_after_daysdelegation.auto_archive_after_days
assistants.delegation.preambledelegation.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 = 600

delegation.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 = 16000

delegation.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 = true

delegation.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 = 5

delegation.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 = 7

delegation.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 = true

delegation.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 = 3

delegation.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 = true

delegation.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 = 5

delegation.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 = 4

Tool 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 = 5

delegation.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 = 30

delegation.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 = true

delegation.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.

  • live hands each call to the executor stream at once. Without a connected pane that takes it, the call ends as context_unavailable.
  • durable lets the call wait for the device: see client_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.

  • never dispatches every planned task without asking.
  • always asks about every batch that contains a task call.
  • plan asks only once a batch reaches plan_min_tasks task calls, so a single lookup goes through unprompted while a multi-step plan is reviewed.
  • async_only asks 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 = 3

assistant_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 = true

assistant_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>.title
  • starter_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 = true

starter_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 exists
  • subtitle - Fallback subtitle shown when no frontend translation exists
  • icon - Optional icon id
  • prompt - Prompt source specification (uses common prompt format)
  • localized_prompts - Optional map of locale code to prompt source specification (each value uses the common prompt format)
  • selected_facets - Optional list of facet ids to preselect
  • chat_provider - Optional chat provider id to preselect

localized_prompts is resolved in the backend against the authenticated user’s profile locale. Resolution order is:

  1. Exact locale match, for example de-DE
  2. Base language match, for example de
  3. 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_mailbox is the Outlook add-in’s signed-in mailbox; desktop_sidecar is the local index of the Erato desktop app on the user’s device. A call without a matching binding, in X-Erato-Executor or X-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-level type = "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’s timeout_ms (default 60000) plus claim_lease_secs. An expired call continues the turn as expired.
  • 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 exceed attempt_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") - read lets a delegated task call the tool when the task names a facet with the same child_host_context. Not allowed together with submission.
  • host_context (required for read) - The host family whose client state the tool reads: word_document, outlook_mailbox or desktop_sidecar. It must equal durable.host_context when both are set.
  • child_context_arg and child_context_input (optional, set together, require read) - child_context_arg names the planning turn’s action-facet argument that holds the client’s context version, for example Word’s document_snapshot. The server writes its value into the input field child_context_input of every call a task makes, replacing what the model sent. All read tools of one host that set them must name the same child_context_arg.
  • child_host_inputs (optional, requires read) - Input fields the host fills from the client context it holds, for example Word’s documentIdentity. 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 = 86400
  • live hands each call to the executor stream at once, as described under child_access.
  • durable turns 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 as expired when no client ran it before then; the expiry is applied the next time one of the user’s Erato clients is open. It ends as declined when the user declined it on the device, and as withdrawn once 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.

CapabilityWeb and add-in behavior
Search field discoveryget_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 searchEmails, 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 readA search email’s readConversation reference supplies the mailbox and Message-ID. Bodies are converted to readable text. Attachment metadata is always returned.
Attachment contentsincludeAttachments=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 contentExplicit 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 - When true, the facet is a hidden baseline: never shown in the pool, not user-selectable, and injected as an always-on system prompt. Defaults to false.
  • 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 with hidden = 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 = 3
  • max_tasks_per_turn - Overrides delegation.tasks.max_tasks_per_turn.
  • max_server_tool_calls_per_task - Overrides delegation.tasks.max_server_tool_calls_per_task.
  • max_client_tool_calls_per_task - Overrides delegation.tasks.max_client_tool_calls_per_task. Raising this while leaving the server budget alone is the usual shape for a planning facet.
  • persona - Overrides delegation.tasks.persona.
  • child_facet_ids - Added to delegation.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 optional model argument of delegate_task and described by their display names. Facet-only: there is no delegation.tasks value, 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 - Overrides delegation.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; wait is always kept available.
  • scheduling - Overrides delegation.tasks.scheduling. The first selected facet that states one wins.
  • approval.mode and approval.plan_min_tasks - Override delegation.tasks.approval.mode and delegation.tasks.approval.plan_min_tasks. Merged like a cap rather than replacing: the strictest mode (always beats plan beats async_only beats never) and the lowest plan_min_tasks win, 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_selection
  • outlook_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 facet
  • platform - Optional platform identifier (e.g. "excel", "outlook")
  • template - Prompt template with {{variable}} placeholders
  • allowed_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:

  1. the model the parent picked for the task from child_chat_providers;
  2. else the child_chat_provider of the first facet in the task’s facet_ids that sets one;
  3. 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:

  1. The action_facet.id must match a configured action facet — unknown IDs are rejected
  2. All keys in args must be present in allowed_args — unexpected keys are rejected
  3. 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

AspectRegular FacetsAction Facets
User-visibleYes, selectable in UINo, resolved from client context
ParameterizedNo, behavior is config-time onlyYes, runtime args from request
Request fieldselected_facet_idsaction_facet
Prompt lifecycleToggle-state based, persistent across messagesPer-request, ephemeral
Base system promptMay suppress historical replay on newly-enabled turnNever 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") - auto uses native enforcement when the adapter, explicit model capability and schema are compatible; otherwise it uses local validation and feedback. off always uses local validation. required rejects request preparation if native enforcement is unavailable, including when compat_omit_strict is enabled.
  • recovery_read_tools (default: []) - Qualified names of configured non-submission client tools permitted to read current context when the host returns submission_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 with submission_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") - full replays arguments and results unchanged. receipt replaces both with a receipt for calls from earlier user turns.
  • keep_input_fields (default: []) - Argument fields to keep in the receipt, as $.key.key paths. 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, $.error and $.submission.status are 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 = 30

budget.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.0

Note: 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 = 4

caches.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 = 6

Performance 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 = 600

generation_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

Last updated on