SIRT Triage Agent Workshop
~15 min

Agents SDK -- durable agents, scoped tools, the Agent class

Convert the single Workers AI call into a proper Agent subclass with an investigate() RPC method, introducing the Agents SDK.

Steps0 / 5
~15 min
  1. Navigate to the lesson-10-agents-sdk directory

    Run `cd ..\lesson-10-agents-sdk` from your current lesson directory.

  2. Replace REPLACE_WITH_YOUR_DATABASE_ID in wrangler.jsonc

    Open `wrangler.jsonc` and replace `REPLACE_WITH_YOUR_DATABASE_ID` with your D1 database_id from lesson 06.

  3. Review the TriageAgent class in the codebase

    Open `src/agents/triage-agent.ts` and read the Agent subclass. Note the `investigate()` method and the state management.

  4. Redeploy with the agent binding

    Run `npm run deploy` — the agent's Durable Object class will be created.

  5. Verify the agent-driven investigate flow

    Click Investigate on an incident. The analysis should now come from the TriageAgent, with the same quality but now running through a durable agent lifecycle.

OpenCode shortcut

Paste this prompt into the OpenCode TUI to let your AI agent walk you through this lesson:

 

Step 0: Navigate to the lesson directory

Open your terminal and navigate to the lesson-10-agents-sdk directory:

cd ..\lesson-10-agents-sdk

This directory contains the app pre-configured for the Agents SDK lesson. The STATE variable is already set to state-5-agent in wrangler.jsonc.

What is the Agents SDK?

Agents SDK is Cloudflare’s framework for building stateful, durable AI agents on Workers. The core primitive is the Agent class — a Durable Object subclass with built-in support for:

  • Persistent statethis.setState() and this.state backed by the DO’s SQLite storage. State survives between requests automatically.
  • RPC methods — Public methods on your Agent class are callable from your Worker via the DO stub. No REST API boilerplate needed.
  • Lifecycle hooksonStart(), onConnect(), onClose() for managing the agent’s lifecycle.
  • Scoped tools — Define what tools and capabilities each agent has access to. Different agents can have different tool sets.

The key insight: an Agent is a Durable Object with opinions. Instead of writing raw DO code with storage APIs and WebSocket handlers, the Agent class gives you a structured pattern for state, communication, and orchestration.

Why agents instead of plain Workers AI calls?

In lesson 09, you called env.AI.run() directly from the Worker. That works, but it’s stateless — every investigate call starts from scratch. The Worker doesn’t remember what it analyzed before, can’t hold intermediate results, and can’t run multi-step workflows.

An Agent-based approach gives you:

  • Durable state — The agent remembers what it has investigated. If you investigate an incident, leave, and come back, the results are still there.
  • Composability — You can add sub-agents, tools, and multi-step flows. The Coordinator pattern in lesson 11 becomes natural.
  • Lifecycle — The agent can run workflows that span multiple requests. Start an investigation now, receive results later.
  • Scoped tools — Each agent only has access to what it needs. The triage agent can call Workers AI; the response agent can read playbooks. Separation of concerns.

The Agent class pattern

Here’s the simplified structure of the TriageAgent class:

import { Agent } from "agents";

export interface TriageState {
  incident_id: string;
  status: "idle" | "investigating" | "triaged" | "responded" | "error";
  triage_card?: object;
  updated_at: string;
}

export class TriageAgent extends Agent<Env, TriageState> {
  initialState: TriageState = {
    incident_id: "",
    status: "idle",
    updated_at: new Date().toISOString(),
  };

  // RPC: invoked from the Worker via getAgentByName(...).investigate(incident)
  async investigate(incident: any): Promise<TriageState> {
    this.setState({
      incident_id: incident.incident_id ?? "unknown",
      status: "investigating",
      updated_at: new Date().toISOString(),
    });

    // Run the triage harness with Workers AI (single call here; lesson 11
    // expands this to a coordinator + parallel sub-agents).
    const triageCard = await this.runTriage(this.env.AI, incident);

    const finalState: TriageState = {
      incident_id: incident.incident_id ?? "unknown",
      status: "responded",
      triage_card: triageCard,
      updated_at: new Date().toISOString(),
    };
    this.setState(finalState); // persisted to the agent's SQLite storage
    return finalState;
  }
}

The real triage-agent.ts you’ll open already runs the full harness (coordinator + four sub-agents + synthesizer + response agent) — this snippet is trimmed to show the Agent shape.

Key things to notice:

  • extends Agent<Env, State> — The generic parameters type the environment bindings and the agent’s state shape.
  • initialState — Defines the default state for a new agent instance. When the agent is first created, this.state returns this value.
  • this.setState() — Persists state to the DO’s SQLite storage. The state survives between requests and even between Worker restarts.
  • this.env — The agent has access to the same environment bindings as the Worker. It can call D1, Workers AI, or any other binding.
  • investigate() — This is an RPC method. The Worker calls it via the DO stub.

How the Worker routes to the agent

In src/server.ts, the investigate handler now routes through the agent instead of calling Workers AI directly:

import { getAgentByName } from "agents";

// Resolve (or create) this incident's TriageAgent instance by name,
// then call its investigate() method directly over RPC.
const agent = await getAgentByName(env.TRIAGE_AGENT, incidentId);
const result = await agent.investigate(incident);

getAgentByName(env.TRIAGE_AGENT, incidentId) is the key — it returns the agent instance for that name, creating it on first use. Every request for the same incident resolves to the same agent instance, with the same persisted state. Call it once or a hundred times — the same TriageAgent handles it.

Step 1: Review the TriageAgent class

Open lesson-10-agents-sdk\src\agents\triage-agent.ts and read through the code. Identify:

  • The initialState definition
  • The investigate() method
  • Where this.setState() is called and what state transitions happen
  • How this.env.AI is used inside the agent

The agent wraps the same Workers AI call from lesson 09, but now it’s inside a durable context with persistent state.

Step 2: Redeploy

Deploy the updated configuration:

npm run deploy

Wrangler will create the TriageAgent’s Durable Object class during deployment. You should see the deployment succeed with the new class registered.

Step 3: Verify the agent-driven flow

  1. Open your app and navigate to the incident queue.
  2. Click on any incident and click “Investigate.”
  3. The analysis should appear as before — same quality, same structure.

The difference is under the hood: the analysis now runs through a durable agent lifecycle. The TriageAgent persists its state, so if you reload the page after investigating, the results are still there. In lesson 09, that state would have been lost.

Try this: investigate an incident, go back to the queue, then re-open the same incident. The triage results should still be visible without making another AI call. That’s the agent’s durable state at work.

What comes next

Right now, the TriageAgent makes a single Workers AI call — the same pattern as lesson 09, just wrapped in an agent. In lesson 11, you’ll replace that single call with a Coordinator + 4 parallel sub-agents pattern. The agent’s investigate() method will fan out to four specialized analyzers (Command-Line, Identity, Network, Activity), collect their results in parallel, and synthesize them into a unified triage card.

The Agents SDK makes this decomposition natural — each sub-agent analysis can be a separate function with its own system prompt, and the Coordinator orchestrates them with Promise.all.

Key takeaways

Before moving on, make sure these ideas land. They are the reason this lesson matters in the larger triage system.

  1. 1

    An Agent is a Durable Object with agent ergonomics

    The Agent class adds state helpers, callable methods, lifecycle hooks, and tool scoping on top of Durable Objects. You get durable coordination without hand-rolling all the plumbing.

  2. 2

    Agent identity determines where state lives

    Routing by incident ID means every investigation for the same incident reaches the same TriageAgent. That gives the agent memory across requests.

  3. 3

    Durable state enables multi-step workflows

    The agent can remember investigation status and results after the page reloads. That is the foundation for longer workflows like parallel analysis and action planning.

Knowledge checkRequired to continue