Welcome back to Agent SDK! Over the past three units, we have built a strong foundation in the Agent SDK, mastered the Python SDK with its built-in tools and configuration options, and learned how to extend agents with custom tools and hooks. We have focused primarily on Python, which offers excellent capabilities for automation and scripting.
In this fourth unit, we expand our toolkit by exploring the TypeScript SDK. While the Python and TypeScript SDKs offer identical features and capabilities, each language brings unique strengths to different scenarios. We will learn when to choose TypeScript over Python, how to leverage the TypeScript type system for safer agent development, and, most importantly, how to build production-ready agents with proper error handling, retry logic, and deployment strategies.
This unit moves beyond basic agent creation to focus on reliability and deployment. We will implement robust error handling patterns, create agents that gracefully recover from failures, and containerize our applications using Docker for consistent deployment across environments. By the end, we will have the skills to build and deploy enterprise-grade agents using TypeScript.
Before diving into the code, let us understand when and why we might choose TypeScript over Python for agent development. Both SDKs provide the same core functionality: the same tools, the same permission modes, and the same agent capabilities. The choice between them depends on our project context and team preferences.
Python excels in certain scenarios: data science and machine learning workflows benefit from libraries like pandas, numpy, and scikit-learn. Quick scripting tasks and system automation are natural fits for Python's concise syntax. Teams that prefer dynamic typing and rapid prototyping often gravitate toward Python.
TypeScript shines in different contexts: Node.js applications and the JavaScript ecosystem. Teams building frontend integrations benefit from sharing types between the client and the server. Organizations that value type safety appreciate catching errors at compile time rather than at runtime. If your team already uses TypeScript, adopting the TypeScript SDK maintains consistency across your codebase.
To begin working with the TypeScript SDK, we need to configure our development environment. The setup involves installing the necessary packages and creating a TypeScript configuration file.
First, let's install the required dependencies:
The typescript package provides the compiler, tsx allows us to run TypeScript files directly without a separate build step, and @anthropic-ai/claude-agent-sdk is the agent library itself.
Next, we create a tsconfig.json file to configure how TypeScript compiles our code:
The target and module settings specify that we are targeting modern Node.js environments. The strict flag enables all of TypeScript's compile-time type checking features, which helps catch errors early. The esModuleInterop option ensures smooth integration with the .
Now we can create our first agent using the TypeScript SDK. The structure will feel familiar if we recall the Python examples from earlier units:
The core pattern mirrors what we learned in Python: we import the query function and the ClaudeAgentOptions interface, configure our agent with allowed tools and permission settings, and iterate through the message stream. The main syntactic differences are TypeScript's explicit type annotations (filepath: string, Promise<void>) and the for await...of loop syntax.
Notice how we specify disallowedTools to block file writing and command execution, preventing the agent from modifying the system. The permissionMode: "bypassPermissions" automatically approves all other tools without prompting, and requires allowDangerouslySkipPermissions: true in TypeScript for automated operation. The guides the agent's behavior, establishing its role as a documentation specialist.
One of TypeScript's most valuable features is compile-time type checking. Unlike Python, where type errors surface at runtime, TypeScript catches them before our code ever executes. Let us see this in action:
We define an interface describing what a quality report should contain: counts, arrays of issues, and a numerical score. This interface serves as a contract; any code working with quality reports must respect this structure.
Now suppose we try to use this interface incorrectly:
TypeScript immediately flags this error at compile time with a message like "Property score is missing." We catch the mistake before running the code, before writing tests, and certainly before deploying to production. This early detection is invaluable for complex agent applications where runtime errors might only surface under specific conditions.
The same protection applies to the SDK itself: the ClaudeAgentOptions type ensures we only use valid configuration keys, and the message stream types guarantee that we handle responses correctly. This safety net makes TypeScript especially appealing for production systems.
The TypeScript SDK uses async iterators for handling agent responses, which might be unfamiliar if we primarily work with callback-based or promise-based patterns. Let us break down how this works.
An async iterator is an object that produces values asynchronously over time, similar to how a Python async generator yields values. The query function returns an async iterable that emits messages as the agent processes our request:
The for await...of syntax automatically handles the asynchronous nature of the iteration. Each time through the loop, we receive one message from the agent. The message has a type field that discriminates between different kinds of events, and the success and error_during_execution subtypes tell us whether the operation completed successfully.
This pattern is more elegant than Promise chains or callbacks because it reads sequentially, like synchronous code, while maintaining full asynchronous capabilities. We can use break to exit early, continue to skip messages, and standard try/catch blocks for error handling.
Building agents that work in development is one thing; building agents that reliably work in production is another. Production systems must handle network failures, rate limits, transient errors, and unexpected conditions. Let us explore how to make our agents production-ready.
The foundation of production reliability is retry logic with exponential backoff. When an operation fails, we do not immediately give up; instead, we retry after a delay, increasing the delay between attempts. This pattern allows temporary issues to resolve themselves:
This function encapsulates retry logic. The ReviewConfig interface defines our configuration: which file to review and how many retries to attempt. We loop through attempts, catching errors and implementing exponential backoff: the first retry waits 2 seconds, the second waits 4 seconds, the third waits 8 seconds, and so on.
If all retries fail, we throw a final error. This pattern prevents infinite loops while giving transient issues time to resolve. The logging helps us understand what is happening during production incidents.
Now let us implement the actual review logic inside our retry function. We configure the agent with appropriate limits and process the message stream:
This code goes inside the try block of our retry function. We use disallowedTools to explicitly block file writing and command execution, preventing the agent from modifying the system while the bypassPermissions mode auto-approves all other tools. We configure maxTurns: 3 to limit the agent's iterations, preventing runaway execution. As we process messages, we look for the result type with the success subtype, which signals completion.
If we receive an error message, we throw an exception, triggering the retry logic. If the stream completes without a result, we also throw, forcing another attempt. Only when we successfully extract a result do we return, breaking out of the retry loop.
The combination of message stream processing and retry logic creates a robust system that handles both transient failures (retried automatically) and permanent failures (reported after exhausting retries).
Let us wrap our retry function in a complete application that handles errors gracefully and saves results to disk:
The main function demonstrates proper error handling at the application level. We attempt the review with up to 3 retries. On success, we write the result to a file and log confirmation. On failure, we log the error and exit with a non-zero status code via process.exit(1), signaling failure to any orchestration system.
When we run this with tsx production_agent.ts, we see:
The agent succeeded on the first attempt, completed the review, and saved the report. If it had failed, we would see retry attempts with increasing delays. This pattern makes our agent reliable in production environments where transient failures are common.
To deploy our agent consistently across different environments, we use Docker containerization. Let us create a Dockerfile that packages our application with all its dependencies:
This Dockerfile follows best practices for Node.js applications. We start from a slim Node 20 base image to minimize size. The WORKDIR command establishes our working directory inside the container.
We copy package.json and package-lock.json first, then run npm ci (which installs exact versions from the lock file). Copying dependency files separately leverages Docker's layer caching: if our dependencies have not changed, Docker reuses the cached layer, speeding up builds.
Next, we copy our application code and configuration, build the TypeScript to JavaScript, and finally specify the command to run our agent. The result is a self-contained image that runs identically regardless of the host environment.
For more complex deployments involving multiple services or configuration, we use Docker Compose. Here is a configuration for our documentation reviewer:
This docker-compose.yml file defines a service named doc-reviewer. The build: . directive tells Docker Compose to build our image using the Dockerfile in the current directory. We pass the ANTHROPIC_API_KEY from the host environment into the container.
The volumes section mounts host directories into the container: ./docs maps to /app/docs inside the container, allowing the agent to access documentation files. Similarly, ./logs provides persistent storage for log files. These volume mounts mean changes to documentation appear immediately inside the container without rebuilding the image.
The restart: unless-stopped policy ensures our agent automatically restarts if it crashes, providing resilience in production environments.
With our Docker setup complete, deploying the agent becomes straightforward. We start the service with Docker Compose:
The -d flag runs the container in detached mode, allowing it to run in the background. We see output like:
Docker Compose builds the image (if needed), starts the container, and confirms it is running. Our agent is now operating in a containerized environment with consistent dependencies, environment variables, and file access.
To monitor the agent, we can check logs using docker-compose logs -f doc-reviewer, which streams log output in real time. To stop the agent, we use docker-compose down. This deployment pattern works identically on development machines, CI/CD systems, and production servers, eliminating environment-specific issues.
The containerized agent benefits from isolation: it cannot interfere with other processes on the host, and changes to the host environment do not affect the container. Combined with our retry logic and error handling, we have built a production-ready agent that deploys consistently and runs reliably.
We have expanded our agent development skills into the TypeScript ecosystem, learning when to choose TypeScript over Python based on project context and team preferences. We explored TypeScript's type safety benefits, which catch errors at compile time rather than at runtime, and mastered async iterators patterns for handling agent message streams.
Most importantly, we learned how to build production-ready agents with proper error handling. We implemented retry logic with exponential backoff, configured agents with appropriate limits, and created robust error handling at the application level. We then packaged everything into Docker containers for consistent deployment across environments.
The key takeaway is that both the Python and TypeScript SDKs offer identical capabilities; the choice depends on our existing infrastructure and team expertise. Regardless of language choice, the patterns we learned, such as retry logic, graceful error handling, and containerization, apply universally to production agent development.
We now have the skills to build enterprise-grade agents that handle failures gracefully, deploy consistently, and operate reliably in production environments. The upcoming practice exercises will challenge you to implement your own production agents with sophisticated error handling and deployment configurations, solidifying these critical skills for real-world agent development!
