> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/agentclientprotocol/typescript-sdk/llms.txt
> Use this file to discover all available pages before exploring further.

# Connection Lifecycle

> Understanding and managing ACP connection state, signals, and cleanup

ACP connections have a well-defined lifecycle from establishment to closure. Understanding this lifecycle is essential for proper resource management and error handling.

## Connection Phases

An ACP connection goes through these phases:

1. **Creation** - Connection object is instantiated with a stream
2. **Active** - Messages can be sent and received
3. **Closing** - Stream is ending (readable stream closed)
4. **Closed** - Connection is terminated, no more messages possible

## Creating a Connection

Connections are created by passing a stream and handler function:

<CodeGroup>
  ```typescript Agent theme={null}
  import * as acp from '@agentclientprotocol/acp';
  import { Readable, Writable } from 'node:stream';

  // Create stream from stdio
  const input = Writable.toWeb(process.stdout);
  const output = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;
  const stream = acp.ndJsonStream(input, output);

  // Create agent-side connection
  const connection = new acp.AgentSideConnection(
    (conn) => new MyAgent(conn),
    stream
  );

  // Connection is now active and ready to receive messages
  ```

  ```typescript Client theme={null}
  import * as acp from '@agentclientprotocol/acp';
  import { spawn } from 'node:child_process';
  import { Readable, Writable } from 'node:stream';

  // Spawn agent subprocess
  const agentProcess = spawn('npx', ['tsx', 'agent.ts'], {
    stdio: ['pipe', 'pipe', 'inherit'],
  });

  // Create stream
  const input = Writable.toWeb(agentProcess.stdin!);
  const output = Readable.toWeb(
    agentProcess.stdout!
  ) as ReadableStream<Uint8Array>;
  const stream = acp.ndJsonStream(input, output);

  // Create client-side connection
  const connection = new acp.ClientSideConnection(
    (agent) => new MyClient(),
    stream
  );

  // Connection is now active
  ```
</CodeGroup>

<Note>
  The handler function (`toAgent` or `toClient`) receives the connection object and should return the Agent or Client implementation.
</Note>

## Monitoring Connection State

The SDK provides two mechanisms for monitoring connection state:

### Using `signal` (AbortSignal)

The `signal` property provides an `AbortSignal` that aborts when the connection closes:

```typescript theme={null}
const connection = new acp.ClientSideConnection(
  (agent) => new MyClient(),
  stream
);

// Check if connection is closed
if (connection.signal.aborted) {
  console.log('Connection is closed');
}

// Listen for connection closure
connection.signal.addEventListener('abort', () => {
  console.log('Connection closed');
  performCleanup();
});

// Pass signal to other APIs for automatic cancellation
fetch('https://api.example.com/data', {
  signal: connection.signal,
});

setTimeout(() => {
  console.log('This will be cancelled if connection closes');
}, 10000, connection.signal);
```

**AbortSignal advantages:**

* Synchronous status check with `.aborted`
* Can be passed to other APIs (fetch, setTimeout)
* Standard Web API for cancellation

### Using `closed` (Promise)

The `closed` property provides a Promise that resolves when the connection closes:

```typescript theme={null}
const connection = new acp.ClientSideConnection(
  (agent) => new MyClient(),
  stream
);

// Async/await style
await connection.closed;
console.log('Connection has closed');
performCleanup();

// Promise style
connection.closed.then(() => {
  console.log('Connection has closed');
  performCleanup();
});
```

**Promise advantages:**

* Natural async/await syntax
* Can be combined with `Promise.race()` for timeouts

## Connection Closure

Connections close when the underlying readable stream ends. This can happen:

1. **Normally** - The other side closes their output stream
2. **Due to error** - A stream error or write failure occurs
3. **Process termination** - The subprocess exits

### Detecting Closure

Both sides should monitor for connection closure:

```typescript theme={null}
class MyAgent implements acp.Agent {
  private connection: acp.AgentSideConnection;
  
  constructor(connection: acp.AgentSideConnection) {
    this.connection = connection;
    
    // Monitor connection closure
    this.connection.closed.then(() => {
      console.log('Client disconnected');
      this.cleanup();
    });
  }
  
  private cleanup() {
    // Clean up resources
    this.sessions.forEach(session => session.cancel());
    this.sessions.clear();
  }
  
  // ... rest of implementation
}
```

## Resource Management

Proper resource management is critical for long-running connections.

### Cleaning Up on Close

Always clean up resources when the connection closes:

```typescript theme={null}
import * as acp from '@agentclientprotocol/acp';

class MyAgent implements acp.Agent {
  private sessions = new Map<string, Session>();
  private timers = new Set<NodeJS.Timeout>();
  private connection: acp.AgentSideConnection;
  
  constructor(connection: acp.AgentSideConnection) {
    this.connection = connection;
    
    // Set up cleanup handler
    connection.closed.then(() => this.cleanup());
  }
  
  private cleanup() {
    console.log('Cleaning up resources...');
    
    // Cancel all active sessions
    for (const session of this.sessions.values()) {
      session.abort();
    }
    this.sessions.clear();
    
    // Clear all timers
    for (const timer of this.timers) {
      clearTimeout(timer);
    }
    this.timers.clear();
    
    // Close any open file handles, database connections, etc.
  }
  
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Pass connection signal to abort long operations
    const result = await this.runLLM(
      params.prompt,
      this.connection.signal
    );
    
    return { stopReason: 'end_turn' };
  }
}
```

### Using Signals for Cancellation

Pass the connection's signal to operations that should be cancelled on disconnect:

```typescript theme={null}
class MyAgent implements acp.Agent {
  private connection: acp.AgentSideConnection;
  
  constructor(connection: acp.AgentSideConnection) {
    this.connection = connection;
  }
  
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Create a combined signal: cancel on either disconnect OR explicit cancel
    const sessionController = new AbortController();
    const combinedSignal = AbortSignal.any([
      this.connection.signal,
      sessionController.signal,
    ]);
    
    try {
      // Pass signal to operations
      const response = await fetch('https://api.openai.com/v1/chat/completions', {
        signal: combinedSignal,
        method: 'POST',
        body: JSON.stringify({ /* ... */ }),
      });
      
      return { stopReason: 'end_turn' };
    } catch (err) {
      if (combinedSignal.aborted) {
        return { stopReason: 'cancelled' };
      }
      throw err;
    }
  }
  
  async cancel(params: acp.CancelNotification): Promise<void> {
    // Cancel specific session
    const session = this.sessions.get(params.sessionId);
    session?.abort();
  }
}
```

## Graceful Shutdown

For clean shutdown, ensure all pending operations complete:

```typescript theme={null}
class MyClient implements acp.Client {
  private pendingRequests = new Set<Promise<unknown>>();
  
  async requestPermission(
    params: acp.RequestPermissionRequest
  ): Promise<acp.RequestPermissionResponse> {
    const promise = this.showPermissionDialog(params);
    
    // Track pending requests
    this.pendingRequests.add(promise);
    try {
      return await promise;
    } finally {
      this.pendingRequests.delete(promise);
    }
  }
  
  async sessionUpdate(params: acp.SessionNotification): Promise<void> {
    // Handle update...
  }
  
  async shutdown(): Promise<void> {
    console.log('Waiting for pending requests to complete...');
    
    // Wait for all pending operations
    await Promise.allSettled(this.pendingRequests);
    
    console.log('Shutdown complete');
  }
}

// Usage
const client = new MyClient();
const connection = new acp.ClientSideConnection(
  (agent) => client,
  stream
);

// Monitor for closure
connection.closed.then(() => client.shutdown());
```

## Handling Stream Errors

Stream errors trigger connection closure:

```typescript theme={null}
const connection = new acp.ClientSideConnection(
  (agent) => new MyClient(),
  stream
);

connection.signal.addEventListener('abort', () => {
  if (connection.signal.reason) {
    console.error('Connection closed due to error:', connection.signal.reason);
  } else {
    console.log('Connection closed normally');
  }
  
  performCleanup();
});
```

## Example: Complete Lifecycle Management

Here's a complete example showing proper lifecycle management:

```typescript theme={null}
import * as acp from '@agentclientprotocol/acp';
import { spawn } from 'node:child_process';
import { Readable, Writable } from 'node:stream';

class LifecycleAwareClient implements acp.Client {
  private isShuttingDown = false;
  private pendingOperations = new Set<Promise<unknown>>();
  
  async requestPermission(
    params: acp.RequestPermissionRequest
  ): Promise<acp.RequestPermissionResponse> {
    if (this.isShuttingDown) {
      return { outcome: { outcome: 'cancelled' } };
    }
    
    const operation = this.showDialog(params);
    this.pendingOperations.add(operation);
    
    try {
      return await operation;
    } finally {
      this.pendingOperations.delete(operation);
    }
  }
  
  async sessionUpdate(params: acp.SessionNotification): Promise<void> {
    if (!this.isShuttingDown) {
      this.handleUpdate(params);
    }
  }
  
  async shutdown(): Promise<void> {
    this.isShuttingDown = true;
    console.log('Shutting down client...');
    
    // Wait for pending operations with timeout
    const timeout = new Promise((resolve) => setTimeout(resolve, 5000));
    await Promise.race([
      Promise.allSettled(this.pendingOperations),
      timeout,
    ]);
    
    console.log('Client shutdown complete');
  }
  
  private async showDialog(
    params: acp.RequestPermissionRequest
  ): Promise<acp.RequestPermissionResponse> {
    // Show dialog implementation...
    return { outcome: { outcome: 'selected', optionId: 'allow' } };
  }
  
  private handleUpdate(params: acp.SessionNotification): void {
    // Handle update implementation...
  }
}

async function main() {
  // Spawn agent
  const agentProcess = spawn('npx', ['tsx', 'agent.ts'], {
    stdio: ['pipe', 'pipe', 'inherit'],
  });
  
  // Create connection
  const input = Writable.toWeb(agentProcess.stdin!);
  const output = Readable.toWeb(
    agentProcess.stdout!
  ) as ReadableStream<Uint8Array>;
  const stream = acp.ndJsonStream(input, output);
  
  const client = new LifecycleAwareClient();
  const connection = new acp.ClientSideConnection(
    (agent) => client,
    stream
  );
  
  // Set up lifecycle handlers
  connection.signal.addEventListener('abort', async () => {
    console.log('Connection closed');
    await client.shutdown();
    process.exit(0);
  });
  
  // Handle process signals
  process.on('SIGINT', async () => {
    console.log('Received SIGINT, shutting down...');
    agentProcess.kill();
    await connection.closed;
    process.exit(0);
  });
  
  try {
    // Initialize and use connection
    await connection.initialize({
      protocolVersion: acp.PROTOCOL_VERSION,
      clientCapabilities: {},
    });
    
    // Do work...
    
  } catch (err) {
    console.error('Error:', err);
  } finally {
    // Ensure cleanup
    agentProcess.kill();
    await connection.closed;
  }
}

main().catch(console.error);
```

## Best Practices

<Warning>
  **Do:**

  * Monitor `connection.closed` or `connection.signal` for disconnection
  * Clean up resources (timers, file handles) when connection closes
  * Pass `connection.signal` to operations that should be cancelled
  * Handle both normal and error-based closure
  * Wait for pending operations during shutdown

  **Don't:**

  * Attempt to send messages after connection closes
  * Leak resources by not cleaning up on close
  * Ignore connection closure errors
  * Block shutdown waiting indefinitely for operations
</Warning>

## Related

* [Streams](/advanced/streams) - Understanding the underlying stream API
* [Error Handling](/advanced/error-handling) - Handling connection errors
