0.0
No release in over 3 years
ClaudeAgent is a Ruby SDK for building autonomous AI agents that interact with Claude Code CLI. It provides both simple one-shot queries and interactive bidirectional sessions with support for tool use, hooks, permissions, and in-process MCP servers.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

ClaudeAgent

Ruby gem for building AI-powered applications with the Claude Agent SDK. This library essentially wraps the Claude Code CLI, providing both simple one-shot queries and interactive bidirectional sessions.

Requirements

Installation

Add to your Gemfile:

gem "claude_agent"

Then run:

bundle install

Or install directly:

gem install claude_agent

Quick Start

One-Shot Query

The simplest way to use Claude:

require "claude_agent"

ClaudeAgent.query(prompt: "What is the capital of France?").each do |message|
  case message
  when ClaudeAgent::AssistantMessage
    puts message.text
  when ClaudeAgent::ResultMessage
    puts "Cost: $#{message.total_cost_usd}"
  end
end

Interactive Client

For multi-turn conversations:

require "claude_agent"

ClaudeAgent::Client.open do |client|
  client.query("Remember the number 42")
  client.receive_response.each { |msg| }  # Process first response

  client.query("What number did I ask you to remember?")
  client.receive_response.each do |msg|
    puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage)
  end
end

Configuration

Use ClaudeAgent::Options to customize behavior:

options = ClaudeAgent::Options.new(
  # Model selection
  model: "claude-sonnet-4-5-20250514",
  fallback_model: "claude-haiku-3-5-20241022",

  # Conversation limits
  max_turns: 10,
  max_budget_usd: 1.0,
  max_thinking_tokens: 10000,

  # System prompt
  system_prompt: "You are a helpful coding assistant.",
  append_system_prompt: "Always be concise.",

  # Tool configuration
  tools: ["Read", "Write", "Bash"],
  allowed_tools: ["Read"],
  disallowed_tools: ["Write"],

  # Permission modes: "default", "acceptEdits", "plan", "delegate", "dontAsk", "bypassPermissions"
  permission_mode: "acceptEdits",

  # Working directory for file operations
  cwd: "/path/to/project",
  add_dirs: ["/additional/path"],

  # Agent configuration
  agent: "my-agent",  # Agent name for main thread

  # Session management
  resume: "session-id",
  continue_conversation: true,
  fork_session: true,
  persist_session: true,  # Default: true

  # Structured output
  output_format: {
    type: "object",
    properties: { answer: { type: "string" } }
  }
)

ClaudeAgent.query(prompt: "Help me refactor this code", options: options)

Tools Preset

Use a preset tool configuration:

# Using ToolsPreset class
options = ClaudeAgent::Options.new(
  tools: ClaudeAgent::ToolsPreset.new(preset: "claude_code")
)

# Or as a Hash
options = ClaudeAgent::Options.new(
  tools: { type: "preset", preset: "claude_code" }
)

Sandbox Settings

Configure sandboxed command execution:

sandbox = ClaudeAgent::SandboxSettings.new(
  enabled: true,
  auto_allow_bash_if_sandboxed: true,
  excluded_commands: ["docker", "kubectl"],
  network: ClaudeAgent::SandboxNetworkConfig.new(
    allowed_domains: ["api.example.com"],
    allow_local_binding: true
  ),
  ripgrep: ClaudeAgent::SandboxRipgrepConfig.new(
    command: "/usr/local/bin/rg"
  )
)

options = ClaudeAgent::Options.new(sandbox: sandbox)

Custom Agents

Define custom subagents:

agents = {
  "test-runner" => ClaudeAgent::AgentDefinition.new(
    description: "Runs tests and reports results",
    prompt: "You are a test runner. Execute tests and report failures clearly.",
    tools: ["Read", "Bash"],
    model: "haiku"
  )
}

options = ClaudeAgent::Options.new(agents: agents)

Message Types

The SDK provides strongly-typed message classes:

AssistantMessage

Claude's responses:

message.text      # Combined text content
message.thinking  # Extended thinking content (if enabled)
message.model     # Model that generated the response
message.uuid      # Message UUID
message.session_id # Session identifier
message.tool_uses # Array of ToolUseBlock if Claude wants to use tools
message.has_tool_use?  # Check if tools are being used

ResultMessage

Final message with usage statistics:

result.session_id      # Session identifier
result.num_turns       # Number of conversation turns
result.duration_ms     # Total duration in milliseconds
result.total_cost_usd  # API cost in USD
result.usage           # Token usage breakdown
result.model_usage     # Per-model usage breakdown
result.is_error        # Whether the session ended in error
result.success?        # Convenience method
result.error?          # Convenience method
result.errors          # Array of error messages (if any)
result.permission_denials  # Array of SDKPermissionDenial (if any)

SystemMessage

Internal system events:

system_msg.subtype  # e.g., "init"
system_msg.data     # Event-specific data

StreamEvent

Real-time streaming events:

event.uuid        # Event UUID
event.session_id  # Session identifier
event.event_type  # Type of stream event
event.event       # Raw event data

CompactBoundaryMessage

Conversation compaction marker:

boundary.uuid       # Message UUID
boundary.session_id # Session identifier
boundary.trigger    # "manual" or "auto"
boundary.pre_tokens # Token count before compaction

StatusMessage

Session status updates:

status.uuid       # Message UUID
status.session_id # Session identifier
status.status     # e.g., "compacting"

ToolProgressMessage

Long-running tool progress:

progress.tool_use_id         # Tool use ID
progress.tool_name           # Tool name
progress.elapsed_time_seconds # Time elapsed

HookResponseMessage

Hook execution output:

hook_response.hook_name  # Hook name
hook_response.hook_event # Hook event type
hook_response.stdout     # Hook stdout
hook_response.stderr     # Hook stderr
hook_response.exit_code  # Exit code

AuthStatusMessage

Authentication status during login:

auth.is_authenticating # Whether auth is in progress
auth.output            # Auth output messages
auth.error             # Error message (if any)

Content Blocks

Assistant messages contain content blocks:

message.content.each do |block|
  case block
  when ClaudeAgent::TextBlock
    puts block.text
  when ClaudeAgent::ThinkingBlock
    puts "Thinking: #{block.thinking}"
  when ClaudeAgent::ToolUseBlock
    puts "Tool: #{block.name}, ID: #{block.id}, Input: #{block.input}"
  when ClaudeAgent::ToolResultBlock
    puts "Result for #{block.tool_use_id}: #{block.content}"
  when ClaudeAgent::ServerToolUseBlock
    puts "MCP Tool: #{block.name} from #{block.server_name}"
  when ClaudeAgent::ServerToolResultBlock
    puts "MCP Result from #{block.server_name}"
  when ClaudeAgent::ImageContentBlock
    puts "Image: #{block.media_type}, source: #{block.source_type}"
  end
end

MCP Tools

Create in-process MCP tools that Claude can use:

# Define a tool
calculator = ClaudeAgent::MCP::Tool.new(
  name: "add",
  description: "Add two numbers together",
  schema: { a: Float, b: Float }
) do |args|
  args["a"] + args["b"]
end

# Create a server with tools
server = ClaudeAgent::MCP::Server.new(
  name: "calculator",
  tools: [calculator]
)

# Use with options (SDK MCP servers)
options = ClaudeAgent::Options.new(
  mcp_servers: {
    "calculator" => { type: "sdk", instance: server }
  }
)

ClaudeAgent.query(
  prompt: "What is 25 + 17?",
  options: options
)

External MCP Servers

Configure external MCP servers:

options = ClaudeAgent::Options.new(
  mcp_servers: {
    "filesystem" => {
      type: "stdio",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
)

Tool Schema

Define schemas using Ruby types or JSON Schema:

# Ruby types (converted automatically)
schema: {
  name: String,
  age: Integer,
  score: Float,
  active: TrueClass,  # boolean
  tags: Array,
  metadata: Hash
}

# Or use JSON Schema directly
schema: {
  type: "object",
  properties: {
    name: { type: "string", description: "User's name" },
    age: { type: "integer", minimum: 0 }
  },
  required: ["name"]
}

Tool Return Values

Tools can return various formats:

# Simple string
ClaudeAgent::MCP::Tool.new(name: "greet", description: "Greet") do |args|
  "Hello, #{args['name']}!"
end

# Number (converted to string)
ClaudeAgent::MCP::Tool.new(name: "add", description: "Add") do |args|
  args["a"] + args["b"]
end

# Custom MCP content
ClaudeAgent::MCP::Tool.new(name: "fancy", description: "Fancy") do |args|
  { content: [{ type: "text", text: "Custom response" }] }
end

Hooks

Intercept tool usage and other events with hooks:

options = ClaudeAgent::Options.new(
  hooks: {
    "PreToolUse" => [
      ClaudeAgent::HookMatcher.new(
        matcher: "Bash|Write",  # Match specific tools
        callbacks: [
          ->(input, context) {
            puts "Tool: #{input.tool_name}"
            puts "Input: #{input.tool_input}"
            puts "Tool Use ID: #{input.tool_use_id}"
            { continue_: true }  # Allow the tool to proceed
          }
        ]
      )
    ],
    "PostToolUse" => [
      ClaudeAgent::HookMatcher.new(
        matcher: /^mcp__/,  # Regex matching
        callbacks: [
          ->(input, context) {
            puts "Result: #{input.tool_response}"
            { continue_: true }
          }
        ]
      )
    ]
  }
)

Hook Events

All available hook events:

  • PreToolUse - Before tool execution
  • PostToolUse - After successful tool execution
  • PostToolUseFailure - After tool execution failure
  • Notification - System notifications
  • UserPromptSubmit - When user submits a prompt
  • SessionStart - When session starts
  • SessionEnd - When session ends
  • Stop - When agent stops
  • SubagentStart - When subagent starts
  • SubagentStop - When subagent stops
  • PreCompact - Before conversation compaction
  • PermissionRequest - When permission is requested

Hook Input Types

Event Input Type Key Fields
PreToolUse PreToolUseInput tool_name, tool_input, tool_use_id
PostToolUse PostToolUseInput tool_name, tool_input, tool_response, tool_use_id
PostToolUseFailure PostToolUseFailureInput tool_name, tool_input, error, tool_use_id, is_interrupt
Notification NotificationInput message, title, notification_type
UserPromptSubmit UserPromptSubmitInput prompt
SessionStart SessionStartInput source, agent_type, model
SessionEnd SessionEndInput reason
Stop StopInput stop_hook_active
SubagentStart SubagentStartInput agent_id, agent_type
SubagentStop SubagentStopInput stop_hook_active, agent_id, agent_transcript_path
PreCompact PreCompactInput trigger, custom_instructions
PermissionRequest PermissionRequestInput tool_name, tool_input, permission_suggestions

All hook inputs inherit from BaseHookInput with: hook_event_name, session_id, transcript_path, cwd, permission_mode.

Permissions

Control tool permissions programmatically:

options = ClaudeAgent::Options.new(
  can_use_tool: ->(tool_name, tool_input, context) {
    # Context includes: permission_suggestions, blocked_path, decision_reason, tool_use_id, agent_id

    # Allow all read operations
    if tool_name == "Read"
      ClaudeAgent::PermissionResultAllow.new
    # Deny writes to sensitive paths
    elsif tool_name == "Write" && tool_input["file_path"].include?(".env")
      ClaudeAgent::PermissionResultDeny.new(
        message: "Cannot modify .env files",
        interrupt: true
      )
    else
      ClaudeAgent::PermissionResultAllow.new
    end
  }
)

Permission Results

# Allow with optional modifications
ClaudeAgent::PermissionResultAllow.new(
  updated_input: { file_path: "/safe/path" },  # Modify tool input
  updated_permissions: [...]  # Update permission rules
)

# Deny
ClaudeAgent::PermissionResultDeny.new(
  message: "Operation not allowed",
  interrupt: true  # Stop the agent
)

Permission Updates

update = ClaudeAgent::PermissionUpdate.new(
  type: "addRules",  # addRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories
  rules: [
    ClaudeAgent::PermissionRuleValue.new(tool_name: "Read", rule_content: "/**")
  ],
  behavior: "allow",
  destination: "session"  # userSettings, projectSettings, localSettings, session, cliArg
)

Error Handling

The SDK provides specific error types:

begin
  ClaudeAgent.query(prompt: "Hello")
rescue ClaudeAgent::CLINotFoundError
  puts "Claude Code CLI not installed"
rescue ClaudeAgent::CLIVersionError => e
  puts "CLI version too old: #{e.message}"
  puts "Required: #{e.required_version}, Actual: #{e.actual_version}"
rescue ClaudeAgent::CLIConnectionError => e
  puts "Connection failed: #{e.message}"
rescue ClaudeAgent::ProcessError => e
  puts "Process error: #{e.message}, exit code: #{e.exit_code}"
rescue ClaudeAgent::TimeoutError => e
  puts "Timeout: #{e.message}"
rescue ClaudeAgent::JSONDecodeError => e
  puts "Invalid JSON response"
rescue ClaudeAgent::MessageParseError => e
  puts "Could not parse message"
rescue ClaudeAgent::AbortError => e
  puts "Operation aborted"
end

Client API

For fine-grained control:

client = ClaudeAgent::Client.new(options: options)

# Connect to CLI
client.connect

# Send queries
client.query("First question")
client.receive_response.each { |msg| process(msg) }

client.query("Follow-up question")
client.receive_response.each { |msg| process(msg) }

# Control methods
client.interrupt                              # Cancel current operation
client.set_model("claude-opus-4-5-20251101")  # Change model
client.set_permission_mode("acceptEdits")     # Change permissions
client.set_max_thinking_tokens(5000)          # Change thinking limit

# File checkpointing (requires enable_file_checkpointing: true)
result = client.rewind_files("user-message-uuid", dry_run: true)
puts "Can rewind: #{result.can_rewind}"
puts "Files changed: #{result.files_changed}"

# Dynamic MCP server management
result = client.set_mcp_servers({
  "my-server" => { type: "stdio", command: "node", args: ["server.js"] }
})
puts "Added: #{result.added}, Removed: #{result.removed}"

# Query capabilities
client.supported_commands.each { |cmd| puts "#{cmd.name}: #{cmd.description}" }
client.supported_models.each { |model| puts "#{model.value}: #{model.display_name}" }
client.mcp_server_status.each { |s| puts "#{s.name}: #{s.status}" }
puts client.account_info.email

# Disconnect
client.disconnect

V2 Session API (Unstable)

⚠️ Alpha API: This API is unstable and may change without notice.

The V2 Session API provides a simpler interface for multi-turn conversations, matching the TypeScript SDK's SDKSession interface.

Create a Session

# Create a new session
session = ClaudeAgent.unstable_v2_create_session(
  model: "claude-sonnet-4-5-20250514",
  permission_mode: "acceptEdits"
)

# Send a message
session.send("Hello, Claude!")

# Stream responses
session.stream.each do |msg|
  case msg
  when ClaudeAgent::AssistantMessage
    puts msg.text
  when ClaudeAgent::ResultMessage
    puts "Done! Cost: $#{msg.total_cost_usd}"
  end
end

# Continue the conversation
session.send("Tell me more")
session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }

# Close when done
session.close

Resume a Session

# Resume an existing session by ID
session = ClaudeAgent.unstable_v2_resume_session(
  "session-abc123",
  model: "claude-sonnet-4-5-20250514"
)

session.send("What were we discussing?")
session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }
session.close

One-Shot Prompt

# Simple one-shot prompt (auto-closes session)
result = ClaudeAgent.unstable_v2_prompt(
  "What is 2 + 2?",
  model: "claude-sonnet-4-5-20250514"
)

puts "Success: #{result.success?}"
puts "Cost: $#{result.total_cost_usd}"

SessionOptions

The V2 API uses a simplified options type:

options = ClaudeAgent::SessionOptions.new(
  model: "claude-sonnet-4-5-20250514",           # Required
  permission_mode: "acceptEdits",                 # Optional
  allowed_tools: ["Read", "Grep"],                # Optional
  disallowed_tools: ["Write"],                    # Optional
  can_use_tool: ->(name, input, ctx) { ... },    # Optional
  hooks: { "PreToolUse" => [...] },               # Optional
  env: { "MY_VAR" => "value" },                   # Optional
  path_to_claude_code_executable: "/custom/path"  # Optional
)

session = ClaudeAgent.unstable_v2_create_session(options)

Types Reference

Return Types

Type Purpose
SlashCommand Available slash commands (name, description, argument_hint)
ModelInfo Available models (value, display_name, description)
McpServerStatus MCP server status (name, status, server_info)
AccountInfo Account information (email, organization, subscription_type)
ModelUsage Per-model usage stats (input_tokens, output_tokens, cost_usd)
McpSetServersResult Result of set_mcp_servers (added, removed, errors)
RewindFilesResult Result of rewind_files (can_rewind, error, files_changed, insertions, deletions)
SDKPermissionDenial Permission denial info (tool_name, tool_use_id, tool_input)

Environment Variables

The SDK sets these automatically:

  • CLAUDE_CODE_ENTRYPOINT=sdk-rb
  • CLAUDE_AGENT_SDK_VERSION=<version>

Skip version checking (for development):

export CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK=true

Development

# Install dependencies
bin/setup

# Run unit tests
bundle exec rake test

# Run integration tests (requires Claude Code CLI v2.0.0+)
bundle exec rake test_integration

# Run all tests
bundle exec rake test_all

# Validate RBS type signatures
bundle exec rake rbs

# Linting
bundle exec rubocop

# Interactive console
bin/console

# Binstubs for convenience
bin/test              # Unit tests only
bin/test-integration  # Integration tests
bin/test-all          # All tests
bin/rbs-validate      # Validate RBS signatures
bin/release 1.2.0     # Release a new version

Architecture

ClaudeAgent.query() / ClaudeAgent::Client
           │
           ▼
┌──────────────────────────┐
│   Control Protocol       │  Request/response routing
│   - Hooks                │  Permission callbacks
│   - MCP bridging         │  Tool interception
└──────────┬───────────────┘
           │
           ▼
┌──────────────────────────┐
│   Subprocess Transport   │  JSON Lines protocol
│   - stdin/stdout         │  Process management
│   - stderr handling      │
└──────────┬───────────────┘
           │
           ▼
     Claude Code CLI

License

MIT License - see LICENSE.txt