Documentation Index
Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt
Use this file to discover all available pages before exploring further.
HQ has a built-in plugin system that lets you hook into workspace events and take action. When a task completes, an agent goes over budget, or a knowledge item is created, plugins receive the event and run your code — post to Slack, sync to Linear, trigger a Zapier workflow, log an alert, or anything else.
Two plugin types, same event system:
- 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.
Both types are managed from Settings → Plugins in the UI.
How it works
Workspace event Plugin runner daemon Your plugin
(task completed, → polls event queue, → local: Python handler
agent provisioned, matches subscribed hooks, webhook: HTTP POST
budget exceeded) dispatches to plugins with HMAC signature
Events originate from two sources:
- 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).
The plugin runner daemon subscribes to the event queue via Supabase Realtime (with a polling fallback every 5 seconds) and dispatches matching events to all enabled plugins.
Event model
Every event follows the same envelope:
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "task.completed",
"occurred_at": "2025-01-15T10:30:00+00:00",
"tenant_id": "00000000-0000-0000-0000-000000000000",
"entity_type": "tasks",
"entity_id": "d4f5e6a7-...",
"payload": {
"title": "Draft Q1 report",
"assignee_agent_id": "...",
"completed_at": "2025-01-15T10:30:00+00:00"
}
}
Events are named <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 the source 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 in gateway/plugins/<plugin-id>/ with two files:
manifest.json — declares identity, hooks, config schema, and capabilities:
{
"id": "slack-notify",
"name": "Slack Notifications",
"description": "Post to Slack when tasks complete or budgets exceed.",
"version": "1.0.0",
"source": "local",
"hooks": ["task.completed", "budget.exceeded"],
"config_schema": {
"type": "object",
"properties": {
"webhook_url": {
"type": "string",
"title": "Slack Webhook URL"
}
},
"required": ["webhook_url"]
},
"capabilities": ["secrets.read", "http.outbound"]
}
handler.py — subclasses BasePlugin and implements on_event():
from gateway.plugins.sdk import BasePlugin, PluginEvent, PluginResponse
class Handler(BasePlugin):
def on_event(self, event: PluginEvent) -> PluginResponse | None:
if event.event_type == "task.completed":
title = event.payload.get("title", "Unknown task")
self.ctx.logger.info(f"Task completed: {title}")
# Post to Slack, update Linear, etc.
return PluginResponse(log_message=f"Notified about: {title}")
return None
Plugin context
Every local plugin receives a PluginContext 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 the StateClient. State is scoped by kind and optional entity:
# Global state
self.ctx.state.set("last_sync", "2025-01-15T10:30:00Z")
cursor = self.ctx.state.get("last_sync")
# Scoped to a specific agent
self.ctx.state.set("warned", True, scope_kind="agent", scope_id=agent_id)
# Scoped to a specific task
self.ctx.state.set("linear_issue_id", "LIN-42", scope_kind="task", scope_id=task_id)
State is stored in the 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
HQ POSTs every matching event to your URL as JSON. The request includes:
| 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 the X-HQ-Signature header before processing:
const crypto = require("crypto");
function verify(secret, body, signature) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
Webhook plugin examples
Zapier integration — register your Zapier catch hook URL as a webhook plugin subscribing to task.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 in manifest.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 the hq_plugin_events table with:
- Plugin ID and hook name
- Status (
success, error, timeout, skipped)
- Duration in milliseconds
- Error message (if any)
You can see recent activity for any plugin in Settings → Plugins → click a plugin → Recent activity. Events are retained for 30 days.
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_queue via Realtime for new events
- Subscribes to
hq_plugins for 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 forwards usage.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. Monitors usage.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