Plugin system¶
Explicit module loading¶
The default deployment model is an explicit plugin list:
Each plugin module exports:
The daemon imports each configured module with importlib.import_module(...) and calls register(registry, config) when the function accepts a second argument.
Both simple and configured forms are supported:
plugins:
- call_baxter.plugins.system
- module: call_baxter.plugins.telegram
config:
database_url: ./tg-state.db
media_storage_mode: file
Optional entry-point discovery¶
The core loader can also opt into Python entry-point discovery. The default group is
call_baxter.plugins, and callers can supply other groups. A discovered entry point must load to a
callable and is invoked as register(registry).
This lets independently packaged interfaces advertise plugins without modifying the Call Baxter
distribution. For example, cb-social-plugin exposes bluesky, mastodon, and x entry points.
Provider-specific configuration still fits the explicit module form because those register()
functions also accept (registry, config).
Reload behavior¶
POST /v1/reload and cb reload currently do three things:
- rebuild the registry
- re-import configured plugins and call
register(...) - re-load YAML rules and re-sync schedules
This is a safe start version because it does not attempt hot code reloading of arbitrary Python module changes.
For code changes, restarting the daemon remains the clean path.
For YAML changes, cb reload is usually enough.
Optional future extensions¶
- file watching for
rules.d/*.yml - separate plugin-owned projection stores for browse-heavy sources like Telegram
Decorator-based plugin DSL (PluginBuilder)¶
New plugins can use PluginBuilder from call_baxter.dsl to reduce boilerplate.
from call_baxter.dsl import PluginBuilder
from call_baxter.registry import Registry
plugin = PluginBuilder("myplugin")
@plugin.event("myplugin.thing", description="A thing happened",
yields={"ref": "string", "label?": "string"})
def _(): pass
@plugin.poller("myplugin.check", description="Poll for things")
def poll(config: dict) -> list:
...
@plugin.action("myplugin.do", description="Do something",
expects={"target": "string"})
def do_action(plugin_config: dict, action, runtime):
# plugin_config injected automatically when first param is named 'plugin_config'
...
def register(registry: Registry, config=None):
plugin.register_all(registry, config)
Field shorthand¶
yields and expects accept dicts of "field_name": "type".
Keys ending in ? become optional fields; all others are required.
Supported types: string, integer, number, boolean, object, array, datetime.
Action safety contract¶
expects and yields are data-shape contracts. They do not claim that an executor is safe to
repeat. Plugin actions should also provide the narrowest accurate ActionExecutionContract through
registry.add_action_type(...) or the DSL action declaration. Leave behavior unknown when the
runtime cannot prove more.
See Arbitrary actions and replay safety for idempotency boundaries,
authorization provenance, replay-eligibility evidence, and recursive on_success / on_error
failure semantics.
Available plugins¶
| Module | Poller(s) | Action(s) | Events |
|---|---|---|---|
call_baxter.plugins.system |
system.heartbeat |
log.write |
system.heartbeat |
call_baxter.plugins.telegram |
telegram.updates |
telegram.send_message, telegram.send_photo, telegram.send_document, telegram.edit_message_text, telegram.set_message_reaction, telegram.delete_message, telegram.pin_chat_message, telegram.unpin_chat_message, telegram.answer_callback_query, telegram.request_decision, telegram.request_confirmation, telegram.send_progress, telegram.update_progress, telegram.persist_update |
telegram.message.new, telegram.message.edited, telegram.channel_post.new, telegram.channel_post.edited, telegram.business_message.new, telegram.business_message.edited, telegram.callback_query.new, telegram.media.present, telegram.message.reaction |
call_baxter.plugins.http_poll |
http.check |
http.request |
http.check.ok, http.check.failed |
call_baxter.plugins.docker_watch |
docker.containers |
docker.restart |
docker.container.up, docker.container.down |
call_baxter.plugins.mqtt |
mqtt.subscribe |
mqtt.publish |
mqtt.message.received |
call_baxter.plugins.caldav |
caldav.agenda |
caldav.add_event, caldav.add_todo, caldav.complete_todo |
caldav.event.upcoming, caldav.event.starting_soon |
call_baxter.plugins.zrok |
— | zrok.run |
— |
call_baxter.plugins.sonic_byte |
— | sonic_byte.inspect, sonic_byte.render, sonic_byte.play |
— |
call_baxter.plugins.file_out |
— | file.out |
— |
See Sonic Byte plugin for secure command configuration, sound-design options, and complete rule examples.