What is an Agent?
An Agent in ACP is the AI-powered component that processes user requests, executes tools, and generates responses. Agents implement the Agent interface to handle requests from clients (typically code editors).
abstract class Agent {
Future < InitializeResponse > initialize ( InitializeRequest params);
Future < NewSessionResponse > newSession ( NewSessionRequest params);
Future < PromptResponse > prompt ( PromptRequest params);
Future < void > cancel ( CancelNotification params);
// ... other methods
}
Creating an Agent
To create an agent, implement the Agent interface and set up an AgentSideConnection:
import 'dart:io' ;
import 'package:acp_dart/acp_dart.dart' ;
class MyAgent implements Agent {
final AgentSideConnection _connection;
final Map < String , SessionState > _sessions = {};
MyAgent ( this ._connection);
@override
Future < InitializeResponse > initialize ( InitializeRequest params) async {
return InitializeResponse (
protocolVersion : 1 ,
agentCapabilities : AgentCapabilities (
loadSession : false ,
mcpCapabilities : McpCapabilities (
http : true ,
sse : false ,
),
promptCapabilities : PromptCapabilities (
image : true ,
audio : false ,
embeddedContext : true ,
),
),
authMethods : [],
);
}
// Implement other required methods...
}
void main () {
final stream = ndJsonStream (stdin, stdout);
final connection = AgentSideConnection (
(conn) => MyAgent (conn),
stream,
);
}
The AgentSideConnection constructor takes a factory function that receives the connection and returns your agent instance. This allows your agent to use the connection for outbound requests.
Agent Lifecycle Methods
initialize
Establishes the connection and negotiates capabilities.
When called : Once at the beginning of the connection
Responsibilities :
Negotiate protocol version (currently 1)
Advertise agent capabilities
Declare supported authentication methods
Return agent information
@override
Future < InitializeResponse > initialize ( InitializeRequest params) async {
// Check client capabilities
final canWriteFiles = params.clientCapabilities ? .fs ? .writeTextFile ?? false ;
final hasTerminal = params.clientCapabilities ? .terminal ?? false ;
return InitializeResponse (
protocolVersion : 1 ,
agentCapabilities : AgentCapabilities (
loadSession : true , // Can restore previous sessions
sessionCapabilities : SessionCapabilities (
fork : SessionForkCapabilities (),
list : SessionListCapabilities (),
),
),
agentInfo : Implementation (
name : 'my-agent' ,
version : '1.0.0' ,
title : 'My Custom Agent' ,
),
authMethods : [
AuthMethod (
id : 'api-key' ,
name : 'API Key' ,
description : 'Authenticate using an API key' ,
),
],
);
}
authenticate
Handles authentication requests from the client.
When called : When the client needs to authenticate before creating sessions
@override
Future < AuthenticateResponse ?> ? authenticate (
AuthenticateRequest params,
) async {
if (params.methodId == 'api-key' ) {
// Validate API key (implement your logic)
final isValid = await validateApiKey ();
if ( ! isValid) {
throw RequestError . authRequired ( 'Invalid API key' );
}
return AuthenticateResponse ();
}
throw RequestError . methodNotFound ( 'authenticate' );
}
If your agent doesn’t require authentication, return AuthenticateResponse() or null and provide an empty authMethods list during initialization.
newSession
Creates a new conversation session.
When called : When the client wants to start a new conversation
Responsibilities :
Generate a unique session ID
Initialize session state
Connect to MCP servers if provided
Return available modes and models
@override
Future < NewSessionResponse > newSession ( NewSessionRequest params) async {
final sessionId = generateUniqueId ();
// Connect to MCP servers
for ( final server in params.mcpServers) {
await connectToMcpServer (server);
}
// Initialize session state
_sessions[sessionId] = SessionState (
cwd : params.cwd,
history : [],
currentMode : 'code' ,
);
return NewSessionResponse (
sessionId : sessionId,
modes : SessionModeState (
availableModes : [
SessionMode (id : 'ask' , name : 'Ask' , description : 'Q&A mode' ),
SessionMode (id : 'code' , name : 'Code' , description : 'Coding mode' ),
SessionMode (id : 'architect' , name : 'Architect' , description : 'Design mode' ),
],
currentModeId : 'code' ,
),
models : SessionModelState (
availableModels : [
ModelInfo (modelId : 'gpt-4' , name : 'GPT-4' ),
ModelInfo (modelId : 'claude-3' , name : 'Claude 3' ),
],
currentModelId : 'gpt-4' ,
),
);
}
loadSession
Restores a previously saved session.
When called : When the client wants to resume a previous conversation
Optional : Only implement if loadSession capability is advertised
@override
Future < LoadSessionResponse > ? loadSession ( LoadSessionRequest params) async {
final session = await loadSessionFromStorage (params.sessionId);
if (session == null ) {
throw RequestError . resourceNotFound (params.sessionId);
}
// Replay entire conversation history via session updates
for ( final message in session.history) {
await _connection. sessionUpdate (
SessionNotification (
sessionId : params.sessionId,
update : message,
),
);
}
return LoadSessionResponse (
modes : session.modeState,
models : session.modelState,
);
}
When loading a session, you MUST stream the entire conversation history back to the client via session/update notifications before returning the response.
prompt
Processes a user prompt and generates a response.
When called : When the user sends a message in a session
Responsibilities :
Process the user’s prompt
Call language models
Execute tool calls
Request permissions for sensitive operations
Stream progress updates
Return with appropriate stop reason
@override
Future < PromptResponse > prompt ( PromptRequest params) async {
final session = _sessions[params.sessionId];
if (session == null ) {
throw RequestError . resourceNotFound (params.sessionId);
}
try {
// Send initial thinking
await _connection. sessionUpdate (
SessionNotification (
sessionId : params.sessionId,
update : AgentThoughtChunkSessionUpdate (
content : TextContentBlock (
text : 'Analyzing the request...' ,
),
),
),
);
// Call language model
final response = await callLanguageModel (params.prompt);
// Stream response chunks
for ( final chunk in response.chunks) {
await _connection. sessionUpdate (
SessionNotification (
sessionId : params.sessionId,
update : AgentMessageChunkSessionUpdate (
content : TextContentBlock (text : chunk),
),
),
);
}
// Execute tool calls if needed
for ( final tool in response.toolCalls) {
await executeToolCall (params.sessionId, tool);
}
return PromptResponse (
stopReason : StopReason .endTurn,
usage : Usage (
inputTokens : response.inputTokens,
outputTokens : response.outputTokens,
totalTokens : response.totalTokens,
),
);
} catch (e) {
if (session.cancelled) {
return PromptResponse (stopReason : StopReason .cancelled);
}
rethrow ;
}
}
cancel
Cancels ongoing operations in a session.
When called : When the user cancels an in-progress prompt
Responsibilities :
Stop language model requests
Abort tool executions
Send final updates
Respond to pending prompt with StopReason.cancelled
@override
Future < void > cancel ( CancelNotification params) async {
final session = _sessions[params.sessionId];
if (session != null && session.pendingPrompt != null ) {
session.cancelled = true ;
session.pendingPrompt ? . complete ();
}
}
Sending Session Updates
Agents communicate progress to clients via session/update notifications:
Message Chunks
// Agent response text
await _connection. sessionUpdate (
SessionNotification (
sessionId : sessionId,
update : AgentMessageChunkSessionUpdate (
content : TextContentBlock (text : 'Here is the solution...' ),
),
),
);
// Agent internal thoughts
await _connection. sessionUpdate (
SessionNotification (
sessionId : sessionId,
update : AgentThoughtChunkSessionUpdate (
content : TextContentBlock (text : 'Analyzing code structure...' ),
),
),
);
// Create a new tool call
await _connection. sessionUpdate (
SessionNotification (
sessionId : sessionId,
update : ToolCallSessionUpdate (
toolCallId : 'call_1' ,
title : 'Reading file' ,
kind : ToolKind .read,
status : ToolCallStatus .pending,
locations : [ ToolCallLocation (path : '/src/main.dart' )],
rawInput : { 'path' : '/src/main.dart' },
),
),
);
// Update the tool call with results
await _connection. sessionUpdate (
SessionNotification (
sessionId : sessionId,
update : ToolCallUpdateSessionUpdate (
toolCallId : 'call_1' ,
status : ToolCallStatus .completed,
content : [
ContentToolCallContent (
content : TextContentBlock (text : fileContents),
),
],
rawOutput : { 'content' : fileContents},
),
),
);
Plans
await _connection. sessionUpdate (
SessionNotification (
sessionId : sessionId,
update : PlanSessionUpdate (
entries : [
PlanEntry (
content : 'Analyze current code structure' ,
priority : PlanEntryPriority .high,
status : PlanEntryStatus .completed,
),
PlanEntry (
content : 'Refactor into smaller functions' ,
priority : PlanEntryPriority .high,
status : PlanEntryStatus .inProgress,
),
PlanEntry (
content : 'Add unit tests' ,
priority : PlanEntryPriority .medium,
status : PlanEntryStatus .pending,
),
],
),
),
);
Requesting Client Operations
Agents can call methods on the client to perform operations:
File Operations
// Read a file
final content = await _connection. readTextFile (
ReadTextFileRequest (
sessionId : sessionId,
path : '/src/config.json' ,
),
);
// Write a file
await _connection. writeTextFile (
WriteTextFileRequest (
sessionId : sessionId,
path : '/src/output.txt' ,
content : 'Generated content' ,
),
);
Permission Requests
final permission = await _connection. requestPermission (
RequestPermissionRequest (
sessionId : sessionId,
options : [
PermissionOption (
optionId : 'allow' ,
name : 'Allow this change' ,
kind : PermissionOptionKind .allowOnce,
),
PermissionOption (
optionId : 'reject' ,
name : 'Skip this change' ,
kind : PermissionOptionKind .rejectOnce,
),
],
toolCall : ToolCallUpdate (
toolCallId : 'call_2' ,
title : 'Delete configuration file' ,
kind : ToolKind .delete,
status : ToolCallStatus .pending,
),
),
);
// Handle the response
switch (permission.outcome) {
case SelectedOutcome (optionId : 'allow' ) :
await performDeletion ();
case SelectedOutcome (optionId : 'reject' ) :
await skipOperation ();
case CancelledOutcome () :
return PromptResponse (stopReason : StopReason .cancelled);
}
Terminal Operations
// Create a terminal
final terminal = await _connection. createTerminal (
CreateTerminalRequest (
sessionId : sessionId,
command : 'npm' ,
args : [ 'test' ],
cwd : '/project' ,
),
);
// Wait for completion
final exit = await _connection. waitForTerminalExit (
WaitForTerminalExitRequest (
sessionId : sessionId,
terminalId : terminal.terminalId,
),
);
// Get output
final output = await _connection. terminalOutput (
TerminalOutputRequest (
sessionId : sessionId,
terminalId : terminal.terminalId,
),
);
// Release resources
await _connection. releaseTerminal (
ReleaseTerminalRequest (
sessionId : sessionId,
terminalId : terminal.terminalId,
),
);
Session Modes
Modes allow agents to operate in different ways with different system prompts and behaviors:
@override
Future < SetSessionModeResponse ?> ? setSessionMode (
SetSessionModeRequest params,
) async {
final session = _sessions[params.sessionId];
if (session == null ) {
throw RequestError . resourceNotFound (params.sessionId);
}
session.currentMode = params.modeId;
// Update system prompt based on mode
switch (params.modeId) {
case 'ask' :
session.systemPrompt = 'You are a helpful Q&A assistant.' ;
case 'code' :
session.systemPrompt = 'You are an expert coding assistant.' ;
case 'architect' :
session.systemPrompt = 'You are a software architect.' ;
}
return SetSessionModeResponse ();
}
Best Practices
Stream updates frequently : Send session updates regularly to provide real-time feedback to users.
Handle cancellation gracefully : Always check for cancellation during long-running operations and clean up properly.
Request permission for sensitive operations : Use requestPermission for destructive actions like deleting files or modifying critical code.
Provide detailed tool call information : Include locations to help clients implement “follow-along” features.
Example: Complete Agent
See the example agent implementation for a complete, runnable example.
Next Steps
Sessions Learn about session management
Connections Understand connection handling