Skip to main content

Claude Code SDK agent

@flow-state-dev/claude-code/sdk — run a Claude Code agent in-process and watch it work through your flow's item stream. The agent's messages, reasoning, tool calls, and sub-agents become flow-state-dev items as they happen, and its session carries across requests.

This is the companion to Claude Code remote dispatch. That page hands a task to a cloud session and returns a handle. This page runs the agent locally, in your process, and streams everything it does.

When to use it

Reach for this when a flow needs a real agent in the loop, not a fire-and-forget dispatch: an assistant that reads and edits files, runs commands, searches the web, and reports back as it goes, with each step visible in the devtool. Because the run is a block, a sequencer can chain it, a router can branch on its result, and the next request can resume the same agent session.

The agent owns its own loop. flow-state-dev does not drive it step by step or re-implement its tools. It runs the Claude Code Agent SDK, observes the stream, and translates each message into a canonical item. You get the SDK's full tool suite and agentic behavior, with flow-state-dev's state, provenance, and streaming wrapped around it.

Installation

The SDK is an optional peer dependency, so the package stays installable for the /cli path without it:

pnpm add @flow-state-dev/claude-code @anthropic-ai/claude-agent-sdk

The Agent SDK bundles its own binary, so there is no separate CLI to install. It needs Anthropic credentials in the environment at runtime.

Quick start

A capability is a reusable bundle you attach to a block. Installing this one lets a generator hand work to the agent as a tool:

import { generator } from "@flow-state-dev/core";
import { createClaudeCodeAgentCapability } from "@flow-state-dev/claude-code/sdk";

const orchestrator = generator({
name: "orchestrator",
model: "openai/gpt-5.4-mini",
uses: [createClaudeCodeAgentCapability()],
});

As a sequencer step

When the host decides to run the agent (rather than letting a model choose), use the block directly:

import { claudeCodeAgent } from "@flow-state-dev/claude-code/sdk";

const agent = claudeCodeAgent({
systemPrompt: "You are a careful refactoring assistant.",
allowedTools: ["Read", "Grep", "Edit"],
});
// seq.step(agent) with input { prompt: "Tidy the imports in src/." }

The block returns a handle describing the run: its terminal status, the final assistant message, the tools it used, and usage when the SDK reports it.

What it emits

As the agent runs, the block translates the SDK stream into items:

SDK outputflow-state-dev item
Assistant textmessage (streamed token by token when partial messages are on)
Extended thinkingreasoning
Tool call and its resulttool_output, with the tool call's id, name, and arguments
Sub-agent (Task/Agent)a container grouping the sub-agent's items
System init, history compactiontransient status
Errorserror

Every item carries provenance, so the devtool shows the full trace of what the agent did.

Session continuity

The block persists the SDK session id in session state and resumes it on the next request, so a follow-up prompt continues the same conversation:

const runs = ctx.session.state.sdkAgentRuns ?? [];

Continuity runs through a BindingProvider, the framework's mechanism for holding a live handle across requests rather than a parallel store. The default provider is thin because the SDK resumes cheaply by id; pass your own to hold a heavier resource (an open connection, for example).

Tool approval

By default the agent governs its own tools through the SDK's permissionMode. To gate a tool call from your flow, pass onToolApproval: it receives each request and returns allow or deny, and the decision surfaces as a status item.

const agent = claudeCodeAgent({
permissionMode: "default",
onToolApproval: async (req) => {
if (req.toolName === "Bash") return { decision: "deny", message: "no shell" };
return { decision: "allow" };
},
});

This is the interim story. Routing approvals through flow-state-dev's own human-in-the-loop suspend and resume is a follow-up; onToolApproval is the seam that work will plug into.

Error handling

SituationBehavior
@anthropic-ai/claude-agent-sdk not installedThrows ClaudeAgentSdkNotInstalledError with an install hint.
The agent finishes with an error result (hit max turns, budget, or a runtime error)Treated as an outcome: the handle's status is "errored" with the SDK's resultSubtype, and an error item is emitted. No throw.
The SDK throws mid-streamWrapped in ClaudeAgentRunError and rethrown after an error item.
A tool or sub-agent is still open when the stream endsIts item is marked incomplete.
Empty promptValidation error before the agent starts.

Limitations

This version runs the agent and observes it. It does not let flow-state-dev drive the SDK loop one step at a time, register flow-state-dev blocks as tools the agent can call, or map sub-agents to nested flow-state-dev generators. Tool approval degrades to the SDK's own mechanism until native human-in-the-loop lands. Prompts are strings, not streamed input.

See also: Tools overview and Claude Code remote dispatch for the fire-and-forget cloud alternative.