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
Parameter | Type | Description |
---|---|---|
pluginInstance | Plugin | Instance 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 createdLOG_ERROR_OCCURRED
: Handle error-level log entries specificallyLOG_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 startupFRAMEWORK_READY
- Framework ready for useFRAMEWORK_SHUTDOWN
- Framework shutdown
Agent Hooks
AGENT_REGISTER
- Agent registrationAGENT_BEFORE_RUN
- Before agent executionAGENT_AFTER_RUN
- After agent executionAGENT_ERROR
- Agent execution error
LLM Hooks
LLM_BEFORE_REQUEST
- Before LLM API callLLM_AFTER_REQUEST
- After LLM API callLLM_STREAM_START
- LLM streaming startLLM_STREAM_END
- LLM streaming end
Tool Hooks
TOOL_BEFORE_EXECUTE
- Before tool executionTOOL_AFTER_EXECUTE
- After tool executionTOOL_ERROR
- Tool execution error
Team/Workflow Hooks
TEAM_BEFORE_RUN
- Before team executionTEAM_AFTER_RUN
- After team executionWORKFLOW_BEFORE_RUN
- Before workflow executionWORKFLOW_AFTER_RUN
- After workflow execution
Logging Hooks
LOG_ENTRY_CREATED
- When any log entry is createdLOG_ERROR_OCCURRED
- When error-level logs are createdLOG_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