How Extensions Work
AgentOS extensions are runtime code packaged as “extension packs”. Packs can add:
- Tools (
ITool) for LLM tool calling - Guardrails (
IGuardrailService) for safety/policy - Workflows (definitions + executors)
- Other runtime services (channels, memory providers, provenance, etc.)
This document focuses on how extensions become callable tools.
Core Building Blocks
Extension Pack
An extension pack is a bundle of descriptors.
export interface ExtensionPack {
name: string;
version?: string;
descriptors: ExtensionDescriptor[];
onActivate?: (ctx) => Promise<void> | void;
onDeactivate?: (ctx) => Promise<void> | void;
}
Extension Descriptor
Descriptors are registered into kind-specific registries (tools, guardrails, workflows, etc.).
export interface ExtensionDescriptor<TPayload = unknown> {
id: string;
kind: string; // 'tool', 'guardrail', ...
payload: TPayload;
priority?: number;
requiredSecrets?: Array<{ id: string; optional?: boolean }>;
onActivate?: (ctx) => Promise<void> | void;
onDeactivate?: (ctx) => Promise<void> | void;
}
Extension Registry (Stacking)
Each (kind, id) is a stack. The active entry is:
- highest
priority - tie-breaker: latest registration
How Tool Calling Uses Extensions
Tools are stored in an extension registry of kind tool. The important invariant:
ToolExecutorlooks up tools by tool call name, which isITool.name- therefore, tool descriptors must use
descriptor.id === tool.name
Loading Packs (Extension Manifest)
AgentOS loads extensions from extensionManifest.packs via ExtensionManager.loadManifest().
Packs load sequentially in manifest order.
Pack Entry Types
Each pack entry can be:
factory: a function returning anExtensionPackpackage: dynamicimport()of an npm package exportingcreateExtensionPack()module: dynamicimport()of a local file/module exportingcreateExtensionPack()
Example: Curated Registry (Recommended)
Use @framers/agentos-extensions-registry to build a manifest from installed curated extensions.
import { AgentOS } from '@framers/agentos';
import { createCuratedManifest } from '@framers/agentos-extensions-registry';
const extensionSecrets = {
'serper.apiKey': process.env.SERPER_API_KEY!,
'giphy.apiKey': process.env.GIPHY_API_KEY!,
};
const manifest = await createCuratedManifest({
tools: 'all',
channels: 'none',
secrets: extensionSecrets,
});
const agentos = new AgentOS();
await agentos.initialize({
extensionManifest: manifest,
// Provide secrets here so `requiredSecrets` gating can be evaluated without relying on env vars.
extensionSecrets,
// ...other required AgentOS config
});
Example: Load a Local Module Pack
extensionManifest: {
packs: [
{
module: './extensions/my-pack.mjs',
options: { /* your config */ },
priority: 10,
},
],
}
Example: Load an npm Package Pack
extensionManifest: {
packs: [
{
package: '@framers/agentos-ext-web-search',
options: { /* your config */ },
priority: 10,
},
],
}
Secrets & requiredSecrets
Descriptors can declare requiredSecrets to declare runtime dependencies (API keys, tokens, credentials).
AgentOS resolves secrets from:
extensionSecretspassed to AgentOS (host-provided)packs[].options.secretsif present- environment variables mapped by the shared secret catalog (
extension-secrets.json)
If a descriptor requires a non-optional secret and it can’t be resolved, AgentOS skips activating that descriptor.
Overrides
You can disable or reprioritize individual descriptors using overrides:
extensionManifest.overrides(inline)extensionOverrideson AgentOS config (host-provided)
Overrides currently apply to:
- tools
- guardrails
- response processors
Example: disable giphy_search:
extensionOverrides: {
tools: {
giphy_search: { enabled: false },
},
},
Lifecycle Hooks
pack.onActivate(ctx)runs once per loaded pack before descriptors registerdescriptor.onActivate(ctx)runs when the descriptor becomes active for its(kind,id)descriptor.onDeactivate(ctx)runs when the descriptor is superseded or removedpack.onDeactivate(ctx)runs on AgentOS shutdown
You can listen to extension events:
extensionManager.on((event) => {
console.log(event.type, event.timestamp, event.source);
});