claude_agent_sdk logo

claude_agent_sdk


A typed Dart SDK for building agents on top of the Claude Code CLI.

Overview

claude_agent_sdk launches an installed Claude Code CLI and exposes one-shot queries, interactive sessions, typed stream messages, runtime permissions, hooks, programmatic subagents, user-dialog callbacks, settings resolution, in-process MCP tools, and persistent session APIs.

The package does not bundle Claude Code or an API client. Install and authenticate the claude CLI before running a live agent.

Install

dart pub add claude_agent_sdk

The package requires Dart 3.10 or newer and Claude Code 2.0 or newer.

Quick Start

import 'dart:io';

import 'package:claude_agent_sdk/claude_agent_sdk.dart';

Future<void> main() async {
  await for (final message in query(
    'Explain the architecture of this repository.',
    options: ClaudeAgentOptions(
      workingDirectory: Directory.current.path,
      allowedTools: const ['Read', 'Glob', 'Grep'],
      permissionMode: PermissionMode.plan,
    ),
  )) {
    if (message case AssistantMessage(:final content)) {
      for (final block in content.whereType<TextBlock>()) {
        stdout.write(block.text);
      }
    }
  }
}

Interactive client

final client = ClaudeAgentClient(
  options: ClaudeAgentOptions(
    systemPrompt: const SystemPrompt.claudeCode(
      append: 'Keep answers concise.',
    ),
  ),
);

await client.connect();
try {
  await client.send('Review the current implementation.');
  await for (final message in client.receiveResponse()) {
    // The stream includes the terminal ResultMessage.
  }

  await client.setPermissionMode(PermissionMode.acceptEdits);
  await client.send('Apply the fixes you found.');
  await client.receiveResponse().drain<void>();
} finally {
  await client.disconnect();
}

Pre-warmed query

Pay process startup and initialization cost before a latency-sensitive prompt. A warm handle accepts exactly one prompt and closes itself after the result:

final warm = await startup(options: ClaudeAgentOptions(model: 'sonnet'));
await for (final message in warm.query('Summarize the current diff.')) {
  // The process was already initialized before query() was called.
}

Subagents

Define subagents in agents and optionally select one as the main-thread agent. Claude invokes a subagent through its Agent tool based on the description. forwardSubagentText exposes its complete nested transcript; otherwise the runtime forwards only tool activity.

final options = ClaudeAgentOptions(
  agent: 'coordinator',
  agents: {
    'coordinator': AgentDefinition(
      description: 'Coordinates implementation work.',
      prompt: 'Delegate focused research and verification tasks.',
      tools: const ['Read', 'Glob', 'Grep', 'Agent'],
    ),
    'test-runner': AgentDefinition(
      description: 'Runs tests and diagnoses failures.',
      prompt: 'Run the smallest relevant test suite and report evidence.',
      tools: const ['Read', 'Glob', 'Grep', 'Bash'],
      model: 'haiku',
      maxTurns: 8,
      runsInBackground: true,
    ),
  },
  forwardSubagentText: true,
  agentProgressSummaries: true,
);

Subagent assistant and user messages carry parentToolUseId, subagentType, and taskDescription when supplied by the runtime. Use listSubagents / getSubagentMessages (or their SessionStore variants) to read persisted nested conversations. supportedAgents() discovers the effective programmatic, filesystem, plugin, and built-in agent set.

Permissions, hooks, and SDK MCP

canUseTool receives interactive permission decisions over the control channel. Hooks are typed by lifecycle event. SdkMcpServer exposes local Dart functions as MCP tools without starting another process.

final options = ClaudeAgentOptions(
  canUseTool: (toolName, input, context) async {
    if (toolName == 'Bash') {
      return const PermissionDenied(message: 'Shell access is disabled.');
    }
    return PermissionAllowed();
  },
  hooks: {
    HookEvent.preToolUse: [
      HookMatcher(
        matcher: 'Write|Edit',
        hooks: [
          (input, toolUseId, context) async =>
              const HookOutput(systemMessage: 'Edit audited by Dart.'),
        ],
      ),
    ],
  },
  mcp: McpServers({
    'local': SdkMcpServer(
      name: 'local',
      tools: [
        SdkMcpTool(
          name: 'lookup',
          description: 'Looks up an application value.',
          inputSchema: {
            'type': 'object',
            'properties': <String, Object?>{},
          },
          handler: (input) async => McpToolResult(
            content: const [McpTextContent('value')],
          ),
        ),
      ],
    ),
  }),
);

Session persistence

Local session helpers read the same JSONL transcripts as Claude Code:

final sessions = listSessions(directory: Directory.current.path);
final messages = getSessionMessages(
  sessions.first.sessionId,
  directory: Directory.current.path,
);

Use SessionStore to mirror transcripts into a database or object store. InMemorySessionStore is the reference implementation. Store-backed resume materializes a temporary Claude config tree, copies only the authentication state needed by the subprocess, removes OAuth refresh tokens from copied credentials, and cleans the tree after disconnect.

Supported Features

Area Dart API
One-shot query, queryStream, pre-warmed startup / ClaudeWarmQuery
Interactive ClaudeAgentClient, UserInput
Messages AssistantMessage, UserMessage, ResultMessage, system/task/rate-limit events, raw-preserving envelopes
Control initialization/reinitialization, interrupt/queue cancellation, permission/model switching, file read/rewind, dynamic MCP, plugin/skill reload, task stop/background, usage and context status
Interaction typed permission and elicitation callbacks with cancellation
Extensibility typed hooks, programmatic/main-thread agents, skills/plugins, tool aliases, canUseTool, stdio/SSE/HTTP MCP config, SdkMcpServer
Sessions local list/read/rename/tag/fork/delete, optional system history, nested subagent ancestry/transcripts, and corresponding SessionStore APIs
Runtime CLI discovery/execution/logout and provenance-aware tiered settings resolution

Unknown top-level messages and content blocks are preserved as UnknownAgentMessage and UnknownContentBlock, keeping newer CLI output forward-compatible.

Model discovery exposes effort, adaptive-thinking, Fast-mode, and auto-mode capabilities. UserMessage.toolResultMetadata provides the runtime's per-tool reason and optional feedback when a denied, interrupted, or cancelled tool never executed. Hook callbacks receive a ControlCallbackContext; its cancellation signal is triggered when the runtime cancels the pending hook.

UserInput accepts optional uuid and priority fields for transports that need turn identity or immediate steering. Message envelopes retain the raw wire payload alongside the typed message when protocol extensions must be inspected without losing forward compatibility.

Examples and verification

dart run example/quick_start.dart
dart run example/subagents.dart
CLAUDE_AGENT_SDK_LIVE_TEST=1 dart run example/quick_start.dart
dart test
dart analyze .
dart pub publish --dry-run

See example/README.md and benchmark/README.md.

Security

Agents can read files, execute tools, and call external services according to their configuration. Read SECURITY.md before enabling tools in an application that processes untrusted input.

Benchmarks

Scenario Runtime
ToolUseBlock construction with nested immutable input 4.57 µs

Measured on the package's development host with Dart 3.12.0.

Contributing

See CONTRIBUTING.md. Run formatting, analysis, tests, the example smoke tool, and the benchmark smoke tool before opening a change.

License

Licensed under the Apache License 2.0.

Libraries

claude_agent_sdk
Build one-shot and stateful agents on top of the Claude Code CLI.
testing
Reusable contract checks for custom Claude Agent SDK adapters.