Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 20 additions & 24 deletions .claude/rules/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ lib/

Separate concerns into distinct layers:

| Layer | Purpose | Example |
|-------|---------|---------|
| CLI | Orchestrates operations, user interaction | `Cli::App#deploy` |
| Commands | Builds command arrays, no execution | `Commands::App#run` |
| Configuration | Loads, validates, provides config | `Configuration::Role` |
| Layer | Purpose | Example |
|---------------|-------------------------------------------|-----------------------|
| CLI | Orchestrates operations, user interaction | `Cli::App#deploy` |
| Commands | Builds command arrays, no execution | `Commands::App#run` |
| Configuration | Loads, validates, provides config | `Configuration::Role` |

### Entry Point Pattern

Expand Down Expand Up @@ -646,27 +646,23 @@ Key conventions:
- `to_*` method compiles to the internal format
- `empty?` predicate for skipping when unconfigured

### Prepending Modules into Data.define Types
### ImmutableRecord Base Class

To add shared behavior (like `deconstruct_keys` overrides) to `Data.define` classes,
use `prepend` not `include`. Data.define generates methods on the class itself, so
`include` would be shadowed. When overriding `deconstruct_keys`, filter virtual keys
out before calling `super` — Data's implementation stops early on unknown member keys.
All immutable value types (messages, content blocks, permissions, sandbox config, etc.)
inherit from `ImmutableRecord`. It provides an `attribute` DSL, freeze-on-initialize,
structural equality, and `deconstruct_keys` for pattern matching.

```ruby
module Message
def deconstruct_keys(keys)
if keys.nil?
{ type: type }.merge(super)
elsif keys.include?(:type)
member_keys = keys - [ :type ]
base = member_keys.empty? ? {} : super(member_keys)
{ type: type }.merge(base)
else
super
end
end
end
class TextBlock < ImmutableRecord
attribute :text

MESSAGE_TYPES.each { |klass| klass.prepend(Message) }
def type = :text
def to_h = { type: "text", text: text }
end
```

Key conventions:
- Required attributes: `attribute :name`
- Optional attributes: `attribute :name, default: nil`
- No need to override `initialize` — the base class handles keyword args and freeze
- The `Message` module is `include`d (not prepended) into message/content block types
4 changes: 2 additions & 2 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ require "mocha/minitest"
Dir[File.join(__dir__, "support", "**", "*.rb")].each { |f| require f }
```

## Testing Data.define Types
## Testing ImmutableRecord Types

This SDK uses `Data.define` for immutable message types:
This SDK uses `ImmutableRecord` for immutable message types:

```ruby
test "data type attributes" do
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Replaced all 63 `Data.define` types with plain class inheritance from `ImmutableRecord` base class
- `Message` module is now `include`d instead of `prepend`ed into message and content block types
- RBS signatures now use real supertype inheritance (`< ImmutableRecord`)

## [0.7.18] - 2026-03-20

### Added
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Ruby SDK for building autonomous AI agents that interact with Claude Code CLI. S

## Stack

- **Ruby** 3.2+ (uses `Data.define` for immutable types)
- **Ruby** 3.2+
- **Minitest** for testing
- **RuboCop** with `rubocop-rails-omakase` for linting
- **RBS** for type signatures (in `sig/`)
Expand Down Expand Up @@ -53,7 +53,7 @@ bin/release VERSION # Release gem (e.g., bin/release 1.2.0)

## Conventions

- **Immutable data types**: All messages and content blocks use `Data.define`, frozen at construction
- **Immutable data types**: All messages and content blocks inherit from `ImmutableRecord`, frozen at construction
- **Frozen string literals**: Every file starts with `# frozen_string_literal: true`
- **Message module**: All message/block types include `ClaudeAgent::Message` (text_content, pattern matching)
- **Stripe-style config**: `ClaudeAgent.model = "opus"`, `ClaudeAgent.configure { |c| ... }`, `Configuration#to_options`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Ruby SDK for building AI-powered applications with the [Claude Agent SDK](https:

## Requirements

- Ruby 3.2+ (uses `Data.define`)
- Ruby 3.2+
- [Claude Code CLI](https://code.claude.com/docs/en/getting-started) v2.0.0+

## Installation
Expand Down
1 change: 0 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,6 @@ Public API surface for SDK clients.

### Ruby SDK (This Repository)
- Feature parity with TypeScript SDK v0.2.80
- Ruby-idiomatic patterns (Data.define, snake_case)
- Complete control protocol, hook, and V2 Session API support
- Dedicated Client class for multi-turn conversations
- `executable`/`executableArgs` marked N/A (JS runtime options)
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Internal architecture of the ClaudeAgent Ruby SDK.

| Module | Role |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ToolActivity` | Immutable (`Data.define`) record of a completed tool execution. Pairs `ToolUseBlock` + `ToolResultBlock` with turn index and wall-clock timestamps. |
| `ToolActivity` | Immutable (`ImmutableRecord`) record of a completed tool execution. Pairs `ToolUseBlock` + `ToolResultBlock` with turn index and wall-clock timestamps. |
| `LiveToolActivity` | Mutable wrapper for real-time status tracking. States: `:running`, `:done`, `:error`. Updated by progress messages. Suitable for live UIs. |
| `ToolActivityTracker` | Enumerable collection with auto-wiring. Attaches to `EventHandler` or `Client`. Callbacks: `on_start`, `on_complete`, `on_progress`, `on_change`. Query methods: `running`, `done`, `errored`, `find_by_id`. |

Expand Down Expand Up @@ -236,7 +236,7 @@ CLI sends control_request { subtype: "can_use_tool" }

## Immutable Types

All message types and content blocks use `Data.define`, frozen at construction:
All message types and content blocks inherit from `ImmutableRecord`, frozen at construction:

**Messages**: `UserMessage`, `UserMessageReplay`, `AssistantMessage`, `SystemMessage`, `ResultMessage`, `StreamEvent`, `CompactBoundaryMessage`, `StatusMessage`, `ToolProgressMessage`, `HookResponseMessage`, `AuthStatusMessage`, `TaskNotificationMessage`, `HookStartedMessage`, `HookProgressMessage`, `ToolUseSummaryMessage`, `FilesPersistedEvent`, `TaskStartedMessage`, `TaskProgressMessage`, `RateLimitEvent`, `PromptSuggestionMessage`, `ElicitationCompleteMessage`, `LocalCommandOutputMessage`, `GenericMessage`

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ options = ClaudeAgent::Options.new(

## Sandbox Settings

`SandboxSettings` configures execution sandboxing for the CLI process. It is an immutable `Data.define` type.
`SandboxSettings` configures execution sandboxing for the CLI process. It is an immutable `ImmutableRecord` type.

### Basic sandbox

Expand Down
2 changes: 1 addition & 1 deletion docs/conversations.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ end

### ToolActivity Accessors

Each `ToolActivity` is an immutable `Data.define` object built after a turn completes.
Each `ToolActivity` is an immutable `ImmutableRecord` object built after a turn completes.

| Method | Return Type | Description |
|-----------------|------------------------|---------------------------------------------------------------|
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide walks you through the ClaudeAgent Ruby SDK from first install to mult

## Requirements

- **Ruby 3.2+** (the SDK uses `Data.define` for immutable types)
- **Ruby 3.2+**
- **Claude Code CLI v2.0.0+** ([install guide](https://code.claude.com/docs/en/getting-started))

Verify both are available:
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ opts = ClaudeAgent::Options.new(hooks: hooks)
turn = ClaudeAgent.ask("Hello", options: opts)
```

Each key is a CLI event name string (e.g., `"PreToolUse"`). Each value is an array of `HookMatcher` instances. A `HookMatcher` is a `Data.define` with three fields:
Each key is a CLI event name string (e.g., `"PreToolUse"`). Each value is an array of `HookMatcher` instances. A `HookMatcher` is an `ImmutableRecord` with three fields:

| Field | Type | Description |
|-------------|------------------|--------------------------------------------------------------|
Expand Down
Loading
Loading