Skip to content

Plugins

Plugins extend the Lota SDK runtime with services, tools, database schemas, and environment variable declarations. They are typed via the LotaPlugin interface and registered through the pluginRuntime config field.

Plugin Interface

Every plugin implements the LotaPlugin interface, exported from @lota-sdk/core:

ts
import type { LotaPlugin, LotaPluginContributions } from '@lota-sdk/core'

interface LotaPluginContributions {
  envKeys: readonly string[]
  schemaFiles: readonly (string | URL)[]
}

interface LotaPlugin<TServices = unknown, TTools = unknown> {
  services: TServices
  tools?: TTools
  contributions: LotaPluginContributions
}
FieldTypeDescription
servicesTServicesPlugin-owned service instances. Can include any methods the plugin needs to expose, including an optional connectDatabase() method for DB initialization.
toolsTToolsPlugin-owned tool factories that agents can use during execution. Optional.
contributions.envKeysreadonly string[]Environment variable names the plugin requires. Surfaced via runtime.contributions.envKeys so the host can validate its environment at startup.
contributions.schemaFilesreadonly (string | URL)[]SurrealDB .surql schema files applied at startup alongside built-in SDK schemas.

Registering Plugins

Plugins are registered via the pluginRuntime field in createLotaRuntime(). Each key becomes the plugin's name on runtime.plugins:

ts
import { createLotaRuntime } from '@lota-sdk/core'

const runtime = await createLotaRuntime({
  // ... other config
  pluginRuntime: {
    github: githubPlugin,
    linear: linearPlugin,
    slack: slackPlugin,
  },
})

Plugin Lifecycle

Plugins participate in the runtime lifecycle at several points:

1. Schema Application

Plugin schema files are collected from all registered plugins and merged into the runtime's contributed schema list. When a host applies schemas (via runtime.connect() or a separate schema step), plugin schemas are included:

ts
// Plugin schema files are available at:
runtime.contributions.schemaFiles
// Contains all schema files from all registered plugins

2. Environment Key Surfacing

All plugin envKeys are merged with the SDK's own required keys and deduplicated:

ts
// All required env keys (SDK + plugins) are available at:
runtime.contributions.envKeys
// e.g. ['AI_GATEWAY_URL', 'REDIS_URL', ..., 'GITHUB_TOKEN', 'LINEAR_API_KEY']

This allows the host to validate that every required environment variable is present before starting.

3. Database Connection

If a plugin exposes a connectDatabase() method on its services, call runtime.connectPluginDatabases() during host startup. This iterates over all registered plugins and invokes services.connectDatabase() on each one that provides it:

ts
await runtime.connect()
await runtime.connectPluginDatabases()

4. Tool Availability

Plugin tools are available on the plugin instance and can be included in agent tool building via the host's buildAgentTools configuration.

Building a Plugin

Here is a step-by-step example of building a Slack notification plugin.

Step 1: Define the Plugin Types

ts
// slack-plugin.ts
import type { LotaPlugin } from '@lota-sdk/core'

interface SlackServices {
  sendMessage(channel: string, text: string): Promise<void>
  connectDatabase(): Promise<void>
}

interface SlackTools {
  sendSlackMessage: ReturnType<typeof createSendSlackMessageTool>
}

Step 2: Create the Plugin Factory

ts
export function createSlackPlugin(config: {
  webhookUrl: string
}): LotaPlugin<SlackServices, SlackTools> {
  return {
    services: {
      async sendMessage(channel, text) {
        await fetch(config.webhookUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ channel, text }),
        })
      },
      async connectDatabase() {
        // Initialize any plugin-specific DB state if needed
      },
    },
    tools: {
      sendSlackMessage: createSendSlackMessageTool(config),
    },
    contributions: {
      envKeys: ['SLACK_WEBHOOK_URL'],
      schemaFiles: [new URL('./schema/slack.surql', import.meta.url)],
    },
  }
}

Step 3: Register the Plugin

ts
import { createSlackPlugin } from './slack-plugin'

const slackPlugin = createSlackPlugin({
  webhookUrl: process.env.SLACK_WEBHOOK_URL!,
})

const runtime = await createLotaRuntime({
  // ... other config
  pluginRuntime: {
    slack: slackPlugin,
  },
})

await runtime.connect()
await runtime.connectPluginDatabases()

Accessing Plugin Services

Registered plugins are available on runtime.plugins keyed by their registration name:

ts
const slack = runtime.plugins.slack as LotaPlugin<SlackServices>
await slack.services.sendMessage('#general', 'Deployment complete.')

You can also access plugin tools for inclusion in agent tool sets:

ts
const slackTools = runtime.plugins.slack as LotaPlugin<SlackServices, SlackTools>
const tool = slackTools.tools?.sendSlackMessage

Plugin Schema Files

Plugin schema files follow the same rules as all SDK schemas:

  • Use IF NOT EXISTS for all DEFINE statements -- never OVERWRITE or REMOVE.
  • Assume a fresh database. No migration DML.
  • Place .surql files alongside the plugin source and reference them with import.meta.url:
sql
-- schema/slack.surql
DEFINE TABLE IF NOT EXISTS slackNotification SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS channel ON TABLE slackNotification TYPE string;
DEFINE FIELD IF NOT EXISTS text ON TABLE slackNotification TYPE string;
DEFINE FIELD IF NOT EXISTS sentAt ON TABLE slackNotification TYPE datetime
  DEFAULT time::now();
DEFINE FIELD IF NOT EXISTS workstreamId ON TABLE slackNotification TYPE record<workstream>;

DEFINE INDEX IF NOT EXISTS idx_slack_workstream
  ON TABLE slackNotification COLUMNS workstreamId;
ts
contributions: {
  envKeys: ['SLACK_WEBHOOK_URL'],
  schemaFiles: [new URL('./schema/slack.surql', import.meta.url)],
}

Summary

ConcernWhere It Lives
Plugin typeLotaPlugin from @lota-sdk/core
RegistrationpluginRuntime field in createLotaRuntime()
Access at runtimeruntime.plugins.<name>
Schema filesruntime.contributions.schemaFiles
Env keysruntime.contributions.envKeys
DB initializationruntime.connectPluginDatabases()