Class: SwarmSDK::Agent::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_sdk/agent/builder.rb

Overview

Builder provides fluent API for configuring agents

This class offers a Ruby DSL for defining agents with a clean, readable syntax. It collects configuration and then adds the agent to the swarm.

Examples:

agent :backend do
  model "gpt-5"
  prompt "You build APIs"
  tools :Read, :Write, :Bash

  hook :pre_tool_use, matcher: "Bash" do |ctx|
    SwarmSDK::Hooks::Result.halt("Blocked!") if dangerous?(ctx)
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Builder

Returns a new instance of Builder.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/swarm_sdk/agent/builder.rb', line 34

def initialize(name)
  @name = name
  @description = nil
  @model = "gpt-5"
  @provider = nil
  @base_url = nil
  @api_version = nil
  @context_window = nil
  @system_prompt = nil
  # Use Set for tools to automatically handle duplicates when tools() is called multiple times.
  # This ensures that if someone does: tools :Read; tools :Write; tools :Read
  # the final set contains only [:Read, :Write] without duplicates.
  # We convert to Array in to_definition for compatibility with Agent::Definition.
  @tools = Set.new
  @delegates_to = []
  @directory = "."
  @parameters = {}
  @headers = {}
  @request_timeout = nil
  @turn_timeout = nil
  @mcp_servers = []
  @disable_default_tools = nil # nil = include all default tools
  @bypass_permissions = false
  @coding_agent = nil # nil = not set (will default to false in Definition)
  @assume_model_exists = nil
  @hooks = []
  @permissions_config = {}
  @default_permissions = {} # Set by SwarmBuilder from all_agents
  @memory_config = nil
  @shared_across_delegations = nil # nil = not set (will default to false in Definition)
  @streaming = nil # nil = not set (will use global config default)
  @thinking = nil # nil = not set (extended thinking disabled)
  @disable_environment_info = nil # nil = not set (will default to false in Definition)
  @context_management_config = nil # Context management DSL hooks
end

Instance Attribute Details

#default_permissions=(value) ⇒ Object (writeonly)

Expose default_permissions for Swarm::Builder to set from all_agents



22
23
24
# File 'lib/swarm_sdk/agent/builder.rb', line 22

def default_permissions=(value)
  @default_permissions = value
end

#mcp_serversObject (readonly)

Expose mcp_servers for tests



25
26
27
# File 'lib/swarm_sdk/agent/builder.rb', line 25

def mcp_servers
  @mcp_servers
end

Instance Method Details

#api_version(version = :__not_provided__) ⇒ Object

Set/get API version (OpenAI-compatible providers only)



92
93
94
95
96
# File 'lib/swarm_sdk/agent/builder.rb', line 92

def api_version(version = :__not_provided__)
  return @api_version if version == :__not_provided__

  @api_version = version
end

#api_version_set?Boolean

Check if api_version has been explicitly set

Used by Swarm::Builder to determine if all_agents api_version should apply.

Returns:

  • (Boolean)

    true if api_version was explicitly set



502
503
504
# File 'lib/swarm_sdk/agent/builder.rb', line 502

def api_version_set?
  !@api_version.nil?
end

#assume_model_exists(enabled) ⇒ Object

Set assume_model_exists flag



204
205
206
# File 'lib/swarm_sdk/agent/builder.rb', line 204

def assume_model_exists(enabled)
  @assume_model_exists = enabled
end

#base_url(url = :__not_provided__) ⇒ Object

Set/get base URL



85
86
87
88
89
# File 'lib/swarm_sdk/agent/builder.rb', line 85

def base_url(url = :__not_provided__)
  return @base_url if url == :__not_provided__

  @base_url = url
end

#base_url_set?Boolean

Check if base_url has been explicitly set

Used by Swarm::Builder to determine if all_agents base_url should apply.

Returns:

  • (Boolean)

    true if base_url was explicitly set



493
494
495
# File 'lib/swarm_sdk/agent/builder.rb', line 493

def base_url_set?
  !@base_url.nil?
end

#bypass_permissions(enabled) ⇒ Object

Set bypass_permissions flag



185
186
187
# File 'lib/swarm_sdk/agent/builder.rb', line 185

def bypass_permissions(enabled)
  @bypass_permissions = enabled
end

#coding_agent(enabled) ⇒ void

This method returns an undefined value.

Set coding_agent flag

When true, includes the base system prompt for coding tasks. When false (default), uses only the custom system prompt.

Examples:

coding_agent true  # Include base prompt for coding tasks

Parameters:

  • enabled (Boolean)

    Whether to include base coding prompt



199
200
201
# File 'lib/swarm_sdk/agent/builder.rb', line 199

def coding_agent(enabled)
  @coding_agent = enabled
end

#coding_agent_set?Boolean

Check if coding_agent has been explicitly set

Used by Swarm::Builder to determine if all_agents coding_agent should apply.

Returns:

  • (Boolean)

    true if coding_agent was explicitly set



529
530
531
# File 'lib/swarm_sdk/agent/builder.rb', line 529

def coding_agent_set?
  !@coding_agent.nil?
end

#context_management { ... } ⇒ void

This method returns an undefined value.

Configure context management handlers

Define custom handlers for context warning thresholds (60%, 80%, 90%). Handlers receive a rich context object with message manipulation methods. When a custom handler is registered, automatic compression is disabled for that threshold, giving full control to the handler.

Examples:

Basic compression at 60%

context_management do
  on :warning_60 do |ctx|
    ctx.compress_tool_results(keep_recent: 10)
  end
end

Multiple thresholds with different strategies

context_management do
  on :warning_60 do |ctx|
    ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
  end

  on :warning_80 do |ctx|
    ctx.prune_old_messages(keep_recent: 30)
    ctx.compress_tool_results(keep_recent: 5, truncate_to: 200)
  end

  on :warning_90 do |ctx|
    ctx.log_action("emergency_pruning", remaining: ctx.tokens_remaining)
    ctx.prune_old_messages(keep_recent: 15)
  end
end

Conditional logic based on metrics

context_management do
  on :warning_80 do |ctx|
    if ctx.usage_percentage > 85
      ctx.prune_old_messages(keep_recent: 10)
    else
      ctx.compress_tool_results(keep_recent: 5)
    end
  end
end

Yields:

  • Context management DSL block



453
454
455
456
457
# File 'lib/swarm_sdk/agent/builder.rb', line 453

def context_management(&block)
  builder = ContextManagement::Builder.new
  builder.instance_eval(&block)
  @context_management_config = builder.build
end

#context_window(tokens = :__not_provided__) ⇒ Object

Set/get explicit context window override



99
100
101
102
103
# File 'lib/swarm_sdk/agent/builder.rb', line 99

def context_window(tokens = :__not_provided__)
  return @context_window if tokens == :__not_provided__

  @context_window = tokens
end

#delegates_to(*agent_names_and_options) ⇒ void

This method returns an undefined value.

Set delegation targets

Supports multiple formats for flexibility:

Examples:

Simple array (backwards compatible)

delegates_to :frontend, :backend, :qa

Hash with custom tool names

delegates_to frontend: "AskFrontend",
             backend: "GetBackendHelp",
             qa: "RequestReview"

Mixed - some auto, some custom

delegates_to :frontend,
             backend: "GetBackendHelp",
             :qa

With delegation options (preserve_context controls context persistence)

delegates_to :frontend,
             { agent: :backend, tool_name: "AskBackend", preserve_context: false }

Parameters:

  • agent_names_and_options (Array<Symbol, Hash>)

    Agent names and/or hash with custom tool names



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/swarm_sdk/agent/builder.rb', line 286

def delegates_to(*agent_names_and_options)
  agent_names_and_options.each do |item|
    case item
    when Symbol, String
      # Simple format: :frontend
      @delegates_to << { agent: item.to_sym, tool_name: nil, preserve_context: true }
    when Hash
      if item.key?(:agent)
        # Full config format: { agent: :backend, tool_name: "Custom", preserve_context: false }
        @delegates_to << {
          agent: item[:agent].to_sym,
          tool_name: item[:tool_name],
          preserve_context: item.fetch(:preserve_context, true),
        }
      else
        # Hash format: { frontend: "AskFrontend", backend: nil }
        item.each do |agent, tool_name|
          @delegates_to << { agent: agent.to_sym, tool_name: tool_name, preserve_context: true }
        end
      end
    else
      raise ConfigurationError, "delegates_to accepts Symbols or Hashes, got #{item.class}"
    end
  end
end

#description(text) ⇒ Object

Set description



214
215
216
# File 'lib/swarm_sdk/agent/builder.rb', line 214

def description(text)
  @description = text
end

#directory(dir) ⇒ Object

Set directory



259
260
261
# File 'lib/swarm_sdk/agent/builder.rb', line 259

def directory(dir)
  @directory = dir
end

#disable_default_tools(*tools) ⇒ Object

Disable default tools

Examples:

Disable all default tools

disable_default_tools true

Disable specific tools (array)

disable_default_tools [:Think, :TodoWrite]

Disable specific tools (separate arguments)

disable_default_tools :Think, :TodoWrite

Parameters:

  • value (Boolean, Array<Symbol>)
    • true: Disable ALL default tools
    • Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])


170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/swarm_sdk/agent/builder.rb', line 170

def disable_default_tools(*tools)
  # Handle different argument forms
  @disable_default_tools = case tools.size
  when 0
    nil
  when 1
    # Single argument: could be true/false/array
    tools.first
  else
    # Multiple arguments: treat as array of tool names
    tools.map(&:to_sym)
  end
end

#disable_environment_info(enabled) ⇒ void

This method returns an undefined value.

Disable environment info (date, platform, OS, working directory) in system prompt

When true, omits the environment information section from the agent's system prompt. Defaults to false if not set.

Examples:

disable_environment_info true  # Omit environment info from prompt

Parameters:

  • enabled (Boolean)

    Whether to disable environment info



543
544
545
# File 'lib/swarm_sdk/agent/builder.rb', line 543

def disable_environment_info(enabled)
  @disable_environment_info = enabled
end

#disable_environment_info_set?Boolean

Check if disable_environment_info has been explicitly set

Used by Swarm::Builder to determine if all_agents disable_environment_info should apply.

Returns:

  • (Boolean)

    true if disable_environment_info was explicitly set



552
553
554
# File 'lib/swarm_sdk/agent/builder.rb', line 552

def disable_environment_info_set?
  !@disable_environment_info.nil?
end

#headers(header_hash = :__not_provided__) ⇒ Object

Set/get custom HTTP headers



113
114
115
116
117
# File 'lib/swarm_sdk/agent/builder.rb', line 113

def headers(header_hash = :__not_provided__)
  return @headers if header_hash == :__not_provided__

  @headers = header_hash
end

#headers_set?Boolean

Check if headers have been set

Used by Swarm::Builder for merging all_agents headers.

Returns:

  • (Boolean)

    true if headers were set



570
571
572
# File 'lib/swarm_sdk/agent/builder.rb', line 570

def headers_set?
  @headers.any?
end

#hook(event, matcher: nil, command: nil, timeout: nil, &block) ⇒ Object

Add a hook (Ruby block OR shell command)

Examples:

Ruby block

hook :pre_tool_use, matcher: "Bash" do |ctx|
  HookResult.halt("Blocked") if dangerous?(ctx)
end

Shell command

hook :pre_tool_use, matcher: "Bash", command: "validate.sh"


321
322
323
324
325
326
327
328
329
# File 'lib/swarm_sdk/agent/builder.rb', line 321

def hook(event, matcher: nil, command: nil, timeout: nil, &block)
  @hooks << {
    event: event,
    matcher: matcher,
    command: command,
    timeout: timeout,
    block: block,
  }
end

#mcp_server(name, **options) ⇒ Object

Add an MCP server configuration

Examples:

stdio transport with discovery

mcp_server :filesystem, type: :stdio, command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem"]

stdio transport with filtered tools (faster boot)

mcp_server :codebase, type: :stdio, command: "mcp-server-codebase", tools: [:search_code, :list_files]

SSE transport

mcp_server :web, type: :sse, url: "https://example.com/mcp", headers: { authorization: "Bearer token" }

HTTP/streamable transport

mcp_server :api, type: :http, url: "https://api.example.com/mcp", timeout: 60

Parameters:

  • name (Symbol)

    Server name

  • type (Symbol)

    Transport type (:stdio, :sse, :http)

  • tools (Array<Symbol>, nil)

    Tool names to expose (nil = discover all tools)

  • options (Hash)

    Transport-specific options



151
152
153
154
# File 'lib/swarm_sdk/agent/builder.rb', line 151

def mcp_server(name, **options)
  server_config = { name: name }.merge(options)
  @mcp_servers << server_config
end

#model(model_name = :__not_provided__) ⇒ Object

Set/get agent model



71
72
73
74
75
# File 'lib/swarm_sdk/agent/builder.rb', line 71

def model(model_name = :__not_provided__)
  return @model if model_name == :__not_provided__

  @model = model_name
end

#model_set?Boolean

Check if model has been explicitly set (not default)

Used by Swarm::Builder to determine if all_agents model should apply.

Returns:

  • (Boolean)

    true if model was explicitly set



475
476
477
# File 'lib/swarm_sdk/agent/builder.rb', line 475

def model_set?
  @model != "gpt-5"
end

#parameters(params = :__not_provided__) ⇒ Object

Set/get LLM parameters



106
107
108
109
110
# File 'lib/swarm_sdk/agent/builder.rb', line 106

def parameters(params = :__not_provided__)
  return @parameters if params == :__not_provided__

  @parameters = params
end

#parameters_set?Boolean

Check if parameters have been set

Used by Swarm::Builder for merging all_agents parameters.

Returns:

  • (Boolean)

    true if parameters were set



561
562
563
# File 'lib/swarm_sdk/agent/builder.rb', line 561

def parameters_set?
  @parameters.any?
end

#permissions(&block) ⇒ Object

Configure permissions for this agent

Examples:

permissions do
  Write.allow_paths "backend/**/*"
  Write.deny_paths "backend/secrets/**"
end


338
339
340
# File 'lib/swarm_sdk/agent/builder.rb', line 338

def permissions(&block)
  @permissions_config = PermissionsBuilder.build(&block)
end

#permissions_hash=(hash) ⇒ void

This method returns an undefined value.

Set permissions directly from hash (for YAML translation)

This is intentionally separate from permissions() to keep the DSL clean. Called by Configuration when translating YAML permissions.

Parameters:

  • hash (Hash)

    Permissions configuration hash



466
467
468
# File 'lib/swarm_sdk/agent/builder.rb', line 466

def permissions_hash=(hash)
  @permissions_config = hash || {}
end

#prepend_tools(*tool_names) ⇒ void

This method returns an undefined value.

Add tools from all_agents configuration

Used by Swarm::Builder to add all_agents tools. Since we use Set, order doesn't matter and duplicates are handled automatically.

Parameters:

  • tool_names (Array)

    Tool names to add



254
255
256
# File 'lib/swarm_sdk/agent/builder.rb', line 254

def prepend_tools(*tool_names)
  @tools.merge(tool_names.map(&:to_sym))
end

#provider(provider_name = :__not_provided__) ⇒ Object

Set/get provider



78
79
80
81
82
# File 'lib/swarm_sdk/agent/builder.rb', line 78

def provider(provider_name = :__not_provided__)
  return @provider if provider_name == :__not_provided__

  @provider = provider_name
end

#provider_set?Boolean

Check if provider has been explicitly set

Used by Swarm::Builder to determine if all_agents provider should apply.

Returns:

  • (Boolean)

    true if provider was explicitly set



484
485
486
# File 'lib/swarm_sdk/agent/builder.rb', line 484

def provider_set?
  !@provider.nil?
end

#request_timeout(seconds = :__not_provided__) ⇒ Object

Set/get request timeout



120
121
122
123
124
# File 'lib/swarm_sdk/agent/builder.rb', line 120

def request_timeout(seconds = :__not_provided__)
  return @request_timeout if seconds == :__not_provided__

  @request_timeout = seconds
end

#request_timeout_set?Boolean

Check if request_timeout has been explicitly set

Used by Swarm::Builder to determine if all_agents request_timeout should apply.

Returns:

  • (Boolean)

    true if request_timeout was explicitly set



511
512
513
# File 'lib/swarm_sdk/agent/builder.rb', line 511

def request_timeout_set?
  !@request_timeout.nil?
end

#shared_across_delegations(enabled) ⇒ self

Configure delegation isolation mode

Examples:

shared_across_delegations true  # Allow sharing (old behavior)

Parameters:

  • enabled (Boolean)

    If true, allows sharing instances across delegations (old behavior) If false (default), creates isolated instances per delegation

Returns:

  • (self)

    Returns self for method chaining



350
351
352
353
# File 'lib/swarm_sdk/agent/builder.rb', line 350

def shared_across_delegations(enabled)
  @shared_across_delegations = enabled
  self
end

#streaming(value = true) ⇒ self

Enable or disable streaming for LLM API responses

Examples:

Enable streaming (default)

streaming true

Disable streaming

streaming false

Parameters:

  • value (Boolean) (defaults to: true)

    If true (default), enables streaming; if false, disables it

Returns:

  • (self)

    Returns self for method chaining



365
366
367
368
# File 'lib/swarm_sdk/agent/builder.rb', line 365

def streaming(value = true)
  @streaming = value
  self
end

#streaming_set?Boolean

Check if streaming has been explicitly set

Returns:

  • (Boolean)

    true if streaming was explicitly set, false otherwise



373
374
375
# File 'lib/swarm_sdk/agent/builder.rb', line 373

def streaming_set?
  !@streaming.nil?
end

#system_prompt(text) ⇒ Object

Set system prompt (matches YAML key)



209
210
211
# File 'lib/swarm_sdk/agent/builder.rb', line 209

def system_prompt(text)
  @system_prompt = text
end

#thinking(effort: nil, budget: nil) ⇒ self

Configure extended thinking for this agent

Extended thinking allows models to reason through complex problems before responding. For Anthropic models, specify a budget (token count). For OpenAI models, specify effort. Both can be specified for cross-provider compatibility.

Examples:

Anthropic thinking with budget

thinking budget: 10_000

OpenAI reasoning effort

thinking effort: :high

Cross-provider (both)

thinking effort: :high, budget: 10_000

Parameters:

  • effort (Symbol, String, nil) (defaults to: nil)

    Reasoning effort level (:low, :medium, :high) — used by OpenAI

  • budget (Integer, nil) (defaults to: nil)

    Token budget for thinking — used by Anthropic

Returns:

  • (self)

    Returns self for method chaining

Raises:

  • (ArgumentError)


395
396
397
398
399
400
# File 'lib/swarm_sdk/agent/builder.rb', line 395

def thinking(effort: nil, budget: nil)
  raise ArgumentError, "thinking requires :effort or :budget" if effort.nil? && budget.nil?

  @thinking = { effort: effort, budget: budget }.compact
  self
end

#thinking_set?Boolean

Check if thinking has been explicitly set

Returns:

  • (Boolean)

    true if thinking was explicitly configured



405
406
407
# File 'lib/swarm_sdk/agent/builder.rb', line 405

def thinking_set?
  !@thinking.nil?
end

#to_definitionAgent::Definition

Build and return an Agent::Definition

This method converts the builder's configuration into a validated Agent::Definition object. The caller is responsible for adding it to a swarm.

Converts @tools Set to Array here because Agent::Definition expects an array. The Set was only used during building to handle duplicates efficiently.

Returns:



583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/swarm_sdk/agent/builder.rb', line 583

def to_definition
  agent_config = {
    description: @description || "Agent #{@name}",
    model: @model,
    system_prompt: @system_prompt,
    tools: @tools.to_a, # Convert Set to Array for Agent::Definition compatibility
    delegates_to: @delegates_to,
    directory: @directory,
  }

  # Add optional fields
  agent_config[:provider] = @provider if @provider
  agent_config[:base_url] = @base_url if @base_url
  agent_config[:api_version] = @api_version if @api_version
  agent_config[:context_window] = @context_window if @context_window
  agent_config[:parameters] = @parameters if @parameters.any?
  agent_config[:headers] = @headers if @headers.any?
  agent_config[:request_timeout] = @request_timeout if @request_timeout
  agent_config[:turn_timeout] = @turn_timeout if @turn_timeout
  agent_config[:mcp_servers] = @mcp_servers if @mcp_servers.any?
  agent_config[:disable_default_tools] = @disable_default_tools unless @disable_default_tools.nil?
  agent_config[:bypass_permissions] = @bypass_permissions
  agent_config[:coding_agent] = @coding_agent
  agent_config[:assume_model_exists] = @assume_model_exists unless @assume_model_exists.nil?
  agent_config[:permissions] = @permissions_config if @permissions_config.any?
  agent_config[:default_permissions] = @default_permissions if @default_permissions.any?
  agent_config[:memory] = @memory_config if @memory_config
  agent_config[:shared_across_delegations] = @shared_across_delegations unless @shared_across_delegations.nil?
  agent_config[:streaming] = @streaming unless @streaming.nil?
  agent_config[:thinking] = @thinking if @thinking
  agent_config[:disable_environment_info] = @disable_environment_info unless @disable_environment_info.nil?

  # Convert DSL hooks to HookDefinition format
  agent_config[:hooks] = convert_hooks_to_definitions if @hooks.any?

  # Merge context management hooks into agent hooks
  if @context_management_config
    agent_config[:hooks] ||= {}
    agent_config[:hooks][:context_warning] ||= []
    agent_config[:hooks][:context_warning].concat(@context_management_config)
  end

  Agent::Definition.new(@name, agent_config)
end

#tools(*tool_names, include_default: true, replace: false) ⇒ Object

Set or add tools

Uses Set internally to automatically deduplicate tool names across multiple calls. This allows calling tools() multiple times without worrying about duplicates.

Examples:

Basic usage with defaults

tools :Grep, :Read  # include_default: true is implicit

Explicit tools only, no defaults

tools :Grep, :Read, include_default: false

Multiple calls (cumulative, automatic deduplication)

tools :Read
tools :Write, :Edit  # @tools now contains Set[:Read, :Write, :Edit]
tools :Read          # Still Set[:Read, :Write, :Edit] - no duplicate

Replace tools (for markdown overrides)

tools :Read, :Write, replace: true  # Replaces all existing tools

Parameters:

  • tool_names (Array<Symbol>)

    Tool names to add

  • include_default (Boolean) (defaults to: true)

    Whether to include default tools (Read, Grep, etc.)

  • replace (Boolean) (defaults to: false)

    If true, replaces existing tools instead of merging (default: false)



240
241
242
243
244
245
# File 'lib/swarm_sdk/agent/builder.rb', line 240

def tools(*tool_names, include_default: true, replace: false)
  @tools = Set.new if replace
  @tools.merge(tool_names.map(&:to_sym))
  # When include_default is false, disable all default tools
  @disable_default_tools = true unless include_default
end

#tools_listArray<Symbol>

Get tools list as array for validation

Returns:

  • (Array<Symbol>)

    List of tools



30
31
32
# File 'lib/swarm_sdk/agent/builder.rb', line 30

def tools_list
  @tools.to_a
end

#turn_timeout(seconds = :__not_provided__) ⇒ Object

Set/get turn timeout



127
128
129
130
131
# File 'lib/swarm_sdk/agent/builder.rb', line 127

def turn_timeout(seconds = :__not_provided__)
  return @turn_timeout if seconds == :__not_provided__

  @turn_timeout = seconds
end

#turn_timeout_set?Boolean

Check if turn_timeout has been explicitly set

Used by Swarm::Builder to determine if all_agents turn_timeout should apply.

Returns:

  • (Boolean)

    true if turn_timeout was explicitly set



520
521
522
# File 'lib/swarm_sdk/agent/builder.rb', line 520

def turn_timeout_set?
  !@turn_timeout.nil?
end