Skip to content

BaseAgent

Abstract base class for all agents in the framework. Provides execution lifecycle management, automatic timeout handling, error recovery, execution statistics, and opt-in distributed tracing.

Import

import { BaseAgent } from "@modernpath/agent-framework";

Constructor

abstract class BaseAgent {
  constructor(
    name: string,
    version: string,
    toolRegistry: ToolRegistry
  )
}
Parameter Type Description
name string Unique identifier for the agent
version string Semantic version string (e.g. "1.0.0")
toolRegistry ToolRegistry Registry of tools available to this agent

The constructor initializes a default AgentConfig with a 60-second timeout and debug mode disabled.

Class Signature

abstract class BaseAgent {
  // -- Read-only properties --
  readonly name: string;
  readonly version: string;
  description: string;

  // -- Core execution --
  async execute(context: AgentContext): Promise<AgentResult>;
  protected abstract executeInternal(context: AgentContext): Promise<any>;

  // -- Tool access --
  protected async useTool(name: string, params: Record<string, any>): Promise<any>;
  protected getToolMetadata(name: string): ToolMetadata | undefined;
  getAvailableTools(): string[];

  // -- Capabilities --
  getCapabilities(): AgentCapability[];
  hasCapability(capability: AgentCapability): boolean;

  // -- Configuration --
  getConfig(): AgentConfig;
  setConfig(config: Partial<AgentConfig>): void;

  // -- Statistics --
  getStats(): AgentStats;
  resetStats(): void;

  // -- Introspection --
  getInfo(): AgentInfo;

  // -- Lifecycle hooks --
  async initialize(): Promise<void>;
  async cleanup(): Promise<void>;

  // -- Tracing --
  setTraceStore(
    store: TraceStore,
    configSnapshotId?: string,
    costCalculator?: CostCalculator
  ): void;
  get traceStore(): TraceStore | undefined;

  // -- Cloning --
  abstract clone(toolRegistry: ToolRegistry): BaseAgent;
}

Methods

Core Execution

Method Returns Description
execute(context) Promise<AgentResult> Public entry point. Validates context, runs executeInternal() with timeout, tracks statistics, and optionally records a trace. Do not override this method.
executeInternal(context) Promise<any> Abstract. Implement your agent's logic here. The return value is wrapped in AgentResult.data.

Tool Access

Method Returns Description
useTool(name, params) Promise<any> Invoke a registered tool by name. Throws if the tool is not found. Automatically tracked in AgentResult.toolsUsed.
getToolMetadata(name) ToolMetadata \| undefined Retrieve metadata for a registered tool. Useful for inspecting parameter schemas at runtime.
getAvailableTools() string[] List all tool names in the agent's registry.

Capabilities

Method Returns Description
getCapabilities() AgentCapability[] Returns a copy of the agent's declared capabilities.
hasCapability(capability) boolean Checks whether all tools required by the capability are present in the registry.

Configuration

Method Returns Description
getConfig() AgentConfig Returns a copy of the current agent configuration.
setConfig(config) void Merges partial config into the current configuration. Use this to change timeout, enable debug mode, etc.

Statistics

Method Returns Description
getStats() AgentStats Returns execution statistics: count, success/failure counts, average duration, and success rate.
resetStats() void Resets all counters to zero.

AgentStats shape:

{
  executionCount: number;
  averageExecutionTime: number;  // milliseconds
  successRate: number;           // 0.0 to 1.0
  successCount: number;
  failureCount: number;
}

Introspection

Method Returns Description
getInfo() AgentInfo Returns a summary of the agent: name, version, description, capabilities, available tools, and stats.

Lifecycle Hooks

Method Returns Description
initialize() Promise<void> Called before the first execution. Override to perform setup (e.g., verify tool availability, warm caches).
cleanup() Promise<void> Called during shutdown. Override to release resources.

Tracing

Method Returns Description
setTraceStore(store, configSnapshotId?, costCalculator?) void Attach a trace store. Once set, every execute() call automatically creates and finalizes a trace.
traceStore (getter) TraceStore \| undefined Read-only access to the configured trace store.

Tracing is opt-in

If no TraceStore is attached, the agent runs identically but without recording traces. This keeps the framework lightweight for development and testing.

Cloning

Method Returns Description
clone(toolRegistry) BaseAgent Abstract. Create a copy of this agent with a different tool registry. Used by the orchestrator to create isolated agent instances.

Execution Lifecycle

When you call agent.execute(context), the following happens in order:

  1. Context validation -- userId, auditingId, and prompt are checked
  2. Trace start -- if a TraceStore is attached, a new trace is created with a unique ID
  3. Timeout race -- executeInternal() races against the configured timeout (default: 60s)
  4. Tool tracking -- every useTool() call is recorded in AgentResult.toolsUsed
  5. Statistics update -- execution count, duration, and success/failure counters are updated
  6. Trace finalization -- the trace is marked as completed or failed with duration and cost data
  7. Result return -- an AgentResult is returned with success, data or error, and metadata
sequenceDiagram
    participant Caller
    participant BaseAgent
    participant executeInternal
    participant TraceStore

    Caller->>BaseAgent: execute(context)
    BaseAgent->>BaseAgent: validateContext()
    opt TraceStore attached
        BaseAgent->>TraceStore: startTrace()
    end
    BaseAgent->>executeInternal: executeInternal(context)
    executeInternal-->>BaseAgent: result data
    BaseAgent->>BaseAgent: updateStats()
    opt TraceStore attached
        BaseAgent->>TraceStore: endTrace()
    end
    BaseAgent-->>Caller: AgentResult

Complete Example

The following example shows how to create a custom agent that uses tools and tracing:

import {
  BaseAgent,
  ToolRegistry,
  AgentContext,
  AgentResult,
  AgentCapability,
} from "@modernpath/agent-framework";
import type { GeminiClient } from "@modernpath/agent-framework";

class SentimentAgent extends BaseAgent {
  // Declare capabilities so callers can inspect what this agent can do
  protected capabilities: AgentCapability[] = [
    {
      name: "sentiment-analysis",
      description: "Analyzes sentiment of user-provided text",
      requiredTools: ["gemini-generate"], // (1)!
      examples: ["Analyze the sentiment of this review"],
    },
  ];

  constructor(
    toolRegistry: ToolRegistry,
    private readonly gemini: GeminiClient,
  ) {
    super("SentimentAgent", "1.0.0", toolRegistry);
    this.description = "Analyzes text sentiment using Gemini.";
  }

  protected async executeInternal(context: AgentContext): Promise<any> {
    const text = context.parameters.text || context.prompt;

    // Use the Gemini client to classify sentiment
    const response = await this.gemini.generateContent(
      `Classify the sentiment of the following text as positive, negative, or neutral. ` +
      `Return JSON: { "sentiment": "...", "confidence": 0.0-1.0 }\n\nText: ${text}`,
      { temperature: 0.1, maxOutputTokens: 256 },
    );

    // Parse the LLM response
    const parsed = JSON.parse(response.text);

    return {
      sentiment: parsed.sentiment,
      confidence: parsed.confidence,
      inputLength: text.length,
    };
  }

  clone(toolRegistry: ToolRegistry): BaseAgent {
    return new SentimentAgent(toolRegistry, this.gemini);
  }
}

// --- Usage ---

const registry = new ToolRegistry();
const agent = new SentimentAgent(registry, geminiClient);

// Optional: enable tracing
agent.setTraceStore(myTraceStore);

// Optional: adjust timeout
agent.setConfig({ timeout: 30_000, debug: true });

const result: AgentResult = await agent.execute({
  userId: 42,
  auditingId: 1001,
  prompt: "Analyze this review",
  parameters: {
    text: "This product exceeded my expectations. Highly recommended!",
  },
});

if (result.success) {
  console.log(result.data);
  // { sentiment: "positive", confidence: 0.95, inputLength: 57 }
}

console.log(agent.getStats());
// { executionCount: 1, successCount: 1, failureCount: 0, ... }
  1. The requiredTools array declares which tools must be present in the registry for this capability to be available. hasCapability() checks this at runtime.

Timeout Handling

The default timeout is 60 seconds. If executeInternal() does not resolve within the timeout, the execution is rejected with an error:

Execution timeout after 60000ms

To change the timeout:

agent.setConfig({ timeout: 120_000 }); // 2 minutes

Timeout does not cancel the internal promise

The timeout mechanism uses Promise.race. The underlying executeInternal() call is not cancelled -- it will continue running in the background. Design your agent logic to handle this gracefully if needed.

Error Handling

If executeInternal() throws, the error is caught and returned as a failed AgentResult:

const result = await agent.execute(context);

if (!result.success) {
  console.error(result.error); // The error message string
}

The agent's failureCount statistic is incremented, and if tracing is enabled, the trace is marked with status "failed".