|
| 1 | +#!/usr/bin/env ruby |
| 2 | +# frozen_string_literal: true |
| 3 | + |
| 4 | +# Example 24: Structured Delegation |
| 5 | +# |
| 6 | +# Demonstrates robot.delegate(to:, task:) for structured inter-robot calls, |
| 7 | +# in both synchronous (blocking) and asynchronous (parallel fan-out) modes. |
| 8 | +# |
| 9 | +# Demonstrates: |
| 10 | +# - robot.delegate(to:, task:) — sync: blocks, returns RobotResult |
| 11 | +# - robot.delegate(to:, task:, async: true) — async: returns DelegationFuture |
| 12 | +# - future.value / future.value(timeout: N) — block until result ready |
| 13 | +# - future.resolved? — non-blocking poll |
| 14 | +# - result.delegated_by — which robot delegated |
| 15 | +# - result.robot_name — which robot did the work |
| 16 | +# - result.duration — wall-clock seconds for the delegated call |
| 17 | +# - result.input_tokens / result.output_tokens — delegatee's token usage |
| 18 | +# - Contrast with bus messaging (fire-and-forget) and pipelines (predefined) |
| 19 | +# |
| 20 | +# Usage: |
| 21 | +# ANTHROPIC_API_KEY=your_key ruby examples/24_structured_delegation.rb |
| 22 | + |
| 23 | +ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts") |
| 24 | + |
| 25 | +require_relative "../lib/robot_lab" |
| 26 | + |
| 27 | +puts "=" * 60 |
| 28 | +puts "Example 24: Structured Delegation" |
| 29 | +puts "=" * 60 |
| 30 | +puts |
| 31 | + |
| 32 | +# --------------------------------------------------------------------------- |
| 33 | +# Build a manager and two specialist robots |
| 34 | +# --------------------------------------------------------------------------- |
| 35 | +manager = RobotLab.build( |
| 36 | + name: "manager", |
| 37 | + system_prompt: "You are a project manager. Delegate tasks concisely." |
| 38 | +) |
| 39 | + |
| 40 | +summarizer = RobotLab.build( |
| 41 | + name: "summarizer", |
| 42 | + system_prompt: "You are a concise summarizer. Produce a 1-2 sentence summary." |
| 43 | +) |
| 44 | + |
| 45 | +analyst = RobotLab.build( |
| 46 | + name: "analyst", |
| 47 | + system_prompt: "You are a data analyst. Identify the key metric in one sentence." |
| 48 | +) |
| 49 | + |
| 50 | +# --------------------------------------------------------------------------- |
| 51 | +# Manager delegates to each specialist in turn |
| 52 | +# --------------------------------------------------------------------------- |
| 53 | +document = <<~TEXT |
| 54 | + Q4 revenue came in at $4.2M, up 18% year-over-year. Customer acquisition |
| 55 | + cost dropped to $120, the lowest in three years. Churn held steady at 2.1%. |
| 56 | + Net promoter score improved from 42 to 58. The mobile app drove 34% of new |
| 57 | + sign-ups, compared to 19% in Q3. |
| 58 | +TEXT |
| 59 | + |
| 60 | +puts "Document:" |
| 61 | +puts document |
| 62 | +puts "-" * 60 |
| 63 | + |
| 64 | +# --------------------------------------------------------------------------- |
| 65 | +# Synchronous delegation — sequential, blocks until each result arrives |
| 66 | +# --------------------------------------------------------------------------- |
| 67 | +puts "── Synchronous (sequential) ──────────────────────────────" |
| 68 | +puts |
| 69 | + |
| 70 | +puts "Delegating to summarizer (blocking)..." |
| 71 | +summary_result = manager.delegate(to: summarizer, task: "Summarize this report:\n\n#{document}") |
| 72 | + |
| 73 | +puts "Summary (from #{summary_result.robot_name}, delegated by #{summary_result.delegated_by}):" |
| 74 | +puts " #{summary_result.reply}" |
| 75 | +puts " Duration: #{"%.2f" % summary_result.duration}s | " \ |
| 76 | + "Tokens: #{summary_result.input_tokens} in / #{summary_result.output_tokens} out" |
| 77 | +puts |
| 78 | + |
| 79 | +puts "Delegating to analyst (blocking)..." |
| 80 | +analysis_result = manager.delegate(to: analyst, task: "What is the single most important metric here?\n\n#{document}") |
| 81 | + |
| 82 | +puts "Analysis (from #{analysis_result.robot_name}, delegated by #{analysis_result.delegated_by}):" |
| 83 | +puts " #{analysis_result.reply}" |
| 84 | +puts " Duration: #{"%.2f" % analysis_result.duration}s | " \ |
| 85 | + "Tokens: #{analysis_result.input_tokens} in / #{analysis_result.output_tokens} out" |
| 86 | +puts |
| 87 | + |
| 88 | +# --------------------------------------------------------------------------- |
| 89 | +# Asynchronous delegation — parallel fan-out, results collected later |
| 90 | +# --------------------------------------------------------------------------- |
| 91 | +puts "── Asynchronous (parallel fan-out) ───────────────────────" |
| 92 | +puts |
| 93 | + |
| 94 | +# Fresh robots — each delegate call should start from a clean slate |
| 95 | +async_summarizer = RobotLab.build( |
| 96 | + name: "summarizer", |
| 97 | + system_prompt: "You are a concise summarizer. Produce a 1-2 sentence summary." |
| 98 | +) |
| 99 | +async_analyst = RobotLab.build( |
| 100 | + name: "analyst", |
| 101 | + system_prompt: "You are a data analyst. Identify the key metric in one sentence." |
| 102 | +) |
| 103 | + |
| 104 | +puts "Firing both delegations in parallel..." |
| 105 | +t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) |
| 106 | + |
| 107 | +f_summary = manager.delegate(to: async_summarizer, task: "Summarize this report:\n\n#{document}", async: true) |
| 108 | +f_analysis = manager.delegate(to: async_analyst, task: "What is the single most important metric?\n\n#{document}", async: true) |
| 109 | + |
| 110 | +puts "Both futures launched. Futures resolved? " \ |
| 111 | + "summary=#{f_summary.resolved?} analysis=#{f_analysis.resolved?}" |
| 112 | +puts "Collecting results..." |
| 113 | + |
| 114 | +summary = f_summary.value(timeout: 60) |
| 115 | +analysis = f_analysis.value(timeout: 60) |
| 116 | + |
| 117 | +elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0 |
| 118 | + |
| 119 | +puts |
| 120 | +puts "Summary (#{summary.robot_name}): #{summary.reply}" |
| 121 | +puts "Analysis (#{analysis.robot_name}): #{analysis.reply}" |
| 122 | +puts |
| 123 | +puts "Total wall time with parallelism: #{"%.2f" % elapsed}s " \ |
| 124 | + "(vs ~#{"%.2f" % (summary.duration + analysis.duration)}s sequential)" |
| 125 | +puts |
| 126 | + |
| 127 | +# --------------------------------------------------------------------------- |
| 128 | +# Contrast with the alternatives |
| 129 | +# --------------------------------------------------------------------------- |
| 130 | +puts "=" * 60 |
| 131 | +puts "When to use delegate vs. the alternatives" |
| 132 | +puts "=" * 60 |
| 133 | +puts <<~TEXT |
| 134 | +
|
| 135 | + bus messaging — fire-and-forget; no return value; async |
| 136 | + use when: you want to notify without waiting |
| 137 | +
|
| 138 | + pipeline — predefined sequence; robots share memory |
| 139 | + use when: you have a fixed workflow graph |
| 140 | +
|
| 141 | + delegate() — synchronous; blocks; returns RobotResult with metadata |
| 142 | + use when: one robot needs the result of another's work |
| 143 | +
|
| 144 | + delegate(async:true) — returns DelegationFuture immediately |
| 145 | + use when: you want to run multiple delegates in |
| 146 | + parallel and collect results when ready |
| 147 | +
|
| 148 | +TEXT |
| 149 | + |
| 150 | +puts "Done." |
0 commit comments