router, executor, aggregator, and sensor — that you combine through manifest-defined graphs. Each primitive covers a distinct role in a workflow: fanning work out, running isolated code, collecting results, or waiting for external signals. On top of these primitives, a set of behavioral templates lets you tune how each agent processes messages without writing custom runtime code.
The four built-in agent types
You select an agent’s primitive by settingagent_type on the node in your manifest.json. Every node must declare exactly one agent_type.
router
Arouter receives an incoming message and re-emits it to downstream nodes. It does not execute external code and does not hold multi-message state. By default it forwards the message using the incoming type, but you can override the outgoing message type with config.emit_type.
Use a router when you need to:
- Fan a single input out to multiple parallel workers
- Rename or retype a message as it moves through the graph
- Act as the entry point (
root_coordinator) that seeds work across other agents
executor
Anexecutor requests an execution lease, runs a command in an OpenShell sandbox, and then emits the result. It is the only built-in primitive that leaves the BEAM process boundary — every other primitive stays in the control plane.
Before running, an executor node acquires a slot from its assigned executor pool. If no slot is available, the request queues until capacity is freed. This keeps sandbox concurrency bounded regardless of how many logical workers are waiting.
Use an executor when you need to:
- Run a Python script or shell command inside an isolated sandbox
- Perform CPU- or I/O-heavy work that should not block BEAM
- Process uploaded payloads from the
payloads/directory
The
source path inside uploads is resolved relative to the payloads/ directory in your job bundle. See Job bundles for the full directory structure.aggregator
Anaggregator accumulates messages from upstream agents and combines them into a single result. It tracks which agent IDs it has already seen to deduplicate deliveries, and it signals job completion once a configurable threshold is met.
You control completion with two config keys:
complete_on_message— complete the job as soon as the first message arrivescomplete_after— complete the job after a specific number of messages have been collected
aggregator when you need to:
- Collect results from a fan-out of parallel executor workers
- Wait for all branches of a graph to report back before completing
- Deduplicate result messages from retried agents
sensor
Asensor observes incoming messages and re-emits them downstream, tracking an observation count. It is designed for deferred or trigger-style patterns — for example, waiting for an external signal before allowing the rest of the graph to proceed.
By default, a sensor emits a sensor_ready message for each observation. You can override this with config.output_message_type. Set complete_after to complete the job once a target number of observations has been recorded.
Use a sensor when you need to:
- Gate downstream work on an external event or signal
- Forward streaming telemetry data through the graph
- Observe and relay messages without modifying them
Agent templates
Every node also carries atype field that selects a behavioral template. Templates provide reusable message-handling patterns that work on top of the primitive. If you omit type, it defaults to "generic".
generic
generic
The default template. It provides no additional state or behavior beyond what the underlying primitive defines. Use
generic when none of the specialized templates fit or when you are building custom agent behavior.map
map
Tracks a
processed counter and emits a map_transformed event for each message handled. Use map on router or executor nodes that fan out one input per message — for example, dispatching one work item per incoming record.reduce
reduce
Accumulates messages into a list and tracks
complete_on_message and complete_after thresholds. When the completion condition is met, it triggers a complete_job action with the collected results. Use reduce on aggregator nodes that gather results from parallel branches.stream
stream
Tracks
chunks_received and items_seen across a stream of messages. Emits a stream_chunk_processed event per chunk. Use stream when an agent is processing a continuous or chunked data feed.batch
batch
Buffers incoming items until a
batch_size threshold is reached, then flushes the batch and resets the buffer. Emits a batch_buffered event on each push. Use batch when you want to group messages before forwarding them to an executor or downstream node.accumulator
accumulator
Shares the same collect-and-complete behavior as
reduce. Use accumulator as a semantic alias when you want to make it explicit that an agent is accumulating state across messages rather than reducing a fan-out.Combining agent_type and type
Theagent_type field picks the primitive, and the type field picks the template. The two work together: not every template is valid for every primitive — the manifest validator will reject incompatible combinations at load time.