SOP Fan-In: Overview
A fan-in is an external event source that starts SOP runs. Each source delivers events to the SOP engine through dispatch_sop_event, which matches every event against every loaded SOP’s triggers and starts runs for those that match.
One ZeroClaw instance can bind several fan-ins at once: an MQTT topic, a filesystem path, and an AMQP routing key can all feed the same engine without separate processes. Each source has a dedicated guide below.
How dispatch works
- One matcher path: a single matcher evaluates every trigger type, so matching behaves the same regardless of source.
- Run-start audit: started runs are persisted via
SopAuditLogger. - Headless safety: in non-agent-loop contexts,
process_headless_resultslogsExecuteStepactions as pending instead of silently executing them. - Untrusted input: topic and payload text are capped, normalized, prompt-guard screened, and framed before reaching model context.
Sources
Every SOP trigger type, its fields, and its dispatch status, projected directly from the SopTrigger registry:
| Type | Fields | Notes |
|---|---|---|
mqtt | topic, optional condition | MQTT message arrival. Live: delivered by the MQTT listener. |
webhook | path | Inbound HTTP request. Defined and matched, but no live route feeds it. |
cron | expression | Time-based firing. Live: dispatched by the SOP maintenance tick (daemon / channel-start paths). |
peripheral | board, signal, optional condition | Hardware signal. Defined and matched, but no peripheral listener feeds it. |
filesystem | path, optional condition, optional events | Filesystem change. Live: delivered by the filesystem watcher. |
calendar | calendar_source, optional calendar_ids, optional condition | Calendar event state. Defined and matched, but no poller feeds it live. |
channel | channel, optional alias, optional condition | Inbound message or forge/platform event on a configured channel (telegram, discord, slack, git, …). Live: delivered by the channel orchestrator when the channel’s SOP dispatch is enabled. The Git forge producer sets an event topic of the form <channel>.<alias>:<event_type> and puts event_type in the payload, so an authored condition filters forge events by type without a second trigger shape. |
manual | none | Agent-initiated run via the sop_execute tool. Not an external fan-in. |
amqp | routing_key, optional condition | AMQP delivery. Live: delivered by the AMQP consumer in a SOP dispatch mode. |
Each source has a dedicated guide in the sidebar. Live sources (delivered by a running listener) start runs as events arrive; cron triggers are dispatched by the daemon’s periodic SOP maintenance tick; agent-initiated runs start from inside an agent turn via sop_execute; the remaining defined-but-unwired sources (webhook, peripheral, calendar) validate and match but have no live event source routing into the dispatcher yet.
Security defaults
| Concern | Mechanism |
|---|---|
| MQTT transport | mqtts:// with use_tls = true for TLS transport |
| Filesystem roots | Broad roots (/, /home, /etc, /var, /proc, /sys, /dev, /tmp) rejected at config validation unless allow_broad_roots; include/exclude globs scope events |
| Filesystem symlinks | Symlink event paths are rejected before any metadata, hash, or content read by default; follow_symlinks = true opts in but still requires the canonical target to resolve inside a watched root |
| Untrusted trigger input | Topic and payload text are capped, normalized, prompt-guard screened, and framed before model context |
| Unsafe trigger block | untrusted_input_guard = "block" refuses unsafe untrusted events with BlockedUnsafe; default warn audits and allows |
| Cron validation | Invalid cron expressions fail closed during parsing and cache build |
| Headless dispatch | Headless callers log run progression instead of auto-executing ExecuteStep |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| SOP never starts from a live source | trigger pattern mismatch or a failing condition | Verify the trigger pattern matches the delivered event; check the condition against the payload |
| SOP started but a step did not execute | headless trigger without an active agent loop | Run an agent loop for ExecuteStep, or design the run to pause on approvals |
| Webhook, peripheral, or calendar trigger never fires | event source not wired into the dispatcher | Use a live source (MQTT, Filesystem, AMQP) or start the run with sop_execute |
| Cron trigger never fires | maintenance tick not running (no zeroclaw daemon or zeroclaw channel start; standalone gateway start does not run it), sops_dir unset, or maintenance_interval_secs = 0 | Run zeroclaw daemon (or zeroclaw channel start) with sop.sops_dir set and sop.maintenance_interval_secs non-zero (default 60) |
See also
- Syntax: the full
SOP.tomlandSOP.mdformat - How SOPs run
- Channels: Overview: the transport side of MQTT, filesystem, and AMQP