Skip to main content

5. Configuration files

There are two layers. Later layers win.

LayerFilesHolds
Environmentconfig/{env}.yaml (development, demo, test, production)ports, database, queue, JWT, model and parser endpoints, the settings: block
Local overrideconfig/{env}.local.yaml (git-ignored)your machine's changes, deep-merged by Loco
Definitionsconfig/agents/**workflows, prompts, schemas, connectors, tools, roles, policies
Tenant overrideconfig/agents/tenants/{slug}.yamlone customer's differences

Choose the environment with LOCO_ENV.

5.1 Environment references​

Secrets never appear in YAML. Read them from the environment with this tag, in both layers:

api_key: '<%= get_env(name="VLLM_API_KEY", default="dev") %>'
  • Quote the tag. Unquoted, an empty default such as default="" becomes YAML null, and boot fails with invalid type: null, expected a string.
  • A variable that is unset and has no default stops boot and names the variable.
  • Nothing else is rendered: {{ session_id }} in a workflow is a runtime placeholder and stays as written.

5.2 The settings: block​

settings:
definitions_dir: config/agents # where the definitions live
documents_dir: storage/documents # uploads: <dir>/<tenant id>/<sha256>
llm:
providers: # referenced by models.yaml `provider`
vllm_chat:
kind: openai_compatible # or `fixture`
base_url: '<%= get_env(name="VLLM_CHAT_URL", default="http://localhost:8000") %>'
api_key: '<%= get_env(name="VLLM_API_KEY", default="dev") %>'
timeout_sec: 120
max_concurrency: 8
options: {} # fixture only: `extract: {…}` is what it returns
documents:
parsers: # referenced by document.parse `parser`
docling:
kind: docling_serve # or `plain_text`
base_url: '<%= get_env(name="DOCLING_URL", default="http://localhost:5001") %>'
api_key: '<%= get_env(name="DOCLING_API_KEY", default="") %>'
options: { do_ocr: true, table_mode: accurate, do_picture_classification: false, do_formula_enrichment: false }
telemetry:
enabled: false # true installs OTLP export to otlp_endpoint
otlp_endpoint: http://localhost:4317
service_name: bo-app
export_prompt_bodies: false
agui:
protocol_version: "1.0"
legacy_interrupt_event: on_interrupt # event name sent to pre-1.0 AG-UI clients
sse_keepalive_sec: 15

5.3 The definition files​

FilePurpose
models.yamlThe model catalogue: provider, name, revision, license, role (chat or embedding), dimensions (embeddings), params (temperature, max_tokens, enable_thinking). allowed_licenses lists what may boot.
prompts/*.yamlid, version, system, user (must contain {{ input }}), guards.fence_tag, guards.max_input_chars.
schemas/*.yamlJSON Schema written as YAML, named by id. Model output is validated against it.
connectors/*.yamlid, kind (accounting_stub or webhook), enabled, credentials, options, actions.<name>.{requires_approval, idempotent}.
tools/*.yamlTools a future chat model may call: id, description, kind, with, input_schema, requires_approval. Validated at boot; not yet offered to a model.
workflows/*.yamlWorkflows — see §6.
rbac.yamlroles.<name>.{inherits, can}.
policies.yamlrate_limits, pii.hash_fields, pii.redact_in_traces, retention.{parsed_markdown_days, audit_log_days}, audit.hash_chain.
tenants/*.yamlPer-tenant overrides — see §8.

Of the policies, pii.hash_fields (applied at extraction) and retention.parsed_markdown_days (applied by retention_purge) take effect today. Rate limits, trace redaction and audit-log retention are declared for the parts not built yet.

Every file rejects keys it does not know. To get autocompletion and validation in your editor, map schemas/*.schema.json to these files. Regenerate the schemas after changing the Rust structs:

LOCO_ENV=demo cargo run -p bo-app -- task schema_export

A test fails if the committed schemas fall out of date.