Plugin System
Agent Forge provides a powerful plugin system that allows you to extend functionality by hooking into various lifecycle events throughout the framework. Plugins can intercept and modify agent behavior, add logging, implement caching, gather metrics, and much more.
Overview
The plugin system operates on a hook-based architecture where plugins register handlers for specific lifecycle events. When these events occur, all registered handlers are executed in priority order, allowing plugins to:
- Monitor agent and tool execution
- Modify inputs and outputs
- Cache results for performance
- Log detailed execution information
- Collect metrics and analytics
- Add security validation
Quick Start
Using the @plugin Decorator
The simplest way to add plugins is using the @plugin
decorator:
import { plugin } from "agent-forge";
import { LoggingPlugin, MetricsPlugin } from "agent-forge/plugins";
@plugin(new LoggingPlugin())
@plugin(new MetricsPlugin())
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class MyApp {
static forge: AgentForge;
}
Built-in Plugins
Agent Forge includes several built-in plugins ready to use:
LoggingPlugin
Provides comprehensive logging of all framework activities:
import { LoggingPlugin } from "agent-forge/plugins";
@plugin(new LoggingPlugin())
class MyApp {
// Automatically logs:
// 🚀 Framework events (initialize, ready, shutdown)
// 🎯 Agent execution start/complete
// 🔧 Tool execution with parameters and results
// 👥 Team and workflow execution
// ❌ Errors with context
}
MetricsPlugin
Tracks performance metrics and token usage:
import { MetricsPlugin } from "agent-forge/plugins";
const metricsPlugin = new MetricsPlugin();
@plugin(metricsPlugin)
class MyApp {
static async run() {
// Your application logic...
// Get metrics anytime
const metrics = metricsPlugin.getMetrics();
console.log({
agentRuns: metrics.agentRuns,
averageTime: metricsPlugin.getAverageExecutionTime(),
totalTokens: metricsPlugin.getTotalTokens(),
errors: metrics.errors
});
}
}
Available Hooks
The plugin system provides hooks for all major lifecycle events:
Framework Lifecycle
framework:initialize
- Framework is initializingframework:ready
- Framework is ready for useframework:shutdown
- Framework is shutting down
Agent Lifecycle
agent:register
- An agent is being registeredagent:before_run
- Before an agent starts executionagent:after_run
- After an agent completes executionagent:error
- When an agent encounters an error
LLM Lifecycle
llm:before_request
- Before making an LLM API requestllm:after_request
- After receiving an LLM API responsellm:stream_start
- When LLM streaming beginsllm:stream_end
- When LLM streaming ends
Tool Lifecycle
tool:before_execute
- Before a tool is executedtool:after_execute
- After a tool completes executiontool:error
- When a tool encounters an error
Team & Workflow Lifecycle
team:before_run
- Before a team starts executionteam:after_run
- After a team completes executionworkflow:before_run
- Before a workflow starts executionworkflow:after_run
- After a workflow completes execution
Creating Custom Plugins
Basic Plugin Structure
All plugins extend the base Plugin
class and implement the required methods:
import { Plugin, PluginLifecycleHooks, type PluginHookData } from "agent-forge/plugins";
export class MyCustomPlugin extends Plugin {
readonly name = "my-custom-plugin";
readonly version = "1.0.0";
readonly priority = 50; // Higher numbers run first
getHooks() {
return {
[PluginLifecycleHooks.AGENT_BEFORE_RUN]: this.handleAgentStart.bind(this),
[PluginLifecycleHooks.AGENT_AFTER_RUN]: this.handleAgentComplete.bind(this),
};
}
private handleAgentStart(data: PluginHookData): any {
const { agent, input } = data.payload;
this.log(`Agent ${agent.name} starting with: ${input}`);
// Optionally modify the payload
return data.payload;
}
private handleAgentComplete(data: PluginHookData): any {
const { agent, result } = data.payload;
this.log(`Agent ${agent.name} completed with: ${result.output}`);
return data.payload;
}
// Optional lifecycle methods
async initialize(context) {
this.log("Plugin initialized");
}
async destroy() {
this.log("Plugin destroyed");
}
}
Advanced Plugin Examples
Caching Plugin
import { Plugin, PluginLifecycleHooks, type PluginHookData } from "agent-forge/plugins";
interface CacheEntry {
result: any;
timestamp: number;
ttl: number;
}
export class CachingPlugin extends Plugin {
readonly name = "caching";
readonly version = "1.0.0";
readonly priority = 50;
private cache = new Map<string, CacheEntry>();
private defaultTTL = 60000; // 1 minute
getHooks() {
return {
[PluginLifecycleHooks.AGENT_BEFORE_RUN]: this.checkCache.bind(this),
[PluginLifecycleHooks.AGENT_AFTER_RUN]: this.storeInCache.bind(this),
};
}
private checkCache(data: PluginHookData): any {
const { agent, input } = data.payload;
const cacheKey = this.generateCacheKey(agent.name, input);
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cached.ttl) {
this.log(`💨 Cache hit for agent ${agent.name}`);
// Return cached result
return {
...data.payload,
_useCachedResult: true,
_cachedResult: cached.result
};
}
return data.payload;
}
private storeInCache(data: PluginHookData): any {
const { agent, input, result } = data.payload;
// Don't cache if this was a cached result
if (data.payload._useCachedResult) {
return data.payload;
}
const cacheKey = this.generateCacheKey(agent.name, input);
this.cache.set(cacheKey, {
result,
timestamp: Date.now(),
ttl: this.defaultTTL
});
this.log(`💾 Cached result for agent ${agent.name}`);
return data.payload;
}
private generateCacheKey(agentName: string, input: string): string {
const inputHash = Buffer.from(input).toString('base64').substring(0, 50);
return `${agentName}:${inputHash}`;
}
// Public API
clearCache(): void {
this.cache.clear();
this.log("🗑️ Cache cleared");
}
getCacheStats() {
return {
size: this.cache.size,
// Add more stats...
};
}
}
Security Plugin
export class SecurityPlugin extends Plugin {
readonly name = "security";
readonly version = "1.0.0";
readonly priority = 90; // High priority for security checks
private allowedTools = ["WebSearchTool", "CalculatorTool"];
private sensitivePatterns = [
/\b\d{16}\b/, // Credit card numbers
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email addresses
// Add more patterns...
];
getHooks() {
return {
[PluginLifecycleHooks.AGENT_BEFORE_RUN]: this.validateInput.bind(this),
[PluginLifecycleHooks.TOOL_BEFORE_EXECUTE]: this.validateToolCall.bind(this),
};
}
private validateInput(data: PluginHookData): any {
const { input } = data.payload;
if (this.containsSensitiveData(input)) {
const sanitized = this.sanitizeInput(input);
this.log("🛡️ Sanitized sensitive data from input", "warn");
return { ...data.payload, input: sanitized };
}
return data.payload;
}
private validateToolCall(data: PluginHookData): any {
const { toolName } = data.payload;
if (!this.isToolAllowed(toolName)) {
this.log(`🚫 Blocked unauthorized tool: ${toolName}`, "error");
throw new Error(`Tool ${toolName} is not authorized for use`);
}
return data.payload;
}
private containsSensitiveData(input: string): boolean {
return this.sensitivePatterns.some(pattern => pattern.test(input));
}
private sanitizeInput(input: string): string {
let sanitized = input;
this.sensitivePatterns.forEach(pattern => {
sanitized = sanitized.replace(pattern, "[REDACTED]");
});
return sanitized;
}
private isToolAllowed(toolName: string): boolean {
return this.allowedTools.includes(toolName);
}
}
Plugin Configuration & Management
Plugin Priority
Plugins execute in priority order (higher numbers first). Use this to ensure critical plugins like security run before others:
export class SecurityPlugin extends Plugin {
readonly priority = 100; // Runs first
}
export class CachingPlugin extends Plugin {
readonly priority = 50; // Runs after security
}
export class LoggingPlugin extends Plugin {
readonly priority = 10; // Runs last
}
Plugin State Management
Plugins can maintain state across hook executions:
export class StatefulPlugin extends Plugin {
private executionCount = 0;
private startTimes = new Map<string, number>();
private trackExecution(data: PluginHookData): any {
this.executionCount++;
this.startTimes.set(data.payload.agent.name, Date.now());
return data.payload;
}
getStats() {
return {
totalExecutions: this.executionCount,
activeAgents: this.startTimes.size
};
}
}
Accessing AgentForge Context
Plugins have access to the main AgentForge instance:
export class ContextAwarePlugin extends Plugin {
async initialize(context) {
const forge = context.forge;
const allAgents = forge.getAgents();
this.log(`Initialized with ${allAgents.length} agents`);
}
}
Hook Data Structure
Each hook receives a PluginHookData
object:
interface PluginHookData {
hook: PluginLifecycleHooks; // The specific hook being triggered
payload: any; // Hook-specific data
context?: PluginContext; // Framework context
}
Common Payload Structures
Agent Hooks:
// agent:before_run, agent:after_run
{
agent: Agent,
input: string,
result?: AgentResult // Only in after_run
}
Tool Hooks:
// tool:before_execute, tool:after_execute
{
toolName: string,
parameters: Record<string, any>,
result?: any // Only in after_execute
}
Team/Workflow Hooks:
// team:before_run, team:after_run
{
team: Team,
input: string,
result?: AgentResult // Only in after_run
}
Best Practices
1. Handle Errors Gracefully
private handleHook(data: PluginHookData): any {
try {
// Plugin logic here
return data.payload;
} catch (error) {
this.log(`Error in hook: ${error.message}`, "error");
// Return original payload to avoid breaking the chain
return data.payload;
}
}
2. Use Descriptive Logging
this.log(`🎯 Processing agent ${agent.name} with ${input.length} chars input`);
3. Clean Up Resources
async destroy(): Promise<void> {
// Clear timers, close connections, etc.
this.timers.forEach(timer => clearInterval(timer));
await this.database?.close();
}
4. Document Plugin Configuration
interface MyPluginConfig {
/** Maximum cache size (default: 100) */
maxCacheSize?: number;
/** Cache TTL in milliseconds (default: 60000) */
cacheTTL?: number;
}
export class MyPlugin extends Plugin {
constructor(private config: MyPluginConfig = {}) {
super();
this.config = { maxCacheSize: 100, cacheTTL: 60000, ...config };
}
}
5. Test Plugin Isolation
Ensure plugins don't interfere with each other by testing with multiple plugins enabled.
Plugin Examples in Action
Here's a complete example showing multiple plugins working together:
import {
agent,
llmProvider,
forge,
plugin,
Agent,
LoggingPlugin,
MetricsPlugin
} from "agent-forge";
// Custom plugins
const cachingPlugin = new CachingPlugin({ cacheTTL: 30000 });
const securityPlugin = new SecurityPlugin();
@agent({
name: "Assistant",
role: "Helpful Assistant",
description: "A multi-plugin enhanced assistant",
objective: "Help users with their questions",
model: "gpt-4",
})
class AssistantAgent extends Agent {}
@plugin(securityPlugin) // Priority 90 - runs first
@plugin(cachingPlugin) // Priority 50 - runs second
@plugin(new LoggingPlugin()) // Priority 100 - runs after security
@plugin(new MetricsPlugin()) // Priority 0 - runs last
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class PluginExample {
static forge: AgentForge;
static async run() {
// Plugins automatically enhance all agent operations
const assistant = new AssistantAgent();
const result = await assistant.run("What is the capital of France?");
// Access plugin data
console.log("Cache stats:", cachingPlugin.getCacheStats());
console.log("Security blocked tools:", securityPlugin.getBlockedTools());
}
}
This example demonstrates how plugins can work together to provide caching, security, logging, and metrics without any changes to your agent code!