Skip to main content

Overview

The Agent interface defines how the orchestrator interacts with specific AI coding tools. Agent plugins know how to launch agents, detect their activity state, extract session information, and handle restoration. Plugin Slot: agent
Default Plugin: claude-code

Interface Definition

export interface Agent {
  readonly name: string;
  readonly processName: string;
  readonly promptDelivery?: "inline" | "post-launch";
  
  getLaunchCommand(config: AgentLaunchConfig): string;
  getEnvironment(config: AgentLaunchConfig): Record<string, string>;
  detectActivity(terminalOutput: string): ActivityState;
  getActivityState(session: Session, readyThresholdMs?: number): Promise<ActivityDetection | null>;
  isProcessRunning(handle: RuntimeHandle): Promise<boolean>;
  getSessionInfo(session: Session): Promise<AgentSessionInfo | null>;
  getRestoreCommand?(session: Session, project: ProjectConfig): Promise<string | null>;
  postLaunchSetup?(session: Session): Promise<void>;
  setupWorkspaceHooks?(workspacePath: string, config: WorkspaceHooksConfig): Promise<void>;
}

Properties

name
string
required
Plugin name identifier (e.g. "claude-code", "codex", "aider").
processName
string
required
Process name to look for when checking if agent is running (e.g. "claude", "codex", "aider").
promptDelivery
'inline' | 'post-launch'
How the initial prompt should be delivered:
  • "inline" (default): prompt included in launch command (e.g. -p flag)
  • "post-launch": prompt sent via runtime.sendMessage() after agent starts
Use "post-launch" for agents where inlining the prompt causes one-shot/exit behavior.

Methods

getLaunchCommand
(config: AgentLaunchConfig) => string
required
Get the shell command to launch this agent.Parameters:
  • config - Launch configuration with session ID, project config, issue, prompt, model, permissions, system prompt
Returns: Shell command string
getEnvironment
(config: AgentLaunchConfig) => Record<string, string>
required
Get environment variables for the agent process.Parameters:
  • config - Launch configuration
Returns: Key-value map of environment variables
detectActivity
(terminalOutput: string) => ActivityState
required
Deprecated: Detect agent activity from terminal output (hacky, use getActivityState() instead).Parameters:
  • terminalOutput - Recent terminal output from runtime
Returns: ActivityState
getActivityState
(session: Session, readyThresholdMs?: number) => Promise<ActivityDetection | null>
required
Get current activity state using agent-native mechanism (JSONL, SQLite, etc.). This is the preferred method.Parameters:
  • session - Session to check
  • readyThresholdMs - Milliseconds before “ready” becomes “idle” (default: 300000 = 5 min)
Returns: ActivityDetection with state and timestamp, or null if cannot determine
isProcessRunning
(handle: RuntimeHandle) => Promise<boolean>
required
Check if agent process is running given a runtime handle.Parameters:
  • handle - Runtime handle from session
Returns: true if process is running, false otherwise
getSessionInfo
(session: Session) => Promise<AgentSessionInfo | null>
required
Extract information from agent’s internal data (summary, cost, session ID).Parameters:
  • session - Session to extract info from
Returns: AgentSessionInfo or null if not available
getRestoreCommand
(session: Session, project: ProjectConfig) => Promise<string | null>
Optional: Get a launch command that resumes a previous session.Parameters:
  • session - Session to restore
  • project - Project configuration
Returns: Command string or null if no previous session found (falls back to getLaunchCommand())
postLaunchSetup
(session: Session) => Promise<void>
Optional: Run setup after agent is launched (e.g. configure MCP servers).Parameters:
  • session - Newly launched session
setupWorkspaceHooks
(workspacePath: string, config: WorkspaceHooksConfig) => Promise<void>
Optional: Set up agent-specific hooks/config in the workspace for automatic metadata updates.Called once per workspace during ao init/start and when creating new worktrees.Critical: The dashboard depends on metadata being auto-updated when agents run git/gh commands. Without this, PRs created by agents never show up.Parameters:
  • workspacePath - Path to workspace
  • config - Hooks configuration with data directory and optional session ID

AgentLaunchConfig

export interface AgentLaunchConfig {
  sessionId: SessionId;
  projectConfig: ProjectConfig;
  issueId?: string;
  prompt?: string;
  permissions?: "skip" | "default";
  model?: string;
  systemPrompt?: string;
  systemPromptFile?: string;
}
sessionId
SessionId
required
Session identifier
projectConfig
ProjectConfig
required
Project configuration from orchestrator config
issueId
string
Issue ID if working on a specific issue
prompt
string
Initial prompt for the agent
permissions
'skip' | 'default'
Permission handling mode
model
string
Model to use (e.g. "claude-sonnet-4-20250514")
systemPrompt
string
System prompt for orchestrator context (short prompts only). Passed via:
  • Claude Code: --append-system-prompt
  • Codex: --system-prompt or AGENTS.md
  • Aider: --system-prompt flag
systemPromptFile
string
Path to file containing system prompt. Preferred for long prompts (e.g. orchestrator prompts) to avoid shell truncation. Takes precedence over systemPrompt.

AgentSessionInfo

export interface AgentSessionInfo {
  summary: string | null;
  summaryIsFallback?: boolean;
  agentSessionId: string | null;
  cost?: CostEstimate;
}
summary
string | null
required
Agent’s auto-generated summary of what it’s working on
summaryIsFallback
boolean
True when summary is a fallback (e.g. truncated first user message), not a real agent summary
agentSessionId
string | null
required
Agent’s internal session ID for resume
cost
CostEstimate
Cost estimate (tokens and USD)

ActivityDetection

export interface ActivityDetection {
  state: ActivityState;
  timestamp?: Date;
}
state
ActivityState
required
Current activity state
timestamp
Date
When activity was last observed (e.g. agent log file mtime)

WorkspaceHooksConfig

export interface WorkspaceHooksConfig {
  dataDir: string;
  sessionId?: string;
}
dataDir
string
required
Data directory where session metadata files are stored
sessionId
string
Optional session ID (may not be known at ao init time)

CostEstimate

export interface CostEstimate {
  inputTokens: number;
  outputTokens: number;
  estimatedCostUsd: number;
}

Usage Examples

Implementing an Agent Plugin

import type { Agent, AgentLaunchConfig, ActivityDetection } from "@composio/ao-core";
import { readFile } from "node:fs/promises";
import { join } from "node:path";

export function create(): Agent {
  return {
    name: "claude-code",
    processName: "claude",
    promptDelivery: "post-launch",
    
    getLaunchCommand(config: AgentLaunchConfig): string {
      const { projectConfig, model, permissions, systemPromptFile } = config;
      
      let cmd = `claude --project ${projectConfig.path}`;
      
      if (model) {
        cmd += ` --model ${model}`;
      }
      
      if (permissions === "skip") {
        cmd += " --skip-permissions";
      }
      
      if (systemPromptFile) {
        cmd += ` --append-system-prompt "$(cat ${systemPromptFile})"`;
      }
      
      return cmd;
    },
    
    getEnvironment(config: AgentLaunchConfig): Record<string, string> {
      return {
        ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
        SESSION_ID: config.sessionId
      };
    },
    
    detectActivity(terminalOutput: string): ActivityState {
      // Legacy method - prefer getActivityState()
      if (terminalOutput.includes("Thinking...")) return "active";
      if (terminalOutput.includes("Reply, or press Ctrl+C to exit")) return "ready";
      return "idle";
    },
    
    async getActivityState(session: Session, readyThresholdMs = 300_000): Promise<ActivityDetection | null> {
      if (!session.workspacePath) return null;
      
      const eventsPath = join(session.workspacePath, ".claude", "events.jsonl");
      
      try {
        const content = await readFile(eventsPath, "utf-8");
        const lines = content.trim().split("\n");
        const lastEvent = JSON.parse(lines[lines.length - 1]);
        
        const timestamp = new Date(lastEvent.timestamp);
        const age = Date.now() - timestamp.getTime();
        
        let state: ActivityState;
        if (lastEvent.type === "thinking") {
          state = "active";
        } else if (lastEvent.type === "user_input_request") {
          state = "waiting_input";
        } else if (age > readyThresholdMs) {
          state = "idle";
        } else {
          state = "ready";
        }
        
        return { state, timestamp };
      } catch {
        return null;
      }
    },
    
    async isProcessRunning(handle: RuntimeHandle): Promise<boolean> {
      const { stdout } = await execFileAsync("pgrep", ["-f", "claude"], {
        timeout: 5_000
      }).catch(() => ({ stdout: "" }));
      return stdout.trim().length > 0;
    },
    
    async getSessionInfo(session: Session): Promise<AgentSessionInfo | null> {
      if (!session.workspacePath) return null;
      
      const dbPath = join(session.workspacePath, ".claude", "session.db");
      // Read from SQLite database
      // ...
      
      return {
        summary: "Fix authentication bug",
        summaryIsFallback: false,
        agentSessionId: "session-123",
        cost: {
          inputTokens: 15000,
          outputTokens: 8000,
          estimatedCostUsd: 0.45
        }
      };
    }
  };
}

Using Agent in Session Manager

import type { Agent, AgentLaunchConfig } from "@composio/ao-core";

const agent: Agent = registry.get("agent", "claude-code");

const launchConfig: AgentLaunchConfig = {
  sessionId: "my-app-1",
  projectConfig: project,
  prompt: "Fix the authentication bug",
  model: "claude-sonnet-4-20250514",
  permissions: "default"
};

const command = agent.getLaunchCommand(launchConfig);
const env = agent.getEnvironment(launchConfig);

// Create runtime with agent command
const handle = await runtime.create({
  sessionId: launchConfig.sessionId,
  workspacePath: "/workspace/my-app-1",
  launchCommand: command,
  environment: env
});

// If post-launch prompt delivery
if (agent.promptDelivery === "post-launch" && launchConfig.prompt) {
  await runtime.sendMessage(handle, launchConfig.prompt);
}

Implementation Notes

Prompt Delivery

For agents where -p "prompt" causes immediate exit after completion:
  • Set promptDelivery: "post-launch"
  • Launch agent in interactive mode
  • Send prompt via runtime.sendMessage() after launch

Activity Detection

Prefer getActivityState() over detectActivity():
  • Uses agent-native mechanisms (JSONL logs, SQLite)
  • More reliable than terminal output parsing
  • Provides timestamps for staleness detection

Workspace Hooks

Critical for dashboard functionality:
  • Claude Code: write .claude/settings.json with PostToolUse hook
  • Hook updates metadata when agent runs git/gh commands
  • Without hooks, PRs created by agents won’t appear in dashboard

Built-in Plugins

  • claude-code - Claude Code (default)
  • codex - Codex
  • aider - Aider
  • opencode - OpenCode

See Also

  • Runtime - Runtime execution environment interface
  • Session - Session interface
  • Workspace - Workspace isolation interface

Build docs developers (and LLMs) love