Skip to content

Dataflow and automation

ESP-Claw is an event-driven Agent framework: an Event is the trigger for Agent messages and automation.

An Event is the message carrier in ESP-Claw: id, source, target, type, chat id, sender, text or JSON payload, timestamp, session policy, and more.

claw_event_t (see claw_event.h) packs those fields.

claw_event_router is the event scheduler: it dispatches Events according to rules.

router_rules.json is ESP-Claw’s automation rules file: it defines how Events that enter claw_event_router are handled. The order of rules in router_rules.json is evaluation order. Each rule typically has:

  • id: unique rule id.
  • ack: optional acknowledgement after a match.
  • consume_on_match: whether matching stops further evaluation.
  • match: match conditions.
  • actions: action list, for example:
    ActionTypical use
    call_capRun a capability without the LLM
    run_agentAsynchronously submit to claw_core; Agent response is published back via agent_response event
    run_scriptRun a Lua script
    send_messageSend IM via outbound binding
    emit_eventEmit a new Event back into claw_event_router
    dropDrop the Event

basic_demo ships a starter router_rules.json for basic IM flows. See router_rules.json.

Full syntax is captured in the JSON Schema.

router_rules.json JSON Schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ESP-Claw Router Rules",
  "description": "Schema for ESP-Claw router rules (router_rules.json)",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "description": "Unique identifier for the rule"
      },
      "description": {
        "type": "string",
        "description": "Optional description of the rule"
      },
      "enabled": {
        "type": "boolean",
        "default": true,
        "description": "Whether the rule is active"
      },
      "consume_on_match": {
        "type": "boolean",
        "default": true,
        "description": "If true, the event is marked as consumed and no further rules are processed for the same event"
      },
      "ack": {
        "type": "string",
        "description": "Optional acknowledgment message template"
      },
      "vars": {
        "type": "object",
        "description": "Rule-specific local variables",
        "additionalProperties": true
      },
      "match": {
        "type": "object",
        "required": ["event_type"],
        "properties": {
          "event_type": {
            "type": "string",
            "description": "The type of event to match (e.g., 'message', 'trigger')"
          },
          "event_key": {
            "type": "string",
            "description": "Specific key to match within the event"
          },
          "source_cap": {
            "type": "string",
            "description": "Capability that emitted the event"
          },
          "channel": {
            "type": "string",
            "description": "Alias for source_channel (kept for compatibility; prefer source_channel)"
          },
          "source_channel": {
            "type": "string",
            "description": "Channel from which the event originated"
          },
          "chat_id": {
            "type": "string",
            "description": "Chat or session identifier"
          },
          "content_type": {
            "type": "string",
            "description": "Type of content in the event"
          },
          "text": {
            "type": "string",
            "description": "Exact text or template to match"
          }
        },
        "additionalProperties": false
      },
      "actions": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "required": ["type"],
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "call_cap",
                "run_agent",
                "run_script",
                "send_message",
                "emit_event",
                "drop"
              ],
              "description": "The action to perform"
            },
            "caller": {
              "type": "string",
              "enum": ["system", "agent", "console"],
              "default": "system",
              "description": "The security context for execution"
            },
            "cap": {
              "type": "string",
              "description": "Specific capability name (required for call_cap)"
            },
            "capture_output": {
              "type": "boolean",
              "default": true,
              "description": "Whether to capture the output for subsequent actions"
            },
            "fail_open": {
              "type": "boolean",
              "default": false,
              "description": "If true, rule processing continues even if this action fails"
            },
            "input": {
              "type": "object",
              "description": "Action configuration. Supports template rendering via {{...}}",
              "additionalProperties": true
            }
          },
          "allOf": [
            {
              "if": {
                "properties": { "type": { "const": "call_cap" } }
              },
              "then": { "required": ["cap", "input"] }
            },
            {
              "if": { "properties": { "type": { "const": "send_message" } } },
              "then": {
                "properties": {
                  "input": {
                    "type": "object",
                    "properties": {
                      "channel": { "type": "string" },
                      "chat_id": { "type": "string" },
                      "message": { "type": "string" }
                    }
                  }
                }
              }
            },
            {
              "if": { "properties": { "type": { "const": "run_agent" } } },
              "then": {
                "properties": {
                  "input": {
                    "type": "object",
                    "properties": {
                      "text": { "type": "string" },
                      "target_channel": { "type": "string" },
                      "target_chat_id": { "type": "string" },
                      "session_policy": {
                        "type": "string",
                        "enum": [
                          "chat",
                          "trigger",
                          "global",
                          "ephemeral",
                          "nosave"
                        ]
                      }
                    }
                  }
                }
              }
            },
            {
              "if": { "properties": { "type": { "const": "run_script" } } },
              "then": {
                "required": ["input"],
                "properties": {
                  "input": {
                    "type": "object",
                    "properties": {
                      "path": { "type": "string" },
                      "async": { "type": "boolean" }
                    },
                    "required": ["path"]
                  }
                }
              }
            },
            {
              "if": { "properties": { "type": { "const": "emit_event" } } },
              "then": {
                "properties": {
                  "input": {
                    "type": "object",
                    "properties": {
                      "source_cap": { "type": "string" },
                      "event_type": { "type": "string" },
                      "source_channel": { "type": "string" },
                      "chat_id": { "type": "string" },
                      "message_id": { "type": "string" },
                      "content_type": { "type": "string" },
                      "text": { "type": "string" },
                      "payload_json": { "type": "string" },
                      "session_policy": {
                        "type": "string",
                        "enum": [
                          "chat",
                          "trigger",
                          "global",
                          "ephemeral",
                          "nosave"
                        ]
                      }
                    }
                  }
                }
              }
            }
          ]
        }
      }
    },
    "required": ["id", "match", "actions"],
    "additionalProperties": false
  }
}

After an Event enters the router, it is dequeued and walked against the rule set. The diagram shows the full logic:

Diagram

agent_response Event and Outbound Messages

Section titled “agent_response Event and Outbound Messages”

When the run_agent action is triggered, the Agent’s response is not returned synchronously. Instead, it is published back to the Event Router as an asynchronous event of type agent_response.

To send the Agent’s response to an IM channel, you need to configure a routing rule for agent_response, for example:

{
  "id": "agent_response_send_message",
  "consume_on_match": true,
  "match": {
    "source_cap": "claw_core",
    "event_type": "agent_response",
    "content_type": "text"
  },
  "actions": [
    {
      "type": "send_message",
      "input": {
        "channel": "{{event.source_channel}}",
        "chat_id": "{{event.chat_id}}",
        "message": "{{event.text}}"
      }
    }
  ]
}

The channel / chat_id fields usually come from target_channel / target_chat_id passed when triggering run_agent.

Agent-request cancellation and queue cleanup

Section titled “Agent-request cancellation and queue cleanup”

After a run_agent action is submitted to claw_core, a request_id is allocated and recorded for later cancellation or tracking.

Accordingly, the framework offers two cancellation layers (C APIs):

  • claw_core_cancel_request(request_id): cancel the currently executing (in-flight) Agent request.
  • claw_event_router_cancel_event(event_id): mark and skip one pending Event in the queue.
  • claw_event_router_purge_queue(event_type_filter, source_cap_filter, &out_cancelled): batch-mark and skip pending queue Events (optional filters by event type and source cap).

agent_stage Event and Execution Progress Notifications

Section titled “agent_stage Event and Execution Progress Notifications”

Besides the final agent_response, claw_core can also publish agent_stage events during multi-round tool calling, which can be used to push staged tool-call progress information.

The agent_stage event structure is:

  • source_cap: claw_core
  • event_type: agent_stage
  • event.text: staged information during tool calls

To auto-publish agent_stage events, adjust this menuconfig option: (Top)Component configESP-Claw CoreAgent stage notification verbosity

  • With Verbose, claw_core publishes agent_stage events to Event Router on turns that contain tool calls.
  • With Simple, agent_stage events are not published at the routing layer.

agent_stage events can be routed to channels such as IM to provide real-time progress updates. For example, basic_demo includes rule agent_stage_im_notify in router_rules.json. When Verbose is enabled and this rule matches, it sends {{event.text}} from agent_stage back to the source IM conversation.

cap_scheduler provides time-based event triggering, supporting three types: once, interval (repeating), and cron (calendar expression, 5 segments, no seconds).

The scheduler is only responsible for publishing Events on time; the specific behavior after triggering is determined by the Event Router’s rules. Therefore, adding a scheduled task typically requires two steps:

  1. Add a schedule item (via schedules.json or scheduler_add).
  2. Add a matching routing rule (via router_rules.json or add_router_rule).

Typical matching: the schedule item sets event_type: "schedule" + event_key, and the routing rule hits it using match.event_type + match.event_key.

scheduler --list                     # list all schedule items
scheduler --reload                   # reload from disk
scheduler --add --json '<json>'      # add a new schedule item
scheduler --enable <id>              # enable
scheduler --disable <id>             # disable
scheduler --pause <id>               # pause
scheduler --resume <id>              # resume
scheduler --trigger <id>             # trigger once immediately

cap_scheduler registers tools like scheduler_list, scheduler_get, scheduler_add, scheduler_enable, scheduler_disable, scheduler_pause, scheduler_resume, scheduler_trigger_now, and scheduler_reload. These tools are available after activating the cap_scheduler Skill.

cap_router_mgr registers tools like list_router_rules, get_router_rule, add_router_rule, update_router_rule, delete_router_rule, and reload_router_rules.

Event Router provides two sets of Console commands. The auto command comes from basic_demo, and the event_router command comes from cap_router_mgr. Both are functionally equivalent and operate on the same set of rules.

# auto command (provided by basic_demo)
auto reload                  # reload router_rules.json
auto rules                   # dump current rules JSON
auto rule <id>               # show one rule JSON
auto add_rule '<json>'       # append a rule
auto update_rule '<json>'    # replace rule by id
auto delete_rule <id>        # delete a rule
auto emit_message ...        # details below
auto emit_trigger ...        # details below
auto last                    # last match stats, details below

# event_router command (provided by cap_router_mgr)
event_router --rules                     # dump current rules JSON
event_router --rule <id>                 # show one rule JSON
event_router --add-rule-json '<json>'    # append a rule
event_router --update-rule-json '<json>' # replace rule by id
event_router --delete-rule <id>          # delete a rule
event_router --reload                    # reload router_rules.json
event_router --emit-message ...          # publish message Event
event_router --emit-trigger ...          # publish trigger Event
event_router --last                      # last match stats
  • auto emit_message synthesizes an IM-style message Event:

    auto emit_message <source_cap> <channel> <chat_id> <text...>
    # auto emit_message qq_gateway    qq      123456   hello world
    ArgMeaningExample
    source_capSource capability nameqq_gateway
    channelSource channelqq
    chat_idChat id (group / topic, etc.)123456
    text...Message text (spaces allowed; words concatenate)hello world
  • auto emit_trigger synthesizes a trigger Event:

    auto emit_trigger <source_cap> <event_type> <event_key> '<payload_json>'
    # auto emit_trigger  tester      trigger     smoke_test '{"ok":true}'
    ArgMeaningExample
    source_capSource capability nametester
    event_typeEvent type for match.event_typetrigger
    event_keyEvent key for match.event_keysmoke_test
    payload_jsonJSON object payload'{"ok":true}'
  • auto last prints the last routing result, e.g.:

    matched=true matched_rules=1 action_count=2 failed_actions=0 route=1 handled_at_ms=...
    first_rule_id=handle-message
    ack=matched:handle-message
    last_error=ESP_OK
    FieldMeaning
    matchedAny rule matched
    matched_rulesNumber of matched rules
    action_countActions executed
    failed_actionsActions that failed
    route0 = PASS (not consumed), 1 = CONSUMED
    first_rule_idid of first matched rule
    ackRendered ack from the matched rule
    last_errorLast action error code