Skip to main content

Plugin Decorators

Plugin decorators enable the integration of custom plugins into Agent Forge applications, providing extensible functionality through lifecycle hooks.

@plugin

Registers a plugin with a decorated class, automatically adding it to the AgentForge instance when created.

Syntax

@plugin(pluginInstance)
@plugin(anotherPluginInstance)
@llmProvider(provider, config)
@forge()
class MyApp {
static forge: AgentForge;
}

Parameters

ParameterTypeDescription
pluginInstancePluginInstance of a plugin class

Examples

Single Plugin

import { LoggingPlugin } from "agent-forge";

@plugin(new LoggingPlugin())
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class LoggedApp {
static forge: AgentForge;
}

Multiple Plugins

import { MyLoggingPlugin, MyMetricsPlugin } from "./plugins";

@plugin(new MyLoggingPlugin())
@plugin(new MyMetricsPlugin())
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class MonitoredApp {
static forge: AgentForge;
}

Custom Plugin Configuration

@plugin(new CustomLoggingPlugin({
logLevel: "debug",
outputFormat: "json"
}))
@plugin(new CustomMetricsPlugin({
enableTokenTracking: true,
enablePerformanceMetrics: true
}))
@plugin(new CustomSecurityPlugin({
allowedDomains: ["example.com", "api.trusted.com"],
maxRequestSize: 1024 * 1024 // 1MB
}))
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class SecureApp {
static forge: AgentForge;
}

Plugin System with Logging Hooks

Agent Forge provides a powerful plugin system with lifecycle hooks, including new logging hooks that allow plugins to intercept and process log entries in real-time.

Logging Lifecycle Hooks

The framework includes specialized hooks for logging that enable plugins to:

  • LOG_ENTRY_CREATED: Process all log entries as they are created
  • LOG_ERROR_OCCURRED: Handle error-level log entries specifically
  • LOG_CRITICAL_OCCURRED: Handle critical-level log entries with priority

These hooks enable plugins to send logs to external systems, filter sensitive data, aggregate metrics, or transform log formats.

Using Logging Hooks

Create plugins that respond to logging events:

export class LoggingPlugin extends Plugin {
readonly name = "logging";
readonly version = "1.0.0";

getHooks() {
return {
[PluginLifecycleHooks.LOG_ENTRY_CREATED]: this.onLogEntry.bind(this),
[PluginLifecycleHooks.LOG_ERROR_OCCURRED]: this.onErrorLog.bind(this),
[PluginLifecycleHooks.LOG_CRITICAL_OCCURRED]: this.onCriticalLog.bind(this),
};
}

private async onLogEntry(data: PluginHookData): Promise<any> {
const { entry, logger } = data.payload;
// Process any log entry
console.log(`[${entry.level}] ${entry.message}`);
return data.payload;
}

private async onErrorLog(data: PluginHookData): Promise<any> {
const { entry, logger } = data.payload;
// Handle error logs specifically
await this.sendAlert(entry);
return data.payload;
}

private async onCriticalLog(data: PluginHookData): Promise<any> {
const { entry, logger } = data.payload;
// Handle critical logs with highest priority
await this.sendUrgentAlert(entry);
return data.payload;
}

private async sendAlert(entry: any): Promise<void> {
// Send alert for error logs
}

private async sendUrgentAlert(entry: any): Promise<void> {
// Send urgent alert for critical logs
}
}

Hook Data Format

The logging hooks receive data in this format:

interface LoggingHookData {
entry: LogEntry; // The log entry being created
logger: AgentLogger; // The logger instance
}

interface LogEntry {
level: LogLevel;
message: string;
timestamp: number;
agentName?: string;
context?: Record<string, any>;
error?: ErrorLogObject;
executionTime?: number;
tokenUsage?: {
prompt: number;
completion: number;
total: number;
};
}

Creating Custom Plugins

Extend the Plugin base class to create custom plugins:

Basic Plugin Structure

import { Plugin, PluginLifecycleHooks, PluginHookData } from "agent-forge";

export class CustomPlugin extends Plugin {
readonly name = "custom";
readonly version = "1.0.0";
readonly priority = 50; // Higher numbers run first

getHooks() {
return {
[PluginLifecycleHooks.AGENT_BEFORE_RUN]: this.beforeAgentRun.bind(this),
[PluginLifecycleHooks.AGENT_AFTER_RUN]: this.afterAgentRun.bind(this),
[PluginLifecycleHooks.TOOL_BEFORE_EXECUTE]: this.beforeToolExecute.bind(this),
};
}

private beforeAgentRun(data: PluginHookData): any {
this.log(`Agent ${data.payload.agent.name} starting...`);
return data.payload;
}

private afterAgentRun(data: PluginHookData): any {
this.log(`Agent ${data.payload.agent.name} completed`);
return data.payload;
}

private beforeToolExecute(data: PluginHookData): any {
this.log(`Executing tool: ${data.payload.toolName}`);
return data.payload;
}
}

Advanced Plugin Examples

Caching Plugin

export class CachingPlugin extends Plugin {
readonly name = "caching";
readonly version = "1.0.0";
readonly priority = 50;

private cache = new Map<string, { result: any; timestamp: number }>();
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 < this.defaultTTL) {
this.log(`Cache hit for ${agent.name}`);
// Return cached result, short-circuit execution
return { ...data.payload, cachedResult: cached.result };
}

return data.payload;
}

private storeInCache(data: PluginHookData): any {
const { agent, input, result } = data.payload;
const cacheKey = this.generateCacheKey(agent.name, input);

this.cache.set(cacheKey, {
result: result,
timestamp: Date.now()
});

this.cleanupExpiredEntries();
return data.payload;
}

private generateCacheKey(agentName: string, input: string): string {
return `${agentName}:${input.slice(0, 50)}`;
}

private cleanupExpiredEntries(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.defaultTTL) {
this.cache.delete(key);
}
}
}
}

External Logging Plugin

export class ExternalLoggingPlugin extends Plugin {
readonly name = "external-logging";
readonly version = "1.0.0";
readonly priority = 50;

private logQueue: any[] = [];
private batchSize = 10;
private flushInterval = 5000; // 5 seconds

constructor(private config: {
endpoint: string;
apiKey: string;
batchSize?: number;
flushInterval?: number;
}) {
super();
this.batchSize = config.batchSize || 10;
this.flushInterval = config.flushInterval || 5000;
this.startBatchProcessor();
}

getHooks() {
return {
[PluginLifecycleHooks.LOG_ENTRY_CREATED]: this.handleLogEntry.bind(this),
[PluginLifecycleHooks.LOG_ERROR_OCCURRED]: this.handleErrorLog.bind(this),
[PluginLifecycleHooks.LOG_CRITICAL_OCCURRED]: this.handleCriticalLog.bind(this),
};
}

private async handleLogEntry(data: PluginHookData): Promise<any> {
const { entry } = data.payload;
await this.queueLogEntry(entry);
return data.payload;
}

private async handleErrorLog(data: PluginHookData): Promise<any> {
const { entry } = data.payload;
// Add priority flag for error logs
await this.queueLogEntry({ ...entry, priority: "high" });
return data.payload;
}

private async handleCriticalLog(data: PluginHookData): Promise<any> {
const { entry } = data.payload;
// Send critical logs immediately, bypassing batch queue
await this.sendToExternalSystem([{ ...entry, priority: "critical" }]);
return data.payload;
}

private async queueLogEntry(entry: any): Promise<void> {
this.logQueue.push(this.transformLogEntry(entry));

if (this.logQueue.length >= this.batchSize) {
await this.flushQueue();
}
}

private transformLogEntry(entry: any) {
return {
timestamp: new Date(entry.timestamp).toISOString(),
level: entry.level,
message: entry.message,
agentName: entry.agentName,
context: entry.context,
error: entry.error,
source: "agent-forge",
};
}

private async flushQueue(): Promise<void> {
if (this.logQueue.length === 0) return;

const batch = this.logQueue.splice(0, this.batchSize);
await this.sendToExternalSystem(batch);
}

private async sendToExternalSystem(logs: any[]): Promise<void> {
try {
const response = await fetch(this.config.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({ logs }),
});

if (!response.ok) {
this.log(`Failed to send logs: ${response.statusText}`, "error");
}
} catch (error) {
this.log(`Error sending logs: ${error}`, "error");
}
}

private startBatchProcessor(): void {
setInterval(() => {
this.flushQueue().catch(error => {
this.log(`Batch flush error: ${error}`, "error");
});
}, this.flushInterval);
}
}

Security Plugin

export class SecurityPlugin extends Plugin {
readonly name = "security";
readonly version = "1.0.0";
readonly priority = 90; // High priority for security

private allowedTools = ["WebSearchTool", "CalculatorTool"];
private sensitivePatterns = [
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // Credit card
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
];

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)) {
this.log("Sensitive data detected in input", "warn");
// Sanitize or reject the input
return {
...data.payload,
input: this.sanitizeInput(input)
};
}

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`);
}

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 Lifecycle Hooks

Plugins can hook into various framework events:

Framework Hooks

  • FRAMEWORK_INITIALIZE - Framework startup
  • FRAMEWORK_READY - Framework ready for use
  • FRAMEWORK_SHUTDOWN - Framework shutdown

Agent Hooks

  • AGENT_REGISTER - Agent registration
  • AGENT_BEFORE_RUN - Before agent execution
  • AGENT_AFTER_RUN - After agent execution
  • AGENT_ERROR - Agent execution error

LLM Hooks

  • LLM_BEFORE_REQUEST - Before LLM API call
  • LLM_AFTER_REQUEST - After LLM API call
  • LLM_STREAM_START - LLM streaming start
  • LLM_STREAM_END - LLM streaming end

Tool Hooks

  • TOOL_BEFORE_EXECUTE - Before tool execution
  • TOOL_AFTER_EXECUTE - After tool execution
  • TOOL_ERROR - Tool execution error

Team/Workflow Hooks

  • TEAM_BEFORE_RUN - Before team execution
  • TEAM_AFTER_RUN - After team execution
  • WORKFLOW_BEFORE_RUN - Before workflow execution
  • WORKFLOW_AFTER_RUN - After workflow execution

Logging Hooks

  • LOG_ENTRY_CREATED - When any log entry is created
  • LOG_ERROR_OCCURRED - When error-level logs are created
  • LOG_CRITICAL_OCCURRED - When critical-level logs are created

Complete Plugin Example

@plugin(new CustomLoggingPlugin())
@plugin(new CustomMetricsPlugin())
@plugin(new CachingPlugin())
@plugin(new SecurityPlugin())
@tool(WebSearchTool)
@RateLimiter({ rateLimitPerSecond: 2 })
@llmProvider("openai", { apiKey: process.env.OPENAI_API_KEY })
@forge()
class ComprehensiveApp {
static forge: AgentForge;

static async run() {
const agentClasses = [ResearcherAgent, AnalystAgent];
await readyForge(ComprehensiveApp, agentClasses);

// All plugins are active:
// - Custom Logging: Sends logs to external systems via logging hooks
// - Custom Metrics: Performance tracking and token usage monitoring
// - Caching: Result caching for improved performance
// - Security: Input validation and tool restrictions

const result = await this.forge.runTeam(
"ManagerAgent",
["ResearcherAgent", "AnalystAgent"],
"Analyze market trends in sustainable technology"
);

// Access plugin data
const metrics = this.forge.getPluginManager()
.getPlugin("custom-metrics") as CustomMetricsPlugin;

if (metrics) {
console.log("Performance metrics:", metrics.getMetrics());
}

return result;
}
}

Best Practices

Plugin Development

  • Single Responsibility: Each plugin should have a focused purpose
  • Error Handling: Implement robust error handling in hook methods
  • Performance: Minimize overhead in frequently called hooks
  • Documentation: Document plugin functionality and configuration options

Plugin Configuration

  • Priority: Set appropriate priorities for plugin execution order
  • Conditional Logic: Use conditional logic in hooks when needed
  • Resource Management: Clean up resources in the destroy() method
  • State Management: Keep plugin state minimal and thread-safe

Plugin Usage

  • Order Matters: Consider plugin priority when multiple plugins modify the same data
  • Monitoring: Use logging and metrics plugins to monitor application behavior
  • Security: Always include security plugins in production applications
  • Testing: Test plugin interactions with different combinations

Performance Considerations

  • Hook Overhead: Be mindful of hook execution overhead
  • Data Cloning: Avoid unnecessary data cloning in hook methods
  • Async Operations: Handle async operations properly in hooks
  • Memory Usage: Monitor memory usage in long-running applications with plugins