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

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, 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, the SDK exposes AgentStopHookInput and AgentStopHookOutput classes with a registration API on the Session object.

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 provide AgentStopHookInput, AgentStopHookOutput, and the AgentStopHandler type for async implementations.

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 defines the fundamental AgentStopHookInput and AgentStopHookOutput structs used by all language bindings.

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 expose AgentStopHookInput and AgentStopHookOutput structs with typical Go error handling patterns.

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, returning CompletableFuture for async operations.

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 defines AgentStopHookInput and AgentStopHookOutput structs with IAgentStopHandler interface compliance.

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 Core hook definitions and runtime invocation logic
Python python/copilot/session.py Exposes AgentStopHookInput/Output and registration API
Node.js nodejs/src/types.ts TypeScript definitions and hook registration helpers
Go go/types.go Structs and handler interface for stop hook
Java java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java Java-side handler interface
.NET dotnet/src/Types.cs C# structs and handler contract

| Docs | docs/hooks/session-lifecycle.md | Conceptual description and usage guidelines |

Summary

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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →