# Copilot SDK Agent Stop Hooks and Graceful Shutdown Handling: Implementation Guide

> Learn how to implement Copilot SDK agent stop hooks and graceful shutdown handling. Run cleanup logic and request follow-up turns before your agent session idles or shuts down.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The Copilot SDK provides a session-lifecycle `agent-stop` hook that triggers when the top-level agent finishes its turn, allowing you to run cleanup logic or request a follow-up turn before the session idles or shuts down.**

The `github/copilot-sdk` repository defines a cross-language framework for managing AI agent sessions, including a critical **agent stop hook** that enables graceful shutdown handling. Understanding how to implement this hook ensures your applications can cleanly release resources, flush telemetry, and perform final operations before termination. This guide examines the hook's architecture, contract, and implementation across all supported languages.

## Architecture of the Agent Stop Hook

The **agent stop hook** sits between the core runtime and language bindings, intercepting the session lifecycle at a precise moment: immediately after the top-level agent completes its current turn but before the session transitions to an idle state.

Unlike `onSessionEnd` callbacks that run after session destruction, this hook executes while the session remains alive. In [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), the runtime defines `AgentStopHookInput` and `AgentStopHookOutput` structures that serve as the cross-language contract. The session stays active during hook execution, allowing handlers to spin up new turns or perform cleanup that requires an active session context.

## Hook Contract and Data Structures

The hook operates on a strict contract defined across language bindings. All implementations utilize two primary structures: `AgentStopHookInput` containing request metadata, and `AgentStopHookOutput` containing optional follow-up actions.

The input structure carries these critical fields:

- **`requestId`** – Identifier of the turn that is ending
- **`sessionId`** – Current session identifier for correlation
- **`cancelSignal`** – Signal that the runtime sets if shutdown is forced

The output structure contains:

- **`result`** – Optional `AgentAction` that requests a follow-up turn; if omitted, the session idles

When the runtime receives a stop-hook response, it awaits the handler completion while respecting cancellation tokens. If the handler returns an `AgentAction` in the result field, the runtime immediately starts a new turn, extending the session. If the handler returns empty output or throws, the runtime proceeds to normal shutdown.

## Graceful Shutdown Flow

The SDK implements a four-phase graceful shutdown sequence that protects against data loss in long-running services:

1. **Signal Interception** – The host process sends `SIGTERM` or `CTRL-C`, which the SDK's internal event loop intercepts
2. **Hook Queuing** – The runtime queues the agent-stop hook rather than terminating immediately
3. **Handler Execution** – User-registered handlers run final logging, flush pending writes to `session-fs`, or schedule last agent turns
4. **Resource Disposal** – After the hook resolves or a timeout expires, the SDK tears down the session, disposes of adapters (FS, telemetry, RPC), and exits

This design guarantees that background workers, database connections, and telemetry buffers receive deterministic cleanup time before process termination.

## Implementation Examples by Language

### Python

In [`python/copilot/session.py`](https://github.com/github/copilot-sdk/blob/main/python/copilot/session.py), the SDK exposes `AgentStopHookInput` and `AgentStopHookOutput` classes with a registration API on the `Session` object.

```python
import asyncio
from copilot.session import Session, AgentStopHookInput, AgentStopHookOutput

async def on_agent_stop(input: AgentStopHookInput) -> AgentStopHookOutput:
    # Flush pending telemetry and close DB connections

    await my_db.flush()
    await my_telemetry.flush()
    # No follow‑up turn needed → return empty output

    return AgentStopHookOutput()

session = Session()
session.register_agent_stop_hook(on_agent_stop)

# Run the session; the SDK will invoke the hook on graceful shutdown

asyncio.run(session.start())

```

### Node.js

The TypeScript definitions in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) provide `AgentStopHookInput`, `AgentStopHookOutput`, and the `AgentStopHandler` type for async implementations.

```ts
import { Session, AgentStopHookInput, AgentStopHookOutput } from "./src/types";

async function onAgentStop(input: AgentStopHookInput): Promise<AgentStopHookOutput> {
  await db.flush();
  await telemetry.flush();
  // Returning undefined signals no extra turn
  return {};
}

const session = new Session();
session.registerAgentStopHook(onAgentStop);
await session.start();   // SDK calls the hook when process receives SIGTERM

```

### Rust

The core runtime in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs) defines the fundamental `AgentStopHookInput` and `AgentStopHookOutput` structs used by all language bindings.

```rust
use copilot_sdk::hooks::{AgentStopHookInput, AgentStopHookOutput};

async fn on_agent_stop(input: AgentStopHookInput) -> AgentStopHookOutput {
    // Perform graceful cleanup here
    my_db.flush().await;
    my_telemetry.flush().await;
    AgentStopHookOutput::default()   // No extra turn
}

#[tokio::main]
async fn main() {
    let mut session = copilot_sdk::Session::new();
    session.register_agent_stop_hook(on_agent_stop);
    session.start().await;
}

```

### Go

The Go bindings in [`go/types.go`](https://github.com/github/copilot-sdk/blob/main/go/types.go) expose `AgentStopHookInput` and `AgentStopHookOutput` structs with typical Go error handling patterns.

```go
func onAgentStop(input *AgentStopHookInput) (*AgentStopHookOutput, error) {
    // Flush resources
    db.Flush()
    telemetry.Flush()
    // Return nil to indicate no further turn
    return &AgentStopHookOutput{}, nil
}

func main() {
    sess := copilot.NewSession()
    sess.RegisterAgentStopHook(onAgentStop)
    sess.Start(context.Background())
}

```

### Java

Java implementations utilize the `AgentStopHandler` interface defined in [`java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java`](https://github.com/github/copilot-sdk/blob/main/java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java), returning `CompletableFuture` for async operations.

```java
public class MyAgentStopHandler implements AgentStopHandler {
    @Override
    public CompletableFuture<AgentStopHookOutput> handle(AgentStopHookInput input) {
        return CompletableFuture.supplyAsync(() -> {
            db.flush();
            telemetry.flush();
            return new AgentStopHookOutput(); // no extra turn
        });
    }
}

// Register:
Session session = new Session();
session.registerAgentStopHook(new MyAgentStopHandler());
session.start();

```

### .NET

The C# implementation in [`dotnet/src/Types.cs`](https://github.com/github/copilot-sdk/blob/main/dotnet/src/Types.cs) defines `AgentStopHookInput` and `AgentStopHookOutput` structs with `IAgentStopHandler` interface compliance.

```csharp
public class MyAgentStopHandler : IAgentStopHandler {
    public async Task<AgentStopHookOutput> HandleAsync(AgentStopHookInput input) {
        await db.FlushAsync();
        await telemetry.FlushAsync();
        return new AgentStopHookOutput(); // no follow‑up turn
    }
}

// Usage:
var session = new Session();
session.RegisterAgentStopHook(new MyAgentStopHandler());
await session.StartAsync();

```

## Key Source Files

| Language | File | Purpose |
|----------|------|---------|
| Rust | [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs) | Core hook definitions and runtime invocation logic |
| Python | [`python/copilot/session.py`](https://github.com/github/copilot-sdk/blob/main/python/copilot/session.py) | Exposes `AgentStopHookInput/Output` and registration API |
| Node.js | [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) | TypeScript definitions and hook registration helpers |
| Go | [`go/types.go`](https://github.com/github/copilot-sdk/blob/main/go/types.go) | Structs and handler interface for stop hook |
| Java | [`java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java`](https://github.com/github/copilot-sdk/blob/main/java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java) | Java-side handler interface |
| .NET | [`dotnet/src/Types.cs`](https://github.com/github/copilot-sdk/blob/main/dotnet/src/Types.cs) | C# structs and handler contract |

| Docs | [`docs/hooks/session-lifecycle.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md) | Conceptual description and usage guidelines |

## Summary

- The **agent stop hook** triggers after the top-level agent completes its turn but before the session idles, providing a deterministic cleanup point
- Handlers receive `AgentStopHookInput` with `requestId`, `sessionId`, and `cancelSignal`, and return `AgentStopHookOutput` optionally containing a follow-up `AgentAction`
- The **graceful shutdown flow** queues the hook upon `SIGTERM`, allowing resource flushing before the runtime disposes of adapters and exits
- Language bindings in [`python/copilot/session.py`](https://github.com/github/copilot-sdk/blob/main/python/copilot/session.py), [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts), [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), [`go/types.go`](https://github.com/github/copilot-sdk/blob/main/go/types.go), [`java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java`](https://github.com/github/copilot-sdk/blob/main/java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java), and [`dotnet/src/Types.cs`](https://github.com/github/copilot-sdk/blob/main/dotnet/src/Types.cs) maintain semantic parity with the core Rust runtime

## Frequently Asked Questions

### What triggers the agent-stop hook in the Copilot SDK?

The hook triggers when the top-level agent finishes its current turn and the session is about to transition to an idle state. According to the SDK architecture implemented in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), this occurs before the session terminates, distinguishing it from cleanup hooks that run after session destruction.

### How does the agent-stop hook differ from onSessionEnd callbacks?

While `onSessionEnd` runs after the session has already been destroyed, the agent-stop hook executes while the session remains active. This distinction allows the stop hook to request follow-up turns, access session-scoped resources, and perform operations that require an active session context.

### Can the agent-stop hook prevent session termination?

Yes. If the hook returns an `AgentAction` in the `result` field of `AgentStopHookOutput`, the runtime immediately starts a new turn instead of idling, effectively extending the session. If the hook returns an empty result or throws an exception, the session proceeds to shutdown.

### How should I handle cancellation signals in the stop hook?

The `cancelSignal` field in `AgentStopHookInput` indicates when the runtime forces shutdown. Your handler should check this signal and expedite cleanup, potentially skipping non-critical operations. The runtime respects cancellation tokens during hook execution, but handlers should still attempt minimal cleanup (flushing buffers, closing connections) before yielding control.