Overview
While the simple examples demonstrate the core concepts, production implementations showcase how ACP is used in real-world applications with additional patterns for error handling, state management, and integration with AI models.
Featured Production Implementation
Gemini CLI Agent
Google Gemini CLI A complete, production-ready ACP agent implementation using Google’s Gemini AI models
The Gemini CLI Agent is an excellent reference for building production-grade agents. It demonstrates:
LLM Integration Complete integration with Google’s Gemini API including streaming responses and function calling
Robust Error Handling Comprehensive error handling, retry logic, and graceful degradation
Advanced Tool Management Dynamic tool registration, execution, and result handling
Production Patterns Real-world patterns for state management, logging, and debugging
Production Architecture Patterns
Agent Architecture
Modular Design
Separate concerns into distinct components: class ProductionAgent {
private llmClient : LLMClient ; // AI model integration
private toolRegistry : ToolRegistry ; // Available tools
private sessionManager : SessionManager ; // Session state
private permissionHandler : PermissionHandler ; // Permission logic
constructor ( connection : AgentSideConnection ) {
// Initialize components
}
}
State Management
Maintain comprehensive session state: interface ProductionSession {
id : string ;
conversationHistory : Message [];
pendingToolCalls : Map < string , ToolCall >;
activeAbortController : AbortController | null ;
context : {
cwd : string ;
environment : Record < string , string >;
capabilities : ClientCapabilities ;
};
}
Error Boundaries
Implement robust error handling at each layer: async prompt ( params : PromptRequest ): Promise < PromptResponse > {
try {
return await this . executePrompt ( params );
} catch (error) {
if ( error instanceof CancellationError ) {
return { stopReason: "cancelled" };
}
if ( error instanceof PermissionDeniedError ) {
return { stopReason: "permission_denied" };
}
// Log and report unexpected errors
this.logger.error( "Prompt execution failed" , error);
throw error;
}
}
Streaming Integration
Stream LLM responses in real-time: for await ( const chunk of llmClient . stream ( prompt )) {
if ( abortSignal . aborted ) break ;
await this . connection . sessionUpdate ({
sessionId ,
update: {
sessionUpdate: "agent_message_chunk" ,
content: { type: "text" , text: chunk . text },
},
});
}
Client Architecture
Agent Lifecycle Management
Manage agent processes robustly: class AgentManager {
private agentProcess : ChildProcess | null = null ;
private connection : ClientSideConnection | null = null ;
async start () : Promise < void > {
// Spawn agent with proper error handling
this . agentProcess = spawn ( agentCommand , agentArgs , {
stdio: [ 'pipe' , 'pipe' , 'pipe' ],
});
// Handle process events
this . agentProcess . on ( 'error' , this . handleProcessError );
this . agentProcess . on ( 'exit' , this . handleProcessExit );
// Establish connection
await this . initializeConnection ();
}
async stop () : Promise < void > {
// Graceful shutdown
await this . connection ?. close ();
this . agentProcess ?. kill ( 'SIGTERM' );
}
}
Permission Policies
Implement sophisticated permission handling: class PermissionPolicy {
async evaluate (
toolCall : ToolCall ,
) : Promise < 'auto_approve' | 'request_user' | 'auto_deny' > {
// Auto-approve safe operations
if ( toolCall . kind === 'read' && this . isInWorkspace ( toolCall . path )) {
return 'auto_approve' ;
}
// Auto-deny dangerous operations
if ( this . isDangerousPath ( toolCall . path )) {
return 'auto_deny' ;
}
// Ask user for everything else
return 'request_user' ;
}
}
UI Integration
Connect ACP to your application’s UI: class UIBridge {
async sessionUpdate ( update : SessionUpdate ) : Promise < void > {
switch ( update . sessionUpdate ) {
case 'agent_message_chunk' :
this . chatView . appendMessage ( update . content . text );
break ;
case 'tool_call' :
this . toolPanel . showToolExecution ( update );
break ;
case 'agent_thought_chunk' :
this . reasoningPanel . appendThought ( update . content . text );
break ;
}
}
}
Best Practices from Production
Logging and Observability
Structured Logging
Protocol Tracing
import { Logger } from 'winston' ;
class ObservableAgent implements Agent {
private logger : Logger ;
async prompt ( params : PromptRequest ) : Promise < PromptResponse > {
this . logger . info ( 'Prompt received' , {
sessionId: params . sessionId ,
promptLength: params . prompt . length ,
});
const startTime = Date . now ();
try {
const result = await this . executePrompt ( params );
this . logger . info ( 'Prompt completed' , {
sessionId: params . sessionId ,
duration: Date . now () - startTime ,
stopReason: result . stopReason ,
});
return result ;
} catch ( error ) {
this . logger . error ( 'Prompt failed' , {
sessionId: params . sessionId ,
duration: Date . now () - startTime ,
error: error . message ,
});
throw error ;
}
}
}
Batching Updates
Parallel Tool Execution
class BatchedSessionUpdates {
private pending : SessionUpdate [] = [];
private flushTimer : NodeJS . Timeout | null = null ;
queue ( update : SessionUpdate ) : void {
this . pending . push ( update );
if ( ! this . flushTimer ) {
this . flushTimer = setTimeout (() => this . flush (), 16 ); // ~60fps
}
}
private async flush () : Promise < void > {
const updates = this . pending . splice ( 0 );
this . flushTimer = null ;
// Send batched updates
for ( const update of updates ) {
await this . connection . sessionUpdate ( update );
}
}
}
Security Considerations
Production implementations must carefully consider security:
Path validation : Always validate and sanitize file paths
Permission boundaries : Enforce strict permission policies
Resource limits : Limit agent resource consumption (CPU, memory, file I/O)
Audit logging : Log all sensitive operations
Input sanitization : Validate all user inputs before passing to agents
class SecurePathValidator {
constructor ( private workspaceRoot : string ) {}
validate ( requestedPath : string ) : string {
// Resolve to absolute path
const resolved = path . resolve ( this . workspaceRoot , requestedPath );
// Ensure it's within workspace
if ( ! resolved . startsWith ( this . workspaceRoot )) {
throw new SecurityError ( 'Path outside workspace' );
}
// Check for sensitive directories
const sensitive = [ '.git' , '.env' , 'node_modules/.env' ];
if ( sensitive . some ( dir => resolved . includes ( dir ))) {
throw new SecurityError ( 'Access to sensitive path denied' );
}
return resolved ;
}
}
Testing Strategies
Mock Client
Integration Tests
class MockClient implements Client {
public receivedUpdates : SessionUpdate [] = [];
public permissionResponses : Map < string , PermissionOutcome > = new Map ();
async sessionUpdate ( params : SessionNotification ) : Promise < void > {
this . receivedUpdates . push ( params . update );
}
async requestPermission (
params : RequestPermissionRequest
) : Promise < RequestPermissionResponse > {
const outcome = this . permissionResponses . get ( params . toolCall . toolCallId );
if ( ! outcome ) {
throw new Error ( 'Unexpected permission request' );
}
return { outcome };
}
}
// Use in tests
const mockClient = new MockClient ();
mockClient . permissionResponses . set ( 'call_1' , {
outcome: 'selected' ,
optionId: 'allow' ,
});
Common Integration Patterns
MCP Server Integration
Integrate with Model Context Protocol servers:
const sessionResult = await connection . newSession ({
cwd: process . cwd (),
mcpServers: [
{
name: 'filesystem' ,
command: 'npx' ,
args: [ '-y' , '@modelcontextprotocol/server-filesystem' , workspaceRoot ],
},
{
name: 'github' ,
command: 'npx' ,
args: [ '-y' , '@modelcontextprotocol/server-github' ],
env: {
GITHUB_TOKEN: process . env . GITHUB_TOKEN ,
},
},
],
});
Multi-Session Management
Handle multiple concurrent sessions:
class SessionManager {
private sessions : Map < string , Session > = new Map ();
async create ( params : NewSessionRequest ) : Promise < Session > {
const session = new Session ( params );
this . sessions . set ( session . id , session );
return session ;
}
get ( sessionId : string ) : Session | null {
return this . sessions . get ( sessionId ) ?? null ;
}
async close ( sessionId : string ) : Promise < void > {
const session = this . sessions . get ( sessionId );
if ( session ) {
await session . cleanup ();
this . sessions . delete ( sessionId );
}
}
}
Additional Resources
Gemini CLI Source View the complete Gemini CLI Agent implementation
Protocol Specification Deep dive into the ACP protocol details
API Reference Complete TypeScript SDK documentation
Community Examples Share and discover community implementations
Contributing Your Examples
Built something with ACP? We’d love to feature it! Share your implementation:
Open a GitHub Discussion
Include a link to your code
Describe what makes your implementation unique
Share any lessons learned
Outstanding community examples may be featured in the official documentation.