Skip to main content
Plugins extend Comis by hooking into lifecycle events, registering custom tools, adding HTTP routes, and defining config schemas. Every plugin implements the PluginPort interface and uses the PluginRegistryApi to register its capabilities. The hook system runs at every lifecycle point — 5 modifying hooks (can change behavior) and 8 void hooks (observe only).

PluginPort Interface

A plugin is any object that satisfies the PluginPort interface. The registry calls register() once during bootstrap, passing a PluginRegistryApi facade scoped to the plugin’s ID.
  • id — Unique identifier for the plugin (e.g., "audit-logger", "webhook-forwarder")
  • name — Human-readable name displayed in diagnostics
  • register() — Called once during plugin loading. Use the registry parameter to register hooks, tools, routes, and config schemas. Must be synchronous.
  • activate() — Optional async initialization (connect to external services, warm caches)
  • deactivate() — Optional async cleanup (close connections, flush buffers)

Registration Methods

The PluginRegistryApi exposes four methods for extending Comis. Each method is called within register() and takes effect immediately.

registerHook

Subscribe to a lifecycle hook. The hookName must be a valid hook name from the HookName type. Options include priority (number, default 0, higher runs first, range -100 to 100).

registerTool

Register a custom tool that agents can invoke. The tool definition includes a name, description, JSON Schema parameters object, and an async execute function. Plugin-provided tools appear alongside built-in tools and skill-provided tools — agents see them identically.

registerHttpRoute

Add a custom HTTP route to the gateway. The route definition includes the HTTP method (GET, POST, PUT, DELETE, PATCH), path (must start with /), and an async handler function.

registerConfigSchema

Register a plugin-specific configuration section with a Zod schema. The section name becomes a key in the config YAML file, and the schema validates values at load time.

Lifecycle Hooks

The hook system is the primary extension mechanism for customizing Comis behavior. Every hook is organized into one of six domains: Agent, Tool, Compaction, Delivery, Session, and Gateway.

Modifying Hooks

Modifying hooks run sequentially in priority order (higher priority first). Each handler can return an object with fields to modify. Results are merged using last-writer-wins for each field — if two hooks both set systemPrompt, the higher-priority hook’s value is used. Return values are validated against Zod schemas before merging. Invalid returns are logged and ignored, protecting the system from malformed hook output.

Void Hooks

Void hooks run in parallel (fire-and-forget). Handlers observe events but cannot change them. Errors in void hooks are caught and logged without affecting the system.

Priority Ordering

Higher priority numbers run first (range -100 to 100, default 0). For example, a hook with priority 10 runs before a hook with priority 0. This applies to modifying hooks where execution order determines which values take precedence.
tool_result_persist handlers MUST be synchronous. This hook runs in a synchronous code path (session transcript append). If you return a Promise, it will cause undefined behavior. All other hooks can be async.

Hook Details

Fires before an agent processes a message. Use this to inject context, modify the system prompt, or add preamble text.Event payload:
  • systemPrompt (string) — The current system prompt
  • messages (unknown[]) — Message history
Context:
  • agentId (string) — The agent processing the message
  • sessionKey (SessionKey) — Current session identifier
  • workspaceDir (string) — Workspace directory path
  • isFirstMessageInSession (boolean) — Whether this is the first user message
Return (optional):
  • systemPrompt (string) — Override the system prompt
  • prependContext (string) — Text prepended to the agent’s context
Fires before a tool executes. Use this to modify parameters, block dangerous tools, or enforce security policies.Event payload:
  • toolName (string) — Name of the tool being called
  • params (Record<string, unknown>) — Tool parameters
Context:
  • agentId (string) — The agent calling the tool
  • sessionKey (SessionKey) — Current session identifier
Return (optional):
  • params (Record<string, unknown>) — Modified parameters
  • block (boolean) — Set to true to prevent the tool from executing
  • blockReason (string) — Reason for blocking (logged and returned to agent)
Fires when a tool result is saved to the session transcript. Use this to redact sensitive data or transform tool output before it is persisted.Event payload:
  • toolName (string) — Name of the tool that executed
  • result (string) — The tool’s result text
Context:
  • agentId (string) — The agent that called the tool
  • sessionKey (SessionKey) — Current session identifier
Return (optional):
  • result (string) — Modified result text to persist
Fires before the context engine’s LLM compaction layer runs (the last-resort summarization step). Use this to cancel compaction for specific sessions or log compaction decisions. See Compaction for how compaction fits into the 10-layer context engine.Event payload:
  • sessionKey (SessionKey) — Session being compacted
  • messageCount (number) — Current message count
  • estimatedTokens (number) — Estimated token count (optional)
Context:
  • agentId (string) — The agent whose session is being compacted
Return (optional):
  • cancel (boolean) — Set to true to prevent compaction
  • cancelReason (string) — Reason for cancellation
Fires after an agent finishes processing a message. Use this for logging, metrics collection, or post-processing.Event payload:
  • durationMs (number) — Total processing time
  • tokenUsage (object) — Token counts: prompt, completion, total (optional)
  • success (boolean) — Whether processing succeeded
  • error (string) — Error message if failed (optional)
Context:
  • agentId (string) — The agent that processed the message
  • sessionKey (SessionKey) — Current session identifier
Fires after a tool completes execution. Use this for audit logging, metrics, or triggering side effects.Event payload:
  • toolName (string) — Name of the tool that executed
  • params (Record<string, unknown>) — Parameters that were passed
  • result (unknown) — The tool’s return value
  • durationMs (number) — Execution time
  • success (boolean) — Whether execution succeeded
Context:
  • agentId (string) — The agent that called the tool
  • sessionKey (SessionKey) — Current session identifier
Fires after the context engine’s LLM compaction layer completes. Use this for logging compaction results or triggering downstream actions like notifying monitoring systems.Event payload:
  • sessionKey (SessionKey) — Session that was compacted
  • removedCount (number) — Messages removed
  • retainedCount (number) — Messages retained
  • durationMs (number) — Compaction duration
Context:
  • agentId (string) — The agent whose session was compacted
Fires before a message is delivered to a channel. Use this to modify the outgoing text, cancel delivery, or attach metadata for downstream processing. See Delivery for how delivery hooks fit into the message pipeline.Event payload:
  • text (string) — The message text about to be delivered
  • channelType (string) — Target channel type (e.g., “telegram”, “discord”)
  • channelId (string) — Target channel identifier
  • options (Record<string, unknown>) — Platform-specific delivery options
  • origin (string) — Where the message originated (e.g., “agent”, “system”)
Context:
  • sessionKey (string) — Current session identifier (optional)
  • agentId (string) — The agent sending the message (optional)
  • traceId (string) — Request trace identifier (optional)
Return (optional):
  • text (string) — Modified message text
  • cancel (boolean) — Set to true to prevent delivery
  • cancelReason (string) — Reason for cancellation (logged)
  • metadata (Record<string, unknown>) — Arbitrary metadata passed to the delivery layer
Fires after a message has been delivered to a channel. Use this for delivery tracking, analytics, or triggering follow-up actions.Event payload:
  • text (string) — The message text that was delivered
  • channelType (string) — Channel type the message was delivered to
  • channelId (string) — Channel identifier
  • result (unknown) — Platform-specific delivery result
  • durationMs (number) — Delivery duration in milliseconds
  • origin (string) — Where the message originated
Context:
  • sessionKey (string) — Current session identifier (optional)
  • agentId (string) — The agent that sent the message (optional)
  • traceId (string) — Request trace identifier (optional)
Fires when a new session begins. Use this for session tracking, initializing per-session state, or logging.Event payload:
  • sessionKey (SessionKey) — The new session identifier
  • isNew (boolean) — Whether this is a brand-new session or a resumed one
Context:
  • agentId (string) — The agent for this session (optional)
Fires when a session ends. Use this for cleanup, final logging, or triggering follow-up actions.Event payload:
  • sessionKey (SessionKey) — The session that ended
  • reason (string) — Why the session ended
  • durationMs (number) — Total session duration (optional)
Context:
  • agentId (string) — The agent for this session (optional)
Fires when the HTTP gateway starts. Use this for initialization that depends on the gateway being ready.Event payload:
  • port (number) — The port the gateway is listening on
  • host (string) — The host address
  • tls (boolean) — Whether TLS is enabled
Fires when the HTTP gateway stops. Use this for cleanup or graceful shutdown coordination.Event payload:
  • reason (string) — Why the gateway is stopping

Complete Plugin Example

This audit logger plugin demonstrates multiple registration types: lifecycle hooks (both observing and modifying), a custom HTTP route, and priority ordering. This is a non-channel plugin — for channel adapter plugins, see Custom Adapters.
This plugin demonstrates hooks (observe + modify), a custom HTTP route, and priority ordering. For channel adapter plugins, see Custom Adapters.

PluginRegistry Lifecycle

The PluginRegistry manages plugin state through the application lifecycle:
  • register(plugin) — Adds the plugin to the registry and calls plugin.register(api) with a scoped facade. Returns err() if the plugin ID is empty or already registered.
  • activateAll() — Calls activate() on all plugins that define it (after all plugins are registered). Collects and reports activation errors.
  • deactivateAll() — Calls deactivate() on all plugins during graceful shutdown. Runs in registration order.
  • getPlugin(id) / getPlugins() — Query registered plugins by ID or get all plugins.
  • unregister(id) — Remove a plugin and all its registered hooks.
The registry is created via createPluginRegistry() and wired in the composition root (bootstrap.ts). Plugins are registered during the bootstrap phase before the daemon starts accepting connections.

Custom Adapters

Channel adapter plugins (ChannelPluginPort)

Event Bus

Events that trigger hooks

Custom Skills

registerTool for agent tools

Architecture

PluginPort in the port system