conductor_ruby 0.1.0 → 0.1.1

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/examples/agents/01_basic_agent.rb +36 -0
  4. data/examples/agents/02a_simple_tools.rb +47 -0
  5. data/examples/agents/02c_tool_retry_config.rb +52 -0
  6. data/examples/agents/04_http_and_mcp_tools.rb +58 -0
  7. data/examples/agents/05_handoffs.rb +71 -0
  8. data/examples/agents/06_sequential_pipeline.rb +47 -0
  9. data/examples/agents/07_parallel_agents.rb +52 -0
  10. data/examples/agents/09_human_in_the_loop.rb +59 -0
  11. data/examples/agents/09c_hitl_streaming.rb +65 -0
  12. data/examples/agents/103_plan_and_compile.rb +85 -0
  13. data/examples/agents/10_guardrails.rb +61 -0
  14. data/examples/agents/13_hierarchical_agents.rb +79 -0
  15. data/examples/agents/16e_credentials_http_tool.rb +44 -0
  16. data/examples/agents/17_swarm_orchestration.rb +56 -0
  17. data/examples/agents/21_regex_guardrails.rb +69 -0
  18. data/examples/agents/22_llm_guardrails.rb +51 -0
  19. data/examples/agents/33_external_workers.rb +65 -0
  20. data/examples/agents/64_swarm_with_tools.rb +71 -0
  21. data/examples/agents/66_handoff_to_parallel.rb +62 -0
  22. data/examples/agents/bug_desk.rb +50 -0
  23. data/examples/agents/catalog.rb +32 -0
  24. data/examples/agents/dump_agent_configs.rb +30 -0
  25. data/examples/agents/external_workers.rb +39 -0
  26. data/examples/agents/golden_agents.rb +385 -0
  27. data/examples/agents/support_approval.rb +51 -0
  28. data/examples/agents/weather.rb +27 -0
  29. data/lib/conductor/agents/agent.rb +324 -0
  30. data/lib/conductor/agents/callback_handler.rb +64 -0
  31. data/lib/conductor/agents/config_serializer.rb +246 -0
  32. data/lib/conductor/agents/errors.rb +26 -0
  33. data/lib/conductor/agents/guardrail.rb +142 -0
  34. data/lib/conductor/agents/handoff.rb +94 -0
  35. data/lib/conductor/agents/memory.rb +75 -0
  36. data/lib/conductor/agents/plans.rb +22 -0
  37. data/lib/conductor/agents/prompt_template.rb +23 -0
  38. data/lib/conductor/agents/runtime/agent_config.rb +52 -0
  39. data/lib/conductor/agents/runtime/agent_runtime.rb +260 -0
  40. data/lib/conductor/agents/runtime/approval_request.rb +111 -0
  41. data/lib/conductor/agents/runtime/dispatch.rb +137 -0
  42. data/lib/conductor/agents/runtime/execution.rb +265 -0
  43. data/lib/conductor/agents/runtime/secrets.rb +54 -0
  44. data/lib/conductor/agents/runtime/sse_client.rb +175 -0
  45. data/lib/conductor/agents/runtime/status_poller.rb +49 -0
  46. data/lib/conductor/agents/runtime/system_workers.rb +115 -0
  47. data/lib/conductor/agents/runtime/tool_call.rb +13 -0
  48. data/lib/conductor/agents/runtime/tool_registry.rb +164 -0
  49. data/lib/conductor/agents/termination.rb +192 -0
  50. data/lib/conductor/agents/tool.rb +283 -0
  51. data/lib/conductor/agents/tools/schema_builder.rb +190 -0
  52. data/lib/conductor/agents/tools/secret_scanner.rb +45 -0
  53. data/lib/conductor/agents/tools.rb +173 -0
  54. data/lib/conductor/agents.rb +75 -0
  55. data/lib/conductor/client/agent_client.rb +117 -0
  56. data/lib/conductor/client/task_client.rb +7 -0
  57. data/lib/conductor/configuration.rb +43 -10
  58. data/lib/conductor/exceptions.rb +36 -0
  59. data/lib/conductor/http/api/agent_resource_api.rb +138 -0
  60. data/lib/conductor/http/api/scheduler_resource_api.rb +31 -12
  61. data/lib/conductor/http/api/task_resource_api.rb +15 -0
  62. data/lib/conductor/http/models/task.rb +10 -2
  63. data/lib/conductor/http/models/task_def.rb +14 -3
  64. data/lib/conductor/http/rest_client.rb +9 -0
  65. data/lib/conductor/orkes/orkes_clients.rb +5 -1
  66. data/lib/conductor/version.rb +1 -1
  67. data/lib/conductor/worker/lease_renewer.rb +55 -0
  68. data/lib/conductor/worker/ractor_task_runner.rb +1 -1
  69. data/lib/conductor/worker/task_definition_registrar.rb +2 -2
  70. data/lib/conductor/worker/task_runner.rb +43 -10
  71. data/lib/conductor/worker/worker.rb +3 -2
  72. data/lib/conductor/worker/worker_config.rb +2 -1
  73. data/lib/conductor.rb +3 -1
  74. metadata +71 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5eaac8568f7cf211e8337d9c41ccf454d9362d8c1b5f4d8adefb2a6f2faff64e
4
- data.tar.gz: 5c34b569b0080d6ea6ea2b20411614076ba1fbefd5de6901ea581a917f3ac30c
3
+ metadata.gz: 58e85067049af7ceef18c133c58b3f562b53d62421cdafb61bf8bdc5eec4ebe8
4
+ data.tar.gz: 9d9d0cc7ec214d7734b09d0d179269119d516f716069ed8fa7c15e477cd97c66
5
5
  SHA512:
6
- metadata.gz: 364e5f398619c081bc979e0158d15daf6cbe2df9f2b548e90d81d8811da900c828f88a34e619a992e0847ce210ae24f0fe3a4a6b29fff6c2e3ce743bdbbe81eb
7
- data.tar.gz: 66b81f3609e971ce064ab042287fea9e96e9ce197765d1ba5cf46b5265583f37f73311cec754f6ef97ec03b28391e310809cbb7504e4ce147dd8519f7086e12c
6
+ metadata.gz: 1ba8b9dccdbdd29fdbc1afbc48e42c492c4cc8be47cb34ca763abf0bba558093480b0d016461a1a1215d9e1fe4278586de536d762cb0f8f05fa0f4c4d63a8dfd
7
+ data.tar.gz: ce5edabd959da8b8c68184146bbbed0489b620c4ffad7f9e6e335c0b23348fa96cd11c8d0c79188b0dab74f693b9d14e1225e6a23d2afbe9b955e97fd8e63838
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.1]
9
+
10
+ ### Added
11
+
12
+ - **Conductor Agents** (`require 'conductor/agents'`): define agents in Ruby and run them on a Conductor server (#29)
13
+ - `Agent` with instructions, model, tools, guardrails, handoffs, memory, termination conditions and prompt templates
14
+ - `tool def` DSL that turns a method into a worker tool with a schema derived from keyword defaults, plus factories for HTTP, OpenAPI, MCP, human, agent, media, PDF and RAG tools
15
+ - Regex, LLM and custom guardrails with `raise`, `retry`, `fix` and `human` failure modes
16
+ - Sequential, parallel, hierarchical and swarm multi-agent patterns, handoffs and human-in-the-loop approval
17
+ - `AgentRuntime` with streaming, status polling, secrets resolution and automatic worker registration
18
+ - Examples under `examples/agents/` and documentation under `docs/agents/`
19
+ - Agent playback integration tests driven by the shared Conductor HTTP and MCP playback services (#29)
20
+ - Integration (e2e) test suite now runs against a Conductor OSS stack on every PR and gates the Orkes cloud run (#26)
21
+
8
22
  ## [0.1.0]
9
23
 
10
24
  ### Added
@@ -139,4 +153,5 @@ end
139
153
  - Supports both OSS Conductor and Orkes Cloud
140
154
  - Ruby 2.6+ compatible (Ruby 3+ recommended for Ractor support)
141
155
 
156
+ [0.1.1]: https://github.com/conductor-oss/ruby-sdk/releases/tag/v0.1.1
142
157
  [0.1.0]: https://github.com/conductor-oss/ruby-sdk/releases/tag/v0.1.0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/01_basic_agent.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/01_basic_agent.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example01BasicAgent
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
12
+ agent = Agent.new(
13
+ name: "greeter",
14
+ model: model,
15
+ instructions: "You are a friendly assistant. Keep responses brief."
16
+ )
17
+ agent
18
+ end
19
+
20
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
21
+ agent = build
22
+ executions = []
23
+ execution = runtime.call_async(agent, "Say hello and tell me a fun fact about Python.")
24
+ output.puts execution.result(timeout: 180)
25
+ executions << execution
26
+ executions
27
+ end
28
+ end
29
+
30
+ if $PROGRAM_NAME == __FILE__
31
+ begin
32
+ Example01BasicAgent.run
33
+ ensure
34
+ Conductor::Agents.shutdown
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/02a_simple_tools.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/02a_simple_tools.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example02aSimpleTools
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def get_weather(city: String)
12
+ { city: city, temp_f: 72, condition: "Sunny" }
13
+ end
14
+ tool :get_weather, description: "Get the current weather for a city."
15
+
16
+ def get_stock_price(symbol: String)
17
+ { symbol: symbol, price: 182.50, change: "+1.2%" }
18
+ end
19
+ tool :get_stock_price, description: "Get the current stock price for a ticker symbol."
20
+
21
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
22
+ agent = Agent.new(
23
+ name: "weather_stock_agent",
24
+ model: model,
25
+ tools: [self[:get_weather], self[:get_stock_price]],
26
+ instructions: "You are a helpful assistant. Use tools to answer questions."
27
+ )
28
+ agent
29
+ end
30
+
31
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
32
+ agent = build
33
+ executions = []
34
+ execution = runtime.call_async(agent, "What's the weather like in San Francisco?")
35
+ output.puts execution.result(timeout: 180)
36
+ executions << execution
37
+ executions
38
+ end
39
+ end
40
+
41
+ if $PROGRAM_NAME == __FILE__
42
+ begin
43
+ Example02aSimpleTools.run
44
+ ensure
45
+ Conductor::Agents.shutdown
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/02c_tool_retry_config.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/02c_tool_retry_config.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example02cToolRetryConfig
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def call_external_api(query: String)
12
+ { result: "Data for: #{query}", source: "external_api" }
13
+ end
14
+ tool :call_external_api, description: "Call an unreliable external API that may need aggressive retries.", retry_policy: "exponential_backoff", retry_count: 5, retry_delay_seconds: 1
15
+
16
+ def query_database(sql: String)
17
+ { rows: [{ id: 1, value: sql }], count: 1 }
18
+ end
19
+ tool :query_database, description: "Run a database query with fixed-interval retries for transient connection issues." , retry_policy: "fixed", retry_count: 3, retry_delay_seconds: 5
20
+
21
+ def process_data(data: String)
22
+ { processed: data, status: "ok" }
23
+ end
24
+ tool :process_data, description: "Process data locally — light retries with linear backoff." , retry_policy: "linear_backoff", retry_count: 2, retry_delay_seconds: 2
25
+
26
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
27
+ agent = Agent.new(
28
+ name: "retry_config_demo",
29
+ model: model,
30
+ tools: [self[:call_external_api], self[:query_database], self[:process_data]],
31
+ instructions: "You help users fetch and process data. Use the appropriate tool for each request."
32
+ )
33
+ agent
34
+ end
35
+
36
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
37
+ agent = build
38
+ executions = []
39
+ execution = runtime.call_async(agent, "Look up the latest Python release info from the API.")
40
+ output.puts execution.result(timeout: 180)
41
+ executions << execution
42
+ executions
43
+ end
44
+ end
45
+
46
+ if $PROGRAM_NAME == __FILE__
47
+ begin
48
+ Example02cToolRetryConfig.run
49
+ ensure
50
+ Conductor::Agents.shutdown
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/04_http_and_mcp_tools.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/04_http_and_mcp_tools.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example04HttpAndMcpTools
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def format_report(title: String, body: String)
12
+ { report: "=== #{title} ===\n#{body}\n#{'=' * (title.length + 8)}" }
13
+ end
14
+ tool :format_report, description: "Format a title and body into a structured report."
15
+
16
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
17
+ reverse_api = Tool.http(
18
+ "reverse_string",
19
+ "http://localhost:3001/api/string/reverse",
20
+ description: "Reverse a string using the HTTP API",
21
+ method: "POST",
22
+ headers: { "Authorization" => "Bearer ${HTTP_TEST_API_KEY}" },
23
+ credentials: ["HTTP_TEST_API_KEY"],
24
+ input_schema: { "type" => "object", "properties" => { "text" => { "type" => "string", "description" => "Text to reverse" } }, "required" => ["text"] }
25
+ )
26
+ mcp_test_tools = Tool.mcp(
27
+ "http://localhost:3001/mcp",
28
+ name: "mcp_test_tools",
29
+ description: "Deterministic test tools via MCP — math, string, collection, encoding, hash, datetime, validation, and conversion operations.",
30
+ headers: { "Authorization" => "Bearer ${MCP_TEST_API_KEY}" },
31
+ credentials: ["MCP_TEST_API_KEY"]
32
+ )
33
+ agent = Agent.new(
34
+ name: "http_tools_demo",
35
+ model: model,
36
+ tools: [self[:format_report], reverse_api, mcp_test_tools],
37
+ instructions: "You can reverse strings and format reports. When asked to reverse a string, use reverse_string first, then format_report with the result."
38
+ )
39
+ agent
40
+ end
41
+
42
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
43
+ agent = build
44
+ executions = []
45
+ execution = runtime.call_async(agent, "Reverse the string 'hello world' and add 33 and 21 append the result to that string, then write a report with the result.")
46
+ output.puts execution.result(timeout: 180)
47
+ executions << execution
48
+ executions
49
+ end
50
+ end
51
+
52
+ if $PROGRAM_NAME == __FILE__
53
+ begin
54
+ Example04HttpAndMcpTools.run
55
+ ensure
56
+ Conductor::Agents.shutdown
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/05_handoffs.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/05_handoffs.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example05Handoffs
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def check_balance(account_id: String)
12
+ { account_id: account_id, balance: 5432.10, currency: "USD" }
13
+ end
14
+ tool :check_balance, description: "Check the balance of a bank account."
15
+
16
+ def lookup_order(order_id: String)
17
+ { order_id: order_id, status: "shipped", eta: "2 days" }
18
+ end
19
+ tool :lookup_order, description: "Look up the status of an order."
20
+
21
+ def get_pricing(product: String)
22
+ { product: product, price: 99.99, discount: "10% off" }
23
+ end
24
+ tool :get_pricing, description: "Get pricing information for a product."
25
+
26
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
27
+ billing_agent = Agent.new(
28
+ name: "billing",
29
+ model: model,
30
+ instructions: "You handle billing questions: balances, payments, invoices.",
31
+ tools: [self[:check_balance]]
32
+ )
33
+ technical_agent = Agent.new(
34
+ name: "technical",
35
+ model: model,
36
+ instructions: "You handle technical questions: order status, shipping, returns.",
37
+ tools: [self[:lookup_order]]
38
+ )
39
+ sales_agent = Agent.new(
40
+ name: "sales",
41
+ model: model,
42
+ instructions: "You handle sales questions: pricing, products, promotions.",
43
+ tools: [self[:get_pricing]]
44
+ )
45
+ support = Agent.new(
46
+ name: "support",
47
+ model: model,
48
+ instructions: "Route customer requests to the right specialist: billing, technical, or sales.",
49
+ agents: [billing_agent, technical_agent, sales_agent],
50
+ strategy: :handoff
51
+ )
52
+ support
53
+ end
54
+
55
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
56
+ support = build
57
+ executions = []
58
+ execution = runtime.call_async(support, "What's the balance on account ACC-123?")
59
+ output.puts execution.result(timeout: 180)
60
+ executions << execution
61
+ executions
62
+ end
63
+ end
64
+
65
+ if $PROGRAM_NAME == __FILE__
66
+ begin
67
+ Example05Handoffs.run
68
+ ensure
69
+ Conductor::Agents.shutdown
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/06_sequential_pipeline.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/06_sequential_pipeline.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example06SequentialPipeline
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
12
+ researcher = Agent.new(
13
+ name: "researcher",
14
+ model: model,
15
+ instructions: "You are a researcher. Given a topic, provide key facts and data points. Be thorough but concise. Output raw research findings."
16
+ )
17
+ writer = Agent.new(
18
+ name: "writer",
19
+ model: model,
20
+ instructions: "You are a writer. Take research findings and write a clear, engaging article. Use headers and bullet points where appropriate."
21
+ )
22
+ editor = Agent.new(
23
+ name: "editor",
24
+ model: model,
25
+ instructions: "You are an editor. Review the article for clarity, grammar, and tone. Make improvements and output the final polished version."
26
+ )
27
+ pipeline = researcher >> writer >> editor
28
+ pipeline
29
+ end
30
+
31
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
32
+ pipeline = build
33
+ executions = []
34
+ execution = runtime.call_async(pipeline, "The impact of AI agents on software development in 2025")
35
+ output.puts execution.result(timeout: 180)
36
+ executions << execution
37
+ executions
38
+ end
39
+ end
40
+
41
+ if $PROGRAM_NAME == __FILE__
42
+ begin
43
+ Example06SequentialPipeline.run
44
+ ensure
45
+ Conductor::Agents.shutdown
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/07_parallel_agents.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/07_parallel_agents.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example07ParallelAgents
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
12
+ market_analyst = Agent.new(
13
+ name: "market_analyst",
14
+ model: model,
15
+ instructions: "You are a market analyst. Analyze the given topic from a market perspective: market size, growth trends, key players, and opportunities."
16
+ )
17
+ risk_analyst = Agent.new(
18
+ name: "risk_analyst",
19
+ model: model,
20
+ instructions: "You are a risk analyst. Analyze the given topic for risks: regulatory risks, technical risks, competitive threats, and mitigation strategies."
21
+ )
22
+ compliance_checker = Agent.new(
23
+ name: "compliance",
24
+ model: model,
25
+ instructions: "You are a compliance specialist. Check the given topic for compliance considerations: data privacy, regulatory requirements, and industry standards."
26
+ )
27
+ analysis = Agent.new(
28
+ name: "analysis",
29
+ model: model,
30
+ agents: [market_analyst, risk_analyst, compliance_checker],
31
+ strategy: :parallel
32
+ )
33
+ analysis
34
+ end
35
+
36
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
37
+ analysis = build
38
+ executions = []
39
+ execution = runtime.call_async(analysis, "Launching an AI-powered healthcare diagnostic tool in the US market")
40
+ output.puts execution.result(timeout: 180)
41
+ executions << execution
42
+ executions
43
+ end
44
+ end
45
+
46
+ if $PROGRAM_NAME == __FILE__
47
+ begin
48
+ Example07ParallelAgents.run
49
+ ensure
50
+ Conductor::Agents.shutdown
51
+ end
52
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/09_human_in_the_loop.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/09_human_in_the_loop.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example09HumanInTheLoop
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def check_balance(account_id: String)
12
+ { account_id: account_id, balance: 15000.00 }
13
+ end
14
+ tool :check_balance, description: "Check the balance of an account."
15
+
16
+ def transfer_funds(from_acct: String, to_acct: String, amount: Float)
17
+ { status: "completed", from: from_acct, to: to_acct, amount: amount }
18
+ end
19
+ tool :transfer_funds, description: "Request a funds transfer; runtime pauses for human approval before execution." , approval_required: true
20
+
21
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
22
+ agent = Agent.new(
23
+ name: "banker",
24
+ model: model,
25
+ tools: [self[:check_balance], self[:transfer_funds]],
26
+ instructions: "You are a banking assistant. Use check_balance for balance inquiries. When asked to transfer money, first check the balance, then call transfer_funds to request the transfer. The runtime will pause for human approval before the transfer executes."
27
+ )
28
+ agent
29
+ end
30
+
31
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
32
+ agent = build
33
+ agent.on_approval do |request|
34
+ output.puts "Approval requested: #{request.tool_calls}"
35
+ response = {}
36
+ request.response_schema.fetch('properties').each do |field, schema|
37
+ output.print "#{schema['description'] || schema['title'] || field}: "
38
+ answer = input.gets
39
+ raise EOFError, "No response supplied for #{field}" unless answer
40
+
41
+ response[field] = schema['type'] == 'boolean' ? %w[y yes].include?(answer.strip.downcase) : answer.strip
42
+ end
43
+ request.respond(response)
44
+ end
45
+ executions = []
46
+ execution = runtime.call_async(agent, "Transfer $500 from ACC-789 to ACC-456. Check the balance first.")
47
+ output.puts execution.result(timeout: 180)
48
+ executions << execution
49
+ executions
50
+ end
51
+ end
52
+
53
+ if $PROGRAM_NAME == __FILE__
54
+ begin
55
+ Example09HumanInTheLoop.run
56
+ ensure
57
+ Conductor::Agents.shutdown
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/09c_hitl_streaming.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/09c_hitl_streaming.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example09cHitlStreaming
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def check_service(service_name: String)
12
+ { service: service_name, status: "unhealthy", uptime: "0m" }
13
+ end
14
+ tool :check_service, description: "Check the health of a service."
15
+
16
+ def restart_service(service_name: String)
17
+ { service: service_name, status: "restarted", new_uptime: "0m" }
18
+ end
19
+ tool :restart_service, description: "Restart a service. Safe operation, no approval needed."
20
+
21
+ def delete_service_data(service_name: String, data_type: String)
22
+ { service: service_name, data_type: data_type, status: "deleted" }
23
+ end
24
+ tool :delete_service_data, description: "Delete service data. Destructive — requires human approval." , approval_required: true
25
+
26
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
27
+ agent = Agent.new(
28
+ name: "ops_agent",
29
+ model: model,
30
+ tools: [self[:check_service], self[:restart_service], self[:delete_service_data]],
31
+ instructions: "You are an operations assistant. Work through the request one tool call at a time, in this order:\n1. Check the service with check_service.\n2. If it is unhealthy, restart it with restart_service.\n3. Last, if the user asked you to clear or delete data, call delete_service_data.\nA human approves the deletion, not you — delete_service_data pauses for that approval by itself, so never ask for approval in your own reply."
32
+ )
33
+ agent
34
+ end
35
+
36
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
37
+ agent = build
38
+ agent.on_approval do |request|
39
+ output.puts "Approval requested: #{request.tool_calls}"
40
+ response = {}
41
+ request.response_schema.fetch('properties').each do |field, schema|
42
+ output.print "#{schema['description'] || schema['title'] || field}: "
43
+ answer = input.gets
44
+ raise EOFError, "No response supplied for #{field}" unless answer
45
+
46
+ response[field] = schema['type'] == 'boolean' ? %w[y yes].include?(answer.strip.downcase) : answer.strip
47
+ end
48
+ request.respond(response)
49
+ end
50
+ executions = []
51
+ on_event = ->(event) { output.puts "[#{event['event']}] #{event['data']}" }
52
+ execution = runtime.call_async(agent, "The payments service is down. Check it, restart it, and clear its stale cache data.", on_event: on_event)
53
+ output.puts execution.result(timeout: 180)
54
+ executions << execution
55
+ executions
56
+ end
57
+ end
58
+
59
+ if $PROGRAM_NAME == __FILE__
60
+ begin
61
+ Example09cHitlStreaming.run
62
+ ensure
63
+ Conductor::Agents.shutdown
64
+ end
65
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/103_plan_and_compile.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/103_plan_and_compile.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example103PlanAndCompile
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def factorial(n: Integer)
12
+ return "ERROR: n must be in [0, 20], got #{n}" unless (0..20).cover?(n)
13
+
14
+ (1..n).reduce(1, :*).to_s
15
+ end
16
+ tool :factorial, output_schema: { 'type' => 'string' }, description: "Compute n! and return it as a string.\n\nArgs:\n n: Non-negative integer. Capped at 20 to keep things sane."
17
+
18
+ def write_summary(text: String)
19
+ text
20
+ end
21
+ tool :write_summary, output_schema: { 'type' => 'string' }, description: "Persist a short summary string. Returns it back for the validator."
22
+
23
+ def check_summary(text: String, min_chars: Integer)
24
+ JSON.generate(passed: text.length >= min_chars, length: text.length, min_chars: min_chars)
25
+ end
26
+ tool :check_summary, output_schema: { 'type' => 'string' }, description: "Return JSON ``{passed, length, min_chars}`` for the validator.\n\nArgs:\n text: The summary to check.\n min_chars: Minimum acceptable length in characters."
27
+
28
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
29
+ planner_instructions = "You are a math-explainer planner. Plan a workflow that:\n\n1. Computes factorials of 1, 2, 3, 4, 5 in PARALLEL using ``factorial`` (static args).\n2. Writes a short prose summary about factorial growth using ``write_summary``\n (use a ``generate`` block — the LLM produces the ``text`` arg at run time).\n3. Validates the summary is at least 30 characters via ``check_summary``,\n with ``success_condition: \"$.passed === true\"``.\n"
30
+ harness = Conductor::Agents.plan_execute(
31
+ name: "plan_and_compile_demo",
32
+ tools: [self[:factorial], self[:write_summary], self[:check_summary]],
33
+ planner_instructions: planner_instructions,
34
+ fallback_instructions: "The plan failed. Use the available tools to recover.",
35
+ fallback_max_turns: 4,
36
+ model: model
37
+ )
38
+ harness
39
+ end
40
+
41
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout, topic: 'factorials')
42
+ harness = build
43
+ executions = []
44
+ execution = runtime.call_async(harness, "Topic: #{topic}")
45
+ output.puts execution.result(timeout: 180)
46
+ compiled = find_plan_and_compile_output(runtime, execution.execution_id)
47
+ raise 'No PLAN_AND_COMPILE task found in the workflow tree' unless compiled
48
+ raise "Plan compilation failed: #{compiled['error']}" if compiled['error']
49
+
50
+ output.puts "Compiled workflow: #{compiled['workflowName']}"
51
+ output.puts "Stats: #{compiled['stats']}"
52
+ Array(compiled.dig('workflowDef', 'tasks')).each do |task|
53
+ output.puts "#{task['type']}: #{task['taskReferenceName']}"
54
+ end
55
+ executions << execution
56
+ executions
57
+ end
58
+
59
+ def self.find_plan_and_compile_output(runtime, execution_id)
60
+ client = Conductor::Client::WorkflowClient.new(runtime.configuration)
61
+ pending = [execution_id]
62
+ visited = []
63
+ until pending.empty?
64
+ id = pending.pop
65
+ next if visited.include?(id)
66
+
67
+ visited << id
68
+ workflow = client.get_workflow(id)
69
+ workflow.tasks.each do |task|
70
+ return task.output_data if task.task_type == 'PLAN_AND_COMPILE'
71
+
72
+ pending << task.sub_workflow_id if task.sub_workflow_id
73
+ end
74
+ end
75
+ nil
76
+ end
77
+ end
78
+
79
+ if $PROGRAM_NAME == __FILE__
80
+ begin
81
+ Example103PlanAndCompile.run(topic: ARGV.empty? ? 'factorials' : ARGV.join(' '))
82
+ ensure
83
+ Conductor::Agents.shutdown
84
+ end
85
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Port of python-sdk/examples/agents/10_guardrails.py.
4
+ # Run: bundle exec ruby -Ilib examples/agents/10_guardrails.rb
5
+ require 'conductor/agents'
6
+
7
+ module Example10Guardrails
8
+ include Conductor::Agents
9
+ extend Conductor::Agents::Tools
10
+
11
+ def get_order_status(order_id: String)
12
+ { order_id: order_id, status: "shipped", tracking: "1Z999AA10123456784", estimated_delivery: "2026-02-22" }
13
+ end
14
+ tool :get_order_status, description: "Look up the current status of an order."
15
+
16
+ def get_customer_info(customer_id: String)
17
+ { customer_id: customer_id, name: "Alice Johnson", email: "alice@example.com",
18
+ card_on_file: "4532-0150-1234-5678", membership: "gold" }
19
+ end
20
+ tool :get_customer_info, description: "Retrieve customer details including payment info on file."
21
+
22
+ def self.pii_guardrail
23
+ Guardrail.new(name: "no_pii", position: :output, on_fail: :retry) do |content|
24
+ pii = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b|\b\d{3}-\d{2}-\d{4}\b/
25
+ if pii.match?(content)
26
+ GuardrailResult.new(passed: false, message: "Your response contains PII (credit card or SSN). Redact all card numbers and SSNs before responding.")
27
+ else
28
+ GuardrailResult.new(passed: true)
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
34
+ no_pii = pii_guardrail
35
+ agent = Agent.new(
36
+ name: "support_agent",
37
+ model: model,
38
+ tools: [self[:get_order_status], self[:get_customer_info]],
39
+ instructions: "You are a customer support assistant. Use the available tools to answer questions about orders and customers. Always include all details from the tool results in your response.",
40
+ guardrails: [no_pii]
41
+ )
42
+ agent
43
+ end
44
+
45
+ def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
46
+ agent = build
47
+ executions = []
48
+ execution = runtime.call_async(agent, "I need a full summary: What's the status of order ORD-42, and what's the profile for customer CUST-7?")
49
+ output.puts execution.result(timeout: 180)
50
+ executions << execution
51
+ executions
52
+ end
53
+ end
54
+
55
+ if $PROGRAM_NAME == __FILE__
56
+ begin
57
+ Example10Guardrails.run
58
+ ensure
59
+ Conductor::Agents.shutdown
60
+ end
61
+ end