Skip to main content

Plugin SDK reference

A plugin is an npm package that exports an EthosPlugin and is loaded at wiring time. The SDK provides the activation API, type-safe tool helpers, and a test runtime.

Source

packages/plugin-sdk/src/index.ts — activation API. Tool helpers in tool-helpers.ts and test utilities in testing.ts.

EthosPlugin

Signature

import type { EthosPlugin, EthosPluginApi } from '@ethosagent/plugin-sdk';

export interface EthosPlugin {
activate(api: EthosPluginApi): void | Promise<void>;
deactivate?(): void | Promise<void>;
}

Members

FieldTypeDescription
activate(api) => void | Promise<void>Called once on load. Register every tool, hook, injector, and personality here.
deactivate() => void | Promise<void>Optional. Called on unload. Plugins do NOT need to manually unregister via this — PluginApiImpl.cleanup() removes everything tagged with the plugin id. Use only for external resources (open handles, sockets).

Minimal plugin

import type { EthosPlugin } from '@ethosagent/plugin-sdk';
import { defineTool, ok } from '@ethosagent/plugin-sdk';

const helloTool = defineTool<{ name: string }>({
name: 'hello',
description: 'Say hello.',
schema: {
type: 'object',
required: ['name'],
properties: { name: { type: 'string' } },
},
async execute({ name }) {
return ok(`Hello, ${name}!`);
},
});

const plugin: EthosPlugin = {
async activate(api) {
api.registerTool(helloTool);
},
};

export default plugin;

EthosPluginApi

The activation surface. Every registration call tags the entry with the plugin's id so the loader can clean up on unload.

Signature

export interface EthosPluginApi {
readonly pluginId: string;
registerTool(tool: Tool): void;
registerVoidHook<K extends keyof VoidHooks>(
name: K,
handler: (payload: VoidHooks[K]) => Promise<void>,
): void;
registerModifyingHook<K extends keyof ModifyingHooks>(
name: K,
handler: (payload: ModifyingHooks[K][0]) => Promise<Partial<ModifyingHooks[K][1]> | null>,
): void;
registerInjector(injector: ContextInjector): void;
registerPersonality(config: PersonalityConfig): void;
registerContextEngine(engine: ContextEngine): void;
}

Methods

MethodDescription
pluginIdStable id assigned by the loader (typically the package name). Reuse for log lines so log analysis can correlate.
registerTool(tool)Add a Tool. The tool only appears when its personality lists this plugin in plugins:.
registerVoidHook(name, handler)Subscribe to a void hook. Sequential failure-isolated execution.
registerModifyingHook(name, handler)Subscribe to a modifying hook. Sequential, merged results.
registerInjector(injector)Add a ContextInjector that contributes to the system prompt.
registerPersonality(config)Define a personality in code (no ~/.ethos/personalities/ directory needed).
registerContextEngine(engine)E4 — register a custom context-compaction engine. Throws if the host wiring did not expose a ContextEngineRegistry.

Notes

  • registerClaimingHook is NOT exposed. Claiming hooks are gateway-level routing decisions that must be coordinated centrally; plugins cannot register them.
  • Personalities registered via registerPersonality are NOT removed on plugin unload (the underlying registry has no unregister). They persist in memory until the process exits. Treat them as additive.
  • Per-personality gating is enforced by the pluginId tag. A plugin's tools / hooks fire only when the active personality lists the plugin in personality.plugins:.

defineTool

Type-safe Tool<TArgs> factory.

Signature

import { defineTool } from '@ethosagent/plugin-sdk';

export function defineTool<TArgs = unknown>(def: ToolDefinition<TArgs>): Tool<TArgs>;

export interface ToolDefinition<TArgs = unknown> {
name: string;
description: string;
schema: Record<string, unknown>;
toolset?: string;
maxResultChars?: number;
isAvailable?: () => boolean;
execute: (args: TArgs, ctx: ToolContext) => Promise<ToolResult>;
}

Notes

  • Identical to writing a Tool literal — pure types, no runtime wrapping.
  • The generic TArgs flows into execute(args, ctx) so args is typed inside the function body.
  • See the Tool interface reference for the full set of Tool fields (outputIsUntrusted, alwaysInclude); ToolDefinition exposes the subset plugins normally need.

ok / err

ToolResult shorthands.

Signature

import { ok, err } from '@ethosagent/plugin-sdk';

export function ok(value: string): ToolResult;
export function err(
error: string,
code?: 'input_invalid' | 'not_available' | 'execution_failed',
): ToolResult;

err's code defaults to 'execution_failed'. Pass 'input_invalid' when the LLM provided bad args, 'not_available' when a dependency is missing.

Test utilities

Importable from @ethosagent/plugin-sdk/testing.

mockLLM

import { mockLLM } from '@ethosagent/plugin-sdk/testing';

const llm = mockLLM(['Hello!', 'Goodbye.']);

Returns an LLMProvider that streams the given strings in order, one text_delta chunk + one done chunk per response. callCount cycles modulo the array length.

mockTool

import { mockTool } from '@ethosagent/plugin-sdk/testing';

const t1 = mockTool('greeter', 'Hello, world!');
const t2 = mockTool('fail', { ok: false, error: 'boom', code: 'execution_failed' });

Creates a Tool whose execute always returns the given ToolResult. Pass a string as shorthand for { ok: true, value: string }.

createTestRuntime

import { createTestRuntime, mockLLM } from '@ethosagent/plugin-sdk/testing';

const loop = createTestRuntime({ llm: mockLLM(['Hello!']) });
for await (const event of loop.run('hi')) {
if (event.type === 'done') console.log(event.text);
}

Constructs a minimal AgentLoop for plugin tests. tools defaults to a fresh DefaultToolRegistry, hooks to a fresh DefaultHookRegistry; everything else passes through to AgentLoopConfig.

Re-exported types

The SDK re-exports common types from @ethosagent/types so plugin authors only need one import:

import type {
ContextInjector,
InjectionResult,
ModifyingHooks,
PersonalityConfig,
PromptContext,
Tool,
ToolContext,
ToolResult,
VoidHooks,
} from '@ethosagent/plugin-sdk';

Used by

ConsumerRole
extensions/plugin-loader/src/Discovers npm packages, instantiates PluginApiImpl, calls activate.
apps/ethos/src/wiring.tsWires the plugin loader's registries into AgentLoop.
Third-party plugin packagesImport @ethosagent/plugin-sdk to register their tools, hooks, injectors.

See also