Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_results logs ExecuteStep actions 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:

TypeFieldsNotes
mqtttopic, optional conditionMQTT message arrival. Live: delivered by the MQTT listener.
webhookpathInbound HTTP request. Live: gateway /sop/* and SOP-first /webhook routes.
cronexpressionTime-based firing. Live: dispatched by the SOP maintenance tick (daemon / channel-start paths).
peripheralboard, signal, optional conditionHardware signal. Defined and matched, but no peripheral listener feeds it.
filesystempath, optional condition, optional eventsFilesystem change. Live: delivered by the filesystem watcher.
calendarcalendar_source, optional calendar_ids, optional conditionCalendar event state. Defined and matched, but no poller feeds it live.
channelchannel, optional alias, optional conditionInbound 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.
manualnoneAgent-initiated run via the sop_execute tool. Not an external fan-in.
amqprouting_key, optional conditionAMQP 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 or authenticated gateway request) 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 (peripheral and calendar) validate and match but have no live event source routing into the dispatcher yet.

Security defaults

ConcernMechanism
Webhook authenticationGateway pairing bearer authentication plus optional gateway.webhook_secret/X-Webhook-Secret; /sop/* and /webhook share the same rate limiter. At least one control must be configured for SOP dispatch, and every configured control must pass. The separate [channels.webhook] alias secrets never authorize these routes
Webhook replay protectionOptional X-Idempotency-Key, namespaced per SOP path as well as separately between /sop/* and /webhook. Keys are reserved before dispatch and mean at-most-once attempt, not proof that a prior run started
MQTT transportmqtts:// with use_tls = true for TLS transport
Filesystem rootsBroad roots (/, /home, /etc, /var, /proc, /sys, /dev, /tmp) rejected at config validation unless allow_broad_roots; include/exclude globs scope events
Filesystem symlinksSymlink 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 inputTopic and payload text are capped, normalized, prompt-guard screened, and framed before model context
Unsafe trigger blockuntrusted_input_guard = "block" refuses unsafe untrusted events with BlockedUnsafe; default warn audits and allows
Cron validationInvalid cron expressions fail closed during parsing and cache build
Headless dispatchHeadless callers log run progression instead of auto-executing ExecuteStep

Troubleshooting

SymptomLikely causeFix
SOP never starts from a live sourcetrigger pattern mismatch or a failing conditionVerify the trigger pattern matches the delivered event; check the condition against the payload
SOP started but a step did not executeheadless trigger without an active agent loopRun an agent loop for ExecuteStep, or design the run to pause on approvals
Webhook trigger never firesexact trigger path mismatch, SOP subsystem unavailable, or authentication rejectedRun zeroclaw daemon with sop.sops_dir configured, match the full request path exactly, and provide the configured bearer/secret headers
Peripheral or calendar trigger never firesevent source not wired into the dispatcherUse a live source (Webhook, MQTT, Filesystem, AMQP) or start the run with sop_execute
Cron trigger never firesmaintenance tick not running (no zeroclaw daemon or zeroclaw channel start; standalone gateway start does not run it), sops_dir unset/empty, or maintenance_interval_secs = 0Run zeroclaw daemon (or zeroclaw channel start) with sop.sops_dir set to a non-empty value (unset by default; the documented value is shared/sops) and sop.maintenance_interval_secs non-zero (default 60)

See also