TurnKit

Gem Version Ruby License

Build durable Ruby AI agents with turns, tools, skills, and Rails persistence.

Installation

Add this line to your application's Gemfile:

gem "turnkit"

Run:

bundle install

Quick Start

Set a provider key:

export ANTHROPIC_API_KEY=...

Create an agent:

require "turnkit"

agent = TurnKit::Agent.new(
  name: "helper",
  instructions: "Answer briefly."
)

turn = agent.conversation.ask("Explain Ruby blocks in one sentence.")
puts turn.output_text

Usage

Choose a model:

TurnKit.default_model = "claude-sonnet-4-5"

Use OpenAI:

export OPENAI_API_KEY=...
TurnKit.default_model = "gpt-4.1-mini"

Create a conversation:

agent = TurnKit::Agent.new(
  name: "writer",
  instructions: "Write clear release notes."
)

conversation = agent.conversation(subject: "v1 launch")
conversation.say("Mention faster tool execution.")

turn = conversation.run!
puts turn.output_text

Create a tool:

class SaveReport < TurnKit::Tool
  description "Save a report."
  usage_hint "Use when the user asks to persist a report."
  parameter :title, :string, required: true
  parameter :body, :string, required: true

  def self.ends_turn? = true
  def self.completion_message(result) = "Saved #{result.fetch("report_id")}."

  def call(title:, body:, context:)
    { report_id: "rep_1", title: title, body: body }
  end
end

Use a tool:

agent = TurnKit::Agent.new(
  name: "reporter",
  instructions: "Save reports when asked.",
  tools: [SaveReport]
)

turn = agent.conversation.ask("Save a short status report.")
puts turn.output_text

Add skills:

skill = TurnKit::Skill.from_file("skills/research.md")

agent = TurnKit::Agent.new(
  name: "researcher",
  skills: [skill]
)

Delegate to sub-agents:

writer = TurnKit::Agent.new(
  name: "writer",
  description: "Draft concise copy."
)

editor = TurnKit::Agent.new(
  name: "editor",
  sub_agents: [writer]
)

turn = editor.conversation.ask("Ask the writer for three headlines.")
puts turn.output_text

Use prompt caching:

TurnKit.prompt_cache = :auto

Disable prompt caching:

TurnKit.prompt_cache = :off

Split custom prompts:

agent = TurnKit::Agent.new(
  name: "cached",
  system_prompt: [
    "Stable instructions and tool guidance.",
    TurnKit::SystemPrompt::CACHE_BOUNDARY,
    "Dynamic subject and live context."
  ].join("\n")
)

Inspect usage:

record = TurnKit.store.load_turn(turn.id)
record.fetch("usage")

Return usage from custom clients:

class MyClient < TurnKit::Client
  def chat(model:, messages:, tools:, instructions:, temperature: nil, metadata: nil)
    TurnKit::Result.new(
      text: "provider response",
      model: model,
      usage: TurnKit::Usage.new(
        input_tokens: 100,
        output_tokens: 20,
        cached_tokens: 80,
        cache_write_tokens: 100
      )
    )
  end
end

Split instructions inside custom clients:

stable, dynamic = TurnKit::SystemPrompt.split_cache_boundary(instructions)

Send stable with provider cache controls.

Send dynamic as normal prompt content.

Use a custom client:

TurnKit.client = MyClient.new

Install Rails persistence:

bin/rails generate turnkit:install

Run migrations:

bin/rails db:migrate

Configure Rails:

TurnKit.store = TurnKit::ActiveRecordStore.new
TurnKit.default_model = "claude-sonnet-4-5"

Reconcile stale turns:

TurnKit.reconcile_stale!

Options

Configure defaults:

TurnKit.default_model = "claude-sonnet-4-5"
TurnKit.max_iterations = 25
TurnKit.timeout = 300
TurnKit.max_depth = 3
TurnKit.max_tool_executions = 100
TurnKit.cost_limit = nil
TurnKit.prompt_cache = :auto

Override an agent:

agent = TurnKit::Agent.new(
  name: "analyst",
  model: "gpt-4.1-mini",
  max_iterations: 10,
  timeout: 60,
  cost_limit: 0.25
)
Option Description
default_model Set the default RubyLLM model.
client Set the model client.
store Set the conversation store.
max_iterations Limit model calls per turn.
timeout Limit seconds per root turn.
max_depth Limit sub-agent nesting.
max_tool_executions Limit tool calls per root turn.
cost_limit Limit cost per root turn.
prompt_cache Use provider prompt caching.
prompt_sections Set default prompt sections.

Contributing

Report bugs and open pull requests on GitHub:

https://github.com/samuelcouch/turnkit

Run tests:

bundle exec rake test

License

See the MIT License.