Agent Development Guide

Tier 2 | Walkthrough for creating and extending agents Hub: README.md | Architecture: AGENT_SYSTEM.md


Overview

This guide walks through creating custom agents for nexus-agents. Agents follow the IAgent interface and can participate in collaboration protocols.


Agent Architecture

Core Interfaces

interface IAgent {
  readonly id: string;
  readonly role: AgentRole;
  readonly state: AgentState;
  readonly capabilities: readonly AgentCapability[];

  execute(task: Task): Promise<Result<TaskResult, AgentError>>;
  handleMessage(msg: AgentMessage): Promise<Result<AgentResponse, AgentError>>;
  initialize(ctx: AgentContext): Promise<Result<void, AgentError>>;
  cleanup(): Promise<void>;
}

type AgentState = 'idle' | 'thinking' | 'acting' | 'waiting' | 'error';

type AgentRole =
  | 'tech-lead'
  | 'architect'
  | 'code'
  | 'security'
  | 'docs'
  | 'test'
  | 'research'
  | 'devops'
  | 'worker'
  | 'thinker'
  | 'verifier';

State Machine

Idle → Thinking → Acting → Waiting → Thinking → ...
         ↓          ↓         ↓
       Error      Error     Error
         ↓          ↓         ↓
       Idle       Idle      Idle

Creating a Basic Agent

Step 1: Define the Expert Configuration

// src/agents/experts/my-expert.ts
import type { ExpertConfig } from './expert-types.js';

export const myExpertConfig: ExpertConfig = {
  type: 'my-domain',
  prompt: `You are an expert in [domain].

Your capabilities:
- Capability 1
- Capability 2

Your constraints:
- Always [constraint]
- Never [constraint]`,
  tier: 'balanced', // 'fast' | 'balanced' | 'powerful'
  tools: ['read_files', 'analyze_code'],
};

Step 2: Add to Built-In Experts

// src/agents/experts/expert-config.ts
// Add your expert to the BUILT_IN_EXPERTS map
export const BUILT_IN_EXPERTS: Record<BuiltInExpertType, ExpertConfig> = {
  // ... existing experts ...
  'my-domain': myExpertConfig,
};

Step 3: Use via MCP Tools

// Via orchestrate tool
{ task: "Review this code for [domain] issues" }

// Via expert tool
{ type: "my-domain", task: "Analyze..." }

Creating a Custom Agent Class

For complex agents requiring custom logic:

Step 1: Create Agent Class

// src/agents/custom/my-agent.ts
import type { IAgent, Task, TaskResult, AgentContext } from '../../core/types/index.js';
import { Result } from '../../core/result.js';
import { AgentError } from '../../core/errors.js';

export class MyAgent implements IAgent {
  readonly id: string;
  readonly role: AgentRole = 'worker';
  readonly capabilities = ['custom-capability'] as const;

  private _state: AgentState = 'idle';
  private context: AgentContext | null = null;

  constructor(id: string) {
    this.id = id;
  }

  get state(): AgentState {
    return this._state;
  }

  async initialize(ctx: AgentContext): Promise<Result<void, AgentError>> {
    this.context = ctx;
    this._state = 'idle';
    return { ok: true, value: undefined };
  }

  async execute(task: Task): Promise<Result<TaskResult, AgentError>> {
    this._state = 'thinking';

    try {
      // 1. Analyze task
      const analysis = this.analyzeTask(task);

      this._state = 'acting';

      // 2. Execute logic
      const result = await this.performAction(analysis);

      this._state = 'idle';

      return {
        ok: true,
        value: {
          agentId: this.id,
          output: result,
          metadata: { duration: Date.now() - task.createdAt },
        },
      };
    } catch (error) {
      this._state = 'error';
      return {
        ok: false,
        error: new AgentError(`Execution failed: ${error}`),
      };
    }
  }

  async handleMessage(msg: AgentMessage): Promise<Result<AgentResponse, AgentError>> {
    // Handle inter-agent messages
    return {
      ok: true,
      value: { type: 'ack', content: 'Received' },
    };
  }

  async cleanup(): Promise<void> {
    this.context = null;
    this._state = 'idle';
  }

  private analyzeTask(task: Task): TaskAnalysis {
    // Task analysis logic
    return {
      /* ... */
    };
  }

  private async performAction(analysis: TaskAnalysis): Promise<string> {
    // Action execution logic
    return 'Result';
  }
}

Step 2: Add Tests

// src/agents/custom/my-agent.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { MyAgent } from './my-agent.js';

describe('MyAgent', () => {
  let agent: MyAgent;

  beforeEach(() => {
    agent = new MyAgent('test-agent');
  });

  it('should initialize correctly', async () => {
    const result = await agent.initialize({
      /* context */
    });
    expect(result.ok).toBe(true);
    expect(agent.state).toBe('idle');
  });

  it('should execute task', async () => {
    await agent.initialize({
      /* context */
    });

    const result = await agent.execute({
      id: 'task-1',
      description: 'Test task',
      createdAt: Date.now(),
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.agentId).toBe('test-agent');
    }
  });

  it('should handle errors gracefully', async () => {
    // Test error handling
  });
});

Collaboration Protocols

Integrating with Protocols

Agents can participate in collaboration protocols:

import { CollaborationSession } from '../collaboration/collaboration-session.js';

// Sequential collaboration
const session = new CollaborationSession({
  pattern: 'sequential',
  agents: [agent1, agent2, agent3],
});

const result = await session.execute(task);

Implementing Protocol-Aware Agents

For agents that need to participate in specific protocols:

class TrinityWorkerAgent implements IAgent {
  readonly role: AgentRole = 'worker';

  async execute(task: Task): Promise<Result<TaskResult, AgentError>> {
    // Worker role: Execute based on thinker's plan
    const plan = task.context?.plan;

    if (!plan) {
      return { ok: false, error: new AgentError('No plan provided') };
    }

    // Execute plan steps
    const results = await this.executePlan(plan);

    return {
      ok: true,
      value: { output: results, metadata: {} },
    };
  }
}

Event Bus Integration

Emitting Events

import { getGlobalEventBus } from '../collaboration/event-bus.js';

class MyAgent implements IAgent {
  private eventBus = getGlobalEventBus();

  async execute(task: Task): Promise<Result<TaskResult, AgentError>> {
    // Emit task started event
    this.eventBus.emit({
      topic: 'agent.task_delegated',
      agentId: this.id,
      payload: { taskId: task.id },
      timestamp: Date.now(),
    });

    // ... execution ...

    // Emit result
    this.eventBus.emit({
      topic: 'agent.result_broadcast',
      agentId: this.id,
      payload: { taskId: task.id, success: true },
      timestamp: Date.now(),
    });

    return result;
  }
}

Subscribing to Events

constructor() {
  const eventBus = getGlobalEventBus();

  // Subscribe to consensus events
  eventBus.subscribe('consensus.*', (event) => {
    if (event.topic === 'consensus.vote_requested') {
      this.handleVoteRequest(event.payload);
    }
  });
}

Best Practices

State Management

  • Always transition through proper states
  • Reset to idle after error
  • Never skip states (idle → acting is invalid)

Error Handling

async execute(task: Task): Promise<Result<TaskResult, AgentError>> {
  try {
    // Execution logic
  } catch (error) {
    this._state = 'error';

    // Log error with context
    logger.error('Agent execution failed', {
      agentId: this.id,
      taskId: task.id,
      error: error instanceof Error ? error.message : String(error),
    });

    // Reset state
    this._state = 'idle';

    return {
      ok: false,
      error: new AgentError(`Execution failed: ${error}`),
    };
  }
}

Context Management

  • Use ContextPruner for large contexts
  • Prioritize recent and relevant information
  • Clear context on cleanup

Source Files

FilePurpose
src/core/types/agent.tsAgent type definitions
src/agents/base-agent.tsBase agent implementation
src/agents/experts/expert-factory.tsExpert registration
src/agents/collaboration/Collaboration protocols
src/agents/context-pruner.tsContext management