Skip to main content

Agent Plugin

Overview

The Agent package serves as the core orchestration system for managing AI agents within the TokenRing ecosystem. It provides a complete agent framework with comprehensive state management, event handling, command execution, tool integration, and lifecycle management. This package enables the creation and management of individual AI agents that integrate seamlessly with the TokenRing application framework.

Key Features

  • Agent Management: Create, spawn, and manage individual AI agents with unique configurations
  • State Management: Persistent state with serialization and checkpointing for session recovery
  • Event System: Comprehensive event handling and emission for chat output, reasoning, and system messages
  • Command System: Extensible slash command interface with built-in commands like help, cost, and settings
  • Tool Integration: Tool execution with context and parameter validation
  • Hook System: Lifecycle hooks for extensibility (before/after chat completion, agent events)
  • Human Interface: Request/response system for human interaction and input with form support
  • Sub-Agent Support: Create and manage child agents with independent state
  • Cost Tracking: Monitor and track resource usage across API calls and tokens
  • RPC Integration: JSON-RPC endpoints for remote agent management
  • Plugin Integration: Automatic integration with TokenRing applications
  • Idle/Max Runtime Management: Automatic cleanup of idle or long-running agents
  • Status Line Management: Visual indicators for busy state and status messages
  • Todo Management: Built-in todo list with sub-agent state transfer capability

Core Components

Agent Class

The central agent implementation providing comprehensive AI agent functionality:

import Agent from "@tokenring-ai/agent";

const agent = new Agent(app, {
name: "My Agent",
description: "Custom development agent",
category: "development",
debug: false,
initialCommands: [],
headless: false,
callable: true,
idleTimeout: 0,
maxRunTime: 0,
subAgent: {},
enabledHooks: [],
todos: {}
});

Key Properties:

  • id: Unique agent identifier (UUID)
  • name: Agent name
  • description: Agent description
  • config: Parsed agent configuration
  • debugEnabled: Debug logging toggle
  • headless: Headless operation mode
  • app: TokenRing application instance
  • stateManager: State management system

State Management Methods:

  • initializeState<T>(ClassType, props): Initialize state slice
  • getState<T>(ClassType): Retrieve state slice
  • mutateState<T>(ClassType, callback): Modify state slice
  • subscribeState<T>(ClassType, callback): Subscribe to state changes
  • waitForState<T>(ClassType, predicate): Wait for state condition
  • timedWaitForState<T>(ClassType, predicate, timeout): Wait with timeout
  • subscribeStateAsync<T>(ClassType, callback): Subscribe asynchronously
  • generateCheckpoint(): Create state checkpoint
  • restoreState(state): Restore from checkpoint

Input Processing:

  • handleInput({message}): Process user input with event emission
  • runCommand(command): Execute agent commands
  • busyWhile<T>(message, awaitable): Execute with busy state
  • setBusyWith(message): Set busy status indicator
  • setStatusLine(status): Set status line indicator

Event Emission:

  • chatOutput(content): Emit chat output
  • reasoningOutput(content): Emit reasoning content
  • infoMessage(...messages): Emit info messages
  • warningMessage(...messages): Emit warning messages
  • errorMessage(...messages): Emit error messages
  • debugMessage(...messages): Emit debug messages (if debugEnabled)
  • emit(event): Emit custom events

Human Interface:

  • askForConfirmation({ message, label, default, timeout }): Request confirmation
  • askForText({ message, label, masked }): Request text input
  • askQuestion<T>(question): Request human input with various question types
  • sendQuestionResponse(requestId, response): Send human response

Lifecycle Management:

  • requestAbort(reason): Abort current operations
  • getAbortSignal(): Get abort signal
  • getIdleDuration(): Get time since last activity (returns number in milliseconds)
  • getRunDuration(): Get total run duration (returns number in milliseconds)
  • reset(what): Reset specific state components
  • shutdown(reason): Shutdown agent completely

Configuration Access:

  • getAgentConfigSlice<T>(key, schema): Get config value with validation

Checkpoint Creation:

  • static createAgentFromCheckpoint(app, checkpoint, {headless}): Create agent from checkpoint

AgentManager Service

Central service for managing agent lifecycles and configurations:

const agentManager = new AgentManager(app);

// Add agent configurations
agentManager.addAgentConfigs({
myAgent: {
name: "My Agent",
description: "Custom agent description",
category: "development",
debug: false,
initialCommands: [],
headless: false,
callable: true,
idleTimeout: 0,
maxRunTime: 0,
subAgent: {},
enabledHooks: [],
todos: {}
}
});

// Spawn agents
const agent = await agentManager.spawnAgent({
agentType: "myAgent",
headless: false
});

Key Methods:

  • addAgentConfig(name, config): Register agent configuration
  • addAgentConfigs(configs): Register multiple agent configurations
  • getAgentTypes(): Get all available agent types
  • getAgentConfigs(): Get all agent configurations
  • spawnAgent({agentType, headless}): Create new agent
  • spawnSubAgent(agent, {agentType, headless, config}): Create sub-agent
  • spawnAgentFromConfig(config, {headless}): Create agent from config
  • spawnAgentFromCheckpoint(app, checkpoint, {headless}): Create from checkpoint
  • getAgent(id): Get agent by ID
  • getAgents(): Get all active agents
  • deleteAgent(agent): Shutdown and remove agent

Automatic Lifecycle Management:

  • Idle agent cleanup every 15 seconds
  • Configurable idleTimeout per agent (default: 0 = no limit, in seconds)
  • Configurable maxRunTime per agent (default: 0 = no limit, in seconds)
  • Configurable minimumRunning per agent type (default: 0 = no minimum)

AgentCommandService Service

Service for managing and executing agent commands:

const commandService = new AgentCommandService();

// Commands are automatically registered via plugin
// Execute commands via agent
await agent.runCommand("/help");
await agent.runCommand("Hello, agent!");

Command Processing:

  • Automatic slash command parsing
  • Default chat command fallback (/chat send)
  • Command singular/plural name handling
  • Error handling for unknown commands

Key Methods:

  • addAgentCommands(commands): Register commands
  • getCommandNames(): Get all command names
  • getCommands(): Get all commands
  • getCommand(name): Get specific command
  • executeAgentCommand(agent, message): Execute command

AgentLifecycleService Service

Service for managing hooks and lifecycle events:

const lifecycleService = new AgentLifecycleService();

// Hooks are automatically registered via plugin
lifecycleService.enableHooks(["myPlugin/afterChatCompletion"], agent);

// Execute hooks manually
await lifecycleService.executeHooks(agent, "afterChatCompletion", args);

Hook Management:

  • registerHook(name, config): Register individual hook
  • addHooks(pkgName, hooks): Register hooks with package namespacing
  • getRegisteredHooks(): Get all registered hooks
  • getEnabledHooks(agent): Get enabled hooks for agent
  • setEnabledHooks(hookNames, agent): Set enabled hooks
  • enableHooks(hookNames, agent): Enable specific hooks
  • disableHooks(hookNames, agent): Disable hooks
  • executeHooks(agent, hookType, ...args): Execute hooks

Hook Types:

  • beforeChatCompletion: Called before chat completion
  • afterChatCompletion: Called after chat completion
  • afterAgentInputComplete: Called after agent input is fully processed

Usage Examples

Basic Agent Creation and Usage

import Agent from "@tokenring-ai/agent";
import TokenRingApp from "@tokenring-ai/app";

const app = new TokenRingApp();

// Create agent
const agent = new Agent(app, {
name: "My Agent",
description: "Custom development agent",
category: "development",
debug: false,
initialCommands: [],
headless: false,
callable: true,
idleTimeout: 0,
maxRunTime: 0,
subAgent: {},
enabledHooks: [],
todos: {}
});

// Handle user input
const requestId = agent.handleInput({ message: "Hello! How can you help me?" });

// Listen to events
agent.subscribeState(AgentEventState, (state) => {
for (const event of state.events) {
console.log("Event:", event.type, event);
}
});

State Management and Checkpointing

// Initialize custom state
class MyCustomState implements AgentStateSlice {
name = "MyCustomState";
data: string[] = [];

reset(what: ResetWhat[]) {
if (what.includes('chat')) this.data = [];
}

show() {
return [`Data items: ${this.data.length}`];
}

serialize() {
return { data: this.data };
}

deserialize(obj: any) {
this.data = obj.data || [];
}
}

// Use state in agent
agent.initializeState(MyCustomState, {});

// Modify state
agent.mutateState(MyCustomState, (state) => {
state.data.push("item");
});

// Create checkpoint
const checkpoint = agent.generateCheckpoint();
console.log("Checkpoint:", checkpoint);

// Restore from checkpoint
const restoredAgent = await Agent.createAgentFromCheckpoint(
app,
checkpoint,
{ headless: false }
);

Sub-Agent Creation

// Create sub-agent from parent
const subAgent = await agentManager.spawnSubAgent(agent, {
agentType: "backgroundWorker",
headless: true
});

// Send message to sub-agent
await subAgent.handleInput({ message: "Process this data" });

// Sub-agent state is automatically copied from parent
await agentManager.deleteAgent(subAgent);

Advanced Sub-Agent Execution

import { runSubAgent } from "@tokenring-ai/agent/runSubAgent";

// Run sub-agent with custom options
const result = await runSubAgent({
agentType: "code-assistant",
headless: true,
command: "/work Analyze this code: function test() { return true; }",
background: false,
forwardChatOutput: true,
forwardSystemOutput: true,
forwardReasoning: false,
forwardHumanRequests: true,
forwardInputCommands: true,
forwardArtifacts: false,
timeout: 60,
maxResponseLength: 500,
minContextLength: 300
}, agent, true);

console.log("Result:", result.status, result.response);

Tool Execution

// Built-in tool: runAgent
const result = await agent.runAgent({
agentType: "dataProcessor",
message: "Analyze this dataset",
context: "File: data.csv\nColumns: name,age,income",
forwardChatOutput: true,
forwardSystemOutput: true,
timeout: 300
});

console.log("Tool result:", result);

Hook System

// Register hook
const hookConfig: HookConfig = {
name: "myPlugin/afterChatCompletion",
description: "Custom after chat completion hook",
afterChatCompletion: async (agent, ...args) => {
console.log("Chat completed:", args);
}
};

// Enable hook for agent
lifecycleService.registerHook("myPlugin/afterChatCompletion", hookConfig);
lifecycleService.enableHooks(["myPlugin/afterChatCompletion"], agent);

// Hooks automatically execute on lifecycle events

Human Interface Requests

// Simple confirmation
const confirmed = await agent.askForConfirmation({
message: "Are you sure you want to proceed?",
label: "Confirm?",
default: false,
timeout: 30
});

// Text input
const text = await agent.askForText({
message: "Enter your name:",
label: "Name",
masked: false
});

// Single tree selection
const selection = await agent.askQuestion({
message: "Choose an option",
question: {
type: 'treeSelect',
label: 'Select',
minimumSelections: 1,
maximumSelections: 1,
defaultValue: [],
tree: [
{
name: "Option 1",
value: "opt1"
},
{
name: "Option 2",
value: "opt2"
}
]
}
});

// Complex form
const formData = await agent.askQuestion({
message: "Fill out the contact form",
question: {
type: 'form',
sections: [
{
name: "personal",
description: "Personal Information",
fields: {
name: {
type: 'text',
label: 'Full Name',
defaultValue: ''
},
email: {
type: 'text',
label: 'Email',
defaultValue: ''
}
}
},
{
name: "preferences",
description: "Preferences",
fields: {
category: {
type: 'treeSelect',
label: 'Category',
defaultValue: [],
tree: [
{
name: "Support",
value: "support"
},
{
name: "Sales",
value: "sales"
}
]
}
}
}
]
}
});

// Handle human response
agent.sendQuestionResponse(requestId, { result: selection });

Output Artifacts

// Emit an artifact (e.g., markdown file)
agent.artifactOutput({
name: "report.md",
encoding: "text",
mimeType: "text/markdown",
body: `# Report\n\nGenerated content...`
});

// Emit binary artifact
agent.artifactOutput({
name: "image.png",
encoding: "base64",
mimeType: "image/png",
body: "base64_encoded_data..."
});

Cost Tracking

// Add cost tracking
agent.addCost("api_calls", 1);
agent.addCost("tokens", 1500);

// View cost information
await agent.runCommand("/cost");

Status Line Management

// Set busy status
agent.setBusyWith("Processing request...");

// Set status line
agent.setStatusLine("Ready for input");

// Clear status indicators
agent.setBusyWith(null);
agent.setStatusLine(null);

Configuration

AgentConfig Schema

const agentConfig = {
name: string, // Agent identifier
description: string, // Agent purpose
category: string, // Agent category
debug?: boolean, // Enable debug logging (default: false)
workHandler?: Function, // Custom work handler with (input: string, agent: Agent) => Promise<any>
agentType?: string, // Agent type identifier
initialCommands: string[], // Startup commands
createMessage: string, // Message displayed when agent is created (default: "Agent Created")
headless?: boolean, // Headless mode (default: false)
callable?: boolean, // Enable tool calls (default: true)
idleTimeout?: number, // Idle timeout in seconds (default: 0 = no limit)
maxRunTime?: number, // Max runtime in seconds (default: 0 = no limit)
subAgent: { // Sub-agent configuration
forwardChatOutput?: boolean, // Forward chat output (default: true)
forwardSystemOutput?: boolean, // Forward system output (default: true)
forwardHumanRequests?: boolean,// Forward human requests (default: true)
forwardReasoning?: boolean, // Forward reasoning (default: false)
forwardInputCommands?: boolean,// Forward input commands (default: true)
forwardArtifacts?: boolean, // Forward artifacts (default: false)
timeout?: number, // Sub-agent timeout in seconds (default: 0)
maxResponseLength?: number, // Max response length in characters (default: 500)
minContextLength?: number, // Minimum context length in characters (default: 300)
},
enabledHooks: string[], // Enabled hook names (default: [])
todos: { // Todo list configuration
copyToChild: boolean, // Copy todos to child agents (default: true)
initialItems: Array<{ // Initial todo items (default: [])
id: string,
content: string,
status: "pending" | "in_progress" | "completed"
}>
}
};

Event System

Event Types

Input Events:

  • input.received - Input received from user
  • input.handled - Input processing completed (status: success, error, or cancelled)

Output Events:

  • output.chat - Chat output
  • output.reasoning - Reasoning output
  • output.info - Informational messages
  • output.warning - Warning messages
  • output.error - Error messages
  • output.artifact - Output artifact (files, documents, etc.)

State Events:

  • reset - State reset
  • abort - Operation aborted
  • agent.created - Agent was created
  • agent.stopped - Agent was stopped

Question Events:

  • question.request - Human input requested
  • question.response - Human response provided

Event Schema

All events follow this structure:

{
type: EventType,
timestamp: number,
// Event-specific fields
}

Human Interface Types

Question Types

The agent supports several question types for human interaction:

Text Question:

{
type: 'text',
label: 'Name',
description: 'Enter your name',
required: false,
defaultValue: '',
expectedLines: 1,
masked: false,
autoSubmitAfter: number
}

Tree Select Question:

{
type: "treeSelect",
label: 'Choose an option',
minimumSelections: 1,
maximumSelections: 1,
defaultValue: [],
allowFreeform: false,
tree: [
{
name: "Option 1",
value: "opt1",
children: [...]
}
]
}

File Select Question:

{
type: 'fileSelect',
allowFiles: true,
allowDirectories: true,
label: 'Select files',
description: 'Choose files or folders',
minimumSelections: 1,
maximumSelections: 5,
defaultValue: []
}

Form Question:

{
type: 'form',
sections: [
{
name: "personal",
description: "Personal Information",
fields: {
name: { type: 'text', label: 'Full Name', defaultValue: '' },
email: { type: 'text', label: 'Email', defaultValue: '' }
}
}
]
}

Tree Leaf Structure

{
name: string,
value?: string,
children?: Array<TreeLeaf>
}

Plugin Configuration

The agent package automatically integrates with TokenRing applications:

// Automatic registration via plugin
const app = new TokenRingApp();

// Agents configured in app config
const config = {
agents: {
myAgent: {
name: "My Agent",
description: "Custom agent",
category: "development",
debug: false,
initialCommands: [],
headless: false,
callable: true,
idleTimeout: 0,
maxRunTime: 0,
subAgent: {},
enabledHooks: [],
todos: {}
}
}
};

Integration

TokenRing Plugin Integration

The agent package automatically integrates with TokenRing applications:

// Plugin automatically registers:
// - Chat service integration
// - Agent command service
// - Agent manager service
// - Agent lifecycle service
// - Web host RPC endpoints
// - Context handlers
// - Tools and commands

Chat Commands

The agent package includes built-in chat commands:

  • /agent - Agent management commands
  • /cost - Cost tracking
  • /help - Help system
  • /hook - Hook management
  • /reset - State reset
  • /settings - Settings display
  • /work - Work handler invocation
  • /debug - Debug commands

Tools

The agent package includes built-in tools:

  • runAgent - Execute sub-agent
  • todo - Todo list management

Context Handlers

  • available-agents: Provides list of available agent types
  • todo-list: Provides todo list context

RPC Endpoints

EndpointRequest ParamsResponse
getAgent{agentId}Agent details
getAgentEvents{agentId, fromPosition}Events from position
streamAgentEvents{agentId, fromPosition}Streaming events
getAgentExecutionState{agentId}Execution state
streamAgentExecutionState{agentId}Streaming execution state
listAgentsNoneArray of agent information
getAgentTypesNoneArray of agent types
createAgent{agentType, headless}Created agent details
deleteAgent{agentId}Success status
sendInput{agentId, message}Request ID
sendQuestionResponse{agentId, requestId, response}Success status
abortAgent{agentId, reason}Success status
resetAgent{agentId, what}Success status
getCommandHistory{agentId}Command history
getAvailableCommands{agentId}Available command names

State Management

State Slices

Agents support multiple state slices for different concerns:

Built-in State Slices:

  • AgentEventState: Event history and current state
  • AgentExecutionState: Execution state (busy status, status line, input queue, idle state)
  • CommandHistoryState: Command execution history
  • CostTrackingState: Resource usage tracking
  • HooksState: Hook configuration and enabled hooks
  • TodoState: Task list management

Custom State Slices:

class CustomState implements AgentStateSlice {
name = "CustomState";
reset(what: ResetWhat[]) {
// Implementation
}
show(): string[] {
return ["Custom state data"];
}
serialize() {
return { data: this.data };
}
deserialize(obj: any) {
// Implementation
}
}

Checkpointing

// Generate checkpoint
const checkpoint = agent.generateCheckpoint();

// Restore from checkpoint
const restoredAgent = await Agent.createAgentFromCheckpoint(
app,
checkpoint,
{ headless: false }
);

AgentExecutionState

Tracks the execution state of an agent:

interface AgentExecutionState {
busyWith: string | null; // Currently busy with operation
statusLine: string | null; // Status line indicator
waitingOn: Array<ParsedQuestionRequest>; // Pending human requests
inputQueue: Array<InputReceived>; // Input queue
currentlyExecuting: { // Currently executing operation
requestId: string;
abortController: AbortController;
} | null;
}

// Properties
state.idle: boolean; // Whether agent is idle (computed: inputQueue.length === 0)

ResetWhat Types

The reset operation supports multiple target types:

type ResetWhat = "context" | "chat" | "history" | "settings" | "memory" | "costs";

Development

Testing

bun run test
bun run test:coverage

Plugin Development

Create custom plugins for agent functionality:

import { TokenRingPlugin } from "@tokenring-ai/app";

const myAgentPlugin: TokenRingPlugin = {
name: "my-plugin",
install(app) {
// Register custom commands
// Register custom tools
// Register custom hooks
// Register custom state slices
}
};

Package Structure

pkg/agent/
├── Agent.ts # Core Agent class implementation
├── AgentEvents.ts # Event type definitions
├── types.ts # Core type definitions
├── schema.ts # Agent configuration schema
├── index.ts # Package exports
├── plugin.ts # TokenRing plugin integration
├── package.json # Package configuration
├── chatCommands.ts # Command exports
├── tools.ts # Tool exports
├── runSubAgent.ts # Sub-agent execution helper
├── question.ts # Question type definitions
├── commands/ # Built-in commands
│ ├── agent.ts # Agent management commands
│ ├── cost.ts # Cost tracking commands
│ ├── work.ts # Work handler invocation
│ ├── settings.ts # Settings display
│ ├── reset.ts # State reset
│ ├── hook.ts # Hook management
│ ├── help.ts # Help system
│ └── debug/
│ ├── logging.ts # Debug logging controls
│ ├── markdown.ts # Markdown rendering test
│ ├── services.ts # Service logs display
│ ├── questions.ts # Debug questions display
│ └── app.ts # Debug app info
├── services/ # Core services
│ ├── AgentManager.ts # Agent management service
│ ├── AgentLifecycleService.ts # Lifecycle and hooks service
│ └── AgentCommandService.ts # Command execution service
├── state/ # State management
│ ├── agentEventState.ts # Event state management
│ ├── agentExecutionState.ts # Execution state management
│ ├── commandHistoryState.ts # Command history tracking
│ ├── costTrackingState.ts # Cost tracking state
│ ├── hooksState.ts # Hook configuration state
│ └── todoState.ts # Todo state management
├── tools/ # Built-in tools
│ ├── runAgent.ts # Sub-agent execution tool
│ └── todo.ts # Todo list management tool
├── contextHandlers/ # Context providers
│ ├── availableAgents.ts # Available agents context
│ └── todo.ts # Todo context provider
├── rpc/ # RPC endpoints
│ ├── agent.ts # Agent RPC implementation
│ └── schema.ts # RPC schema definitions
└── util/ # Utilities
├── formatAgentId.ts # Agent ID formatting
└── subcommandRouter.ts # Command routing utilities

License

MIT License - see LICENSE for details.