Call Baxter architecture¶
Call Baxter is a generic event-driven callback runtime. Its organizing flow is:
external fact
|
v
Signal / EventEnvelope
|
v
RuleSpec match + policy
|
v
ActionInvocation
|
+--> on_success / on_error follow-ups
|
v
external or local effect
Telegram is one interface to that flow, not the definition of the system. A signal can come from Telegram, HTTP polling, MQTT, CalDAV, Docker state, a webhook, or another plugin. An action can send a Telegram message, write a file, publish to WordPress or a social network, make an HTTP request, or invoke any other registered executor.
Four primitives¶
Signal — EventEnvelope¶
A signal is a normalized fact. It has a source, an event kind, an occurrence time, optional
subject/trace references, and an attrs object carrying source-specific data. Plugins declare the
context fields their event kinds yield, so rules and downstream actions have an explicit data
contract.
Examples include telegram.message.new, http.check.ok, mqtt.message.received, and
caldav.event.upcoming.
Rule — RuleSpec¶
A rule is declarative YAML that decides which actions should run for a matching signal. Rules can match source, kind and attributes; render action arguments from event context; define variables; apply priority/conflict/rate-limit policy; and select sequential or parallel execution.
Rules are configuration. They do not contain the side-effect implementation itself.
Action — ActionInvocation¶
An action names a registered action kind plus structured arguments. The runtime validates the
action's expects contract before execution, records the action and result, and can recursively
run on_success or on_error follow-ups.
The runtime deliberately allows arbitrary plugin actions. That makes the scaffold useful, but it also means replay safety cannot be inferred from an action name. See Arbitrary actions and replay safety.
Schedule — ScheduleSpec¶
A schedule is a persisted periodic trigger for a plugin poller. A due schedule claims a renewable
SQLite execution lease, invokes the poller, and the poller publishes zero or more ordinary
EventEnvelope signals back through the same runtime. Schedules therefore create signals; they do
not bypass the signal → rule → action model.
Runtime boundaries¶
The core runtime owns normalization, validation, rule evaluation, durable audit state, schedule leases, action-chain execution, replay evidence, and daemon/CLI control surfaces. Interface plugins own protocol-specific I/O and translate between external systems and those core contracts.
That separation gives Call Baxter three useful layers:
| Layer | Responsibility | Examples |
|---|---|---|
| Core | signal/rule/action execution and durable policy | runtime, daemon, CLI, registry, replay evidence, schedule leases |
| Interfaces | produce signals and/or execute actions against outside systems | Telegram, WordPress, Bluesky, Mastodon, X, HTTP, MQTT, CalDAV |
| Deployment/examples | assemble concrete plugins, schedules and rule YAML | Docker, Compose, Ansible, Telegram→WordPress, social mirroring |
A complete flow¶
id: report-health-failure
enabled: true
match:
source: http
kind: http.check.failed
actions:
- kind: telegram.send_message
args:
chat_id: 123456
text: "Health check failed: {{ event.attrs.url }}"
on_error:
- kind: file.out
args:
path: "failed-notifications.log"
content: "{{ event.attrs.url }}\n"
mode: append
The important architecture is not “HTTP talks to Telegram.” It is that an HTTP plugin emits a typed signal, a rule selects an action, and the registered Telegram executor performs the effect. The fallback is another action with its own safety properties.
Where to go next¶
- Core runtime architecture — rule policy, reload, leases and failure semantics.
- Plugin system — registration, discovery and context contracts.
- Arbitrary actions and replay safety — side effects, replay evidence and follow-up chains.
- Interface plugin catalog — built-in signal sources and action sinks.
- Signal → action recipes — practical rule compositions.