Plugins / Creating

Creating a Plugin

This guide walks through creating a plugin from scratch — from scaffolding the directory to registering tools, mounting routes, and subscribing to events.

Scaffold

bash
# 1. Create the plugin directory
mkdir -p plugins/core/my-plugin

# 2. Create the entry point
touch plugins/core/my-plugin/index.ts

# 3. Restart the gateway (or let hot-reload pick it up in dev)

Minimal plugin

The simplest possible plugin — just a name, version, and init function.

typescript
import type { GatewayPlugin } from '../../types.js'

const plugin: GatewayPlugin = {
  name: 'hello-world',
  version: '0.1.0',

  init(ctx) {
    ctx.logger.info('Hello world plugin loaded')
  }
}

export default plugin

Plugin that registers a tool

Tools registered by plugins are available to all agents (subject to the agent's tools.allow / tools.deny configuration).

typescript
import type { GatewayPlugin } from '../../types.js'

const plugin: GatewayPlugin = {
  name: 'weather',
  version: '1.0.0',

  init(ctx) {
    ctx.registerTool({
      name: 'get_weather',
      description: 'Get current weather for a city',
      parameters: {
        type: 'object',
        properties: {
          city: { type: 'string', description: 'City name' }
        },
        required: ['city']
      },
      execute: async (args) => {
        const response = await fetch(
          `https://api.weather.example/v1/current?city=${args.city}`
        )
        return await response.json()
      }
    })
  }
}

export default plugin
Tool naming. Use snake_case for tool names to match the convention used by built-in tools. The agent sees the tool name in its schema, so keep it descriptive and concise.

Plugin with custom routes and events

This example tracks agent completions via the event bus and exposes an analytics endpoint.

typescript
import type { GatewayPlugin } from '../../types.js'

const plugin: GatewayPlugin = {
  name: 'analytics',
  version: '1.0.0',

  init(ctx) {
    const bus = ctx.getEventBus()
    const metrics = { totalCalls: 0, byAgent: {} as Record<string, number> }

    bus.on('agent.completed', (event) => {
      metrics.totalCalls++
      metrics.byAgent[event.agentId] = (metrics.byAgent[event.agentId] || 0) + 1
    })

    ctx.app.get('/api/analytics/summary', (req, res) => {
      res.json(metrics)
    })
  }
}

export default plugin

Error handling

text
// Plugin errors during init() are caught and logged
// The gateway continues loading other plugins
// Event: "plugin.error" is emitted with the error details

// Best practices:
// - Validate config in init() and throw early with clear messages
// - Clean up resources in stop() (timers, connections, file handles)
// - Use ctx.logger instead of console.log for consistent formatting
// - Never throw in start() or stop() — wrap in try/catch

Plugin checklist

RequirementDetails
Directoryplugins/core/<name>/index.ts
ExportDefault export implementing GatewayPlugin
nameUnique string identifier
versionSemver string
init(ctx)Required — receives PluginContext
start()Optional — called after HTTP server is ready
stop()Optional — called on graceful shutdown (cleanup)