Plugin SDK
Extend Iron Rain with custom hooks that run before, during, and after task dispatch.
Installation
npm install @howlerops/iron-rain-plugin
The plugin package re-exports all core types, so you don't need to install @howlerops/iron-rain separately.
Quick example
import { definePlugin } from '@howlerops/iron-rain-plugin';
export default definePlugin({
name: 'my-logger',
version: '1.0.0',
description: 'Logs every dispatch to the console',
hooks: {
onInit(ctx) {
console.log('Logger plugin loaded');
},
onBeforeDispatch(task, ctx) {
console.log(`Dispatching: ${task.prompt.slice(0, 50)}...`);
return task; // pass through unchanged
},
onAfterDispatch(episode, ctx) {
console.log(
`[${episode.slot}] ${episode.status} — ` +
`${episode.tokens} tokens, ${episode.duration}ms`
);
}
}
});
Plugin definition
interface PluginDefinition {
name: string; // Unique plugin name
version: string; // Semver version
description?: string; // Human-readable description
hooks: PluginHooks; // Lifecycle hooks
}
The definePlugin() function is an identity function used for TypeScript type inference. Wrap your plugin in it for full autocompletion.
Lifecycle hooks
onInit(ctx)
Called once when the plugin is loaded. Use it for setup, connections, or logging.
onInit(ctx: PluginContext): void | Promise<void>;
onBeforeDispatch(task, ctx)
Called before every task is dispatched to a slot worker. You can inspect or mutate the task.
onBeforeDispatch(
task: OrchestratorTask,
ctx: PluginContext
): OrchestratorTask | void;
- Return a modified
OrchestratorTaskto change the prompt, target slot, or metadata - Return
void(or nothing) to pass the task through unchanged
Example: add system prompt to all tasks
onBeforeDispatch(task, ctx) {
return {
...task,
metadata: {
...task.metadata,
systemPrompt: 'Always respond in JSON format'
}
};
}
onAfterDispatch(episode, ctx)
Called after every dispatch completes. The episode is read-only — use this for logging, analytics, or side effects.
onAfterDispatch(
episode: EpisodeSummary,
ctx: PluginContext
): void;
Example: track costs
let totalTokens = 0;
onAfterDispatch(episode, ctx) {
totalTokens += episode.tokens;
console.log(`Total tokens used: ${totalTokens}`);
}
onSlotChange(slot, config, ctx)
Called when a slot's model configuration is updated at runtime.
onSlotChange(
slot: SlotName,
config: SlotConfig,
ctx: PluginContext
): void;
onDestroy(ctx)
Called on shutdown. Use for cleanup — close connections, flush buffers, etc.
onDestroy(ctx: PluginContext): void | Promise<void>;
Plugin context
Every hook receives a PluginContext with access to the running system:
interface PluginContext {
config: IronRainConfig; // Current config
getSlot(name: SlotName): SlotConfig; // Read a slot
dispatch(task: OrchestratorTask): Promise<EpisodeSummary>; // Dispatch
}
Calling ctx.dispatch() inside onBeforeDispatch or onAfterDispatch can create infinite recursion. Only dispatch from onInit or from external triggers.
TypeScript setup
The plugin SDK exports all the types you need:
import type {
PluginDefinition,
PluginHooks,
PluginContext,
OrchestratorTask,
EpisodeSummary,
SlotName,
SlotConfig,
IronRainConfig,
ChatMessage,
MessageContent,
ResolvedReference
} from '@howlerops/iron-rain-plugin';
The core package also exports context reference utilities:
import {
parseReferences,
getTextContent
} from '@howlerops/iron-rain';
import type {
ResolvedReference,
ParsedInput,
ImageContent,
TextContent
} from '@howlerops/iron-rain';