robot_lab 0.0.7 → 0.0.9

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -3
  3. data/README.md +125 -26
  4. data/Rakefile +39 -1
  5. data/docs/api/core/robot.md +284 -8
  6. data/docs/api/core/tool.md +28 -2
  7. data/docs/api/mcp/client.md +1 -0
  8. data/docs/api/mcp/server.md +27 -8
  9. data/docs/api/mcp/transports.md +21 -6
  10. data/docs/architecture/core-concepts.md +1 -1
  11. data/docs/architecture/robot-execution.md +20 -2
  12. data/docs/concepts.md +11 -3
  13. data/docs/examples/index.md +1 -1
  14. data/docs/examples/rails-application.md +1 -1
  15. data/docs/guides/building-robots.md +245 -11
  16. data/docs/guides/creating-networks.md +18 -0
  17. data/docs/guides/mcp-integration.md +74 -2
  18. data/docs/guides/rails-integration.md +145 -17
  19. data/docs/guides/using-tools.md +45 -4
  20. data/docs/index.md +62 -6
  21. data/examples/05_streaming.rb +113 -94
  22. data/examples/14_rusty_circuit/.gitignore +1 -0
  23. data/examples/14_rusty_circuit/open_mic.rb +1 -1
  24. data/examples/17_skills.rb +242 -0
  25. data/examples/18_rails/.envrc +3 -0
  26. data/examples/18_rails/.gitignore +5 -0
  27. data/examples/18_rails/Gemfile +10 -0
  28. data/examples/18_rails/README.md +48 -0
  29. data/examples/18_rails/Rakefile +4 -0
  30. data/examples/18_rails/app/controllers/application_controller.rb +4 -0
  31. data/examples/18_rails/app/controllers/chat_controller.rb +46 -0
  32. data/examples/18_rails/app/jobs/application_job.rb +4 -0
  33. data/examples/18_rails/app/jobs/robot_run_job.rb +79 -0
  34. data/examples/18_rails/app/models/application_record.rb +5 -0
  35. data/examples/18_rails/app/models/robot_lab_result.rb +36 -0
  36. data/examples/18_rails/app/models/robot_lab_thread.rb +23 -0
  37. data/examples/18_rails/app/robots/chat_robot.rb +14 -0
  38. data/examples/18_rails/app/tools/time_tool.rb +9 -0
  39. data/examples/18_rails/app/views/chat/_user_message.html.erb +1 -0
  40. data/examples/18_rails/app/views/chat/index.html.erb +67 -0
  41. data/examples/18_rails/app/views/layouts/application.html.erb +49 -0
  42. data/examples/18_rails/bin/dev +7 -0
  43. data/examples/18_rails/bin/rails +6 -0
  44. data/examples/18_rails/bin/setup +15 -0
  45. data/examples/18_rails/config/application.rb +33 -0
  46. data/examples/18_rails/config/cable.yml +2 -0
  47. data/examples/18_rails/config/database.yml +5 -0
  48. data/examples/18_rails/config/environment.rb +4 -0
  49. data/examples/18_rails/config/initializers/robot_lab.rb +3 -0
  50. data/examples/18_rails/config/routes.rb +6 -0
  51. data/examples/18_rails/config.ru +4 -0
  52. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +32 -0
  53. data/examples/README.md +30 -0
  54. data/examples/prompts/audit_trail.md +8 -0
  55. data/examples/prompts/incident_responder.md +18 -0
  56. data/examples/prompts/parameterized_main_test.md +6 -0
  57. data/examples/prompts/pii_redactor.md +7 -0
  58. data/examples/prompts/runbook_protocol.md +12 -0
  59. data/examples/prompts/skill_a_test.md +4 -0
  60. data/examples/prompts/skill_b_test.md +4 -0
  61. data/examples/prompts/skill_config_test.md +5 -0
  62. data/examples/prompts/skill_cycle_a_test.md +6 -0
  63. data/examples/prompts/skill_cycle_b_test.md +6 -0
  64. data/examples/prompts/skill_description_test.md +4 -0
  65. data/examples/prompts/skill_leaf_test.md +4 -0
  66. data/examples/prompts/skill_nested_test.md +6 -0
  67. data/examples/prompts/skill_refs_main_test.md +6 -0
  68. data/examples/prompts/skill_self_ref_test.md +6 -0
  69. data/examples/prompts/skill_with_params_test.md +6 -0
  70. data/examples/prompts/sre_compliance.md +10 -0
  71. data/examples/prompts/structured_output.md +7 -0
  72. data/examples/prompts/template_with_skills_test.md +6 -0
  73. data/lib/generators/robot_lab/install_generator.rb +12 -0
  74. data/lib/generators/robot_lab/templates/initializer.rb.tt +36 -22
  75. data/lib/generators/robot_lab/templates/job.rb.tt +92 -0
  76. data/lib/generators/robot_lab/templates/robot.rb.tt +6 -21
  77. data/lib/generators/robot_lab/templates/robot_test.rb.tt +5 -3
  78. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +41 -35
  79. data/lib/robot_lab/mcp/client.rb +6 -4
  80. data/lib/robot_lab/mcp/server.rb +21 -3
  81. data/lib/robot_lab/mcp/transports/base.rb +10 -2
  82. data/lib/robot_lab/mcp/transports/stdio.rb +52 -26
  83. data/lib/robot_lab/{rails → rails_integration}/engine.rb +1 -1
  84. data/lib/robot_lab/{rails → rails_integration}/railtie.rb +1 -1
  85. data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +72 -0
  86. data/lib/robot_lab/robot/mcp_management.rb +61 -4
  87. data/lib/robot_lab/robot/template_rendering.rb +181 -4
  88. data/lib/robot_lab/robot.rb +196 -33
  89. data/lib/robot_lab/robot_result.rb +3 -1
  90. data/lib/robot_lab/run_config.rb +1 -1
  91. data/lib/robot_lab/tool.rb +26 -0
  92. data/lib/robot_lab/version.rb +1 -1
  93. data/lib/robot_lab.rb +6 -4
  94. metadata +55 -19
  95. data/examples/14_rusty_circuit/scout_notes.md +0 -89
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ APP_PATH = File.expand_path("../config/application", __dir__)
5
+ require_relative "../config/application"
6
+ require "rails/commands"
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ cd "$(dirname "$0")/.."
5
+
6
+ echo "==> Installing gems..."
7
+ bundle install
8
+
9
+ echo "==> Creating database..."
10
+ bundle exec rails db:create
11
+
12
+ echo "==> Running migrations..."
13
+ bundle exec rails db:migrate
14
+
15
+ echo "==> Done! Run bin/dev to start the server."
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ require "rails"
6
+ require "active_record/railtie"
7
+ require "active_job/railtie"
8
+ require "action_controller/railtie"
9
+ require "action_view/railtie"
10
+ require "action_cable/engine"
11
+
12
+ Bundler.require(*Rails.groups)
13
+
14
+ module RobotLabDemo
15
+ class Application < Rails::Application
16
+ config.load_defaults 8.0
17
+
18
+ config.eager_load = false
19
+ config.consider_all_requests_local = true
20
+ config.secret_key_base = "demo-secret-key-for-development-only"
21
+
22
+ # Use async adapter for jobs — no external process needed
23
+ config.active_job.queue_adapter = :async
24
+
25
+ # Autoload app/robots and app/tools
26
+ config.autoload_paths << root.join("app", "robots")
27
+ config.autoload_paths << root.join("app", "tools")
28
+
29
+ # Disable unused middleware
30
+ config.api_only = false
31
+ config.hosts.clear
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ development:
2
+ adapter: async
@@ -0,0 +1,5 @@
1
+ development:
2
+ adapter: sqlite3
3
+ database: db/development.sqlite3
4
+ pool: 5
5
+ timeout: 5000
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "application"
4
+ Rails.application.initialize!
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ RobotLab.config.logger = Rails.logger
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ root "chat#index"
5
+ post "chat", to: "chat#create"
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "config/environment"
4
+ run Rails.application
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRobotLabTables < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :robot_lab_threads do |t|
6
+ t.string :session_id, null: false, index: { unique: true }
7
+ t.text :initial_input
8
+ t.json :input_metadata, default: {}
9
+ t.json :state_data, default: {}
10
+ t.text :last_user_message
11
+ t.datetime :last_user_message_at
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ create_table :robot_lab_results do |t|
17
+ t.string :session_id, null: false, index: true
18
+ t.string :robot_name, null: false
19
+ t.integer :sequence_number, null: false, default: 0
20
+ t.json :output_data, default: []
21
+ t.json :tool_calls_data, default: []
22
+ t.string :stop_reason
23
+ t.string :checksum
24
+
25
+ t.timestamps
26
+ end
27
+
28
+ add_index :robot_lab_results, [:session_id, :sequence_number]
29
+ add_foreign_key :robot_lab_results, :robot_lab_threads,
30
+ column: :session_id, primary_key: :session_id
31
+ end
32
+ end
data/examples/README.md CHANGED
@@ -45,6 +45,15 @@ examples/
45
45
  scout.rb # Talent scout with analyst spawning
46
46
  display.rb # Terminal formatting (color, wrapping, file output)
47
47
  prompts/ # Templates for comic, heckler, and scout
48
+ 18_rails/ # Minimal Rails 8 demo app (full integration)
49
+ app/robots/chat_robot.rb # Robot factory with system prompt + TimeTool
50
+ app/tools/time_tool.rb # Custom RobotLab::Tool subclass
51
+ app/jobs/robot_run_job.rb # Background job with Turbo Stream callbacks
52
+ app/controllers/ # Chat controller (index + create)
53
+ app/views/ # Layout with CDN importmap, chat view with streaming
54
+ app/models/ # RobotLabThread, RobotLabResult
55
+ config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
56
+ db/migrate/ # Migration from generator template
48
57
  prompts/ # Prompt templates (.md with YAML front matter)
49
58
  ```
50
59
 
@@ -152,6 +161,27 @@ Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic
152
161
 
153
162
  **Requires:** LLM API key
154
163
 
164
+ ### 18 — Rails Integration Demo
165
+
166
+ A minimal, hand-built Rails 8 app that exercises every piece of RobotLab's Rails integration end-to-end. No `rails new` — every file is hand-crafted for minimum size.
167
+
168
+ **What it demonstrates:**
169
+ - **ChatRobot** with a custom `TimeTool` (`app/robots/`, `app/tools/`)
170
+ - **RobotRunJob** — background execution via `ActiveJob` async adapter
171
+ - **Turbo Stream token streaming** — real-time content chunks broadcast to the browser over ActionCable
172
+ - **Persistence** — `RobotLabThread` + `RobotLabResult` with conversation history
173
+ - **Auto-scrolling chat** — MutationObserver keeps the view pinned to the latest streaming content
174
+
175
+ **No Redis, no Solid Queue, no asset pipeline.** Uses `:async` adapters for both ActiveJob and ActionCable. Turbo JS loaded via importmap from CDN (`@hotwired/turbo-rails`).
176
+
177
+ ```bash
178
+ cd examples/18_rails
179
+ bin/setup # bundle install + db:create + db:migrate
180
+ bin/dev # starts Puma on http://localhost:3000
181
+ ```
182
+
183
+ **Requires:** LLM API key, Ruby 3.2+
184
+
155
185
  ## Prompt Templates
156
186
 
157
187
  Templates live in `examples/prompts/` as `.md` files with YAML front matter. Each template defines a robot's personality and behavior:
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Include audit metadata in every response
3
+ ---
4
+ Include an "audit" field in every response with:
5
+ - "timestamp": the current date/time you were asked (use ISO 8601)
6
+ - "confidence": your confidence level from 0.0 to 1.0
7
+ - "sources": list of data sources or signals you used for your assessment
8
+ - "assumptions": any assumptions you made due to missing information
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Incident response analyst for production systems
3
+ parameters:
4
+ team_name: null
5
+ services: null
6
+ on_call_runbook_url: null
7
+ ---
8
+ You are the on-call incident response analyst for the <%= team_name %> team.
9
+
10
+ Your team owns these services: <%= Array(services).join(", ") %>.
11
+
12
+ When an incident is reported, analyze the provided alert data, logs, and metrics
13
+ to help the on-call engineer understand what is happening and what to do next.
14
+
15
+ Reference the team runbook at: <%= on_call_runbook_url %>
16
+
17
+ Prioritize reducing customer impact over finding root cause.
18
+ If you are uncertain, say so and recommend escalation.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Main template with same parameter as skill
3
+ parameters:
4
+ company_name: null
5
+ ---
6
+ Welcome to <%= company_name %> support.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Redact personally identifiable information from output
3
+ ---
4
+ Never include real personally identifiable information (PII) in your responses.
5
+ If log data contains email addresses, IP addresses, usernames, or account IDs,
6
+ replace them with redacted placeholders like [EMAIL], [IP], [USER], [ACCOUNT_ID].
7
+ This applies to all fields in your output including explanations.
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: Follow structured runbook steps for incident response
3
+ ---
4
+ When analyzing an incident, follow this protocol:
5
+
6
+ 1. ASSESS — Identify the affected service, severity, and blast radius.
7
+ 2. DIAGNOSE — Examine symptoms, recent changes, and correlated events.
8
+ 3. MITIGATE — Recommend immediate actions to reduce impact.
9
+ 4. RESOLVE — Propose a root-cause fix and verification steps.
10
+ 5. FOLLOW-UP — Suggest post-incident review items and preventive measures.
11
+
12
+ Always label each section clearly. Never skip a step.
@@ -0,0 +1,4 @@
1
+ ---
2
+ description: Skill A for testing
3
+ ---
4
+ You have Skill A capabilities.
@@ -0,0 +1,4 @@
1
+ ---
2
+ description: Skill B for testing
3
+ ---
4
+ You have Skill B capabilities.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Skill with config overrides
3
+ temperature: 0.9
4
+ ---
5
+ You have config skill capabilities.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Cycle test A
3
+ skills:
4
+ - skill_cycle_b_test
5
+ ---
6
+ You have cycle A capabilities.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Cycle test B
3
+ skills:
4
+ - skill_cycle_a_test
5
+ ---
6
+ You have cycle B capabilities.
@@ -0,0 +1,4 @@
1
+ ---
2
+ description: Skill description from frontmatter
3
+ ---
4
+ You have description skill capabilities.
@@ -0,0 +1,4 @@
1
+ ---
2
+ description: Leaf skill with no nested skills
3
+ ---
4
+ You have leaf skill capabilities.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Nested skill that depends on leaf
3
+ skills:
4
+ - skill_leaf_test
5
+ ---
6
+ You have nested skill capabilities.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Skill that references the main template
3
+ skills:
4
+ - assistant
5
+ ---
6
+ You have a skill that references the main template.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Self-referencing skill
3
+ skills:
4
+ - skill_self_ref_test
5
+ ---
6
+ You have self-referencing capabilities.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Skill with parameters
3
+ parameters:
4
+ company_name: null
5
+ ---
6
+ You work for <%= company_name %>.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: SRE compliance bundle — PII redaction and audit trail
3
+ skills:
4
+ - pii_redactor
5
+ - audit_trail
6
+ ---
7
+ You operate under SRE compliance requirements.
8
+ All responses must be safe for inclusion in incident tickets visible to external stakeholders.
9
+ Do not speculate about blame or assign fault to individuals.
10
+ Focus on systems, processes, and technical root causes.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Respond with structured JSON output
3
+ temperature: 0.2
4
+ ---
5
+ Always respond with valid JSON. Structure your response as a JSON object.
6
+ Do not include any text outside the JSON block.
7
+ If you need to explain something, include it in an "explanation" field.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Main template that uses skills via front matter
3
+ skills:
4
+ - skill_a_test
5
+ ---
6
+ You are the main template with skills from front matter.
@@ -17,6 +17,8 @@ module RobotLab
17
17
 
18
18
  class_option :skip_migration, type: :boolean, default: false,
19
19
  desc: "Skip database migration generation"
20
+ class_option :skip_job, type: :boolean, default: false,
21
+ desc: "Skip background job generation"
20
22
 
21
23
  # Returns the next migration number for ActiveRecord migrations.
22
24
  #
@@ -52,6 +54,15 @@ module RobotLab
52
54
  template "result_model.rb.tt", "app/models/robot_lab_result.rb"
53
55
  end
54
56
 
57
+ # Creates the background job for robot execution.
58
+ #
59
+ # @return [void]
60
+ def create_job
61
+ return if options[:skip_job]
62
+
63
+ template "job.rb.tt", "app/jobs/robot_run_job.rb"
64
+ end
65
+
55
66
  # Creates the robots and tools directories.
56
67
  #
57
68
  # @return [void]
@@ -71,6 +82,7 @@ module RobotLab
71
82
  say " 1. Run migrations: rails db:migrate"
72
83
  say " 2. Configure your LLM API keys in config/initializers/robot_lab.rb"
73
84
  say " 3. Generate your first robot: rails g robot_lab:robot MyRobot"
85
+ say " 4. Enqueue robot runs via RobotRunJob (app/jobs/robot_run_job.rb)" unless options[:skip_job]
74
86
  say ""
75
87
  end
76
88
  end
@@ -2,27 +2,41 @@
2
2
 
3
3
  # RobotLab Configuration
4
4
  #
5
- # Configure your AI robot framework settings here.
5
+ # RobotLab uses MywayConfig for configuration. Settings are loaded from:
6
6
  #
7
- RobotLab.configure do |config|
8
- # Default model for robots (optional)
9
- # config.default_model = "claude-sonnet-4"
10
- # config.default_provider = :anthropic
11
-
12
- # Logger configuration
13
- config.logger = Rails.logger
14
-
15
- # Maximum iterations per robot run
16
- # config.max_iterations = 10
17
-
18
- # Enable debug mode in development
19
- # config.debug = Rails.env.development?
20
- end
21
-
22
- # Configure Ruby LLM (required for LLM interactions)
7
+ # 1. Bundled defaults (lib/robot_lab/config/defaults.yml)
8
+ # 2. Environment-specific overrides (development, test, production)
9
+ # 3. XDG user config (~/.config/robot_lab/config.yml)
10
+ # 4. Project config (./config/robot_lab.yml)
11
+ # 5. Environment variables (ROBOT_LAB_* prefix)
12
+ #
13
+ # Create config/robot_lab.yml for project-specific settings:
14
+ #
15
+ # defaults:
16
+ # ruby_llm:
17
+ # anthropic_api_key: <%%= ENV['ANTHROPIC_API_KEY'] %>
18
+ # openai_api_key: <%%= ENV['OPENAI_API_KEY'] %>
19
+ # model: claude-sonnet-4
20
+ # request_timeout: 180
23
21
  #
24
- # RubyLLM.configure do |config|
25
- # config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
26
- # config.openai_api_key = ENV["OPENAI_API_KEY"]
27
- # config.gemini_api_key = ENV["GEMINI_API_KEY"]
28
- # end
22
+ # development:
23
+ # ruby_llm:
24
+ # log_level: :debug
25
+ #
26
+ # test:
27
+ # streaming_enabled: false
28
+ # ruby_llm:
29
+ # model: claude-3-haiku-20240307
30
+ # request_timeout: 30
31
+ #
32
+ # Or use environment variables with double underscores for nested keys:
33
+ #
34
+ # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
35
+ # ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
36
+ #
37
+ # RobotLab also falls back to standard provider env vars
38
+ # (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY) when the prefixed
39
+ # versions are not set.
40
+
41
+ # Set the RobotLab logger to use Rails.logger
42
+ RobotLab.config.logger = Rails.logger
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Background job for executing robot runs asynchronously.
4
+ #
5
+ # Resolves a robot class, optionally wires Turbo Stream callbacks for
6
+ # real-time token streaming, persists the result, and broadcasts
7
+ # completion/error events.
8
+ #
9
+ # @example Enqueue from a controller
10
+ # RobotRunJob.perform_later(
11
+ # robot_class: "SupportRobot",
12
+ # message: params[:message],
13
+ # thread_id: session_id
14
+ # )
15
+ #
16
+ class RobotRunJob < ApplicationJob
17
+ queue_as :default
18
+
19
+ retry_on StandardError, wait: 5.seconds, attempts: 3
20
+ discard_on ActiveJob::DeserializationError
21
+
22
+ def perform(robot_class:, message:, thread_id:, **context)
23
+ thread = RobotLabThread.find_or_create_by_session_id(thread_id)
24
+ thread.update!(last_user_message: message, last_user_message_at: Time.current)
25
+
26
+ robot = resolve_robot(robot_class, thread_id)
27
+ result = robot.run(message, **context)
28
+
29
+ persist_result(thread, result)
30
+ broadcast_completion(thread_id)
31
+ rescue StandardError => e
32
+ broadcast_error(thread_id, e)
33
+ raise
34
+ end
35
+
36
+ private
37
+
38
+ def resolve_robot(robot_class, thread_id)
39
+ klass = robot_class.to_s.constantize
40
+ stream_name = "robot_lab_thread_#{thread_id}"
41
+
42
+ if turbo_available?
43
+ on_content = RobotLab::RailsIntegration::TurboStreamCallbacks.build_content_callback(
44
+ stream_name: stream_name
45
+ )
46
+ on_tool_call = RobotLab::RailsIntegration::TurboStreamCallbacks.build_tool_call_callback(
47
+ stream_name: stream_name
48
+ )
49
+ klass.build(on_content: on_content, on_tool_call: on_tool_call)
50
+ else
51
+ klass.build
52
+ end
53
+ end
54
+
55
+ def persist_result(thread, result)
56
+ sequence = thread.results.maximum(:sequence_number).to_i + 1
57
+ exported = result.export
58
+
59
+ thread.results.create!(
60
+ robot_name: result.robot_name,
61
+ sequence_number: sequence,
62
+ output_data: exported[:output],
63
+ tool_calls_data: exported[:tool_calls],
64
+ stop_reason: result.stop_reason,
65
+ checksum: result.checksum
66
+ )
67
+ end
68
+
69
+ def broadcast_completion(thread_id)
70
+ return unless turbo_available?
71
+
72
+ Turbo::StreamsChannel.broadcast_replace_to(
73
+ "robot_lab_thread_#{thread_id}",
74
+ target: "robot_status",
75
+ html: "<span class=\"robot-status-complete\">Complete</span>"
76
+ )
77
+ end
78
+
79
+ def broadcast_error(thread_id, error)
80
+ return unless turbo_available?
81
+
82
+ Turbo::StreamsChannel.broadcast_append_to(
83
+ "robot_lab_thread_#{thread_id}",
84
+ target: "robot_errors",
85
+ html: "<div class=\"robot-error\">#{ERB::Util.html_escape(error.message)}</div>"
86
+ )
87
+ end
88
+
89
+ def turbo_available?
90
+ defined?(Turbo::StreamsChannel)
91
+ end
92
+ end
@@ -5,42 +5,27 @@
5
5
  # <%= robot_description %>
6
6
  #
7
7
  class <%= class_name %>Robot
8
- include RobotLab::Lifecycle
9
-
10
8
  SYSTEM_PROMPT = <<~PROMPT
11
9
  You are a helpful <%= class_name.titleize %> assistant.
12
10
 
13
11
  Your role is to assist users with their requests in a friendly and efficient manner.
14
12
  PROMPT
15
13
 
16
- def self.build
17
- RobotLab.create_robot(
14
+ def self.build(**options)
15
+ RobotLab.build(
18
16
  name: "<%= file_name %>",
19
17
  description: "<%= robot_description %>",
20
- system: SYSTEM_PROMPT,
21
- tools: tools,
22
- lifecycle: lifecycle_hooks
18
+ system_prompt: SYSTEM_PROMPT,
19
+ local_tools: tools,
20
+ **options
23
21
  )
24
22
  end
25
23
 
26
24
  def self.tools
27
25
  [
28
26
  <%- robot_tools.each do |tool| -%>
29
- # <%= tool.camelize %>Tool.build,
27
+ # <%= tool.camelize %>,
30
28
  <%- end -%>
31
29
  ]
32
30
  end
33
-
34
- def self.lifecycle_hooks
35
- RobotLab::Lifecycle::Hooks.new(
36
- on_start: ->(robot:, network:, prompt:, history:) {
37
- # Called before robot execution
38
- { prompt: prompt, history: history }
39
- },
40
- on_finish: ->(robot:, network:, result:) {
41
- # Called after robot execution
42
- result
43
- }
44
- )
45
- end
46
31
  end
@@ -16,7 +16,7 @@ class <%= class_name %>RobotTest < ActiveSupport::TestCase
16
16
  end
17
17
 
18
18
  test "robot has system prompt" do
19
- assert_not_nil @robot.instance_variable_get(:@system)
19
+ assert_not_nil @robot.system_prompt
20
20
  end
21
21
 
22
22
  # Add more tests as needed:
@@ -24,9 +24,11 @@ class <%= class_name %>RobotTest < ActiveSupport::TestCase
24
24
  # test "robot runs successfully" do
25
25
  # result = @robot.run("Hello, I need help")
26
26
  # assert result.is_a?(RobotLab::RobotResult)
27
+ # assert_not_nil result.last_text_content
27
28
  # end
28
29
  #
29
- # test "robot uses correct tools" do
30
- # # Test tool invocation
30
+ # test "robot has expected tools" do
31
+ # tool_names = @robot.local_tools.map(&:name)
32
+ # assert_includes tool_names, "expected_tool_name"
31
33
  # end
32
34
  end