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 namedescription: Agent descriptionconfig: Parsed agent configurationdebugEnabled: Debug logging toggleheadless: Headless operation modeapp: TokenRing application instancestateManager: State management system
State Management Methods:
initializeState<T>(ClassType, props): Initialize state slicegetState<T>(ClassType): Retrieve state slicemutateState<T>(ClassType, callback): Modify state slicesubscribeState<T>(ClassType, callback): Subscribe to state changeswaitForState<T>(ClassType, predicate): Wait for state conditiontimedWaitForState<T>(ClassType, predicate, timeout): Wait with timeoutsubscribeStateAsync<T>(ClassType, callback): Subscribe asynchronouslygenerateCheckpoint(): Create state checkpointrestoreState(state): Restore from checkpoint
Input Processing:
handleInput({message}): Process user input with event emissionrunCommand(command): Execute agent commandsbusyWhile<T>(message, awaitable): Execute with busy statesetBusyWith(message): Set busy status indicatorsetStatusLine(status): Set status line indicator
Event Emission:
chatOutput(content): Emit chat outputreasoningOutput(content): Emit reasoning contentinfoMessage(...messages): Emit info messageswarningMessage(...messages): Emit warning messageserrorMessage(...messages): Emit error messagesdebugMessage(...messages): Emit debug messages (if debugEnabled)emit(event): Emit custom events
Human Interface:
askForConfirmation({ message, label, default, timeout }): Request confirmationaskForText({ message, label, masked }): Request text inputaskQuestion<T>(question): Request human input with various question typessendQuestionResponse(requestId, response): Send human response
Lifecycle Management:
requestAbort(reason): Abort current operationsgetAbortSignal(): Get abort signalgetIdleDuration(): Get time since last activity (returns number in milliseconds)getRunDuration(): Get total run duration (returns number in milliseconds)reset(what): Reset specific state componentsshutdown(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 configurationaddAgentConfigs(configs): Register multiple agent configurationsgetAgentTypes(): Get all available agent typesgetAgentConfigs(): Get all agent configurationsspawnAgent({agentType, headless}): Create new agentspawnSubAgent(agent, {agentType, headless, config}): Create sub-agentspawnAgentFromConfig(config, {headless}): Create agent from configspawnAgentFromCheckpoint(app, checkpoint, {headless}): Create from checkpointgetAgent(id): Get agent by IDgetAgents(): Get all active agentsdeleteAgent(agent): Shutdown and remove agent
Automatic Lifecycle Management:
- Idle agent cleanup every 15 seconds
- Configurable
idleTimeoutper agent (default: 0 = no limit, in seconds) - Configurable
maxRunTimeper agent (default: 0 = no limit, in seconds) - Configurable
minimumRunningper 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 commandsgetCommandNames(): Get all command namesgetCommands(): Get all commandsgetCommand(name): Get specific commandexecuteAgentCommand(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 hookaddHooks(pkgName, hooks): Register hooks with package namespacinggetRegisteredHooks(): Get all registered hooksgetEnabledHooks(agent): Get enabled hooks for agentsetEnabledHooks(hookNames, agent): Set enabled hooksenableHooks(hookNames, agent): Enable specific hooksdisableHooks(hookNames, agent): Disable hooksexecuteHooks(agent, hookType, ...args): Execute hooks
Hook Types:
beforeChatCompletion: Called before chat completionafterChatCompletion: Called after chat completionafterAgentInputComplete: 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 userinput.handled- Input processing completed (status: success, error, or cancelled)
Output Events:
output.chat- Chat outputoutput.reasoning- Reasoning outputoutput.info- Informational messagesoutput.warning- Warning messagesoutput.error- Error messagesoutput.artifact- Output artifact (files, documents, etc.)
State Events:
reset- State resetabort- Operation abortedagent.created- Agent was createdagent.stopped- Agent was stopped
Question Events:
question.request- Human input requestedquestion.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-agenttodo- Todo list management
Context Handlers
- available-agents: Provides list of available agent types
- todo-list: Provides todo list context
RPC Endpoints
| Endpoint | Request Params | Response |
|---|---|---|
getAgent | {agentId} | Agent details |
getAgentEvents | {agentId, fromPosition} | Events from position |
streamAgentEvents | {agentId, fromPosition} | Streaming events |
getAgentExecutionState | {agentId} | Execution state |
streamAgentExecutionState | {agentId} | Streaming execution state |
listAgents | None | Array of agent information |
getAgentTypes | None | Array 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.