what is common ctx shutdown practices for type scr...
# ask-ai
r
what is common ctx shutdown practices for type script agents -> is this true The Fix The fix adds a Promise that never resolves until shutdown: // Keep entry function alive until call actually ends await new Promise<void>((resolve) => { ctx.addShutdownCallback(() => resolve()); }); This ensures the entry function stays alive for the entire call duration, because: 1. The Promise only resolves when ctx.shutdown() is called 2. ctx.shutdown() is only called when the call ends (participant leaves, etc.) Before: Entry function completes → Agent dies → Call connects to dead agent After: Entry function waits for shutdown → Call completes → Agent exits cleanly
t
Thanks for reaching out, Bobvi. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — you're asking whether the Promise +
ctx.addShutdownCallback()
pattern you showed is a correct/common shutdown practice for TypeScript agents. Short answer: yes. Why this is correct •
ctx.addShutdownCallback()
is the intended hook to run cleanup when the agent is shutting down. • Awaiting a Promise that resolves inside that callback keeps the entry function alive until
ctx.shutdown()
is invoked, preventing the process from exiting while the call/session is still active. •
ctx.shutdown()
triggers the registered callbacks; the framework will wait (up to the configured grace period) to let callbacks finish before forcefully terminating. Recommended usage • Register cleanup logic in
ctx.addShutdownCallback()
(close connections, save state, etc.). • Keep the entry alive with the Promise pattern you posted so the agent only exits after shutdown:
Copy code
await new Promise<void>((resolve) => {
  ctx.addShutdownCallback(() => resolve());
});
• Call
ctx.shutdown()
when you want to end the session programmatically (or rely on the framework to call it when participants leave). Notes and caveats • Ensure shutdown callbacks run quickly or complete within the framework’s grace period to avoid forced termination. • If you have other long-running async work, either await it before calling
ctx.shutdown()
or register appropriate cleanup inside the shutdown callback. If you want, I can: • Show an example that includes cleanup (closing tracks/connections) inside the shutdown callback, or • Help adapt this pattern to your existing agent code — share the entry function and I’ll suggest exactly where to add the Promise and callbacks. Sources: Job lifecycle | LiveKit docs
r
• Show an example that includes cleanup (closing tracks/connections) inside the shutdown callback, or
t
Thanks — below is a concise, practical TypeScript agent example that shows: 1) keeping the entry alive with the Promise pattern, and 2) cleaning up tracks/connections inside
ctx.addShutdownCallback()
.
Copy code
import { defineAgent, JobContext } from '@livekit/agents';
import { LocalTrack, RemoteTrack } from 'livekit-client';

export default defineAgent({
  entry: async (ctx: JobContext) => {
    // Keep references to resources that need cleanup
    let publishedTracks: LocalTrack[] = [];
    let backgroundTask: Promise<void> | null = null;

    // Example setup: publish a track and start a background task
    try {
      // Replace with your actual track creation/publishing code
      // const track = await createLocalAudioTrack();
      // await ctx.room.localParticipant.publishTrack(track);
      // publishedTracks.push(track);

      // Example background task (e.g., processing loop)
      backgroundTask = (async () => {
        while (!ctx.isShutdownRequested) {
          // do work...
          await new Promise((r) => setTimeout(r, 1000));
        }
      })();
    } catch (err) {
      console.error('setup error', err);
      // Optionally trigger shutdown on fatal setup errors
      // ctx.shutdown('setup failed');
    }

    // Register a shutdown callback to unpublish/stop tracks and close resources
    ctx.addShutdownCallback(async () => {
      console.log('Shutdown requested — cleaning up resources');

      // Stop and unpublish local tracks
      for (const t of publishedTracks) {
        try {
          // Unpublish from the local participant if still published
          try {
            await ctx.room.localParticipant.unpublishTrack(t);
          } catch (unpubErr) {
            // ignore if already unpublished
            console.warn('unpublish error', unpubErr);
          }

          // Stop/close the track itself (API depends on track type)
          try {
            t.stop?.();
          } catch (stopErr) {
            console.warn('track stop error', stopErr);
          }
        } catch (err) {
          console.error('error cleaning track', err);
        }
      }
      publishedTracks = [];

      // Wait for background tasks to finish or cancel them if applicable
      if (backgroundTask) {
        try {
          await backgroundTask;
        } catch (err) {
          console.warn('background task error during shutdown', err);
        }
      }

      // Optionally close any other external connections here
      // await someExternalClient.close();

      console.log('Cleanup complete');
    });

    // Keep the entry function alive until shutdown is invoked
    await new Promise<void>((resolve) => {
      ctx.addShutdownCallback(() => resolve());
    });
  },
});
If you want, I can adapt this to your actual publish/track APIs (share the code that creates/publishes tracks) and show exact cleanup calls for those objects. Sources: Job lifecycle | LiveKit docs