tutorialNov 5, 2026·7 min read

Build AI Agents in TypeScript Without the Boilerplate

By Jonathan Stocco, Founder

In 2026, the gap between "we use AI" and "we run AI agents in production" is still enormous. According to McKinsey's State of AI 2024 report, 72% of organizations now use AI in at least one business function, up from 50% in previous years. Most of that usage is still prompt-in, answer-out. The teams actually running autonomous, multi-step reasoning pipelines are a much smaller group, and the barrier isn't intelligence: it's tooling. TypeScript developers, in particular, have been handed Python-first frameworks that fight their mental model at every turn.

That's the problem a new TypeScript-native library for building Claude-powered agentic applications is trying to solve. It abstracts the orchestration patterns that take weeks to get right, and it does so in idiomatic TypeScript. This tutorial walks through how to scaffold, build, and ship your first autonomous reasoning pipeline using it, including the production patterns that matter once you move past toy examples.

The Scenario: A Research-and-Draft Pipeline

Picture this: your team needs a system that takes a company name, researches it using a web-search tool, scores the account against your ICP criteria, and drafts a personalized outreach message. Four discrete tasks. One pipeline. The naive approach is a single LLM call with a massive prompt. That breaks the moment your context window fills up, and it's untestable because you can't isolate which step failed.

The right architecture splits those four tasks into discrete modules with explicit handoff contracts between them. I learned this the hard way building our first Autonomous SDR. We used a flat three-component architecture: research, scoring, and writing all reported to a single orchestrator. It worked on five leads. At fifty, the scorer sat idle waiting on research that had nothing to do with scoring. Splitting into discrete components with typed handoff schemas cut processing time and made each module independently testable. That's why every pipeline we build now uses explicit inter-component schemas. Implicit data passing doesn't hold up when volume increases.

Scaffolding the Project

Install the library and initialize a TypeScript project. The library ships with a CLI that generates the folder structure, type definitions, and a sample orchestrator:

npm install @anthropic-ai/agent-sdk
npx agent-sdk init my-research-pipeline
cd my-research-pipeline

The generated structure separates concerns cleanly: /agents for individual reasoning modules, /schemas for inter-module contracts, /tools for external integrations, and /orchestrator for the coordination layer. This isn't arbitrary. The library enforces typed handoffs between modules, which means TypeScript's compiler catches schema mismatches before runtime.

Set your API key in .env:

ANTHROPIC_API_KEY=your_key_here

Defining Inter-Module Schemas

Before writing any reasoning logic, define what each module receives and returns. This is the step most tutorials skip, and it's the one that determines whether your pipeline survives production load.

// schemas/research-output.ts
export interface ResearchOutput {
  companyName: string;
  industry: string;
  recentNews: string[];
  employeeCount: number | null;
  techStack: string[];
}

// schemas/score-output.ts
export interface ScoreOutput {
  icpScore: number; // 0-100
  primarySignal: string;
  disqualifyingFactors: string[];
}

Typed schemas do two things. First, they force you to think about what each module actually needs, which often reveals that you were planning to pass far more context than necessary. Second, they give you a contract you can write unit tests against without running the full pipeline.

Building the Reasoning Modules

Each module is a class that extends the library's BaseAgent. The library handles context management, retry logic, and tool-call parsing. You write the prompt and the output parser.

// agents/researcher.ts
import { BaseAgent } from '@anthropic-ai/agent-sdk';
import { ResearchOutput } from '../schemas/research-output';

export class ResearcherAgent extends BaseAgent<{ companyName: string }, ResearchOutput> {
  protected systemPrompt = `You are a B2B research specialist. 
  Given a company name, return structured data about the company.
  Always respond with valid JSON matching the ResearchOutput schema.`;

  protected tools = [this.webSearchTool()];

  async run(input: { companyName: string }): Promise {
    const response = await this.callModel(
      `Research this company and return structured data: ${input.companyName}`
    );
    return this.parseJSON(response);
  }
}

The callModel method handles token budgeting and retries. The parseJSON utility validates the output against your schema and throws a typed error if the reasoning layer returns malformed data. That error surfaces in your orchestrator, where you decide whether to retry, skip, or escalate.

One honest caveat here: the library's context management works well for pipelines with clear, bounded tasks. If you're building a module that needs to reason over a large, open-ended document corpus, you'll hit limits that the library doesn't solve for you. In those cases, you need a retrieval layer in front of the reasoning module, and the library doesn't include one out of the box. Plan for that before you commit to the architecture.

Wiring the Orchestrator

The orchestrator sequences the modules and passes typed outputs as inputs to the next stage:

// orchestrator/pipeline.ts
import { ResearcherAgent } from '../agents/researcher';
import { ScorerAgent } from '../agents/scorer';
import { WriterAgent } from '../agents/writer';

export async function runPipeline(companyName: string) {
  const researcher = new ResearcherAgent();
  const scorer = new ScorerAgent();
  const writer = new WriterAgent();

  const research = await researcher.run({ companyName });

  // Only score if research succeeded
  if (!research.industry) {
    throw new Error(`Research returned incomplete data for ${companyName}`);
  }

  const score = await scorer.run({ research });

  // Only draft if score clears threshold
  if (score.icpScore < 60) {
    return { skipped: true, reason: score.disqualifyingFactors };
  }

  const draft = await writer.run({ research, score });
  return { draft, score };
}

Notice the explicit gate at each handoff. The orchestrator doesn't blindly pass data forward. It checks for completeness and applies business logic before the next module runs. This is what makes the pipeline testable: you can mock any module's output and verify the orchestrator's branching behavior in isolation.

For teams building more complex coordination patterns, what ForgeWorkflows calls agentic logic, the library supports parallel execution via Promise.all across independent modules, with typed merge functions for combining their outputs before passing to a downstream step. We cover the tradeoffs of parallel versus sequential orchestration in more depth in our post on why AI coding pipelines break under production conditions.

What We'd Do Differently

Start with the schema, not the prompt. Every time we've written the reasoning prompt first, we've ended up with a schema that reflects what the LLM naturally returns rather than what the downstream module actually needs. Define the contract first, then write the prompt to satisfy it. The prompt is easier to iterate on than the schema once other modules depend on it.

Add a dead-letter queue before you need one. The orchestrator above throws on incomplete research data. In production, you want those failures routed somewhere observable, not just logged and lost. We'd wire a simple queue or webhook to capture failed runs from day one, not after the first time a batch of leads silently disappears. If you're connecting this kind of pipeline to a broader automation infrastructure, the ForgeWorkflows blueprint catalog includes pre-built patterns for failure routing and retry orchestration.

Don't parallelize until you've profiled the sequential version. Parallel execution looks appealing on paper. In practice, if your bottleneck is the reasoning layer's latency rather than I/O, parallelizing modules doesn't help and adds coordination complexity. Run the sequential pipeline against real data first, measure where time actually goes, then decide whether parallel execution solves the right problem.

Related Articles