Skip to main content

App Configuration

The YAML file has 6 top-level blocks. Only app: and agents: are required.

YAML Structure

app:          # Required — application identity
variables: # Optional — template variables
modules: # Optional — module configuration
agents: # Required — agent definitions (list)
channels: # Optional — output channel instances
execution: # Optional — runtime configuration
capabilities: # Optional — security configuration

App Block

app:
app_id: my-app # Required. Unique identifier
name: "My Application" # Required. Human-readable name
version: "1.0" # Version string (default: "1.0")
description: "What this app does" # Optional description
author: "your-name" # Optional author
tags: [coding, assistant] # Searchable tags

Field Reference

FieldTypeDefaultDescription
app_idstringrequiredUnique application identifier
namestringrequiredHuman-readable name
versionstring"1.0"Version string
descriptionstring""Description
authorstring""Author name
tagslist[string][]Searchable tags

Variables

The variables: block defines reusable values accessible throughout the YAML via {{variable_name}}.

variables:
workspace: "{{env.PWD}}"
max_file_lines: 500
api_token: "{{env.MY_API_KEY}}"

Accessing Variables

Variables are resolved in all blocks of the YAML:

  • modules.*.config — module configuration values
  • modules.*.setup[].params — setup step parameters
  • modules.*.constraints — constraint values
  • agents[].brain.config — provider config (api_key, base_url, etc.)
  • agents[].system_prompt — system prompt text
  • execution.greeting — greeting message
  • execution.workspace — working directory
agents:
- id: assistant
system_prompt: |
Working directory: {{workspace}}
Max lines: {{max_file_lines}}

Built-in Variables

VariableDescription
{{env.VAR_NAME}}Environment variable
{{secret.VAR_NAME}}Per-app secret (DB first, env fallback)

Secrets ({{secret.XXX}})

The {{secret.XXX}} syntax resolves secrets with a two-step lookup:

  1. Database — checks the per-app encrypted secret store first
  2. Environment — falls back to os.environ (backward compatible)

This means you can start with environment variables and migrate to DB secrets without changing your YAML.

Storing secrets in the database

# Via CLI (daemon must be running)
digitorn secret set my-app API_KEY "sk-live-abc123"
digitorn secret set my-app API_KEY # prompts for value (hidden input)
digitorn secret list my-app # list keys (values never shown)
digitorn secret delete my-app API_KEY

# Via API
curl -X PUT http://localhost:8000/api/apps/my-app/secrets/API_KEY \
-H "Content-Type: application/json" \
-d '{"value": "sk-live-abc123"}'

Secrets are encrypted at rest with Fernet (AES-128-CBC + HMAC-SHA256), isolated per app, and never returned in plaintext by the API.

Usage in YAML

modules:
mcp:
config:
servers:
notion:
auth:
client_id: "{{secret.NOTION_CLIENT_ID}}"
client_secret: "{{secret.NOTION_CLIENT_SECRET}}"
agents:
- id: assistant
brain:
config:
api_key: "{{secret.OPENAI_API_KEY}}"

Modules Block

The modules: block declares which modules to load and configures them.

modules:
# Empty config — just load the module
hello: {}

# With constraints
filesystem:
constraints:
allowed_actions: [read, ls, find, grep]

# With config and setup steps
database:
config:
timeout_seconds: 10
setup:
- action: connect
params:
connection_id: main
driver: sqlite
database: "{{workspace}}/data.db"
constraints:
allowed_actions: [fetch_results, list_tables]
blocked_actions: [execute_query]

ModuleBlock Fields

FieldTypeDefaultDescription
configdict{}Static module configuration, pushed via on_config_update() at bootstrap
setuplist[SetupStep][]Ordered actions executed at bootstrap time
constraintsdict{}Runtime restrictions (allowed_actions, blocked_actions, module-specific)

SetupStep Fields

FieldTypeDefaultDescription
actionstringrequiredAction name on the module
paramsdict{}Parameters (may contain {{variables}})

Currently Implemented Modules

ModuleDescription
helloSimple greeting module (test/demo)
filesystemFile read, list, find, grep, write, mkdir operations
databaseMulti-driver database operations (SQLite, PostgreSQL, MySQL, MSSQL, Oracle, MongoDB, Redis)
httpHTTP client: GET, POST, JSON API, page fetch, download with progress tracking
shellShell execution: run commands, scripts, background processes, env/which
llm_providerLLM provider management (auto-configured from brain)
context_builderTool discovery engine (system module, auto-loaded)

Note: The context_builder module is loaded automatically — you never declare it in modules:. The llm_provider module is auto-configured from the brain: block in each agent.

Setup Steps and Pre-Configured Resources

When a module has setup: steps, they are executed at bootstrap time (app startup). The runtime automatically summarizes all successful setup steps and injects them into the agent's system prompt under a # PRE-CONFIGURED RESOURCES section.

This means the agent knows what's already configured without having to discover it. For example, with:

modules:
database:
setup:
- action: connect
params:
connection_id: main_db
driver: postgresql
host: db.example.com
database: myapp
password_env: DB_PASSWORD

The agent's system prompt will include:

# PRE-CONFIGURED RESOURCES

The following resources were set up at startup and are ready to use:
- database.connect | connection_id=main_db | driver=postgresql | host=db.example.com | database=myapp | password_env=***

You do NOT need to configure these again — use them directly.

Sensitive fields (password, password_env, api_key, secret, token) are automatically redacted. If no module has setup steps, this section is not injected.

Auto-Schema Injection (Database)

When the database module has active connections (from setup steps), the runtime automatically introspects all connected databases and injects the full schema into the agent's system prompt. The agent knows the table structure from the first message — no tool calls needed to discover the schema.

The schema includes:

  • Table names and DB-native comments (COMMENT ON in PostgreSQL, column comments in MySQL)
  • Column names, types, constraints (PK, NOT NULL)
  • Foreign key relationships
  • Business annotations (from YAML annotate steps — see below)

Example system prompt injection:

DATABASE SCHEMA:

[main_db] (postgresql)
users — Registered platform users
- id INTEGER PK NOT NULL
- name TEXT NOT NULL
- email TEXT NOT NULL — Primary email, unique, used for authentication
- created_at TIMESTAMP NOT NULL — Registration date
FK: team_id -- teams.id
orders — Customer orders
- id INTEGER PK NOT NULL
- user_id INTEGER NOT NULL — References users.id
- total DECIMAL NOT NULL — Order total in cents
- status TEXT NOT NULL — pending|confirmed|shipped|delivered

Business Annotations

Use the annotate setup step to add business context to tables and columns. Annotations are prioritized over DB-native comments and give the agent a deep understanding of the data model.

modules:
database:
setup:
- action: connect
params:
connection_id: main_db
driver: postgresql
host: "{{env.DB_HOST}}"
database: myapp
password_env: DB_PASSWORD
policy:
preset: safe_write

# Table-level annotation
- action: annotate
params:
connection_id: main_db
table: users
description: "Registered platform users — one row per account"
tags: [core, pii]

# Column-level annotations
- action: annotate
params:
connection_id: main_db
table: users
column: email
description: "Primary email, unique, used for login and notifications"
tags: [pii, unique]

- action: annotate
params:
connection_id: main_db
table: orders
column: status
description: "Order lifecycle: pending -- confirmed -- shipped -- delivered"
tags: [enum]

Annotation fields:

FieldTypeDescription
descriptionstringBusiness description (prioritized over DB comment)
tagslist[string]Searchable tags (e.g. pii, financial, immutable)
glossarydictBusiness glossary (e.g. {"SKU": "Stock Keeping Unit"})
ruleslist[string]Business rules (e.g. "status transitions are one-way")

Priority: YAML annotation > DB-native comment > empty. If the database already has COMMENT ON (PostgreSQL) or column comments (MySQL), they are used as fallback when no YAML annotation exists.

Database High-Level Actions

In addition to execute_query and fetch_results, the database module provides optimized actions for common operations:

ActionRiskDescription
bulk_insertmediumInsert multiple rows in one call. Provide columns + rows (array of arrays). Atomic transaction.
batch_executehighExecute multiple SQL statements in a single atomic transaction. All succeed or all roll back.
upsertmediumInsert or update rows. If conflict_columns match an existing row, it updates instead of failing.

These actions are much faster than calling execute_query repeatedly — they reduce tool calls from N to 1 and use transactional batching.

Upsert Example

# The agent calls upsert with:
{
"connection_id": "main_db",
"table": "users",
"columns": ["email", "name", "status"],
"rows": [
["alice@example.com", "Alice Updated", "active"],
["bob@example.com", "Bob New", "pending"]
],
"conflict_columns": ["email"],
"update_columns": ["name", "status"]
}

Generates driver-appropriate SQL:

  • SQLite/PostgreSQL: INSERT ... ON CONFLICT (email) DO UPDATE SET name=EXCLUDED.name, status=EXCLUDED.status
  • MySQL: INSERT ... ON DUPLICATE KEY UPDATE name=VALUES(name), status=VALUES(status)
  • MSSQL: MERGE ... WHEN MATCHED THEN UPDATE ... WHEN NOT MATCHED THEN INSERT ...

All high-level actions enforce the same security layers: QueryGuard policy, table/column access control, audit logging, and transaction timeouts.

Module Constraints

Universal constraints available for any module:

ConstraintTypeDescription
allowed_actionslist[string]Whitelist of allowed action names
blocked_actionslist[string]Blacklist of blocked action names

Modules may declare additional constraints via their ConstraintSpec — use digitorn app schema {module_id} to see them.

Discovering Module Schemas

Use the CLI to see what's available:

# List all available modules
digitorn app schema hello

# Shows:
# - All actions with their parameter schemas
# - All supported constraints
# - Config fields (if any)
# - YAML template for quick copy-paste

Channels Block

The channels: block declares named output channel instances for delivering notifications from scheduled jobs, watchers, and background tasks. Channels are the notification delivery infrastructure — they route results to external systems (Slack, email, Kafka, webhooks, etc.).

channels:
slack_alerts:
type: webhook
config:
url: "{{env.SLACK_WEBHOOK_URL}}"
headers:
Content-Type: "application/json"

audit_log:
type: log
config:
logger_name: "digitorn.audit"
level: "INFO"
format: json
include_data: true

Channel Instance Fields

FieldTypeDefaultDescription
typestringrequiredChannel type ID: webhook, log, or any installed plugin (slack, telegram, etc.)
configdict{}Channel-specific configuration (supports {{variables}} and {{env.VAR}})
user_resolverobjectnullOptional auto-resolution of per-user delivery targets (email, phone, chat_id) from a data source. See Per-User Channel Resolution
user_resolver.modulestringrequiredModule ID to query (e.g. database, http)
user_resolver.actionstringrequiredAction to call on the module
user_resolver.paramsdict{}Action parameters (:session_id is replaced with the user's session)
user_resolver.mappingdict{}Maps result fields to per-delivery config fields
user_resolver.cache_ttlfloat300Cache duration in seconds (0 = no cache)

Built-in Channel Types

TypeDescription
llm_notificationPush to agent conversation (always available, no config needed)
webhookHTTP POST to any URL (Slack, Discord, Teams, Zapier, n8n compatible)
logStructured Python logging (debugging, audit trails)

Plugin channels are installed via pip (pip install digitorn-channel-slack) and auto-discovered. See Output Channels for the full channel system documentation.

Execution Block

The execution: block configures runtime behavior.

execution:
mode: conversation # 'one_shot', 'conversation', or 'background'
greeting: "Hello!" # Greeting message (conversation mode)
max_turns: 10 # Maximum agent loop iterations
timeout: 120.0 # Total timeout in seconds
entry_agent: assistant # Which agent starts (multi-agent)
workspace: "{{workspace}}" # Working directory for file operations
context: # Default context management for all agents
max_tokens: 0
strategy: summarize
compression_trigger: 0.75
hooks: [] # Custom hooks (see Context Management)
triggers: [] # Triggers for background mode
watchers: false # Enable persistent monitoring (watch_* primitives)
scheduler: false # Enable scheduler + remember primitives
default_channel: llm_notification # Default output channel for jobs/watchers

Execution Fields

FieldTypeDefaultDescription
modestring"one_shot"Execution mode: one_shot, conversation, or background
entry_agentstring""Agent to start with. Empty = first agent in list
max_turnsint50Max agent loop iterations (per turn for conversation, per activation for background)
timeoutfloat300.0Timeout in seconds (per turn for conversation, per activation for background)
greetingstring""Greeting message displayed at conversation start
workspacestring""Working directory for file operations. Resolution: (1) explicit value in YAML, (2) parent directory of the YAML source file, (3) CLI mode: current working directory, (4) daemon mode: managed directory under ~/.local/share/digitorn/workspaces/{app_id}/
inputInputConfigInputConfig()Input contract (one_shot mode only)
outputOutputConfigOutputConfig()Output contract (one_shot mode only)
contextContextConfigContextConfig()Default context management for all agents (see Context Management)
hookslist[HookConfig][]Custom hooks (see Context Management)
watchersboolfalseEnable persistent monitoring. When true, the agent gets watch_* primitives for periodic data source monitoring with smart escalation (see Execution Primitives)
schedulerboolfalseEnable time-based scheduling. When true, the agent gets schedule_once, schedule_cron, schedule_cancel, schedule_list, schedule_status, and remember primitives (see Execution Primitives)
default_channelstring"llm_notification"Default output channel for scheduled jobs and watchers. Must reference a channel instance name from the channels: block, or "llm_notification" (always available). See Output Channels
triggerslist[TriggerConfig][]Triggers for background mode

Execution Modes

ModeDescription
one_shotProcess a single input and return. Uses input/output contracts.
conversationInteractive multi-turn conversation. Uses greeting, max_turns.
backgroundDaemon mode, activated by triggers (cron, file watch). Uses triggers.

Input/Output Contracts (one_shot mode)

Define what your application accepts and produces.

Input types:

TypeDescriptionModel requirement
textPlain text (default)All models
imageImage file (PNG, JPEG, WebP)Vision models (GPT-4o, Claude Sonnet, Gemini)
audioAudio file (WAV, MP3, M4A)Audio models (GPT-4o-audio, Gemini)
videoVideo file (MP4)Gemini
fileAny file (read via filesystem module)All models
jsonStructured JSON inputAll models
anyText, images, or filesDepends on model

Output types:

TypeDescriptionCLI behavior
textPlain textPrinted to stdout
jsonStructured JSONPretty-printed, validated against schema
markdownMarkdown textRendered with Rich (headers, code blocks, tables)
fileFile written to diskPath printed to stdout
imageGenerated imageSaved to file, path printed
audioGenerated audioSaved to file, path printed

Examples:

# Text analysis with JSON output
execution:
mode: one_shot
input:
type: text
description: "Code to analyze"
required: true
output:
type: json
description: "Analysis report"
schema:
type: object
properties:
bugs: { type: array }
score: { type: integer }

# Image analysis
execution:
mode: one_shot
input:
type: image
accept: ["image/png", "image/jpeg", "image/webp"]
max_size: "10MB"
description: "Image to analyze"
output:
type: json
description: "Detected objects and description"

# Audio transcription
execution:
mode: one_shot
input:
type: audio
accept: ["audio/wav", "audio/mp3", "audio/m4a"]
max_size: "50MB"
description: "Audio to transcribe"
output:
type: text
description: "Transcription"

# Conversation with image support
execution:
mode: conversation
input:
type: any
accept: ["image/png", "image/jpeg", "application/pdf"]
description: "Text or images"

# Code generator with file output
execution:
mode: one_shot
input:
type: text
description: "Description of what to generate"
output:
type: file
format: ".py"
description: "Generated Python file"

Input fields:

FieldTypeDefaultDescription
typestring"text"Input type (text, image, audio, video, file, json, any)
acceptlist[]Accepted MIME types. Empty = infer from type
max_sizestring""Max input size (e.g. "10MB"). Empty = no limit
descriptionstring""Human-readable description
requiredbooltrueWhether input is mandatory

Output fields:

FieldTypeDefaultDescription
typestring"text"Output type (text, json, markdown, file, image, audio)
formatstring""Format hint (.py, .svg, png, etc.)
descriptionstring""Human-readable description
schemaobject{}JSON Schema for output validation (json type only)

Workspace

The workspace field sets the working directory for file operations. It supports template variables:

variables:
workspace: "{{env.PWD}}"

execution:
workspace: "{{workspace}}"

If not set explicitly, the workspace defaults to: explicit value > YAML source file's parent directory > current working directory.

Background Mode and Triggers

Background mode turns the app into a daemon that reacts to events. Each trigger activates the agent with a message.

execution:
mode: background
triggers:
# Run every hour
- id: hourly_check
type: cron
schedule: "0 * * * *"
message: "Hourly check: analyze recent changes."

# Watch for new files
- id: new_csv
type: watch
paths: ["./inbox/*.csv"]
message: "New file detected: {{event.path}}"

Trigger Types

TypeRequired FieldsDescription
cronscheduleCron expression (e.g., "0 * * * *" = every hour)
watchpathsFile glob patterns to watch for changes
httppath, methodHTTP endpoint trigger (not yet implemented)

TriggerConfig Fields

FieldTypeDefaultDescription
idstringrequiredUnique trigger identifier
typestringrequiredTrigger type: cron, watch, or http
schedulestring""Cron expression (cron type only)
pathslist[string][]File glob patterns (watch type only)
pathstring""HTTP endpoint path (http type only)
methodstring"POST"HTTP method (http type only)
messagestring""Message sent to the agent when triggered

Note: HTTP triggers are defined in the schema but not yet implemented in the runtime.

Complete Example

This example uses all 6 top-level blocks and demonstrates most configuration options: variables with environment references, modules with config/setup/constraints, brain with context management and summary brain, execution with workspace and hooks, and capabilities with grants/denials.

app:
app_id: data-analyst
name: "Data Analyst"
version: "2.0"
description: "AI data analysis assistant with read-only access."
author: "digitorn"
tags: [data, analysis, sql]

variables:
workspace: "{{env.PWD}}"
db_path: "{{workspace}}/data.db"
api_key: "{{env.DEEPSEEK_API_KEY}}"

modules:
hello: {}
filesystem:
constraints:
allowed_actions: [read, ls, find, grep]
database:
config:
timeout_seconds: 30
setup:
- action: connect
params:
connection_id: main
driver: sqlite
database: "{{db_path}}"
constraints:
allowed_actions: [fetch_results, list_tables]

agents:
- id: analyst
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
temperature: 0.2
max_tokens: 8192
config:
api_key: "{{api_key}}"
context:
max_tokens: 80000
output_reserved: 4096
strategy: summarize
keep_recent: 10
compression_trigger: 0.75
summary_max_tokens: 1024
auto_compact: true
summary_brain:
provider: ollama
model: qwen2.5:3b
backend: openai_compat
system_prompt: |
You are a data analyst. Query databases and read files.
Workspace: {{workspace}}

WORKFLOW:
1. list_categories -> see available modules
2. browse_category(category="name") -> see module tools
3. execute_tool(name="module.action", params={...}) -> execute

IMPORTANT:
- Go directly to execute_tool once you know the tool name.
- Limit yourself to 3-5 tool calls per question.
- If a tool fails, explain the error instead of retrying.

execution:
mode: conversation
greeting: "Data Analyst ready. Ask me about your data."
workspace: "{{workspace}}"
max_turns: 40
timeout: 600.0
hooks:
- id: pressure_log
on: turn_start
condition:
type: always
action:
type: log
message: "Turn {turn}: ~{tokens} tokens, {messages} messages"
cooldown: 0

capabilities:
default_policy: auto
max_risk_level: low
grant:
- module: filesystem
actions: [read, ls, find, grep]
- module: database
actions: [fetch_results, list_tables]
- module: hello
deny:
- module: filesystem
actions: [write, delete]
reason: "Read-only mode"
- module: database
actions: [execute_query]
reason: "Only fetch_results allowed"