Skip to main content

Triggers

Triggers define how an app is started. An app can have multiple triggers.

Trigger Types

TypeDescription
cliInteractive terminal input
httpHTTP endpoint
webhookIncoming webhook with authentication
scheduleCron-based scheduling
watchFile system change watcher
eventEvent bus subscription

CLI Trigger

The most common trigger for interactive use:

triggers:
- type: cli
mode: conversation # conversation | one_shot
prompt: "> " # Input prompt string
multiline: true # Allow multi-line input
history: true # Enable input history
greeting: | # Message shown on startup
My App v1.0
Workspace: {{workspace}}
Type your request. Press Ctrl+C to exit.

CLI Modes

Conversation mode — Multi-turn interaction. The agent maintains context across messages:

triggers:
- type: cli
mode: conversation
greeting: "Hello! Ask me anything."
$ digitorn app run my-app.app.yaml
Hello! Ask me anything.
> Read the README
[agent reads and responds]
> Now summarize it
[agent has context from previous turn]
> /clear
[conversation reset]

One-shot mode — Single input, single response, then exit:

triggers:
- type: cli
mode: one_shot
greeting: "Enter your query:"
$ digitorn app run my-app.app.yaml
Enter your query:
> What's in the src directory?
[agent responds and exits]

Or pass input directly:

digitorn app run my-app.app.yaml --input "List files in src/"

HTTP Trigger

Expose the app as an HTTP endpoint:

triggers:
- type: http
path: /review # URL path
method: POST # HTTP method (default: POST)

When the daemon is running, this creates an endpoint at POST /apps/{app_id}/run.

HTTP with Authentication

triggers:
- type: http
path: /analyze
method: POST
auth:
type: bearer # bearer | api_key | hmac | none
header: "Authorization"

HTTP Response Format

triggers:
- type: http
path: /api
response:
format: json # json | streaming_json | sse

Webhook Trigger

Receive events from external services:

triggers:
- type: webhook
path: /github-webhook
auth:
type: hmac
secret: "{{secret.GITHUB_WEBHOOK_SECRET}}"
header: "X-Hub-Signature-256"
events: [push, pull_request] # Filter by event type
transform: |
Review PR #{{trigger.body.pull_request.number}}:
{{trigger.body.pull_request.title}}
filters:
- "{{trigger.body.action == 'opened'}}"

Webhook Authentication Types

TypeDescription
noneNo authentication
bearerBearer token in header
api_keyAPI key in header
hmacHMAC signature verification

Webhook Fields

FieldTypeDescription
pathstringURL path for the webhook
authobjectAuthentication configuration
eventslistEvent types to accept
bodydictExpected body schema
responsedictResponse configuration
transformstringTemplate to transform payload to input
filterslistFilter expressions (must all be true)

Schedule Trigger

Partial implementation: Currently only */N minute/hour patterns are supported (e.g. */5 * * * *, 0 */6 * * *). Full 5-field cron with day-of-week, ranges, and step values is planned.

Run the app on a schedule:

triggers:
- type: schedule
cron: "0 9 * * 1-5" # Every weekday at 9 AM
timezone: "America/New_York"
input: "Generate daily report"

- type: schedule
when: "every 30 minutes" # Natural language schedule
input: "Check system health"

Cron Syntax

 minute (0-59)
hour (0-23)
day of month (1-31)
month (1-12)
day of week (0-6, Sun=0)

* * * * *

Examples:

  • "0 */6 * * *" — Every 6 hours
  • "30 8 * * 1" — Monday at 8:30 AM
  • "0 0 1 * *" — First of every month at midnight

Watch Trigger

React to file system changes:

triggers:
- type: watch
paths:
- "{{workspace}}/src/**/*.py"
- "{{workspace}}/tests/**/*.py"
debounce: "2s" # Wait before triggering (default: 2s)
transform: "File changed: {{trigger.path}}"

Event Trigger

Not yet implemented: Event triggers are designed but not yet wired in the runtime. The event bus infrastructure exists (SessionEventBus) but trigger-level subscription is planned for a future version.

React to events from the LLMOS event bus:

triggers:
- type: event
topic: "digitorn.modules.installed"
transform: "New module installed: {{trigger.event.module_id}}"

Common Fields

These fields work on all trigger types:

FieldTypeDescription
idstringUnique trigger identifier
transformstringTemplate to transform the trigger payload into input
filterslistExpression filters (all must be true to trigger)
inputstringStatic input text (for schedule triggers)

Trigger Context

Inside templates, access trigger data with {{trigger.*}}:

agent:
system_prompt: |
User input: {{trigger.input}}
Trigger type: {{trigger.type}}

flow:
- agent: default
input: "Process: {{trigger.input}}"

Available Trigger Data

PathDescription
trigger.inputThe input text/payload
trigger.typeTrigger type (cli, http, etc.)
trigger.bodyHTTP/webhook request body
trigger.headersHTTP headers
trigger.pathWatch: changed file path
trigger.eventEvent: event payload

Trigger Categories

LLMOS triggers fall into two categories:

Entry-Point Triggers (External -- App)

These triggers define how an app is invoked by a user or external service:

TypeSourceHandled By
cliTerminal user inputCLI REPL
httpHTTP API requestFastAPI server
webhookExternal service (GitHub, Slack)FastAPI server

Entry-point triggers are always handled by their respective servers. They are NOT registered with the daemon's TriggerDaemon.

Background Triggers (Daemon-Managed)

Partial implementation: schedule (basic interval patterns only) and watch (file polling) are implemented. event triggers, croniter full cron, watchfiles/inotify, throttling, conflict resolution, and priority scheduling are planned.

These triggers run continuously and start the app when conditions are met:

TypeSourceDaemon InfrastructureStatus
scheduleCron/interval timerBasic interval parserPartial
watchFile system changesGlob polling (5s interval)Implemented
eventEventBus messagesEventBus subscriptionNot yet

When the daemon is running, background triggers are delegated to background mode via background.py. Planned features:

  • Real cron scheduling via croniter (not approximate sleep loops) — planned
  • Real filesystem watching via watchfiles (inotify, not polling) — planned
  • Priority scheduling with configurable concurrency limits — planned
  • Throttling — max fires per hour, minimum interval between fires — planned
  • Conflict resolution — queue, preempt, or reject duplicate fires — planned
  • Health monitoring — crashed watchers are detected and reported — planned
  • Persistence — triggers survive daemon restarts (SQLite-backed) — planned

Without the daemon, a lightweight TriggerManager provides basic standalone support.

Daemon Mode: Background Triggers

When an app is registered with the daemon and its status is set to "running", background triggers (schedule, watch, event) are automatically started by the daemon. You don't need to run the CLI — the daemon manages trigger lifecycles.

How It Works

  1. Register your app: digitorn app register my-app.app.yaml (or POST /apps/register)
  2. Set status to running: PUT /apps/{id}/status with {"status": "running"}
  3. The AppTriggerBridge converts YAML triggers to daemon TriggerDefinition objects
  4. The TriggerDaemon creates appropriate watchers (CronWatcher, FileSystemWatcher, etc.)
  5. When a watcher fires, the daemon invokes AppRuntime.run() for the app
  6. When status changes to "stopped", triggers are deactivated and deleted
# This app runs a health check every 30 minutes — no CLI needed
app:
name: health-monitor
version: "1.0"

triggers:
- type: schedule
when: "every 30 minutes"
input: "Check system health and report any issues"

- type: event
topic: "digitorn.modules.installed"
transform: "New module installed: {{trigger.event.module_id}}. Verify it works."

agent:
brain:
provider: anthropic
model: claude-sonnet-4-20250514
system_prompt: "You are a system health monitor."
tools:
- module: os_exec
action: run_command

security:
profile: local_worker

After registering and setting to "running", the daemon will:

  • Execute the health check every 30 minutes
  • React whenever a new module is installed via the event bus
  • All executions go through the full security pipeline (PermissionGuard, Scanner, Sanitizer, Audit)

Trigger Lifecycle

App StatusCLI TriggerSchedule/Watch/Event
registeredManual onlyNot started
runningManual onlyAuto-started by daemon
stoppedDisabledAuto-stopped
errorDisabledAuto-stopped

Multiple Triggers

An app can have multiple triggers. Each defines a different way to start the app:

triggers:
# Interactive CLI
- type: cli
mode: conversation
greeting: "Code Reviewer ready."

# HTTP API
- type: http
path: /review
method: POST

# GitHub webhook
- type: webhook
path: /github
auth:
type: hmac
secret: "{{secret.GITHUB_SECRET}}"
events: [pull_request]
filters:
- "{{trigger.body.action == 'opened'}}"
transform: "Review PR: {{trigger.body.pull_request.html_url}}"

# Daily schedule
- type: schedule
cron: "0 9 * * 1-5"
input: "Run daily code quality check"

Transform Templates

The transform field uses the full ExpressionEngine with {{expression}} syntax:

triggers:
- type: webhook
path: /github
transform: "Review PR #{{trigger.body.pull_request.number}}: {{trigger.body.pull_request.title}}"

- type: schedule
cron: "0 9 * * 1-5"
transform: "Daily report for {{trigger.input}}"

- type: event
topic: "digitorn.modules.installed"
transform: "New module: {{trigger.event.module_id}}"

Available template variables in transforms:

VariableDescription
{{input}}Raw input text
{{payload}}Alias for input
{{trigger.input}}The trigger input
{{trigger.type}}Trigger type (cli, http, etc.)
Any metadata key{{source}}, {{event}}, etc.

Filter Expressions

Filters can be either glob patterns or expression conditions:

triggers:
# Glob patterns — matched against input text
- type: cli
filters:
- "fix*"
- "bug*"

# Expression conditions — evaluated as boolean
- type: webhook
path: /github
filters:
- "{{trigger.body.action == 'opened'}}"
- "{{trigger.body.pull_request.draft != true}}"

If any filter matches, the trigger fires. If no filters match, the trigger is skipped.

Compiler Validation

The compiler validates triggers at compile time (step 18):

  • Duplicate HTTP paths — Two triggers with the same method:path are rejected
  • HTTP path format — Paths must start with /
  • Transform brackets — Mismatched {{ / }} are caught
  • Schedule intervals — Very short intervals (<10s) produce warnings
  • Wildcard topics — Subscribing to * or # produces warnings
  • Required fields — Each trigger type has required fields (schedule needs cron/when, watch needs paths, event needs topic, http needs path)
  • Cron format — Cron expressions must have 5 or 6 fields

Architecture: AppTriggerBridge

The AppTriggerBridge connects YAML app triggers to the daemon's TriggerDaemon:

YAML AppDefinition
triggers:
cli / http / webhook -- Handled by CLI REPL / FastAPI (entry points)
schedule / watch / event -- AppTriggerBridge
Converts to daemon TriggerDefinition
TriggerDaemon.register()
CronWatcher (croniter)
IntervalWatcher
FileSystemWatcher (watchfiles)
EventBus subscription
On fire -- AppRuntime.run()

Standalone Fallback

When the daemon is not running (e.g., digitorn app run without a daemon), the lightweight TriggerManager provides basic support:

  • Schedule triggers use asyncio.sleep loops (approximate intervals)
  • Watch triggers use Path.glob() polling (not inotify)
  • Event triggers subscribe directly to the EventBus

The daemon mode is always preferred for production use.