claude_agent 0.7.14 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/rules/conventions.md +66 -16
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +24 -4
- data/README.md +52 -1529
- data/SPEC.md +56 -29
- data/docs/architecture.md +339 -0
- data/docs/client.md +526 -0
- data/docs/configuration.md +571 -0
- data/docs/conversations.md +461 -0
- data/docs/errors.md +127 -0
- data/docs/events.md +225 -0
- data/docs/getting-started.md +310 -0
- data/docs/hooks.md +380 -0
- data/docs/logging.md +96 -0
- data/docs/mcp.md +308 -0
- data/docs/messages.md +871 -0
- data/docs/permissions.md +611 -0
- data/docs/queries.md +227 -0
- data/docs/sessions.md +335 -0
- data/lib/claude_agent/abort_controller.rb +24 -0
- data/lib/claude_agent/client/commands.rb +32 -0
- data/lib/claude_agent/client.rb +10 -4
- data/lib/claude_agent/configuration.rb +129 -0
- data/lib/claude_agent/control_protocol/commands.rb +28 -0
- data/lib/claude_agent/conversation.rb +37 -4
- data/lib/claude_agent/errors.rb +21 -4
- data/lib/claude_agent/event_handler.rb +14 -0
- data/lib/claude_agent/fork_session.rb +117 -0
- data/lib/claude_agent/hook_registry.rb +110 -0
- data/lib/claude_agent/hooks.rb +4 -0
- data/lib/claude_agent/mcp/server.rb +22 -0
- data/lib/claude_agent/mcp/tool.rb +24 -3
- data/lib/claude_agent/message.rb +93 -0
- data/lib/claude_agent/messages/streaming.rb +37 -0
- data/lib/claude_agent/options.rb +10 -0
- data/lib/claude_agent/permission_policy.rb +174 -0
- data/lib/claude_agent/permission_request.rb +17 -0
- data/lib/claude_agent/session.rb +100 -11
- data/lib/claude_agent/session_paths.rb +5 -2
- data/lib/claude_agent/turn_result.rb +20 -2
- data/lib/claude_agent/types/sessions.rb +8 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +187 -0
- data/sig/claude_agent.rbs +38 -1
- metadata +20 -1
data/SPEC.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
This document provides a comprehensive specification of the Claude Agent SDK, comparing feature parity across the official TypeScript and Python SDKs with this Ruby implementation.
|
|
4
4
|
|
|
5
5
|
**Reference Versions:**
|
|
6
|
-
- TypeScript SDK: v0.2.
|
|
7
|
-
- Python SDK: from GitHub (commit
|
|
6
|
+
- TypeScript SDK: v0.2.76 (npm package)
|
|
7
|
+
- Python SDK: from GitHub (commit 302ceb6)
|
|
8
8
|
- Ruby SDK: This repository
|
|
9
9
|
|
|
10
|
-
**Last Updated:** 2026-03-
|
|
10
|
+
**Last Updated:** 2026-03-15
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -305,29 +305,31 @@ Bidirectional control protocol for SDK-CLI communication.
|
|
|
305
305
|
|
|
306
306
|
### Control Request Types
|
|
307
307
|
|
|
308
|
-
| Request Subtype | TypeScript | Python | Ruby | Notes
|
|
309
|
-
|
|
310
|
-
| `initialize` | ✅ | ✅ | ✅ | Initialize session with hooks/MCP
|
|
311
|
-
| `interrupt` | ✅ | ✅ | ✅ | Interrupt current operation
|
|
312
|
-
| `can_use_tool` | ✅ | ✅ | ✅ | Permission callback
|
|
313
|
-
| `hook_callback` | ✅ | ✅ | ✅ | Execute hook callback
|
|
314
|
-
| `set_permission_mode` | ✅ | ✅ | ✅ | Change permission mode
|
|
315
|
-
| `set_model` | ✅ | ✅ | ✅ | Change model
|
|
316
|
-
| `set_max_thinking_tokens` | ✅ | ❌ | ✅ | Change thinking tokens limit
|
|
317
|
-
| `rewind_files` | ✅ | ✅ | ✅ | Rewind file checkpoints (supports dry_run)
|
|
318
|
-
| `mcp_message` | ✅ | ✅ | ✅ | Route MCP message
|
|
319
|
-
| `mcp_set_servers` | ✅ | ❌ | ✅ | Dynamically set MCP servers
|
|
320
|
-
| `mcp_status` | ✅ | ✅ | ✅ | Get MCP server status
|
|
321
|
-
| `mcp_reconnect` | ✅ | ✅ | ✅ | Reconnect to MCP server
|
|
322
|
-
| `mcp_toggle` | ✅ | ✅ | ✅ | Enable/disable MCP server
|
|
323
|
-
| `stop_task` | ✅ | ✅ | ✅ | Stop a running background task
|
|
324
|
-
| `mcp_authenticate` | ✅ | ❌ | ✅ | Authenticate MCP server (v0.2.52)
|
|
325
|
-
| `mcp_clear_auth` | ✅ | ❌ | ✅ | Clear MCP server auth (v0.2.52)
|
|
326
|
-
| `supported_commands` | ✅ | ❌ | ✅ | Get available slash commands
|
|
327
|
-
| `supported_models` | ✅ | ❌ | ✅ | Get available models
|
|
328
|
-
| `account_info` | ✅ | ❌ | ✅ | Get account information
|
|
329
|
-
| `apply_flag_settings` | ✅ | ❌ | ✅ | Merge settings into flag layer
|
|
330
|
-
| `supported_agents` | ✅ | ❌ | ✅ | Get available subagents (v0.2.63)
|
|
308
|
+
| Request Subtype | TypeScript | Python | Ruby | Notes |
|
|
309
|
+
|---------------------------|:----------:|:------:|:----:|----------------------------------------------|
|
|
310
|
+
| `initialize` | ✅ | ✅ | ✅ | Initialize session with hooks/MCP |
|
|
311
|
+
| `interrupt` | ✅ | ✅ | ✅ | Interrupt current operation |
|
|
312
|
+
| `can_use_tool` | ✅ | ✅ | ✅ | Permission callback |
|
|
313
|
+
| `hook_callback` | ✅ | ✅ | ✅ | Execute hook callback |
|
|
314
|
+
| `set_permission_mode` | ✅ | ✅ | ✅ | Change permission mode |
|
|
315
|
+
| `set_model` | ✅ | ✅ | ✅ | Change model |
|
|
316
|
+
| `set_max_thinking_tokens` | ✅ | ❌ | ✅ | Change thinking tokens limit |
|
|
317
|
+
| `rewind_files` | ✅ | ✅ | ✅ | Rewind file checkpoints (supports dry_run) |
|
|
318
|
+
| `mcp_message` | ✅ | ✅ | ✅ | Route MCP message |
|
|
319
|
+
| `mcp_set_servers` | ✅ | ❌ | ✅ | Dynamically set MCP servers |
|
|
320
|
+
| `mcp_status` | ✅ | ✅ | ✅ | Get MCP server status |
|
|
321
|
+
| `mcp_reconnect` | ✅ | ✅ | ✅ | Reconnect to MCP server |
|
|
322
|
+
| `mcp_toggle` | ✅ | ✅ | ✅ | Enable/disable MCP server |
|
|
323
|
+
| `stop_task` | ✅ | ✅ | ✅ | Stop a running background task |
|
|
324
|
+
| `mcp_authenticate` | ✅ | ❌ | ✅ | Authenticate MCP server (v0.2.52) |
|
|
325
|
+
| `mcp_clear_auth` | ✅ | ❌ | ✅ | Clear MCP server auth (v0.2.52) |
|
|
326
|
+
| `supported_commands` | ✅ | ❌ | ✅ | Get available slash commands |
|
|
327
|
+
| `supported_models` | ✅ | ❌ | ✅ | Get available models |
|
|
328
|
+
| `account_info` | ✅ | ❌ | ✅ | Get account information |
|
|
329
|
+
| `apply_flag_settings` | ✅ | ❌ | ✅ | Merge settings into flag layer |
|
|
330
|
+
| `supported_agents` | ✅ | ❌ | ✅ | Get available subagents (v0.2.63) |
|
|
331
|
+
| `cancel_async_message` | ✅ | ❌ | ✅ | Cancel queued user message by UUID (v0.2.76) |
|
|
332
|
+
| `get_settings` | ✅ | ❌ | ✅ | Get effective merged settings (v0.2.72) |
|
|
331
333
|
|
|
332
334
|
### Return Types
|
|
333
335
|
|
|
@@ -426,6 +428,7 @@ Event hooks for intercepting and modifying SDK behavior.
|
|
|
426
428
|
| `SubagentStart` | ✅ | ✅ | ✅ | Subagent starts (Py v0.1.29) |
|
|
427
429
|
| `SubagentStop` | ✅ | ✅ | ✅ | Subagent stops |
|
|
428
430
|
| `PreCompact` | ✅ | ✅ | ✅ | Before compaction |
|
|
431
|
+
| `PostCompact` | ✅ | ❌ | ✅ | After compaction (v0.2.76) |
|
|
429
432
|
| `PermissionRequest` | ✅ | ✅ | ✅ | Permission requested (Py v0.1.29) |
|
|
430
433
|
| `Setup` | ✅ | ❌ | ✅ | Initial setup/maintenance |
|
|
431
434
|
| `TeammateIdle` | ✅ | ❌ | ✅ | Teammate idle (v0.2.33) |
|
|
@@ -452,6 +455,7 @@ Event hooks for intercepting and modifying SDK behavior.
|
|
|
452
455
|
| `SubagentStartHookInput` | ✅ | ✅ | ✅ |
|
|
453
456
|
| `SubagentStopHookInput` | ✅ | ✅ | ✅ |
|
|
454
457
|
| `PreCompactHookInput` | ✅ | ✅ | ✅ |
|
|
458
|
+
| `PostCompactHookInput` | ✅ | ❌ | ✅ |
|
|
455
459
|
| `PermissionRequestHookInput` | ✅ | ✅ | ✅ |
|
|
456
460
|
| `SetupHookInput` | ✅ | ❌ | ✅ |
|
|
457
461
|
| `TeammateIdleHookInput` | ✅ | ❌ | ✅ |
|
|
@@ -537,6 +541,13 @@ Event-specific fields returned via `hookSpecificOutput`:
|
|
|
537
541
|
|---------------------|:----------:|:------:|:----:|----------------------------------|
|
|
538
542
|
| `additionalContext` | ✅ | ✅ | ✅ | Context string returned to model |
|
|
539
543
|
|
|
544
|
+
#### PostCompactHookInput Fields
|
|
545
|
+
|
|
546
|
+
| Field | TypeScript | Python | Ruby | Notes |
|
|
547
|
+
|-------------------|:----------:|:------:|:----:|--------------------------------|
|
|
548
|
+
| `trigger` | ✅ | ❌ | ✅ | 'manual' or 'auto' |
|
|
549
|
+
| `compact_summary` | ✅ | ❌ | ✅ | Summary produced by compaction |
|
|
550
|
+
|
|
540
551
|
#### SetupHookSpecificOutput
|
|
541
552
|
|
|
542
553
|
| Field | TypeScript | Python | Ruby | Notes |
|
|
@@ -740,6 +751,7 @@ Session management and resumption.
|
|
|
740
751
|
| `getSessionInfo()` | ✅ | ❌ | ✅ | Get single session metadata (v0.2.75) |
|
|
741
752
|
| `renameSession()` | ✅ | ✅ | ✅ | Rename a session (v0.2.74) |
|
|
742
753
|
| `tagSession()` | ✅ | ✅ | ✅ | Tag a session (v0.2.75) |
|
|
754
|
+
| `forkSession()` | ✅ | ❌ | ✅ | Fork/branch a session (v0.2.76) |
|
|
743
755
|
|
|
744
756
|
#### ListSessionsOptions
|
|
745
757
|
|
|
@@ -783,6 +795,20 @@ Session management and resumption.
|
|
|
783
795
|
| `tag` | ✅ | ❌ | ✅ | User-set tag (v0.2.75) |
|
|
784
796
|
| `createdAt` | ✅ | ❌ | ✅ | Creation time in ms (v0.2.75) |
|
|
785
797
|
|
|
798
|
+
#### ForkSessionOptions
|
|
799
|
+
|
|
800
|
+
| Field | TypeScript | Python | Ruby | Notes |
|
|
801
|
+
|------------------|:----------:|:------:|:----:|------------------------------------------------|
|
|
802
|
+
| `dir` | ✅ | ❌ | ❌ | Project directory |
|
|
803
|
+
| `upToMessageId` | ✅ | ❌ | ❌ | Slice transcript up to this UUID (inclusive) |
|
|
804
|
+
| `title` | ✅ | ❌ | ❌ | Custom title for the fork |
|
|
805
|
+
|
|
806
|
+
#### ForkSessionResult
|
|
807
|
+
|
|
808
|
+
| Field | TypeScript | Python | Ruby | Notes |
|
|
809
|
+
|-------------|:----------:|:------:|:----:|--------------------------------|
|
|
810
|
+
| `sessionId` | ✅ | ❌ | ❌ | New forked session UUID |
|
|
811
|
+
|
|
786
812
|
### V2 Session API (Unstable)
|
|
787
813
|
|
|
788
814
|
| Feature | TypeScript | Python | Ruby | Notes |
|
|
@@ -899,6 +925,7 @@ Public API surface for SDK clients.
|
|
|
899
925
|
| `getSessionInfo()` | ✅ | ❌ | ✅ | Get single session metadata (v0.2.75) |
|
|
900
926
|
| `renameSession()` | ✅ | ✅ | ✅ | Rename a session (v0.2.74) |
|
|
901
927
|
| `tagSession()` | ✅ | ✅ | ✅ | Tag a session (v0.2.75) |
|
|
928
|
+
| `forkSession()` | ✅ | ❌ | ✅ | Fork/branch a session (v0.2.76) |
|
|
902
929
|
|
|
903
930
|
### Query Interface
|
|
904
931
|
|
|
@@ -989,6 +1016,7 @@ Public API surface for SDK clients.
|
|
|
989
1016
|
- v0.2.73: Fixed `options.env` being overridden by `~/.claude/settings.json`
|
|
990
1017
|
- v0.2.74: Added `renameSession()` for renaming session files
|
|
991
1018
|
- v0.2.75: Added `tag`/`createdAt` fields on `SDKSessionInfo`; `getSessionInfo()` for single-session lookup; `offset` on `listSessions` for pagination; `tagSession()` for tagging sessions; `supportsAutoMode` in `ModelInfo`; `description` on `SDKControlPermissionRequest`; `prompt` on `SDKTaskStartedMessage`; `fast_mode_state` on `SDKControlInitializeResponse`; `queued_to_running` status on `AgentToolOutput`
|
|
1019
|
+
- v0.2.76: Added `forkSession(sessionId, opts?)` for branching conversations from a point; `cancel_async_message` control subtype to drop queued user messages; `PostCompact` hook event with `compact_summary` field; `get_settings` control request for reading effective merged settings; `planFilePath` field on `ExitPlanMode` tool input
|
|
992
1020
|
- Includes `Elicitation`/`ElicitationResult` hook events, `onElicitation` option, `ElicitationCompleteMessage`, `LocalCommandOutputMessage`, `FastModeState` (undocumented in changelog, present in types)
|
|
993
1021
|
|
|
994
1022
|
### Python SDK
|
|
@@ -1017,12 +1045,11 @@ Public API surface for SDK clients.
|
|
|
1017
1045
|
- v0.1.48: Fixed fine-grained tool streaming regression
|
|
1018
1046
|
- Added `RateLimitEvent` message type with `RateLimitInfo`
|
|
1019
1047
|
- Added `rename_session()` and `tag_session()` session management functions
|
|
1020
|
-
- Missing: `onElicitation`, `Elicitation`/`ElicitationResult` hooks, `ElicitationCompleteMessage`, `LocalCommandOutputMessage`, `FastModeState`, `InstructionsLoaded` hook, `agentProgressSummaries`, `getSessionInfo()`
|
|
1048
|
+
- Missing: `onElicitation`, `Elicitation`/`ElicitationResult` hooks, `ElicitationCompleteMessage`, `LocalCommandOutputMessage`, `FastModeState`, `InstructionsLoaded` hook, `agentProgressSummaries`, `getSessionInfo()`, `forkSession()`, `PostCompact` hook, `cancel_async_message`, `get_settings`
|
|
1021
1049
|
|
|
1022
1050
|
### Ruby SDK (This Repository)
|
|
1023
|
-
- Feature parity with TypeScript SDK v0.2.
|
|
1051
|
+
- Feature parity with TypeScript SDK v0.2.76
|
|
1024
1052
|
- Ruby-idiomatic patterns (Data.define, snake_case)
|
|
1025
1053
|
- Complete control protocol, hook, and V2 Session API support
|
|
1026
1054
|
- Dedicated Client class for multi-turn conversations
|
|
1027
1055
|
- `executable`/`executableArgs` marked N/A (JS runtime options)
|
|
1028
|
-
- Full v0.2.75 parity: `agentProgressSummaries`, `getSessionInfo()`, `renameSession()`, `tagSession()`, `offset` on `listSessions`, `tag`/`createdAt` on `SessionInfo`, `supportsAutoMode` on `ModelInfo`
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Internal architecture of the ClaudeAgent Ruby SDK.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Layer Diagram
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ClaudeAgent.ask / .chat / .query
|
|
11
|
+
|
|
|
12
|
+
Configuration (Stripe-style global defaults, Forwardable delegators)
|
|
13
|
+
|
|
|
14
|
+
Options (validation, CLI arg serialization, env vars)
|
|
15
|
+
|
|
|
16
|
+
Conversation / Client (lifecycle, turns, event dispatch)
|
|
17
|
+
|
|
|
18
|
+
EventHandler / TurnResult / CumulativeUsage
|
|
19
|
+
|
|
|
20
|
+
ControlProtocol (request/response routing, hooks, MCP, permissions)
|
|
21
|
+
Primitives | Lifecycle | Messaging | Commands | RequestHandling
|
|
22
|
+
|
|
|
23
|
+
Transport::Subprocess (JSON Lines framing, stdin/stdout, process mgmt)
|
|
24
|
+
|
|
|
25
|
+
Claude Code CLI (spawned as subprocess)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Module Responsibilities
|
|
31
|
+
|
|
32
|
+
### Entry Points
|
|
33
|
+
|
|
34
|
+
| Module | Role |
|
|
35
|
+
|--------------------------|-------------------------------------------------------------------------------------------------------|
|
|
36
|
+
| `ClaudeAgent.ask` | One-shot query, returns `TurnResult`. Merges global config, builds `EventHandler` from `on_*` kwargs. |
|
|
37
|
+
| `ClaudeAgent.chat` | Multi-turn conversation. Block form auto-cleans; no block returns `Conversation`. |
|
|
38
|
+
| `ClaudeAgent.query` | Low-level streaming enumerator. Returns `Enumerator<Message>`. |
|
|
39
|
+
| `ClaudeAgent.query_turn` | Like `query` but accumulates into `TurnResult` with optional `EventHandler`. |
|
|
40
|
+
|
|
41
|
+
### Configuration Layer
|
|
42
|
+
|
|
43
|
+
| Module | Role |
|
|
44
|
+
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
45
|
+
| `Configuration` | Stripe-style global defaults. Holds all configurable fields (Tier 1/2/3), global `PermissionPolicy`, `HookRegistry`, and MCP server registrations. `to_options(**overrides)` merges config + per-request kwargs into an `Options` instance. |
|
|
46
|
+
| `Options` | All configurable attributes with validation and CLI arg serialization. Includes `Serializer` mixin for `to_cli_args` and `to_env`. Auto-compiles `PermissionPolicy` to lambda, `HookRegistry` to hash. Auto-sets `permission_prompt_tool_name = "stdio"` when `can_use_tool` or `permission_queue` is present. |
|
|
47
|
+
|
|
48
|
+
### Conversation Layer
|
|
49
|
+
|
|
50
|
+
| Module | Role |
|
|
51
|
+
|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
52
|
+
| `Conversation` | High-level lifecycle manager. Wraps `Client` with auto-connect on first `say`, multi-turn history, tool activity timeline with timestamps, and cumulative cost tracking. Partitions kwargs into callbacks / conversation keys / options keys. Supports `open` (block), `resume` (session ID), and permission mode mapping (`:queue`, `:accept_edits`, policy, callable). |
|
|
53
|
+
| `Client` | Bidirectional connection to CLI. Composes `Transport`, `ControlProtocol`, `EventHandler`, `CumulativeUsage`, and `PermissionQueue`. Provides `send_message`, `receive_turn`, `send_and_receive`, `stream_input`, `interrupt`, and `abort!`. Includes `Commands` mixin for control operations (permission mode, model changes, file rewind, MCP server management). |
|
|
54
|
+
|
|
55
|
+
### Event & Accumulation Layer
|
|
56
|
+
|
|
57
|
+
| Module | Role |
|
|
58
|
+
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
59
|
+
| `EventHandler` | Three-layer event dispatch: (1) `:message` catch-all, (2) type-based (`:assistant`, `:stream_event`, `:status`, etc.), (3) decomposed (`:text`, `:thinking`, `:tool_use`, `:tool_result`). Pairs tool results with their originating tool use blocks. Supports `EventHandler.define` DSL and method chaining. |
|
|
60
|
+
| `TurnResult` | Message accumulator for a single agent turn. Convenience accessors: `text`, `thinking`, `tool_uses`, `tool_results`, `tool_executions`, `cost`, `session_id`, `usage`, `model`, `structured_output`, `permission_denials`. Accumulates streaming text deltas as fallback for aborted turns. |
|
|
61
|
+
| `CumulativeUsage` | Thread-safe (Mutex) token and cost tracker across turns. Sums `input_tokens`, `output_tokens`, cache tokens. Takes session-cumulative `total_cost_usd` and `num_turns` from the most recent `ResultMessage`. |
|
|
62
|
+
|
|
63
|
+
### Protocol Layer
|
|
64
|
+
|
|
65
|
+
| Module | Role |
|
|
66
|
+
|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
67
|
+
| `ControlProtocol` | Core protocol handler. Composed of five submodules (below). Manages shared state: transport, parser, request counter, pending requests/results, threading primitives (Mutex, ConditionVariable, Queue), abort signal. |
|
|
68
|
+
| `Primitives` | Low-level read/write helpers. `write_message` serializes and sends JSON. Request/response ID generation. Stateless except for shared counters and pending-request maps. |
|
|
69
|
+
| `Lifecycle` | Connection lifecycle: `start` (connect transport, spawn reader thread, send initialize), `stop` (end input, join reader, close transport), `abort!` (cancel pending requests, drain permission queue, terminate transport). Background `reader_loop` routes `control_request`, `control_response`, and SDK messages to appropriate handlers or the message queue. |
|
|
70
|
+
| `Messaging` | Consumer-facing message delivery: `each_message`, `receive_response`, `send_user_message`, `stream_input`, `stream_conversation`. Reads from the internal `Queue`, parses via `MessageParser`, checks abort signal. |
|
|
71
|
+
| `Commands` | Control commands sent to CLI: `change_permission_mode`, `change_model`, `rewind_files`, `mcp_server_status`, `set_mcp_servers`, `interrupt`. Each sends a `control_request` and waits for the response. |
|
|
72
|
+
| `RequestHandling` | Handles incoming control requests from CLI: `can_use_tool` (three modes: synchronous callback, queue-based, default allow), `hook_callback`, `mcp_message` (routes to SDK MCP server instances), `elicitation`. Normalizes Ruby field names to CLI camelCase keys. |
|
|
73
|
+
|
|
74
|
+
### Transport Layer
|
|
75
|
+
|
|
76
|
+
| Module | Role |
|
|
77
|
+
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
78
|
+
| `Transport::Base` | Abstract base class defining the transport interface. |
|
|
79
|
+
| `Transport::Subprocess` | Spawns Claude Code CLI via `Open3.popen3` or custom spawn function. Manages stdin/stdout/stderr streams. JSON Lines framing with partial-JSON buffering. Version checking against minimum CLI version. Supports graceful termination (SIGTERM) and force kill (SIGKILL). Custom spawn support via `SpawnOptions` / `SpawnedProcess` for non-standard process management. |
|
|
80
|
+
|
|
81
|
+
### Parsing Layer
|
|
82
|
+
|
|
83
|
+
| Module | Role |
|
|
84
|
+
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
85
|
+
| `MessageParser` | Registry-based router. Maps raw JSON hashes (string keys, camelCase) to typed message objects. `deep_transform_keys` normalizes to snake_case symbols. Dispatches by `type` (top-level) or `type:subtype` (system messages). Unknown types wrapped in `GenericMessage`. |
|
|
86
|
+
|
|
87
|
+
### Permission System
|
|
88
|
+
|
|
89
|
+
| Module | Role |
|
|
90
|
+
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
91
|
+
| `PermissionPolicy` | Declarative DSL for permission rules: `allow`, `deny`, `allow_matching`, `deny_matching`, `allow_all`, `deny_all`, `ask` (custom fallback). Compiles to a `can_use_tool` lambda. Rules evaluated in order; first match wins. |
|
|
92
|
+
| `PermissionQueue` | Thread-safe `Queue` wrapper for deferred permission requests. Non-blocking `poll`, blocking `pop(timeout:)`, and `drain!` for abort cleanup. |
|
|
93
|
+
| `PermissionRequest` | Deferred permission request resolved from any thread. `allow!` / `deny!` unblock the reader thread via Mutex + ConditionVariable. Supports hybrid mode: callback can call `context.request.defer!` to enqueue instead of returning synchronously. |
|
|
94
|
+
|
|
95
|
+
### Hook System
|
|
96
|
+
|
|
97
|
+
| Module | Role |
|
|
98
|
+
|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
99
|
+
| `HookRegistry` | Ruby-friendly DSL mapping idiomatic method names (`before_tool_use`, `after_tool_use`, `on_session_start`, etc.) to CLI event names (`PreToolUse`, `PostToolUse`, `SessionStart`, etc.). Compiles to the `Hash{String => Array<HookMatcher>}` format consumed by `Options#hooks`. Supports regex/string tool matchers and additive merge. |
|
|
100
|
+
|
|
101
|
+
### MCP Layer
|
|
102
|
+
|
|
103
|
+
| Module | Role |
|
|
104
|
+
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
105
|
+
| `MCP::Server` | In-process MCP server. Handles JSON-RPC messages: `initialize`, `tools/list`, `tools/call`. Registered via `Options#mcp_servers` with `type: "sdk"`. Block DSL for inline tool definition. |
|
|
106
|
+
| `MCP::Tool` | Single tool definition with name, description, JSON Schema (auto-normalized from Ruby types/symbols), optional annotations, and handler block. Formats results as MCP content blocks. |
|
|
107
|
+
|
|
108
|
+
### Session Layer
|
|
109
|
+
|
|
110
|
+
| Module | Role |
|
|
111
|
+
|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
112
|
+
| `Session` | Rails-like finder with Stripe-style resource methods. `find` / `retrieve` / `all` / `where` class methods. Instance methods: `messages` (returns `SessionMessageRelation`), `rename`, `tag_session`, `fork`, `reload`, `resume`. |
|
|
113
|
+
| `SessionMessageRelation` | Chainable Enumerable query object. Lazy evaluation with `where(limit:, offset:)`. Wraps `GetSessionMessages`. |
|
|
114
|
+
| `ListSessions` | Reads session metadata from disk without spawning CLI. Returns `SessionInfo` sorted by last modified. Supports directory scoping and git worktree inclusion. |
|
|
115
|
+
| `GetSessionMessages` | Reads JSONL session transcript, reconstructs main conversation thread, returns `SessionMessage` array with pagination. |
|
|
116
|
+
| `GetSessionInfo` | Targeted single-session lookup by UUID. |
|
|
117
|
+
| `ForkSession` | Creates a new session file with remapped UUIDs, optional truncation point. |
|
|
118
|
+
| `SessionMutations` | Appends `custom-title` and `tag` entries to session files. |
|
|
119
|
+
| `SessionPaths` | Shared infrastructure for resolving session file paths across projects and worktrees. |
|
|
120
|
+
|
|
121
|
+
### Abort & Signal
|
|
122
|
+
|
|
123
|
+
| Module | Role |
|
|
124
|
+
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
125
|
+
| `AbortController` | JavaScript-style abort controller. Owns an `AbortSignal`. `abort(reason)` triggers the signal; `reset!` clears it for reuse. |
|
|
126
|
+
| `AbortSignal` | Thread-safe (Mutex + ConditionVariable) signal. `aborted?`, `reason`, `on_abort` callbacks, `wait(timeout:)`, `check!` (raises `AbortError`). Used by `ControlProtocol` (reader loop check, queue push), `Conversation` (auto-reset per turn). |
|
|
127
|
+
|
|
128
|
+
### Tool Activity Tracking
|
|
129
|
+
|
|
130
|
+
| Module | Role |
|
|
131
|
+
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
132
|
+
| `ToolActivity` | Immutable (`Data.define`) record of a completed tool execution. Pairs `ToolUseBlock` + `ToolResultBlock` with turn index and wall-clock timestamps. |
|
|
133
|
+
| `LiveToolActivity` | Mutable wrapper for real-time status tracking. States: `:running`, `:done`, `:error`. Updated by progress messages. Suitable for live UIs. |
|
|
134
|
+
| `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`. |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Data Flow
|
|
139
|
+
|
|
140
|
+
### One-Shot Query (`ask`)
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
User calls ClaudeAgent.ask(prompt, **kwargs)
|
|
144
|
+
|
|
|
145
|
+
+-- extract_callbacks separates on_* from config overrides
|
|
146
|
+
+-- Configuration.to_options merges global defaults + overrides --> Options
|
|
147
|
+
+-- build_events creates EventHandler from callbacks
|
|
148
|
+
|
|
|
149
|
+
+-- query_turn(prompt, options, events)
|
|
150
|
+
|
|
|
151
|
+
+-- ClaudeAgent.query(prompt, options) returns Enumerator
|
|
152
|
+
| |
|
|
153
|
+
| +-- Transport::Subprocess.new(options)
|
|
154
|
+
| +-- ControlProtocol.new(transport, options)
|
|
155
|
+
| +-- protocol.start(streaming: true)
|
|
156
|
+
| | +-- transport.connect --> spawn CLI subprocess
|
|
157
|
+
| | +-- reader_loop starts in background Thread
|
|
158
|
+
| | +-- send_initialize --> handshake with CLI
|
|
159
|
+
| +-- protocol.send_user_message(prompt)
|
|
160
|
+
| | +-- write JSON to stdin
|
|
161
|
+
| +-- protocol.each_message yields parsed messages
|
|
162
|
+
| +-- reader_loop reads JSON Lines from stdout
|
|
163
|
+
| +-- routes control_request to RequestHandling
|
|
164
|
+
| +-- routes control_response to pending request
|
|
165
|
+
| +-- queues SDK messages for consumer
|
|
166
|
+
| +-- consumer pops from Queue
|
|
167
|
+
| +-- MessageParser.parse(raw) --> typed message
|
|
168
|
+
| +-- yield message to Enumerator
|
|
169
|
+
|
|
|
170
|
+
+-- TurnResult << message (accumulates)
|
|
171
|
+
+-- EventHandler.handle(message) (dispatches events)
|
|
172
|
+
+-- yield message to caller block (if given)
|
|
173
|
+
+-- return TurnResult
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Multi-Turn Conversation (`chat`)
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
User calls ClaudeAgent.chat(**kwargs)
|
|
180
|
+
|
|
|
181
|
+
+-- merge_config_into_kwargs applies global defaults
|
|
182
|
+
+-- Conversation.new(**merged)
|
|
183
|
+
|
|
|
184
|
+
+-- partition_kwargs --> callbacks / conversation_kwargs / options_kwargs
|
|
185
|
+
+-- build_options (compiles PermissionPolicy, HookRegistry, permission mode)
|
|
186
|
+
+-- Client.new(options)
|
|
187
|
+
+-- register_callbacks on Client's EventHandler
|
|
188
|
+
+-- register_timing_hooks for tool activity timestamps
|
|
189
|
+
|
|
|
190
|
+
+-- conversation.say(prompt)
|
|
191
|
+
|
|
|
192
|
+
+-- ensure_connected! (auto-connects on first call)
|
|
193
|
+
| +-- Client.connect
|
|
194
|
+
| +-- ControlProtocol.start(streaming: true)
|
|
195
|
+
+-- Client.send_and_receive(prompt)
|
|
196
|
+
| +-- send_message --> protocol.send_user_message
|
|
197
|
+
| +-- receive_turn
|
|
198
|
+
| +-- TurnResult.new
|
|
199
|
+
| +-- receive_response yields messages
|
|
200
|
+
| +-- TurnResult << message
|
|
201
|
+
| +-- EventHandler.handle(message)
|
|
202
|
+
| +-- CumulativeUsage.track(message)
|
|
203
|
+
| +-- stops on ResultMessage
|
|
204
|
+
+-- build_tool_activities (timestamps from hooks)
|
|
205
|
+
+-- return TurnResult
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Permission Request Flow
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
CLI sends control_request { subtype: "can_use_tool" }
|
|
212
|
+
|
|
|
213
|
+
+-- reader_loop receives raw message
|
|
214
|
+
+-- handle_control_request dispatches to handle_can_use_tool
|
|
215
|
+
|
|
|
216
|
+
+-- Mode 1: Synchronous callback
|
|
217
|
+
| +-- options.can_use_tool.call(name, input, context)
|
|
218
|
+
| +-- callback returns PermissionResultAllow or PermissionResultDeny
|
|
219
|
+
| +-- (or callback calls context.request.defer! to switch to queue mode)
|
|
220
|
+
|
|
|
221
|
+
+-- Mode 2: Queue-based
|
|
222
|
+
| +-- PermissionRequest created with Mutex + ConditionVariable
|
|
223
|
+
| +-- pushed to PermissionQueue
|
|
224
|
+
| +-- reader thread blocks on perm_request.wait(timeout:)
|
|
225
|
+
| +-- main thread polls client.pending_permission
|
|
226
|
+
| +-- main thread calls request.allow! or request.deny!
|
|
227
|
+
| +-- ConditionVariable.broadcast unblocks reader thread
|
|
228
|
+
|
|
|
229
|
+
+-- Mode 3: Default allow (no callback, no queue)
|
|
230
|
+
|
|
|
231
|
+
+-- normalize_permission_result --> Hash
|
|
232
|
+
+-- send_control_response back to CLI
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Immutable Types
|
|
238
|
+
|
|
239
|
+
All message types and content blocks use `Data.define`, frozen at construction:
|
|
240
|
+
|
|
241
|
+
**Messages**: `UserMessage`, `UserMessageReplay`, `AssistantMessage`, `SystemMessage`, `ResultMessage`, `StreamEvent`, `CompactBoundaryMessage`, `StatusMessage`, `ToolProgressMessage`, `HookResponseMessage`, `AuthStatusMessage`, `TaskNotificationMessage`, `HookStartedMessage`, `HookProgressMessage`, `ToolUseSummaryMessage`, `FilesPersistedEvent`, `TaskStartedMessage`, `TaskProgressMessage`, `RateLimitEvent`, `PromptSuggestionMessage`, `ElicitationCompleteMessage`, `LocalCommandOutputMessage`, `GenericMessage`
|
|
242
|
+
|
|
243
|
+
**Content Blocks**: `TextBlock`, `ThinkingBlock`, `ToolUseBlock`, `ToolResultBlock`, `ServerToolUseBlock`, `ServerToolResultBlock`, `ImageContentBlock`, `GenericBlock`
|
|
244
|
+
|
|
245
|
+
**Data Types**: `SessionInfo`, `SessionMessage`, `ForkSessionResult`, `ToolActivity`, `TaskUsage`, `SDKPermissionDenial`, `RewindFilesResult`, `ToolsPreset`, `SlashCommand`, `McpServerStatus`, `McpSetServersResult`, `PermissionResultAllow`, `PermissionResultDeny`
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Thread Safety
|
|
250
|
+
|
|
251
|
+
| Component | Mechanism | Purpose |
|
|
252
|
+
|---------------------------------------------|-------------------------------|----------------------------------------------------------------------------------------------------|
|
|
253
|
+
| `PermissionRequest` | `Mutex` + `ConditionVariable` | Reader thread blocks on `wait`; main thread resolves via `allow!` / `deny!` |
|
|
254
|
+
| `PermissionQueue` | `Queue` (thread-safe stdlib) | Bridges reader thread and consumer thread for permission requests |
|
|
255
|
+
| `AbortSignal` | `Mutex` + `ConditionVariable` | Multiple consumers check `aborted?`; `on_abort` callbacks fire once; `wait` blocks until triggered |
|
|
256
|
+
| `CumulativeUsage` | `Mutex` | All reads and writes synchronized |
|
|
257
|
+
| `ControlProtocol` | `Mutex` + `ConditionVariable` | Shared state for pending requests/results; reader thread signals consumer |
|
|
258
|
+
| `Transport::Subprocess` | `Mutex` | Protects stdin writes and stream close operations |
|
|
259
|
+
| `ControlProtocol.reader_loop` | Background `Thread` | Reads transport, routes control messages, queues SDK messages |
|
|
260
|
+
| `Transport::Subprocess.start_stderr_reader` | Background `Thread` | Drains stderr to prevent pipe buffer fill; forwards to `stderr_callback` |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Types Reference
|
|
265
|
+
|
|
266
|
+
### Core Return Types
|
|
267
|
+
|
|
268
|
+
| Type | Description |
|
|
269
|
+
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
270
|
+
| `TurnResult` | Accumulator for a single turn. Accessors: `text`, `thinking`, `tool_uses`, `tool_results`, `tool_executions`, `cost`, `session_id`, `usage`, `model`, `stop_reason`, `structured_output`, `permission_denials` |
|
|
271
|
+
| `CumulativeUsage` | Cross-turn token/cost tracker. Fields: `input_tokens`, `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`, `total_cost_usd`, `num_turns`, `duration_ms`, `duration_api_ms` |
|
|
272
|
+
| `EventHandler` | Event dispatcher. Events: `message`, `user`, `assistant`, `system`, `result`, `stream_event`, `status`, `tool_progress`, `text`, `thinking`, `tool_use`, `tool_result`, and more |
|
|
273
|
+
|
|
274
|
+
### Tool Activity
|
|
275
|
+
|
|
276
|
+
| Type | Description |
|
|
277
|
+
|-----------------------|-----------------------------------------------------------------------------------------------|
|
|
278
|
+
| `ToolActivity` | Immutable. Post-turn record of a tool execution with timestamps and turn index |
|
|
279
|
+
| `LiveToolActivity` | Mutable. Real-time status (`:running`, `:done`, `:error`) with elapsed time |
|
|
280
|
+
| `ToolActivityTracker` | Enumerable collection with `on_start` / `on_complete` / `on_progress` / `on_change` callbacks |
|
|
281
|
+
|
|
282
|
+
### Permissions
|
|
283
|
+
|
|
284
|
+
| Type | Description |
|
|
285
|
+
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
286
|
+
| `PermissionRequest` | Deferred request with `allow!` / `deny!` / `defer!`. Thread-safe resolution |
|
|
287
|
+
| `PermissionQueue` | Thread-safe queue with `poll` (non-blocking) and `pop` (blocking) |
|
|
288
|
+
| `PermissionPolicy` | Declarative DSL. Compiles to `can_use_tool` lambda |
|
|
289
|
+
| `PermissionResultAllow` | Allow response with optional `updated_input` and `updated_permissions` |
|
|
290
|
+
| `PermissionResultDeny` | Deny response with `message` and `interrupt` flag |
|
|
291
|
+
| `ToolPermissionContext` | Context passed to `can_use_tool`: `permission_suggestions`, `blocked_path`, `decision_reason`, `tool_use_id`, `agent_id`, `description`, `signal`, `request` |
|
|
292
|
+
|
|
293
|
+
### Sessions
|
|
294
|
+
|
|
295
|
+
| Type | Description |
|
|
296
|
+
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
297
|
+
| `Session` | Rich finder object. Class methods: `find`, `retrieve`, `all`, `where`. Instance: `messages`, `rename`, `fork`, `resume`, `reload` |
|
|
298
|
+
| `SessionInfo` | Immutable metadata: `session_id`, `summary`, `last_modified`, `file_size`, `custom_title`, `first_prompt`, `git_branch`, `cwd`, `tag`, `created_at` |
|
|
299
|
+
| `SessionMessage` | Immutable transcript entry: `type`, `uuid`, `session_id`, `message`, `parent_tool_use_id` |
|
|
300
|
+
| `SessionMessageRelation` | Chainable Enumerable with `where(limit:, offset:)` |
|
|
301
|
+
| `ForkSessionResult` | Fork result with new `session_id` |
|
|
302
|
+
|
|
303
|
+
### MCP
|
|
304
|
+
|
|
305
|
+
| Type | Description |
|
|
306
|
+
|-----------------------|-----------------------------------------------------------------------------------------------|
|
|
307
|
+
| `MCP::Server` | In-process MCP server hosting `MCP::Tool` instances |
|
|
308
|
+
| `MCP::Tool` | Tool definition with schema normalization and handler block |
|
|
309
|
+
| `McpServerStatus` | Status of an MCP server: `name`, `status`, `server_info`, `error`, `config`, `scope`, `tools` |
|
|
310
|
+
| `McpSetServersResult` | Result of dynamic server management: `added`, `removed`, `errors` |
|
|
311
|
+
|
|
312
|
+
### Abort
|
|
313
|
+
|
|
314
|
+
| Type | Description |
|
|
315
|
+
|-------------------|-------------------------------------------------------------------------------------------|
|
|
316
|
+
| `AbortController` | Owns an `AbortSignal`. Methods: `abort(reason)`, `reset!` |
|
|
317
|
+
| `AbortSignal` | Thread-safe signal. Methods: `aborted?`, `reason`, `on_abort`, `wait`, `check!`, `reset!` |
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Development
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
bin/setup # Install dependencies
|
|
325
|
+
bundle exec rake # Unit tests + RBS + RuboCop (default task)
|
|
326
|
+
bundle exec rake test # Unit tests only
|
|
327
|
+
bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
|
|
328
|
+
bundle exec rake test_smoke # Smoke tests against local LLM (e.g. Ollama)
|
|
329
|
+
bundle exec rake test_all # All tests
|
|
330
|
+
bundle exec rake rbs # Validate RBS type signatures
|
|
331
|
+
bundle exec rubocop # Lint
|
|
332
|
+
bin/console # IRB with gem loaded
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Binstubs**: `bin/test`, `bin/test-integration`, `bin/test-all`, `bin/test-smoke`, `bin/rbs-validate`
|
|
336
|
+
|
|
337
|
+
**Test structure**: Unit tests in `test/claude_agent/` (no CLI required), integration tests in `test/integration/` (require `INTEGRATION=true`), smoke tests in `test/smoke/` (require Ollama + `SMOKE=true`). Support files and mocks in `test/support/`.
|
|
338
|
+
|
|
339
|
+
**Single file**: `bundle exec ruby -Itest test/claude_agent/test_foo.rb`
|