Skip to content

Repository files navigation

Code Agent SDK for Ruby

CI Ruby License

Autohand Code Agent SDK for Ruby: a CLI-backed Ruby gem for controlling Autohand Code agents over JSON-RPC, with streaming events, run lifecycle helpers, permissions, skills, sessions, hooks, and Rails-friendly configuration.

Beta: this SDK is actively evolving while the Agent SDK APIs stabilize. Pin versions in production and review release notes before upgrading.

Overview

This SDK wraps the Autohand Code CLI in RPC mode and exposes a Ruby API for agentic coding workflows.

Ruby app -> autohand_sdk gem -> Autohand Code CLI subprocess -> Provider -> HTTP

The gem:

  • Starts the Autohand Code CLI as a subprocess in JSON-RPC mode.
  • Streams agent lifecycle, message, tool, permission, and file-change events.
  • Provides AutohandSDK::Client for low-level control and AutohandSDK::Agent / Run for application code.
  • Exposes slash commands, persistent goals, and the replayable autoresearch ledger through exact JSON-RPC methods.
  • Returns immutable typed values for skill-registry and MCP discovery APIs.
  • Keeps Rails optional through a Railtie that only loads when Rails is present.
  • Uses Ruby stdlib for runtime behavior; development dependencies stay out of production installs.

Other Programming Languages (Beta)

The Agent SDK is available in multiple beta language packages. Use the same CLI-backed SDK model from another programming language:

  • TypeScript - Agent, Run, streaming, and JSON helpers for Node and Bun hosts.
  • Go - idiomatic Go package with context.Context, typed events, and channel-based streaming.
  • Python - async Python package with async for event streams and typed Pydantic models.
  • Java - Java 21 records, sealed events, and virtual-thread-ready APIs.
  • Swift - SwiftPM package with Agent, Runner, async streams, tools, hooks, and permissions.
  • Rust - async Rust crate with Tokio, typed events, and stream-based runs.
  • C++ - modern C++20 package with CMake targets and typed event callbacks.
  • C# - .NET package with IAsyncEnumerable, CancellationToken, and System.Text.Json.
  • Ruby - this gem, with Ruby enumerators, block APIs, Rails-friendly configuration, and JSON helpers.

Installation

Until the first RubyGems release is published, install from GitHub:

# Gemfile
gem "autohand_sdk", git: "https://github.com/autohandai/code-agent-sdk-ruby"

After the gem is released to RubyGems:

gem "autohand_sdk", "~> 0.1"

Then run:

bundle install

Install the Autohand Code CLI for this user:

bundle exec autohand-sdk install-cli
bundle exec autohand-sdk doctor

The Ruby gem stays small instead of vendoring every platform binary into one RubyGems package. The installer downloads the correct Autohand Code CLI release asset for macOS, Linux, or Windows and installs it into ~/.autohand/bin. The SDK then discovers the CLI from cli_path:, a bundled cli/ binary, ~/.autohand/bin/autohand, or PATH.

Use cli_path: when you need a custom CLI build:

AutohandSDK::Client.open(cli_path: "/path/to/autohand", cwd: ".") do |sdk|
  puts sdk.get_state
end

Quick Start

Use AutohandSDK::Agent for application code:

require "autohand_sdk"

agent = AutohandSDK::Agent.create(
  cwd: ".",
  instructions: "Review code with staff-level Ruby judgement.",
  permission_mode: "interactive"
)

run = agent.send("Review this repository for release readiness")

run.stream.each do |event|
  print event["delta"] if event["type"] == "message_update"
end

result = run.wait
puts result.fetch(:text)

agent.close

If the last Agent#stream or Run#stream consumer exits early, the run aborts, drains the CLI turn, and joins its background pump. An active Run#wait caller or another stream consumer keeps the shared run alive.

For one-shot tasks:

result = agent.run("Summarize the public API surface")
puts result.fetch(:text)

Run current CLI command surfaces through the same streamed lifecycle:

research = agent.deep_research("Hermes self-evolving systems")
research.stream.each { |event| puts event["type"] }
research.wait

if agent.supports_command?("/autoresearch")
  agent.autoresearch("Improve benchmark accuracy").wait
end

The typed-in-TypeScript autoresearch ledger is represented as idiomatic Ruby hashes while preserving the exact RPC contract:

started = agent.start_autoresearch(
  objective: "Reduce test runtime without regressions",
  metric_name: "total_ms",
  metric_unit: "ms",
  direction: "lower",
  measure_command: "bundle exec rake test",
  checks_command: "bundle exec rubocop",
  sampling: { min_samples: 3, max_samples: 7 }
)

history = agent.get_autoresearch_history
agent.replay_autoresearch(attempt_id: history.fetch("attempts").first.fetch("attemptId"))
agent.prune_autoresearch(dry_run: true)
agent.stop_autoresearch if started["success"]

See the replayable autoresearch guide and complete example.

For JSON output:

risk = agent.run_json(
  "Assess publish readiness",
  schema_name: "ReleaseRisk",
  schema: {
    summary: "string",
    risks: [{ title: "string", severity: "low | medium | high" }]
  }
)

puts risk.fetch("summary")

Low-Level Client

Use AutohandSDK::Client when you want explicit control over the CLI session:

require "autohand_sdk"

AutohandSDK::Client.open(cwd: ".", debug: true) do |sdk|
  sdk.stream_prompt("Analyze this codebase").each do |event|
    case event["type"]
    when "message_update"
      print event["delta"]
    when "tool_start"
      warn "Running #{event["tool_name"] || event["toolName"]}"
    end
  end
end

Prompt acknowledgements are not completion responses: the enumerator remains open through agent_end. Closing it early aborts and drains that prompt before a later prompt can start; event queues are bounded to the newest 1,024 events.

All 16 autohand.hook.* notifications have typed Ruby events. Unknown notifications and malformed known hooks become UnknownNotificationEvent values whose params preserve the original JSON top-level shape, including an array, nil, or scalar. See Event Streaming for the full mapping and the hook example for idiomatic dispatch.

Discover and install community skills with immutable Ruby Data results:

registry = sdk.get_skills_registry(force_refresh: false)
registry.skills.each { |skill| puts [skill.name, skill.download_count] }

installed = sdk.install_skill("typescript", scope: :project)
raise installed.error unless installed.success?

MCP discovery follows the same typed contract:

servers = sdk.list_mcp_servers
tools = sdk.list_mcp_tools(server_name: "github")
configs = sdk.get_mcp_server_configs

The exact wire keys remain forceRefresh, skillName, serverName, toolCount, and autoConnect; Ruby readers use snake_case.

Session and Auto-Mode Control

AutohandSDK::Client and AutohandSDK::Agent expose the same typed session controls. Reset the current conversation or create an expiring, one-time browser handoff for another client:

reset = agent.reset
puts reset.session_id

handoff = agent.create_browser_handoff(
  extension_id: "your-extension-id",
  install_url: "https://example.com/install"
)
puts handoff.url

In the receiving process, choose one attachment strategy: consume a known token or attach the newest unexpired handoff when no token is available:

attached = agent.attach_browser_handoff(ENV.fetch("AUTOHAND_HANDOFF_TOKEN"))
# Or: attached = agent.attach_latest_browser_handoff

warn "handoff unavailable" unless attached.success?

Start a bounded autonomous run and return as soon as the CLI accepts it. Status, control operations, and iteration logs are immutable typed values with snake_case readers:

started = agent.start_automode(
  "Implement and verify the release checklist",
  max_iterations: 25,
  completion_promise: "VALIDATION PASSED",
  use_worktree: true
)
raise(started.error || "auto-mode was not started") unless started.success?

status = agent.get_automode_status
puts status.state&.current_iteration

agent.pause_automode
agent.resume_automode

log = agent.get_automode_log(limit: 10)
log.iterations.each { |entry| puts [entry.iteration, entry.actions].inspect }

agent.cancel_automode(reason: "release window closed")

See the session and auto-mode control example for individual runnable actions and the API reference for every option and result field.

Rails

The gem does not depend on Rails. When Rails is loaded, the Railtie uses Rails.logger by default and leaves all configuration explicit:

# config/initializers/autohand_sdk.rb
AutohandSDK.configure do |config|
  config.cli_path = Rails.application.credentials.dig(:autohand, :cli_path)
  config.env_vars = { "AUTOHAND_NO_BANNER" => "1" }
end

Use the client from jobs, controllers, or service objects with normal Rails lifecycle discipline:

AutohandSDK::Client.open(cwd: Rails.root.to_s, permission_mode: "interactive") do |sdk|
  sdk.stream_prompt("Review app/models/user.rb").each { |event| Rails.logger.info(event.inspect) }
end

Documentation

Examples

Development

Use Ruby 3.3 locally:

bundle install
bundle exec rake
bundle exec yard
gem build autohand_sdk.gemspec
bundle exec ruby benchmarks/startup.rb

CI runs the test suite and RuboCop on Ruby 3.2, 3.3, and 3.4. The startup benchmark enforces 5 warmups and 50 measured samples with p95 below 50 ms for cold public require, public client start, and fixture spawn through a successful first autohand.getState. Ruby VM boot and provider/network readiness are reported separately because the wrapper does not control them. The readiness fixture is a tiny native JSON-RPC process compiled once before sampling, so a second Ruby VM is not counted as SDK startup work. A C compiler is therefore required to run this maintainer benchmark. Its stable JSON contract has top-level language, budgetMs, metrics, and passed; every metric includes samples, medianMs, p95Ms, maxMs, and its own passed result.

License

Apache License 2.0. See LICENSE.txt.

About

Autohand Code Agent SDK for Ruby: CLI-backed agent orchestration with streaming, run lifecycle, examples, JSON helpers, and Rails-friendly configuration.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages