Skip to main content

Examples

Complete app YAML sketches. Prefer tool names from YAML building blocks. Top-level blocks: app, runtime, agents, tools, security, ui, dev, flow, plus optional templates / requirements / docs / ...

channels: lives under tools: (not top-level).

1 · Minimal chat​

The simplest possible conversational app - LLM + filesystem read access.

app.yaml
app:
app_id: chat-assistant
name: Chat Assistant
description: Interactive conversation with tool access.

runtime:
mode: conversation

agents:
- id: assistant
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config:
api_key: "{{secret.DEEPSEEK_API_KEY}}"
base_url: https://api.deepseek.com/v1
system_prompt: |
Tu es un assistant intelligent et amical. Tu réponds en français.
Utilise les outils disponibles quand c'est pertinent.

tools:
modules:
filesystem: {}
capabilities:
default_policy: auto

ui:
greeting: "Bienvenue ! Je suis ton assistant Digitorn."

2 · One-shot task​

Process a single input and return; the agent loop exits as soon as the LLM produces text without calling any tool.

app.yaml
app:
app_id: hello-oneshot
name: Hello One-Shot

runtime:
mode: one_shot
input:
type: text
description: A question or message
output:
type: text

agents:
- id: assistant
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
system_prompt: "Be concise and helpful. Use tools when relevant."

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
capabilities:
default_policy: auto
bash
digitorn install hello-oneshot.yaml
digitorn chat hello-oneshot

Type Say hello in 3 languages as your message.

3 · Smart chat with context management​

Conversation mode with automatic context compaction (summarize strategy + auto_compact: true).

app.yaml
app:
app_id: smart-chat
name: Smart Chat

runtime:
mode: conversation
max_turns: 40
timeout: 1200

agents:
- id: assistant
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
context:
max_tokens: 80000
output_reserved: 1000
strategy: summarize # truncate | summarize
keep_recent: 20 # last N messages always kept verbatim
compression_trigger: 0.9 # compact when usage > 90 %
summary_max_tokens: 5120
auto_compact: true
system_prompt: |
Tu es un assistant intelligent. Réponds en français.
Limite-toi à 3-5 appels d'outils maximum par question.
Si un outil échoue, explique l'erreur, ne ré-essaie pas en boucle.

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep, write]
capabilities:
default_policy: auto

ui:
greeting: "Salut ! Assistant avec gestion automatique du contexte."

4 · Local LLM (Ollama, no native tools)​

Local model, tool definitions injected in the system prompt since the model doesn't support native function calling.

app.yaml
app:
app_id: ollama-chat
name: Ollama Chat

runtime:
mode: conversation
max_turns: 10
timeout: 300

agents:
- id: assistant
role: assistant
brain:
provider: ollama
model: qwen2.5:14b-instruct-q4_K_M
backend: openai_compat
config: {base_url: http://localhost:11434/v1}
context:
max_tokens: 8000
strategy: truncate
keep_recent: 6
compression_trigger: 0.6
auto_compact: true
system_prompt: "Tu es un assistant local. Limite-toi à 3-5 appels d'outils max."

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
capabilities:
default_policy: auto

ui:
greeting: "Assistant local prêt."

Local-LLM tips: timeout: 300 (or higher) - local models are slower than cloud APIs.

5 · Context-management stress test​

Aggressive compression_trigger so the compaction code path fires within a few turns. Includes a runtime.hooks: entry that logs token pressure on every turn.

app.yaml
app:
app_id: context-test
name: Context Management Test

runtime:
mode: conversation
max_turns: 50
timeout: 120
hooks:
- id: pressure_log
"on": turn_start
condition: {type: always}
action:
type: log
message: "Turn {turn}: ~{tokens} tokens, {messages} messages"
cooldown: 0

agents:
- id: assistant
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
context:
max_tokens: 0 # 0 = use provider default
output_reserved: 4096
strategy: summarize
keep_recent: 6
compression_trigger: 0.15 # very aggressive
summary_max_tokens: 512
auto_compact: true
system_prompt: "Réponds en français. Sois détaillé pour générer du contenu."

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
capabilities:
default_policy: auto

6 · Smart chat with summary brain​

Same as example 3 but the summary brain is a small local model - keeps the main conversation on the cloud LLM while compaction runs locally for free.

yaml
agents:
- id: assistant
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
context:
max_tokens: 80000
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
config: {base_url: http://localhost:11434/v1}
system_prompt: "Réponds en français."

7 · Secure read-only analyst​

Read-only filesystem + database access, with explicit grant: / deny: and max_risk_level: low. Demonstrates the security model in Security.

app.yaml
app:
app_id: secure-analyst
name: Secure Analyst

runtime:
mode: conversation
workdir: "{{env.PWD}}"

agents:
- id: analyst
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
system_prompt: |
You are a data analyst with read-only access.
Never attempt to modify files or execute write queries.

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
database:
config:
databases:
- name: main
kind: postgres
dsn: "{{secret.ANALYST_DATABASE_URL}}"
security:
mode: read_only
constraints:
allowed_actions: [query]
capabilities:
default_policy: auto
max_risk_level: low
grant:
- {module: filesystem, actions: [read, glob, grep]}
- {module: database, actions: [query]}
deny:
- {module: filesystem, actions: [write, edit], reason: "Read-only mode"}
- {module: database, actions: [disconnect], reason: "Only query allowed"}

ui:
greeting: "Data analyst ready. I can read files and query databases."

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

8 · Multi-agent (coordinator + worker)​

Two agents. The coordinator delegates with agent(agent="worker", task="...", wait=true). delegate_to on the coordinator is required, not optional: it both builds the automatic "Available specialists" prompt block and is the actual runtime authorization list - a coordinator can only spawn an id that's in its own delegate_to, even with agent_spawn granted and worker a real agent in the file. See Multi-agent.

app.yaml
app:
app_id: multi-agent
name: Multi-Agent

runtime:
mode: conversation
entry_agent: coordinator
max_turns: 30
workdir: "{{env.PWD}}"

agents:
- id: coordinator
role: coordinator
delegate_to:
- id: worker
instructions: "Delegate here for any task the user asks to have done."
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
system_prompt: "Orchestrate via the agent tool (agent + task params)."

- id: worker
role: specialist
specialty: "Executes the delegated task"
brain:
provider: groq
model: llama-3.3-70b-versatile
backend: openai_compat
config:
api_key: "{{secret.GROQ_API_KEY}}"
base_url: https://api.groq.com/openai/v1
system_prompt: "Execute the delegated task."

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
agent_spawn: {}
capabilities:
default_policy: auto
grant:
- module: agent_spawn
- module: filesystem

ui:
greeting: "Multi-agent system ready."

See Multi-agent for real agent modes (agent/task, batch agents, wait, list, cancel). There is no reassign tool param in this build.

9 · Background mode with channel providers​

A daemon app that sweeps an inbox folder every 10 minutes and emits an hourly summary. Digitorn has no file watcher: polling on a cron is how you pick up new files today. See Channels for the adapters that arm.

app.yaml
app:
app_id: csv-watcher
name: CSV Watcher

runtime:
mode: background
max_turns: 10
timeout: 60
workdir: "{{env.PWD}}"

agents:
- id: analyst
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
config: {api_key: "{{secret.DEEPSEEK_API_KEY}}"}
system_prompt: |
When activated, glob the inbox for CSV files, read the ones
you have not summarised yet, and report what they contain.

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
channels:
config:
providers:
inbox_sweep:
adapter: cron
enabled: true
config:
schedule: "*/10 * * * *"
activation:
agent: analyst
message: "Sweep {{workspace}}/inbox/ for new CSV files and analyse them."
reply: none

hourly_report:
adapter: cron
enabled: true
config:
schedule: "0 * * * *"
activation:
agent: analyst
message: "Generate an hourly summary of {{workspace}}/inbox/."
reply: none
capabilities:
default_policy: auto

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

Background mode requires at least one channel provider. The agent is activated each time a provider fires with the activation's message as input. For multi-user routing, set the provider's activation.session (per_event default, or a template like {{event.payload.user_id}} to group one user's events into a recurring session) - see Background Sessions.

10 · Parallel execution + background tasks​

A polyvalent assistant showing the auto-injected execution primitives (run_parallel, background_run) - no YAML config needed for those.

app.yaml
app:
app_id: smart-chat
name: Smart Chat

runtime:
mode: conversation
max_turns: 200
timeout: 1200
workdir: "{{env.PWD}}"

agents:
- id: assistant
role: assistant
brain:
provider: openai
model: gpt-4o-mini
backend: openai_compat
config: {api_key: "{{secret.OPENAI_API_KEY}}"}
context:
max_tokens: 128000
output_reserved: 2000
strategy: summarize
keep_recent: 20
compression_trigger: 0.85
auto_compact: true
system_prompt: |
Tu disposes de primitives d'exécution :
- run_parallel : actions indépendantes en parallèle
- background_run : tâches longues non bloquantes
Utilise run_parallel pour les actions indépendantes.
Utilise background_run pour les téléchargements et opérations lentes.

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep, write]
bash:
constraints:
allowed_actions: [run]
http:
constraints:
allowed_actions: [request, download, upload]
database:
config:
databases:
- name: test_db
kind: postgres
dsn: "{{secret.TEST_DATABASE_URL}}"
security:
mode: read_write
constraints:
allowed_actions: [connect, query, disconnect]
capabilities:
default_policy: approve

ui:
greeting: "Assistant polyvalent : exécution parallèle + tâches background."

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

Key takeaways:

  • Modules such as filesystem, bash, http, database can load together.
  • Execution primitives: run_parallel and background_run (one tool, several modes - status / result / cancel / list / wait) come from auto-loaded context_builder.
  • default_policy: approve makes tool calls ask for confirmation by default when configured that way.

11 · Monitoring bot - cron + http​

A background-oriented monitor. A cron channel provider wakes it every 5 minutes; the agent itself calls http.request both for the health check and to post the alert to Slack.

app.yaml
app:
app_id: monitoring-bot
name: Monitoring Bot

runtime:
mode: background
entry_agent: monitor
max_turns: 200
timeout: 3600
workdir: "{{env.PWD}}"

agents:
- id: monitor
role: assistant
brain:
provider: openai
model: gpt-4o-mini
backend: openai_compat
config: {api_key: "{{secret.OPENAI_API_KEY}}"}
system_prompt: |
You are a monitoring agent. Each wake-up: use http.request to
check the monitored endpoints. On a failure, use http.request
again to POST a Slack message to {{secret.SLACK_WEBHOOK_URL}}
({"text": "..."} JSON body). Use memory.remember for durable
notes across wake-ups.

tools:
modules:
http: {}
filesystem:
constraints:
allowed_actions: [read, glob, grep]
memory: {}
channels:
config:
providers:
cron_health:
adapter: cron
enabled: true
config:
schedule: "*/5 * * * *"
activation:
agent: monitor
message: "Run a health check on all monitored endpoints."
reply: none
capabilities:
default_policy: auto
grant:
- module: http
- module: filesystem
- module: memory

ui:
greeting: |
Monitoring bot prêt. Je peux surveiller et alerter Slack.
Que veux-tu surveiller ?

Key takeaways:

  • The cron channel provider is what actually wakes this app - runtime: alone does not arm anything.
  • The agent posts to {{secret.SLACK_WEBHOOK_URL}} itself via http.request, the same tool it uses for the health check.
  • scheduler.schedule is a different mechanism, for scheduling a wake-up from inside a live agent turn (e.g. "check back on this in an hour") - see scheduler. It is not needed here since the cron provider already covers the recurring wake-up.

12 · MCP - multiple external servers​

An agent connected to three MCP servers (Slack, GitHub, Brave Search). Each server declares its OS sandbox permissions.

app.yaml
app:
app_id: mcp-multi
name: MCP Multi-Server Agent

runtime:
mode: conversation
max_turns: 200

agents:
- id: assistant
role: assistant
brain:
provider: openai
model: gpt-4o
backend: openai_compat
config: {api_key: "{{secret.OPENAI_API_KEY}}"}
context:
max_tokens: 128000
strategy: summarize
keep_recent: 20
auto_compact: true
system_prompt: |
You are connected to Slack, GitHub, and Brave Search via MCP.
Use search_tools to discover virtual MCP tools.

tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
mcp:
config:
servers:
slack:
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-slack"]
env:
SLACK_BOT_TOKEN: "{{secret.SLACK_BOT_TOKEN}}"
sandbox:
permissions: [process.exec, net.http]
allowed_hosts: [slack.com, api.slack.com]
github:
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "{{secret.GITHUB_PAT}}"
sandbox:
permissions: [process.exec, net.http]
allowed_hosts: [api.github.com, github.com]
brave:
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-brave-search"]
env:
BRAVE_API_KEY: "{{secret.BRAVE_API_KEY}}"
sandbox:
permissions: [process.exec, net.http]
allowed_hosts: [api.search.brave.com]

capabilities:
default_policy: auto
grant:
- module: mcp
- module: filesystem
# Tighten with allowed tool name patterns in your deploy if needed.

ui:
greeting: "3 serveurs MCP connectés : Slack, GitHub, Brave."

Key takeaways:

  • Virtual MCP tools are exposed as mcp_<server>__<tool> names under the mcp module (not as separate compile-time modules).
  • Per-server sandbox with permissions (+ allowed_hosts when needed) is mandatory for inline server blocks (deny-by-default). Catalog shorthand id: {} can omit it.
  • search_tools finds MCP tools the same way it finds native ones.

13 · MCP with OAuth2 - Google Calendar (SSE transport)​

OAuth2 with PKCE for per-user authorization. SSE transport injects the bearer token via HTTP header.

app.yaml
app:
app_id: mcp-oauth-demo
name: Calendar Assistant

runtime:
mode: conversation

agents:
- id: assistant
role: assistant
brain:
provider: openai
model: gpt-4o
backend: openai_compat
config: {api_key: "{{secret.OPENAI_API_KEY}}"}
system_prompt: |
Tu as accès au Google Calendar et à Slack.
Si un outil requiert une autorisation OAuth, présente le lien.

tools:
modules:
mcp:
config:
servers:
google_calendar:
transport: sse
url: http://localhost:3000/sse
auth:
type: oauth2
provider: google
client_id: "{{secret.GOOGLE_CLIENT_ID}}"
client_secret: "{{secret.GOOGLE_CLIENT_SECRET}}"
scopes:
- https://www.googleapis.com/auth/calendar.readonly
- https://www.googleapis.com/auth/calendar.events
sandbox:
permissions: [net.http]
allowed_hosts: [www.googleapis.com, oauth2.googleapis.com]

slack:
transport: stdio
command: npx
args: ["-y", "@anthropic/mcp-server-slack"]
env:
SLACK_TOKEN: "{{secret.SLACK_BOT_TOKEN}}"
sandbox:
permissions: [process.exec, net.http]
allowed_hosts: [slack.com]

capabilities:
grant:
# Virtual MCP tools are namespaced under the mcp module at
# runtime (e.g. mcp_google_calendar__list_events). Grant the
# mcp module; tighten with tool-name patterns in production.
- module: mcp

ui:
greeting: "Assistant Calendar + Slack prêt."

Key takeaways:

  • OAuth2 + PKCE for SSE / HTTP transports - token sent in Authorization: Bearer ... header.
  • Mixed auth models - Google = OAuth2, Slack = static bot token via the credentials vault.
  • mcp_google_calendar / mcp_slack are runtime namespaces for virtual tools under mcp, not modules declared separately.
  • Auto-refresh - the OAuth refresh loop renews tokens within 10 min of expiry (credentials.md).
  • requires_oauth flow - when the user hasn't yet authorised, the tool result carries an auth_url the agent surfaces.

14 · MCP with OAuth2 - Notion (stdio + env_token_var)​

stdio transport injects the OAuth token as an environment variable and restarts the subprocess when the token refreshes.

app.yaml
app:
app_id: notion-agent
name: Notion Agent

runtime:
mode: conversation
max_turns: 200
timeout: 1200
workdir: "{{env.PWD}}"

agents:
- id: assistant
role: assistant
brain:
provider: openai
model: gpt-4o-mini
backend: openai_compat
config: {api_key: "{{secret.OPENAI_API_KEY}}"}
context:
max_tokens: 128000
strategy: summarize
keep_recent: 20
auto_compact: true
system_prompt: |
Tu es connecté au workspace Notion de l'utilisateur.
Tu peux rechercher, lire et modifier ses pages et bases de données.

tools:
modules:
mcp:
config:
servers:
notion:
transport: stdio
command: mcp-notion
args: []
auth:
type: oauth2
provider: notion
client_id: "{{secret.NOTION_CLIENT_ID}}"
client_secret: "{{secret.NOTION_CLIENT_SECRET}}"
env_token_var: NOTION_API_KEY # token injected as env var
redirect_uri: http://localhost:8913/callback
sandbox:
permissions: [process.exec, net.http]
allowed_hosts: [api.notion.com]
capabilities:
default_policy: approve

ui:
greeting: |
Agent Notion prêt ! Première connexion : tu dois autoriser l'accès
à ton workspace Notion (1 clic).

Key takeaways:

  • env_token_var: NOTION_API_KEY - the field that bridges OAuth2 tokens into stdio MCP servers. The daemon restarts the subprocess when the token refreshes.
  • Notion provider - pre-configured in the credentials catalog (Basic auth for token exchange, JSON body - not form-encoded).
  • Local OAuth callback - in standalone mode, a temporary HTTP server on the configured redirect_uri port handles the callback and opens the browser automatically.
  • User-scoped sharing - the user must select the pages / databases to share at authorisation time. Skipping that step yields an empty workspace; modify later in Notion → Settings → My connections.

Cross-references​