Skip to content

v0.33.0

Choose a tag to compare

@ya-luotao ya-luotao released this 21 Sep 08:49
· 504 commits to main since this release

Subagent capabilities for UI builders: metadata reads, background snapshots, cooperative callback cancellation, and the task/background/permission signals the CLI already emits — all raw data and controls, no status model. Ruby-ahead of the Python SDK (0.2.153).

Added

  • get_subagent_metadata / get_subagent_metadata_from_store: read optional subagent metadata with original string keys, including type, spawning tool ID, parent agent, depth, and future CLI fields. Disk reads reuse transcript scoping without parsing the transcript; store reads select the latest metadata entry even before messages arrive. These are historical reads, not live-status queries.
  • background_tasks and session_crons on StopHookInput / SubagentStopHookInput. Raw snapshots preserve unavailable (nil) versus explicitly empty ([]) and describe the parent session, not all foreground/background agents.
  • Cooperative permission cancellation through ToolPermissionContext#signal (CancellationSignal#cancelled? / #wait) and the associated request_id. CLI cancellation, disconnect, EOF, and failed dispatch invalidate pending requests, including callbacks running on worker threads; normal decisions do not. User threads are not forcibly stopped, and late decisions after observed cancellation cannot become allow responses.
  • HookContext#signal / #request_id use the same per-invocation cancellation contract, including hook timeouts. Thread callbacks can cooperate with cancellation; late hook output is discarded.
  • A minimal subagent event subscription example and capability reference covering lifecycle events, metadata, background snapshots, and permission cancellation. UI and application status aggregation remain outside the SDK.
  • Opt-in real-CLI subagent contract tests for ID/metadata/text correlation, permission cancellation on interrupt, and background completion/stop after a parent result. They self-skip without CLI credentials.
  • Subagent UI signals the CLI already emits, read from the schema embedded in Claude Code CLI 2.1.278 (the Python SDK has none of these as of Python SDK 0.2.153; only partly verified live — a smoke run against CLI 2.1.278 confirmed the task_started fields and the targeted-miss background_tasks response; the rest is schema-derived). The SDK exposes raw data and controls only — no status aggregation.
    • TaskStartedMessage#subagent_type / #is_backgrounded / #spawn_depth, TaskProgressMessage#subagent_type, TaskNotificationMessage#reason / #resource_links (raw Array, symbol keys with the wire spelling), the #skip_transcript / #ambient display flags on both TaskStartedMessage and TaskNotificationMessage (hints for the host — the SDK never filters frames or computes activity), and TaskUpdatedMessage#is_backgrounded / #error / #end_time / #total_paused_ms / #description derived from patch like status. is_backgrounded keeps nil (not reported) distinct from an explicit false (foreground, spawning tool call blocking). TaskUpdatedMessage now also reads a string-keyed patch on hand-built messages.
    • BackgroundTasksChangedMessage (background_tasks_changed): the full set of live background tasks, a level signal with REPLACE semantics. The SDK's own stdin-close bookkeeping still deliberately ignores this frame.
    • PermissionDeniedMessage (permission_denied): a tool call auto-denied without an interactive prompt, with agent_id for subagent routing (not a permission request_id). A best-effort advisory, not a complete denial feed — ResultMessage#permission_denials stays authoritative.
    • Client#background_tasks(tool_use_id: nil) / Query#background_tasks: send in-flight foreground tasks to the background (Ctrl+B). Keyed by the spawning tool_use_id, not task_id / agent_id. The targeted form returns { backgrounded: true } or { backgrounded: false } — a definitive miss, after which no event is coming; nil is the explicit all-tasks form and returns {}. Because the CLI treats '' as "all tasks", anything other than nil or a non-empty String raises ArgumentError before a request is written. TaskUpdatedMessage#is_backgrounded / BackgroundTasksChangedMessage report the lifecycle state that follows.
    • ClaudeAgentOptions#agent_progress_summaries: request model-generated progress summaries for subagents; TaskProgressMessage#summary may then be present, and stays optional. Tri-state; nil omits agentProgressSummaries from the initialize request and true / false are forwarded verbatim. An enable switch, not a live toggle: CLI 2.1.278 only acts on a truthy value, so false is equivalent to unset.
    • Compatibility: background_tasks_changed and permission_denied frames previously parsed as a generic SystemMessage. Both new classes subclass it, so when SystemMessage and #data consumers are unaffected; code matching on message.class == SystemMessage will no longer see them.

Fixed

  • Best-effort control error/cancellation replies no longer leak a secondary connection error when the CLI has already exited. Original transport read errors still propagate to the consumer.
  • Control-request tracking is identity-guarded: if the CLI ever reused an in-flight request ID, the first handler finishing no longer untracks the later request's cancellation signal or task, so EOF, disconnect, and control_cancel_request still invalidate it and its late decision cannot be sent as a success.
  • ClaudeAgentSDK.configure defaults now merge by option, not by literal key spelling. ClaudeAgentOptions accepts symbol/string and snake_case/camelCase names, but the defaults merge compared raw keys, so a differently spelled per-call key rode along as a second entry: 'permissionMode' => nil wiped a configured permission_mode: instead of inheriting it, and a Hash option such as 'env' => {...} replaced the configured env: instead of merging into it. Unknown option names are still reported with the caller's own spelling.

Usage

# Send one blocking subagent to the background (the Ctrl+B control request).
# The key is the spawning tool_use id — never pass "" or a missing id: nil means ALL foreground tasks.
result = client.background_tasks(tool_use_id: task_started.tool_use_id)
result[:backgrounded] # => true, or false for a definitive miss

# Cancellation-aware permission callback
can_use_tool = lambda do |_tool, _input, context|
  loop do
    break ClaudeAgentSDK::PermissionResultDeny.new(message: 'Request cancelled') if context.signal.cancelled?
    decision = decisions_for(context.request_id).pop(timeout: 0.1)
    break decision if decision
  end
end

What's changed

  • #60 Add subagent metadata, background snapshots, and callback cancellation — @ya-luotao
  • #61 Expose subagent UI signals the CLI already emits — @ya-luotao
  • #62 Identity-guard control-request tracking against a reused request ID — @ya-luotao
  • #63 Merge configured defaults by option, not by literal key spelling — @ya-luotao

Full Changelog: v0.32.0...v0.33.0


gem install claude-agent-sdk -v 0.33.0