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 / 4
~15 min
  1. 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.

  2. Change STATE to state-5-agent

    Update STATE from `state-4-workers-ai` to `state-5-agent` in wrangler.jsonc.

  3. Redeploy with the agent binding

    Run `npx wrangler deploy` — the agent's Durable Object class will be created.

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

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-sdk";

interface State {
  status: string;
  triageCard: object | null;
}

export class TriageAgent extends Agent<Env, State> {
  initialState: State = { status: "idle", triageCard: null };

  async investigate(incidentId: string) {
    this.setState({ ...this.state, status: "investigating" });

    // Load incident data from D1
    const incident = await this.env.INCIDENTS_DB
      .prepare("SELECT * FROM incidents WHERE id = ?")
      .bind(incidentId)
      .first();

    // Call Workers AI with the incident context
    const result = await this.env.AI.run(
      "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
      {
        messages: [
          { role: "system", content: "You are a security analyst..." },
          { role: "user", content: JSON.stringify(incident) },
        ],
      }
    );

    // Persist the result in agent state
    this.setState({
      ...this.state,
      status: "triaged",
      triageCard: result,
    });

    return result;
  }
}

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:

// Get a stub for this incident's TriageAgent
const agentId = env.TRIAGE_AGENT.idFromName(incidentId);
const stub = env.TRIAGE_AGENT.get(agentId);

// Call the agent's investigate method via RPC
const result = await stub.investigate(incidentId);

The idFromName(incidentId) call is critical — it creates a deterministic ID from the incident ID. This means every request for the same incident routes to the same agent instance. Call it once, call it a hundred times — the same agent handles it, with the same persisted state.

Step 1: Review the TriageAgent class

Open sirt-workshop-app/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: Change STATE to state-5-agent

Open sirt-workshop-app/wrangler.jsonc and update the STATE variable:

"vars": {
  "STATE": "state-5-agent"
}

This tells the Worker to route investigate requests through the TriageAgent instead of making direct Workers AI calls. The state-5-agent code path creates an agent stub and calls its investigate() RPC method.

Step 3: Redeploy

Deploy the updated configuration:

npx wrangler deploy

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

Step 4: 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.

Knowledge check