Skip to main content

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:
  1. 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.
  2. 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:
SourceDescriptionCan uninstall?
builtinShips with HQ (e.g. usage-alerts). Always available.No
localPython module on the gateway filesystem. For self-hosters and power users.Yes
webhookRemote HTTP endpoint. For SaaS integrations and no-code tools.Yes
marketplaceInstalled 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:
AttributeTypeDescription
configdictOperator-supplied config from Settings → Plugins
stateStateClientScoped key-value store (persists across events)
secretsSecretsClientRead-only access to gateway secrets
supabaseSupabaseClientRead-only Supabase queries against HQ tables
loggerLoggerPlugin-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:
  1. Go to Settings → Plugins → Add plugin
  2. Enter a name, your endpoint URL, and optionally a signing secret
  3. Select which events to receive
  4. Save
HQ POSTs every matching event to your URL as JSON. The request includes:
HeaderDescription
Content-Typeapplication/json
X-HQ-EventEvent type (e.g. task.completed)
X-HQ-Plugin-IdYour plugin’s identifier
X-HQ-DeliveryUnique delivery ID for deduplication
X-HQ-Signaturesha256=<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:
CapabilityGrants
secrets.readAccess to ctx.secrets.resolve()
http.outboundPermission to make external HTTP requests
supabase.readAccess to ctx.supabase.query() for reading HQ tables
state.readAccess to ctx.state.get()
state.writeAccess 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

TablePurpose
hq_pluginsPlugin registry — one row per installed plugin. Realtime-enabled for live UI updates.
hq_plugin_eventsExecution log — every dispatch recorded for observability. 30-day retention via pg_cron.
hq_plugin_stateScoped key-value store — plugins persist state across events.
hq_plugin_event_queueInternal 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:
  1. Connects to Supabase on startup
  2. Loads enabled plugins from hq_plugins
  3. Subscribes to hq_plugin_event_queue via Realtime for new events
  4. Subscribes to hq_plugins for config changes (enable/disable/update → hot reload)
  5. Dispatches matching events to plugins (in-process for local, HTTP POST for webhook)
  6. Records results in hq_plugin_events
  7. 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:
FieldTypeDefaultDescription
warn_at_percentnumber80Percentage of budget at which to log a warning

Next reads