- Local plugins — Python modules on the gateway. Full access to the plugin SDK (state, secrets, Supabase queries). Best for logic that needs to read HQ data or maintain state.
- Webhook plugins — Remote HTTP endpoints. Zero gateway code. HQ POSTs events with HMAC signatures. Best for connecting to external services, no-code tools, or teams without gateway access.
How it works
- SQL triggers on core tables (tasks, agents, knowledge, inbox, comments, secrets). These write to an internal event queue (
hq_plugin_event_queue) that the plugin runner daemon polls. - Python daemons that emit events directly (command runner after provisioning, file processor after extraction, embedder after indexing, bootstrap plugin after usage recording).
Event model
Every event follows the same envelope:<entity>.<action>. HQ ships with 16 built-in events covering tasks, agents, knowledge, inbox, routines, comments, secrets, usage, and budgets. See Plugin events reference for the full list.
Plugin sources
Plugins come from four sources, tracked by thesource field:
| Source | Description | Can uninstall? |
|---|---|---|
builtin | Ships with HQ (e.g. usage-alerts). Always available. | No |
local | Python module on the gateway filesystem. For self-hosters and power users. | Yes |
webhook | Remote HTTP endpoint. For SaaS integrations and no-code tools. | Yes |
marketplace | Installed from the HQ marketplace (future). | Yes |
Local plugins
A local plugin is a Python module ingateway/plugins/<plugin-id>/ with two files:
manifest.json — declares identity, hooks, config schema, and capabilities:
handler.py — subclasses BasePlugin and implements on_event():
Plugin context
Every local plugin receives aPluginContext with:
| Attribute | Type | Description |
|---|---|---|
config | dict | Operator-supplied config from Settings → Plugins |
state | StateClient | Scoped key-value store (persists across events) |
secrets | SecretsClient | Read-only access to gateway secrets |
supabase | SupabaseClient | Read-only Supabase queries against HQ tables |
logger | Logger | Plugin-namespaced structured logger |
State management
Plugins can persist state across events using theStateClient. State is scoped by kind and optional entity:
hq_plugin_state table, keyed by (plugin_id, scope_kind, scope_id, state_key).
Webhook plugins
Webhook plugins have no gateway-side code. You register them entirely from the UI:- Go to Settings → Plugins → Add plugin
- Enter a name, your endpoint URL, and optionally a signing secret
- Select which events to receive
- Save
| Header | Description |
|---|---|
Content-Type | application/json |
X-HQ-Event | Event type (e.g. task.completed) |
X-HQ-Plugin-Id | Your plugin’s identifier |
X-HQ-Delivery | Unique delivery ID for deduplication |
X-HQ-Signature | sha256=<hex> HMAC-SHA256 signature (if secret configured) |
Signature verification
If you set a signing secret, verify theX-HQ-Signature header before processing:
Webhook plugin examples
Zapier integration — register your Zapier catch hook URL as a webhook plugin subscribing totask.completed. Every completed task triggers your Zap.
Discord notifications — point a webhook plugin at a Discord webhook URL. HQ sends the event payload; Discord renders it as a message.
Custom relay — deploy a small service (Cloudflare Worker, AWS Lambda, Vercel Edge Function) that transforms HQ events into provider-specific formats (Slack Block Kit, Linear GraphQL mutations, PagerDuty alerts).
Capabilities
Plugins declare what they need inmanifest.json → capabilities[]. This documents intent and will be enforced when sandboxing is added:
| Capability | Grants |
|---|---|
secrets.read | Access to ctx.secrets.resolve() |
http.outbound | Permission to make external HTTP requests |
supabase.read | Access to ctx.supabase.query() for reading HQ tables |
state.read | Access to ctx.state.get() |
state.write | Access to ctx.state.set() and ctx.state.delete() |
Execution and observability
Every plugin dispatch is logged in thehq_plugin_events table with:
- Plugin ID and hook name
- Status (
success,error,timeout,skipped) - Duration in milliseconds
- Error message (if any)
Architecture
Database tables
| Table | Purpose |
|---|---|
hq_plugins | Plugin registry — one row per installed plugin. Realtime-enabled for live UI updates. |
hq_plugin_events | Execution log — every dispatch recorded for observability. 30-day retention via pg_cron. |
hq_plugin_state | Scoped key-value store — plugins persist state across events. |
hq_plugin_event_queue | Internal event queue — SQL triggers write here; plugin runner reads and dispatches. 1-hour retention. |
Gateway daemon
The plugin runner (gateway/daemons/plugin_runner.py) follows the same patterns as the command runner and inbox dispatcher:
- Connects to Supabase on startup
- Loads enabled plugins from
hq_plugins - Subscribes to
hq_plugin_event_queuevia Realtime for new events - Subscribes to
hq_pluginsfor config changes (enable/disable/update → hot reload) - Dispatches matching events to plugins (in-process for local, HTTP POST for webhook)
- Records results in
hq_plugin_events - Falls back to polling every 5 seconds if Realtime disconnects
Relationship to other systems
hq-bootstrap (the OpenClaw plugin) hooks into the LLM call lifecycle — model routing, usage logging, budget enforcement. It’s adapter-specific. The HQ plugin system hooks into the business logic lifecycle — tasks, agents, knowledge, inbox. They coexist: hq-bootstrap forwardsusage.recorded and budget.exceeded events to the plugin event queue.
Source connectors (gateway/connectors/) sync external data into HQ’s knowledge system. They’re purpose-built for data ingestion. Plugins react to HQ events and take actions. If you want to sync GitHub Wiki → HQ knowledge, use a connector. If you want to create Linear issues when HQ tasks are created, use a plugin.
Built-in plugins
Usage Alerts
Ships with HQ. Monitorsusage.recorded events and logs warnings when agents approach their budget threshold (default: 80%). Uses the plugin state system to avoid duplicate warnings.
Configuration:
| Field | Type | Default | Description |
|---|---|---|---|
warn_at_percent | number | 80 | Percentage of budget at which to log a warning |
Next reads
- Plugin events reference — full list of all 16 events with payloads
- Creating plugins guide — step-by-step walkthrough for your first plugin
- Architecture — how plugins fit into the broader system
- Contributing — how to submit a plugin PR

