
Agent Communication
- 44 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with ai & agent building tasks during AI-assisted development.
About
agent-communication is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- agent-communication
- AI & Agent Building
- AI-coding skill
Agent Communication by the numbers
- 44 all-time installs (skills.sh)
- Ranked #7,844 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill agent-communicationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Agent Communication
Identity
You're a distributed systems engineer who has adapted message-passing patterns for LLM agents. You understand that agent communication is fundamentally different from traditional IPC—agents can hallucinate, misinterpret, and generate novel message formats.
You've learned that the key to reliable multi-agent systems is constrained, validated communication. Agents that can say anything will eventually say something wrong. Structure and validation catch errors before they propagate.
Your core principles: 1. Structured over natural language—validate messages against schemas 2. Minimize communication—every message costs tokens and latency 3. Fail fast—catch malformed messages immediately 4. Log everything—communication is where things go wrong 5. Design for replay—enable debugging and recovery
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Agent Communication
Patterns
---
Name
Typed Message Passing
Description
Strongly typed messages with schema validation
When
Agents need to exchange structured data
Example
import { z } from 'zod';
// Define message types const TaskRequestSchema = z.object({ type: z.literal('task_request'), requestId: z.string().uuid(), fromAgent: z.string(), toAgent: z.string(), task: z.object({ description: z.string(), priority: z.enum(['low', 'medium', 'high', 'critical']), deadline: z.number().optional(), context: z.record(z.unknown()) }), timestamp: z.number() });
const TaskResponseSchema = z.object({ type: z.literal('task_response'), requestId: z.string().uuid(), fromAgent: z.string(), status: z.enum(['accepted', 'rejected', 'completed', 'failed']), result: z.unknown().optional(), error: z.string().optional(), timestamp: z.number() });
const MessageSchema = z.discriminatedUnion('type', [ TaskRequestSchema, TaskResponseSchema, // Add more message types... ]);
type Message = z.infer<typeof MessageSchema>;
class TypedMessageBus { private handlers: Map<string, Set<MessageHandler>> = new Map(); private messageLog: Message[] = [];
async send(message: Message): Promise<void> { // Validate message const validated = MessageSchema.parse(message);
// Log for debugging this.messageLog.push({ ...validated, _logged: Date.now() });
// Deliver to subscribers const handlers = this.handlers.get(validated.toAgent); if (!handlers || handlers.size === 0) { throw new Error(No handlers for agent: ${validated.toAgent}); }
await Promise.all( Array.from(handlers).map(h => h(validated)) ); }
subscribe(agentId: string, handler: MessageHandler): () => void { if (!this.handlers.has(agentId)) { this.handlers.set(agentId, new Set()); } this.handlers.get(agentId)!.add(handler);
return () => this.handlers.get(agentId)!.delete(handler); }
// Replay for debugging getMessages(filter?: MessageFilter): Message[] { return this.messageLog.filter(m => { if (filter?.fromAgent && m.fromAgent !== filter.fromAgent) return false; if (filter?.toAgent && m.toAgent !== filter.toAgent) return false; if (filter?.type && m.type !== filter.type) return false; return true; }); } }
// Agent with typed messaging class TypedAgent { constructor( private id: string, private bus: TypedMessageBus ) { this.bus.subscribe(this.id, this.handleMessage.bind(this)); }
async requestTask(toAgent: string, task: TaskRequest['task']): Promise<TaskResponse> { const requestId = crypto.randomUUID();
const request: TaskRequest = { type: 'task_request', requestId, fromAgent: this.id, toAgent, task, timestamp: Date.now() };
await this.bus.send(request);
// Wait for response return this.waitForResponse(requestId); }
private async handleMessage(message: Message): Promise<void> { switch (message.type) { case 'task_request': await this.handleTaskRequest(message); break; case 'task_response': this.resolveResponse(message); break; } } }
---
Name
Blackboard Pattern
Description
Shared knowledge space that agents read/write to
When
Agents need to collaborate on evolving shared state
Example
// Blackboard: shared knowledge space for agent collaboration
interface BlackboardEntry { key: string; value: unknown; author: string; timestamp: number; confidence: number; dependencies: string[]; // Keys this entry depends on }
class Blackboard { private entries: Map<string, BlackboardEntry> = new Map(); private watchers: Map<string, Set<WatchCallback>> = new Map(); private history: BlackboardChange[] = [];
// Write with provenance tracking async write( key: string, value: unknown, author: string, metadata?: { confidence?: number; dependencies?: string[] } ): Promise<void> { const entry: BlackboardEntry = { key, value, author, timestamp: Date.now(), confidence: metadata?.confidence ?? 1.0, dependencies: metadata?.dependencies ?? [] };
const previousValue = this.entries.get(key); this.entries.set(key, entry);
// Log change this.history.push({ type: 'write', key, previousValue, newValue: entry, author, timestamp: Date.now() });
// Notify watchers await this.notifyWatchers(key, entry); }
// Read with dependency tracking read(key: string, reader?: string): BlackboardEntry | undefined { const entry = this.entries.get(key);
if (entry && reader) { this.history.push({ type: 'read', key, reader, timestamp: Date.now() }); }
return entry; }
// Watch for changes watch(key: string, callback: WatchCallback): () => void { if (!this.watchers.has(key)) { this.watchers.set(key, new Set()); } this.watchers.get(key)!.add(callback);
return () => this.watchers.get(key)!.delete(callback); }
// Query by pattern query(pattern: string | RegExp): BlackboardEntry[] { const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
return Array.from(this.entries.values()) .filter(e => regex.test(e.key)); }
// Get entries by author getByAuthor(author: string): BlackboardEntry[] { return Array.from(this.entries.values()) .filter(e => e.author === author); }
// Conflict resolution for concurrent writes async mergeConflict( key: string, entries: BlackboardEntry[], resolver: ConflictResolver ): Promise<BlackboardEntry> { const resolved = await resolver.resolve(entries); await this.write(key, resolved.value, 'conflict_resolver', { confidence: resolved.confidence, dependencies: entries.map(e => ${key}@${e.timestamp}) }); return this.entries.get(key)!; } }
// Agent using blackboard for collaboration class BlackboardAgent { constructor( private id: string, private blackboard: Blackboard, private llm: LLMClient ) {}
async contribute(topic: string): Promise<void> { // Read what others have written const existingEntries = this.blackboard.query(${topic}.*); const context = existingEntries.map(e => [${e.author}]: ${JSON.stringify(e.value)} ).join('\n');
// Generate contribution const response = await this.llm.invoke({ messages: [{ role: 'system', content: `You are contributing to a collaborative analysis on: ${topic}
Existing contributions: ${context}
Add new insights that complement, not duplicate, existing contributions.` }] });
// Write to blackboard await this.blackboard.write( ${topic}.${this.id}, response.content, this.id, { confidence: 0.8 } ); }
// React to changes async watchAndRespond(pattern: string): Promise<void> { const entries = this.blackboard.query(pattern);
for (const entry of entries) { this.blackboard.watch(entry.key, async (newValue) => { await this.respondToChange(entry.key, newValue); }); } } }
---
Name
Event-Driven Agent Communication
Description
Agents publish events, others subscribe and react
When
Loose coupling and async communication needed
Example
import { EventEmitter } from 'events';
// Event types interface AgentEvent { eventId: string; eventType: string; source: string; timestamp: number; payload: unknown; correlationId?: string; }
interface TaskStartedEvent extends AgentEvent { eventType: 'task.started'; payload: { taskId: string; description: string; assignee: string; }; }
interface TaskCompletedEvent extends AgentEvent { eventType: 'task.completed'; payload: { taskId: string; result: unknown; duration: number; }; }
interface ErrorOccurredEvent extends AgentEvent { eventType: 'error.occurred'; payload: { error: string; context: unknown; recoverable: boolean; }; }
type AgentEvents = TaskStartedEvent | TaskCompletedEvent | ErrorOccurredEvent;
class EventBus { private emitter = new EventEmitter(); private eventLog: AgentEvent[] = [];
publish<T extends AgentEvent>(event: T): void { // Add metadata const enrichedEvent = { ...event, eventId: event.eventId || crypto.randomUUID(), timestamp: event.timestamp || Date.now() };
// Log for replay this.eventLog.push(enrichedEvent);
// Emit this.emitter.emit(event.eventType, enrichedEvent); this.emitter.emit('*', enrichedEvent); // Wildcard subscribers }
subscribe<T extends AgentEvent>( eventType: T['eventType'] | '*', handler: (event: T) => void | Promise<void> ): () => void { this.emitter.on(eventType, handler); return () => this.emitter.off(eventType, handler); }
// Replay events for recovery or debugging async replay( filter?: { since?: number; types?: string[] }, handler?: (event: AgentEvent) => Promise<void> ): Promise<AgentEvent[]> { const filtered = this.eventLog.filter(e => { if (filter?.since && e.timestamp < filter.since) return false; if (filter?.types && !filter.types.includes(e.eventType)) return false; return true; });
if (handler) { for (const event of filtered) { await handler(event); } }
return filtered; } }
// Event-driven agent class EventDrivenAgent { constructor( private id: string, private eventBus: EventBus ) { // Subscribe to relevant events this.eventBus.subscribe('task.started', this.onTaskStarted.bind(this)); this.eventBus.subscribe('error.occurred', this.onError.bind(this)); }
private async onTaskStarted(event: TaskStartedEvent): Promise<void> { if (event.payload.assignee !== this.id) return;
try { const result = await this.executeTask(event.payload);
this.eventBus.publish({ eventType: 'task.completed', source: this.id, correlationId: event.eventId, payload: { taskId: event.payload.taskId, result, duration: Date.now() - event.timestamp } } as TaskCompletedEvent); } catch (error) { this.eventBus.publish({ eventType: 'error.occurred', source: this.id, correlationId: event.eventId, payload: { error: error.message, context: { taskId: event.payload.taskId }, recoverable: true } } as ErrorOccurredEvent); } }
private onError(event: ErrorOccurredEvent): void { console.error([${this.id}] Error from ${event.source}: ${event.payload.error}); } }
---
Name
Request-Response with Timeout
Description
Synchronous-style communication with timeout handling
When
Agent needs response before continuing
Example
class RequestResponseChannel { private pendingRequests: Map<string, { resolve: (value: Response) => void; reject: (error: Error) => void; timeout: NodeJS.Timeout; }> = new Map();
private bus: TypedMessageBus; private defaultTimeout = 30000;
async request<T>( toAgent: string, payload: unknown, options?: { timeout?: number } ): Promise<T> { const requestId = crypto.randomUUID(); const timeout = options?.timeout ?? this.defaultTimeout;
return new Promise((resolve, reject) => { // Set timeout const timeoutHandle = setTimeout(() => { this.pendingRequests.delete(requestId); reject(new Error(Request ${requestId} timed out after ${timeout}ms)); }, timeout);
// Store pending request this.pendingRequests.set(requestId, { resolve: resolve as (value: Response) => void, reject, timeout: timeoutHandle });
// Send request this.bus.send({ type: 'request', requestId, fromAgent: this.agentId, toAgent, payload, timestamp: Date.now() }).catch(reject); }); }
respond(requestId: string, response: unknown): void { this.bus.send({ type: 'response', requestId, fromAgent: this.agentId, payload: response, timestamp: Date.now() }); }
handleResponse(message: ResponseMessage): void { const pending = this.pendingRequests.get(message.requestId); if (!pending) return;
clearTimeout(pending.timeout); this.pendingRequests.delete(message.requestId); pending.resolve(message.payload); } }
Anti-Patterns
---
Name
Untyped Messages
Description
Sending arbitrary JSON between agents without schemas
Why
Leads to runtime errors, hard to debug, no IDE support
Instead
Define message schemas with Zod or TypeScript interfaces.
---
Name
Synchronous Everywhere
Description
All agent communication is blocking request-response
Why
Creates bottlenecks, doesn't scale, fails cascade
Instead
Use async events where response not immediately needed.
---
Name
No Message Logging
Description
Messages not persisted for debugging or replay
Why
Impossible to debug failures, can't recover from crashes
Instead
Log all messages with timestamps and correlation IDs.
---
Name
Circular Dependencies
Description
Agent A waits for B which waits for A
Why
Deadlocks, hangs, hard to detect
Instead
Design acyclic communication flows or use async events.
Agent Communication - Sharp Edges
Message Schema Drift
Id
message-schema-drift
Summary
Message schemas change but not all agents update
Severity
high
Situation
Agent A sends new message format, Agent B expects old format, parsing fails
Why
Multiple agents, decentralized development. No schema versioning. Backward compatibility not considered.
Solution
// Versioned message schemas with backward compatibility
import { z } from 'zod';
// Version 1 schema const TaskRequestV1 = z.object({ version: z.literal(1), type: z.literal('task_request'), task: z.string() });
// Version 2 schema (added priority) const TaskRequestV2 = z.object({ version: z.literal(2), type: z.literal('task_request'), task: z.string(), priority: z.enum(['low', 'medium', 'high']) });
// Combined schema supporting both versions const TaskRequest = z.discriminatedUnion('version', [ TaskRequestV1, TaskRequestV2 ]);
class VersionedMessageHandler { async handleMessage(raw: unknown): Promise<ProcessedMessage> { // Parse with version detection const message = TaskRequest.parse(raw);
// Normalize to latest version const normalized = this.normalize(message);
return this.process(normalized); }
private normalize(message: z.infer<typeof TaskRequest>): TaskRequestV2 { if (message.version === 1) { // Upgrade V1 to V2 with default priority return { version: 2, type: message.type, task: message.task, priority: 'medium' // Default for old messages }; } return message; }
// Deprecation handling private warnDeprecation(version: number): void { if (version < 2) { console.warn(Message version ${version} is deprecated. Update to version 2.); } } }
// Schema registry for runtime validation class SchemaRegistry { private schemas: Map<string, Map<number, z.ZodSchema>> = new Map();
register(messageType: string, version: number, schema: z.ZodSchema): void { if (!this.schemas.has(messageType)) { this.schemas.set(messageType, new Map()); } this.schemas.get(messageType)!.set(version, schema); }
validate(messageType: string, version: number, data: unknown): boolean { const typeSchemas = this.schemas.get(messageType); if (!typeSchemas) throw new Error(Unknown message type: ${messageType});
const schema = typeSchemas.get(version); if (!schema) throw new Error(Unknown version ${version} for ${messageType});
return schema.safeParse(data).success; }
getLatestVersion(messageType: string): number { const typeSchemas = this.schemas.get(messageType); if (!typeSchemas) throw new Error(Unknown message type: ${messageType});
return Math.max(...typeSchemas.keys()); } }
Symptoms
- Parsing errors in agent logs
- Some agents work, others fail
- Works in dev, fails in production
Detection Pattern
parse|schema|version|\.parse\(
Message Ordering Assumptions
Id
message-ordering-assumptions
Summary
Agent assumes messages arrive in order they were sent
Severity
medium
Situation
Messages arrive out of order, agent processes in wrong sequence
Why
Async delivery doesn't guarantee order. Parallel processing reorders. Network/queue delays vary.
Solution
// Handle out-of-order message delivery
class OrderedMessageProcessor { private messageBuffer: Map<string, BufferedMessage[]> = new Map(); private expectedSequence: Map<string, number> = new Map();
async processMessage(message: SequencedMessage): Promise<void> { const { correlationId, sequenceNumber } = message;
// Get or initialize expected sequence const expected = this.expectedSequence.get(correlationId) ?? 0;
if (sequenceNumber === expected) { // In order - process immediately await this.process(message); this.expectedSequence.set(correlationId, expected + 1);
// Check buffer for next messages await this.processBuffered(correlationId); } else if (sequenceNumber > expected) { // Out of order - buffer for later this.buffer(correlationId, message);
// Set timeout for missing messages this.setMissingMessageTimeout(correlationId, expected); } else { // Duplicate or old message - ignore or log console.warn(Received old message: ${sequenceNumber} < ${expected}); } }
private buffer(correlationId: string, message: SequencedMessage): void { if (!this.messageBuffer.has(correlationId)) { this.messageBuffer.set(correlationId, []); } this.messageBuffer.get(correlationId)!.push({ message, bufferedAt: Date.now() });
// Sort buffer by sequence number this.messageBuffer.get(correlationId)!.sort( (a, b) => a.message.sequenceNumber - b.message.sequenceNumber ); }
private async processBuffered(correlationId: string): Promise<void> { const buffer = this.messageBuffer.get(correlationId); if (!buffer) return;
const expected = this.expectedSequence.get(correlationId) ?? 0;
while (buffer.length > 0 && buffer[0].message.sequenceNumber === expected) { const { message } = buffer.shift()!; await this.process(message); this.expectedSequence.set(correlationId, expected + 1); } }
private setMissingMessageTimeout(correlationId: string, expected: number): void { setTimeout(async () => { const currentExpected = this.expectedSequence.get(correlationId); if (currentExpected === expected) { // Still missing - request retransmission or skip console.warn(Message ${expected} for ${correlationId} missing, skipping); this.expectedSequence.set(correlationId, expected + 1); await this.processBuffered(correlationId); } }, 5000); } }
// Alternative: Use vector clocks for causal ordering class VectorClock { private clock: Map<string, number> = new Map();
increment(agentId: string): Map<string, number> { this.clock.set(agentId, (this.clock.get(agentId) ?? 0) + 1); return new Map(this.clock); }
merge(other: Map<string, number>): void { for (const [id, count] of other) { this.clock.set(id, Math.max(this.clock.get(id) ?? 0, count)); } }
happensBefore(a: Map<string, number>, b: Map<string, number>): boolean { let atLeastOneLess = false; for (const [id, countB] of b) { const countA = a.get(id) ?? 0; if (countA > countB) return false; if (countA < countB) atLeastOneLess = true; } return atLeastOneLess; } }
Symptoms
- Sporadic incorrect behavior
- Race condition-like bugs
- Works with slow network, fails with fast
Detection Pattern
sequence|order|before|after|depend
Message Loss Not Handled
Id
message-loss-not-handled
Summary
Messages lost in transit cause silent failures
Severity
high
Situation
Agent sends message but receiver never gets it, no retry or detection
Why
Fire-and-forget messaging. No acknowledgment protocol. Network failures not anticipated.
Solution
// Reliable messaging with acknowledgments and retries
interface ReliableMessage { messageId: string; payload: unknown; requiresAck: boolean; retryCount: number; maxRetries: number; sentAt: number; }
class ReliableMessageSender { private pendingAcks: Map<string, { message: ReliableMessage; timeoutHandle: NodeJS.Timeout; retries: number; }> = new Map();
private readonly retryDelayMs = 5000; private readonly maxRetries = 3;
async send( toAgent: string, payload: unknown, options?: { requiresAck?: boolean } ): Promise<SendResult> { const message: ReliableMessage = { messageId: crypto.randomUUID(), payload, requiresAck: options?.requiresAck ?? true, retryCount: 0, maxRetries: this.maxRetries, sentAt: Date.now() };
await this.sendWithRetry(toAgent, message);
if (message.requiresAck) { return this.waitForAck(message.messageId); }
return { success: true, messageId: message.messageId }; }
private async sendWithRetry(toAgent: string, message: ReliableMessage): Promise<void> { await this.transport.send(toAgent, message);
if (message.requiresAck) { const timeoutHandle = setTimeout(() => { this.handleTimeout(message.messageId); }, this.retryDelayMs);
this.pendingAcks.set(message.messageId, { message, timeoutHandle, retries: 0 }); } }
private async handleTimeout(messageId: string): Promise<void> { const pending = this.pendingAcks.get(messageId); if (!pending) return;
if (pending.retries < pending.message.maxRetries) { // Retry pending.retries++; console.warn(Retrying message ${messageId}, attempt ${pending.retries});
await this.transport.send(pending.message.toAgent, pending.message);
// Reset timeout pending.timeoutHandle = setTimeout(() => { this.handleTimeout(messageId); }, this.retryDelayMs * Math.pow(2, pending.retries)); // Exponential backoff } else { // Max retries exceeded this.pendingAcks.delete(messageId); this.emit('messageFailed', { messageId, reason: 'max_retries_exceeded', attempts: pending.retries + 1 }); } }
handleAck(messageId: string): void { const pending = this.pendingAcks.get(messageId); if (pending) { clearTimeout(pending.timeoutHandle); this.pendingAcks.delete(messageId); this.emit('messageAcked', { messageId }); } } }
class ReliableMessageReceiver { private processedIds: Set<string> = new Set(); private idExpiryMs = 60000;
async receive(message: ReliableMessage): Promise<void> { // Deduplicate if (this.processedIds.has(message.messageId)) { // Already processed - just ack again await this.sendAck(message.messageId, message.fromAgent); return; }
// Process await this.process(message.payload);
// Track for deduplication this.processedIds.add(message.messageId); setTimeout(() => { this.processedIds.delete(message.messageId); }, this.idExpiryMs);
// Ack if (message.requiresAck) { await this.sendAck(message.messageId, message.fromAgent); } }
private async sendAck(messageId: string, toAgent: string): Promise<void> { await this.transport.send(toAgent, { type: 'ack', messageId, timestamp: Date.now() }); } }
Symptoms
- Agents "miss" messages randomly
- Work not completed with no error
- Works locally, fails in production
Detection Pattern
send|emit|publish|dispatch
Broadcast Storm
Id
broadcast-storm
Summary
Agents react to events by emitting more events, causing cascade
Severity
critical
Situation
One event triggers chain reaction consuming all resources
Why
Event handlers emit events. No deduplication or rate limiting. Feedback loops in event graph.
Solution
// Prevent broadcast storms with rate limiting and deduplication
class SafeEventBus { private eventCounts: Map<string, number[]> = new Map(); private readonly rateLimitWindow = 1000; // 1 second private readonly maxEventsPerWindow = 100;
private recentEvents: Map<string, number> = new Map(); private readonly dedupeWindow = 5000; // 5 seconds
async publish(event: AgentEvent): Promise<boolean> { // Check rate limit if (this.isRateLimited(event.eventType)) { console.warn(Rate limit exceeded for ${event.eventType}); return false; }
// Check for duplicate/similar events if (this.isDuplicate(event)) { console.debug(Duplicate event suppressed: ${event.eventId}); return false; }
// Track for rate limiting this.trackEvent(event.eventType);
// Track for deduplication this.trackForDedupe(event);
// Actually publish await this.doPublish(event); return true; }
private isRateLimited(eventType: string): boolean { const counts = this.eventCounts.get(eventType) || []; const now = Date.now(); const recentCounts = counts.filter(t => now - t < this.rateLimitWindow);
return recentCounts.length >= this.maxEventsPerWindow; }
private trackEvent(eventType: string): void { if (!this.eventCounts.has(eventType)) { this.eventCounts.set(eventType, []); } this.eventCounts.get(eventType)!.push(Date.now());
// Cleanup old entries const now = Date.now(); this.eventCounts.set( eventType, this.eventCounts.get(eventType)!.filter(t => now - t < this.rateLimitWindow * 2) ); }
private isDuplicate(event: AgentEvent): boolean { // Create content hash for comparison const hash = this.hashEvent(event); const lastSeen = this.recentEvents.get(hash);
if (lastSeen && Date.now() - lastSeen < this.dedupeWindow) { return true; }
return false; }
private trackForDedupe(event: AgentEvent): void { const hash = this.hashEvent(event); this.recentEvents.set(hash, Date.now());
// Cleanup for (const [h, timestamp] of this.recentEvents) { if (Date.now() - timestamp > this.dedupeWindow) { this.recentEvents.delete(h); } } }
private hashEvent(event: AgentEvent): string { // Hash key fields for comparison return JSON.stringify({ type: event.eventType, source: event.source, payload: event.payload }); } }
// Circuit breaker for event handlers class EventHandlerCircuitBreaker { private failures: Map<string, number> = new Map(); private lastFailure: Map<string, number> = new Map(); private readonly failureThreshold = 5; private readonly resetTimeout = 30000;
async wrap( handlerId: string, handler: (event: AgentEvent) => Promise<void> ): Promise<(event: AgentEvent) => Promise<void>> { return async (event: AgentEvent) => { // Check if circuit is open if (this.isOpen(handlerId)) { console.warn(Circuit breaker open for ${handlerId}, skipping); return; }
try { await handler(event); this.recordSuccess(handlerId); } catch (error) { this.recordFailure(handlerId); throw error; } }; }
private isOpen(handlerId: string): boolean { const failures = this.failures.get(handlerId) || 0; const lastFail = this.lastFailure.get(handlerId) || 0;
if (failures >= this.failureThreshold) { // Check if reset timeout has passed if (Date.now() - lastFail > this.resetTimeout) { this.failures.set(handlerId, 0); return false; } return true; }
return false; } }
Symptoms
- System becomes unresponsive
- Memory/CPU spike
- Logs fill with repeated events
Detection Pattern
emit|publish|broadcast|trigger
Agent Communication - Validations
Untyped Message Passing
Id
untyped-message
Severity
medium
Type
regex
Pattern
send\s\([^)]\{[^}]\}|emit\s\([^)]\{[^}]\}
Negative Pattern
Schema|schema|z\.|zod|type:
Message
Sending untyped message. Use schema validation for type safety.
Fix Action
Define message schema with Zod and validate before sending
Applies To
- *.ts
- *.js
Message Without Unique ID
Id
no-message-id
Severity
medium
Type
regex
Pattern
(?:send|emit|publish)\s\(\s\{(?![^}]*(?:id|messageId|eventId))
Message
Message without unique identifier. Debugging and deduplication will fail.
Fix Action
Add messageId: crypto.randomUUID() to all messages
Applies To
- *.ts
- *.js
Missing Correlation ID for Request-Response
Id
no-correlation-id
Severity
medium
Type
regex
Pattern
request|response|reply
Negative Pattern
correlationId|correlation_id|requestId|request_id
Message
Request-response pattern without correlation ID.
Fix Action
Include correlationId to match responses to requests
Applies To
- *.ts
- *.js
Critical Message Without Acknowledgment
Id
fire-and-forget-critical
Severity
high
Type
regex
Pattern
(?:send|emit)\s\([^)](?:critical|important|required)
Negative Pattern
ack|acknowledge|confirm|waitFor
Message
Critical message sent without acknowledgment mechanism.
Fix Action
Use reliable messaging with acknowledgments for critical messages
Applies To
- *.ts
- *.js
Waiting for Message Without Timeout
Id
no-message-timeout
Severity
high
Type
regex
Pattern
await\s+(?:waitFor|receive|getMessage)
Negative Pattern
timeout|Promise\.race|AbortController
Message
Waiting for message without timeout. Could hang indefinitely.
Fix Action
Add timeout: Promise.race([waitFor(), timeout(5000)])
Applies To
- *.ts
- *.js
Event Handler Without Error Handling
Id
event-handler-no-error-handling
Severity
high
Type
regex
Pattern
\.on\s\([^)]+,\s(?:async\s)?\([^)]\)\s=>\s\{
Negative Pattern
try|catch|\.catch|error
Message
Event handler without error handling. Errors will propagate.
Fix Action
Wrap handler in try-catch to prevent cascade failures
Applies To
- *.ts
- *.js
Message Passing Without Logging
Id
no-message-logging
Severity
medium
Type
regex
Pattern
(?:send|emit|publish|receive)\s*\(
Negative Pattern
log|trace|record|audit
Message
Message passing without logging. Debugging will be difficult.
Fix Action
Log all messages with timestamps for debugging
Applies To
- *.ts
- *.js
Event Handler Emitting Same Event Type
Id
circular-event-risk
Severity
critical
Type
regex
Pattern
\.on\s\(["'](\w+)["'][^}]+emit\s\("'["']
Message
Event handler emits same event type it listens to. Risk of infinite loop.
Fix Action
Break the cycle or add deduplication/rate limiting
Applies To
- *.ts
- *.js
Blocking Operation in Event Handler
Id
blocking-in-event-handler
Severity
medium
Type
regex
Pattern
\.on\s\([^)]+,\s\([^)]\)\s=>\s\{[^}](?:sleep|setTimeout.*await|while)
Message
Blocking operation in event handler. May slow down event processing.
Fix Action
Use non-blocking patterns or spawn separate task
Applies To
- *.ts
- *.js
Event Publishing Without Rate Limiting
Id
no-rate-limiting-events
Severity
medium
Type
regex
Pattern
while.emit|for.emit|map.*emit
Negative Pattern
rateLimit|throttle|debounce|limit
Message
Publishing events in loop without rate limiting.
Fix Action
Add rate limiting to prevent broadcast storms
Applies To
- *.ts
- *.js