legate 0.1.0

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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b06e76343d31ff76c3638acd71650a387dc3953c906f8e04a8539d85a3783445
4
+ data.tar.gz: 80d3719fd189ec9143b90ebf068b03fed902a358bece5cf0ead12a30028609aa
5
+ SHA512:
6
+ metadata.gz: b5b88dfcdf545ec9586bf134112344d80919af25398b815c35948a5256c140c1315a0a751149405cc7c93676d90c0f4083b7c47fe72baaf23369afd8a25c81ca
7
+ data.tar.gz: 4780b16ff7582091c688e7cb438762669d1aaa488ffee5bdd693d7d1b8951f374462712cd6d5ab9faf3c3ce0208afc33a581e64de754393c9340a42304b62589
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Taylor Weibley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # Legate
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/legate.svg)](https://badge.fury.io/rb/legate)
4
+ [![CI](https://github.com/tweibley/legate/actions/workflows/ci.yml/badge.svg)](https://github.com/tweibley/legate/actions/workflows/ci.yml)
5
+
6
+ Legate is a framework for building AI agents in Ruby with dynamic tool selection, multi-step planning, and session management.
7
+
8
+ It's **batteries-included** — one gem ships the agent runtime, an LLM planner, a web UI, a CLI, MCP support, and an authentication subsystem. That's a deliberate choice: Legate is a framework, not a micro-library, so it bundles the dependencies those pieces need (Sinatra/Puma for the web UI, Thor for the CLI, and so on). If you only want the library, `require 'legate'` loads **just the core** — the web stack is opt-in via `require 'legate/web'` and the CLI via the `legate` executable, so library-only users never load Sinatra, Puma, or Slim.
9
+
10
+ ## Features
11
+
12
+ - **Flexible Agent Architecture** — Create agents with custom tools, models, and capabilities
13
+ - **Dynamic Tool System** — Register and use tools with automatic parameter validation
14
+ - **LLM-Powered Planning** — Agents break down complex tasks into multi-step plans (Gemini by default; pluggable provider adapters)
15
+ - **Session Management** — Track agent interactions with in-memory session state
16
+ - **Multi-Agent Systems** — Sequential, parallel, and loop agent patterns with delegation
17
+ - **MCP Integration** — Model Context Protocol support for external tool servers (configs are trusted input — see [Security model](#security-model))
18
+ - **Web UI** — Visual interface for agent interaction and monitoring (Sinatra + HTMX; a developer tool, **unauthenticated by default** — see [Security model](#security-model))
19
+ - **CLI** — Command-line interface for running agents, managing auth, and AI-powered code generation
20
+ - **Callbacks** — 6 hooks (before/after agent, model, tool) for monitoring, caching, and authorization
21
+ - **HTTP Client Mixin** — Built-in `HttpClient` module for tools that call external APIs
22
+ - **Webhook Support** — Trigger agent tasks via inbound HTTP webhooks
23
+
24
+ ## Installation
25
+
26
+ Add to your Gemfile:
27
+
28
+ ```ruby
29
+ gem 'legate'
30
+ ```
31
+
32
+ Then run:
33
+
34
+ ```bash
35
+ bundle install
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ Set your Gemini API key (without it, planning is disabled and you'll get a clear warning):
41
+
42
+ ```bash
43
+ export GEMINI_API_KEY=your_gemini_api_key_here
44
+ ```
45
+
46
+ Then ask an agent a question in one line:
47
+
48
+ ```ruby
49
+ require 'legate'
50
+
51
+ agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
52
+ a.name :calculator_agent
53
+ a.description 'Does arithmetic with the calculator tool.'
54
+ a.instruction 'Use the calculator to answer math questions.'
55
+ a.use_tool :calculator
56
+ end)
57
+
58
+ puts agent.ask('What is 21 * 2?').answer
59
+ # => 42.0
60
+ ```
61
+
62
+ `ask` starts the agent, runs the task, and returns the final event — call `.answer`
63
+ for the result (or `.success?` / `.error_message`).
64
+
65
+ Agents come with useful tools out of the box — including `http_request` (an
66
+ SSRF-safe, auth-aware HTTP client), `read_webpage` (fetch a page as readable
67
+ text), `current_time`, and `calculator` — so an agent can do real work without
68
+ you writing a tool first. See [built-in tools](public/docs/tools/legate_built_in_tools.md).
69
+
70
+ Prefer a runnable file or the visual interface?
71
+
72
+ ```bash
73
+ bundle exec ruby examples/00_quickstart.rb # the example above, ready to run
74
+ bundle exec legate web start # then open http://localhost:4567
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ ### Environment Variables
80
+
81
+ | Variable | Purpose | Required |
82
+ |----------|---------|----------|
83
+ | `GOOGLE_API_KEY` | Google Gemini API key for LLM planning (`GEMINI_API_KEY` is accepted as an alias) | Yes (for the default Gemini adapter) |
84
+ | `LEGATE_LOG_LEVEL` | `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, `NONE` | No |
85
+ | `RACK_ENV` | `development` or `production` | No |
86
+ | `SESSION_SECRET` | Web UI session cookie secret | Production |
87
+ | `BASIC_AUTH_USER` / `BASIC_AUTH_PASSWORD` | Enable optional HTTP Basic Auth on the web UI | No |
88
+ | `LEGATE_AUTH_ENCRYPTION_KEY` | Encrypts stored credentials at rest (libsodium) | No (recommended in production) |
89
+ | `LEGATE_ALLOW_PRIVATE_TOOL_URLS` | Let the HTTP tools (`http_request` / `read_webpage`) reach private/loopback hosts — **development only** | No |
90
+ | `LEGATE_ALLOW_PRIVATE_AUTH_URLS` | Let auth/credential-test requests reach private hosts — **development only** | No |
91
+
92
+ > The library never reads `.env` on its own. An application opts in by calling `Legate.load_environment` (as the `legate` CLI and the numbered examples do), which loads `.env` and maps `GEMINI_API_KEY` → `GOOGLE_API_KEY`.
93
+
94
+ ### LLM providers
95
+
96
+ Planning goes through a pluggable `Legate::LLM::Adapter`. Gemini is the default; a local **Ollama** adapter ships in the box (no API key, no cost). Select a provider for every agent with a factory:
97
+
98
+ ```ruby
99
+ # Use a local Ollama model instead of Gemini
100
+ Legate::LLM.default_adapter_factory = lambda do |model:, **|
101
+ Legate::LLM::Ollama.new(model: model) # talks to http://localhost:11434
102
+ end
103
+ ```
104
+
105
+ Or inject an adapter per planner via `Legate::Planner.new(agent:, llm_adapter:)`. Implement `Legate::LLM::Adapter` (`available?`, `model_name`, `generate(prompt, json:)`) to add any provider.
106
+
107
+ ## Examples
108
+
109
+ ### Simple Echo Agent
110
+
111
+ ```ruby
112
+ require 'legate'
113
+
114
+ echo_agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
115
+ a.name :simple_echo_agent
116
+ a.description 'A simple agent that can echo messages'
117
+ a.instruction 'You are an echo agent. Repeat the user input exactly.'
118
+ a.use_tool :echo
119
+ end)
120
+
121
+ puts echo_agent.ask('Hello, world!').answer
122
+ ```
123
+
124
+ `ask` is the convenience path. If you need explicit control over sessions and the
125
+ agent lifecycle (e.g. multi-turn conversations, long-lived hosts), use the
126
+ underlying API directly:
127
+
128
+ ```ruby
129
+ agent = Legate::Agent.new(definition: echo_agent_definition)
130
+ service = Legate::SessionService::InMemory.new
131
+ session = service.create_session(app_name: agent.name, user_id: 'example_user')
132
+
133
+ agent.start
134
+ result = agent.run_task(session_id: session.id, user_input: 'Hello, world!', session_service: service)
135
+ puts result.answer
136
+ agent.stop # tears down MCP connections; skip it to keep the agent warm for more asks
137
+ ```
138
+
139
+ ### Multi-Step Planning
140
+
141
+ ```ruby
142
+ require 'legate'
143
+
144
+ random_calc_definition = Legate::AgentDefinition.new.define do |a|
145
+ a.name :random_calculator_agent
146
+ a.description 'An agent that uses random number and calculator tools.'
147
+ a.instruction 'Generate a random number then perform a calculation with it.'
148
+ a.use_tool :random_number
149
+ a.use_tool :calculator
150
+ end
151
+
152
+ agent = Legate::Agent.new(definition: random_calc_definition)
153
+ result = agent.ask('Get a random number between 10 and 20, then multiply it by 3.')
154
+ puts result.answer
155
+ ```
156
+
157
+ See the `examples/` directory for more: multi-tool agents, MCP integration, webhooks, auth, callbacks, and multi-agent workflows.
158
+
159
+ ## Core Concepts
160
+
161
+ ### Agents
162
+
163
+ Agents are defined via `AgentDefinition` and instantiated with `Agent.new`:
164
+
165
+ ```ruby
166
+ my_definition = Legate::AgentDefinition.new.define do |a|
167
+ a.name :my_agent
168
+ a.description 'Description of what the agent does'
169
+ a.instruction 'You are a helpful assistant.' # Optional — defaults from name/description
170
+ a.use_tool :my_tool_name
171
+ a.model_name 'gemini-3.5-flash' # Optional
172
+ end
173
+
174
+ agent = Legate::Agent.new(definition: my_definition)
175
+ ```
176
+
177
+ Only `name` is required. `instruction` is optional — a tool-only agent gets a sensible default derived from its name and description.
178
+
179
+ **Agent types:** `:llm` (default, uses Gemini for planning), `:sequential`, `:parallel`, `:loop`
180
+
181
+ ### Tools
182
+
183
+ Tools inherit from `Legate::Tool` and define metadata via DSL:
184
+
185
+ ```ruby
186
+ class MyCustomTool < Legate::Tool
187
+ tool_description 'Performs a custom action with input.'
188
+
189
+ parameter :input_data, type: :string, required: true,
190
+ description: 'The data needed for the action'
191
+ parameter :optional_flag, type: :boolean, required: false
192
+
193
+ private
194
+
195
+ def perform_execution(params, context)
196
+ input = params[:input_data]
197
+ Legate::ToolResult.success("Action performed on #{input}")
198
+ end
199
+ end
200
+ ```
201
+
202
+ Return a `Legate::ToolResult` (`.success(value)` / `.error(message)` / `.pending(job_id:)`)
203
+ or the equivalent hash (`{ status: :success, result: ... }`) — both work. The typed
204
+ form mirrors the `Event#answer` / `#success?` accessors and avoids hand-built hashes.
205
+
206
+ **Built-in tools:** `:echo`, `:calculator`, `:cat_facts`, `:random_number`, `:delegate_task`, `:check_job_status`
207
+
208
+ **Selecting tools:** `use_tool` takes a registered name (`:echo` or `'echo'`) **or** a `Legate::Tool` subclass — passing the class registers *and* selects it in one step:
209
+
210
+ ```ruby
211
+ a.use_tool MyCustomTool # registers globally + selects it; no separate register_tool call
212
+ a.use_tool :echo # a built-in by name
213
+ ```
214
+
215
+ A typo'd or unknown tool name produces a warning with a "did you mean?" suggestion and the list of available tools (it stays non-fatal, since MCP tools register when the agent connects).
216
+
217
+ **Parameter types:** `:string`, `:integer`, `:float`/`:numeric`, `:boolean`, `:array`, `:hash`
218
+
219
+ **Return values:** a `Legate::ToolResult` (`.success` / `.error` / `.pending`) or the equivalent `{ status: :success, result: ... }` hash; or raise `Legate::ToolError` / `Legate::ToolArgumentError`
220
+
221
+ **Introspection:** `Legate.tools` lists registered tools (name, description, parameters); `legate tool list` / `legate tool info NAME` do the same from the CLI.
222
+
223
+ ### Sessions
224
+
225
+ ```ruby
226
+ session_service = Legate::SessionService::InMemory.new
227
+ session = session_service.create_session(app_name: agent.name, user_id: 'user123')
228
+ ```
229
+
230
+ ### Making HTTP Requests in Tools
231
+
232
+ Include the `HttpClient` mixin for standardized HTTP with error wrapping:
233
+
234
+ ```ruby
235
+ class MyApiTool < Legate::Tool
236
+ include Legate::Tools::Base::HttpClient
237
+
238
+ def initialize(**options)
239
+ super(**options)
240
+ setup_http_client(base_url: 'https://api.example.com/v2/')
241
+ end
242
+
243
+ private
244
+
245
+ def perform_execution(params, context)
246
+ response = http_get("items/#{params[:id]}")
247
+ data = JSON.parse(response.body)
248
+ { status: :success, result: data }
249
+ end
250
+ end
251
+ ```
252
+
253
+ Errors are automatically wrapped into `ToolTimeoutError`, `ToolNetworkError`, `ToolHttpError`, etc.
254
+
255
+ ### Callbacks
256
+
257
+ ```ruby
258
+ agent_definition = Legate::AgentDefinition.new.define do |a|
259
+ a.name :agent_with_callbacks
260
+ a.instruction 'You are a helpful assistant.'
261
+ a.use_tool :echo
262
+
263
+ a.before_agent_callback { |context| puts "Starting: #{context.session_id}" }
264
+ a.after_agent_callback { |context, response| nil }
265
+ a.before_tool_callback { |tool, args, context| nil }
266
+ a.after_tool_callback { |tool, args, context, result| nil }
267
+ end
268
+ ```
269
+
270
+ ### Inbound Webhooks
271
+
272
+ Trigger agent tasks via HTTP webhooks from external systems:
273
+
274
+ - Dynamic agent routing: `POST /webhooks/agents/:agent_name/trigger`
275
+ - Agent-defined validation and payload transformation
276
+ - Asynchronous processing (returns `202 Accepted`)
277
+ - HMAC signature verification support
278
+
279
+ ## CLI
280
+
281
+ ```bash
282
+ # Start the web UI
283
+ bundle exec legate web start
284
+
285
+ # Agent commands
286
+ bundle exec legate agent list
287
+ bundle exec legate agent execute my_agent "task"
288
+ bundle exec legate agent chat my_agent
289
+
290
+ # AI-powered code generation
291
+ bundle exec legate agent ai-generate
292
+ bundle exec legate tool ai-generate
293
+
294
+ # Authentication management
295
+ bundle exec legate auth status
296
+ bundle exec legate auth scheme list
297
+ bundle exec legate auth credential list
298
+ ```
299
+
300
+ ## Security model
301
+
302
+ Legate is a framework you embed in your own application. A few trust boundaries are worth understanding before you deploy it.
303
+
304
+ **The web UI is a developer tool and is unauthenticated by default.** `legate web start` binds an admin-grade interface — it can create agents, run tasks, and edit configuration. It ships **no application login**; the only built-in gate is optional HTTP Basic Auth, enabled by setting `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD`. CSRF protection is on, and production refuses to boot without `SESSION_SECRET`, but those are not a substitute for authentication. **Run the web UI on localhost or a trusted private network. Do not expose it to untrusted users** without putting your own auth in front of it.
305
+
306
+ **MCP server configurations are trusted input — treat them like a `Gemfile` entry.** Configuring an agent's `mcp_servers` means running code you trust:
307
+
308
+ - **`:stdio` servers launch a local subprocess** from the configured `command`/`args`. Anyone who can set `mcp_servers` can run arbitrary local commands — that is what stdio MCP *is*.
309
+ - **`:sse`/remote MCP URLs are not SSRF-restricted.** MCP servers legitimately live on `localhost`/your private network, so Legate intentionally does not block private/loopback/metadata addresses for them.
310
+
311
+ So the real boundary is **who can supply an agent definition**. In code you control, this is a non-issue. The risk only appears if you let untrusted users create/edit agent definitions — which, combined with the unauthenticated web UI above, is why the UI must not be public.
312
+
313
+ **What *is* guarded:** outbound webhook and auth/credential-test requests run through an SSRF guard (`Legate::Auth::UrlGuard`) that refuses loopback/link-local/private/metadata addresses (incl. IPv4-mapped IPv6) and fails closed on resolution errors — and the webhook tool additionally pins the connection to the validated IP to defeat DNS rebinding; inbound webhooks verify HMAC signatures with a constant-time compare; stored credentials are encrypted at rest (libsodium). MCP is deliberately exempt from the SSRF guard for the reason above.
314
+
315
+ To report a vulnerability, see [SECURITY.md](SECURITY.md).
316
+
317
+ ## Known limitations
318
+
319
+ Worth knowing before you build on Legate:
320
+
321
+ - **The web UI is unauthenticated by default** — it's a developer tool. Run it on localhost or a trusted network, or put your own auth in front of it (see [Security model](#security-model)).
322
+ - **State is in-memory by default.** Sessions and the agent/tool registries live in the process — they're lost on restart and not shared across multiple Puma workers. Opt into `SessionService::ActiveRecord` for durable sessions; run a single web process (or add a shared store) for cross-worker consistency.
323
+ - **LLM planning needs an API key**, and plan quality depends on the model. Without `GOOGLE_API_KEY` (or a local Ollama adapter), planning is disabled and tools can only be invoked directly.
324
+ - **Pin a model you've verified.** Hosted model lineups change — older models get retired and start returning 404s. The default tracks a current Gemini model, but if you set `model_name` explicitly, confirm it's still available for your key.
325
+ - **`read_webpage` is best-effort text extraction, not a browser.** It strips HTML without running JavaScript, so JS-rendered/SPA pages yield little text and complex markup may extract imperfectly.
326
+ - **`current_time` has no named-timezone support** (e.g. `America/New_York`) — only `UTC`, `local`, or a fixed offset like `+09:00` — to avoid a timezone-database dependency.
327
+ - **No built-in rate limiting or cost controls** on LLM calls — budget and throttle at your application layer.
328
+ - **MCP server configs are trusted input** (stdio launches local subprocesses; remote MCP URLs are intentionally not SSRF-restricted) — see [Security model](#security-model).
329
+
330
+ ## Development
331
+
332
+ ```bash
333
+ bundle install
334
+ bundle exec rspec # Run the test suite
335
+ bundle exec rubocop # Lint
336
+ bundle exec legate web start # Start dev server
337
+ ```
338
+
339
+ ## Contributing
340
+
341
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/tweibley/legate](https://github.com/tweibley/legate). See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
342
+
343
+ ## License
344
+
345
+ Legate is released under the [MIT License](LICENSE).
data/bin/legate ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Suppress logs when stdin is piped (for clean stdout output in pipe workflows)
5
+ # or when --json flag is used (for clean machine-readable output)
6
+ # This must happen BEFORE requiring Legate to prevent log initialization noise
7
+ ENV['LEGATE_LOG_LEVEL'] ||= 'SILENT' if !$stdin.tty? || ARGV.include?('--json')
8
+
9
+ require_relative '../lib/legate' # Load Legate library first
10
+ Legate.load_environment # Handle Bundler, Dotenv, etc.
11
+ require_relative '../lib/legate/cli' # Use require_relative for CLI component
12
+
13
+ Legate::CLI::Main.start(ARGV)
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # If running from project root: bundle exec ruby examples/00_quickstart.rb
5
+ require_relative '../lib/legate'
6
+
7
+ # Load .env and map GEMINI_API_KEY -> GOOGLE_API_KEY (as the `legate` CLI does).
8
+ # The library never reads .env on its own; an application must opt in.
9
+ Legate.load_environment
10
+
11
+ puts '--- Quickstart: agent.ask in one line ---'
12
+
13
+ # Define an agent. Only `name` is required — `instruction` is optional (it
14
+ # defaults from the name/description), and `use_tool` selects a tool (here a
15
+ # built-in; you can also pass a Legate::Tool subclass to register + select it).
16
+ agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
17
+ a.name :quickstart_agent
18
+ a.description 'Repeats what the user says.'
19
+ a.use_tool :echo
20
+ end)
21
+
22
+ # `ask` is the convenience path: it starts the agent, creates a session, runs
23
+ # the task, and returns the final event. No start/create_session/stop dance.
24
+ event = agent.ask('Echo this back: Hello, Legate!')
25
+
26
+ # Read the result off the event — no reaching into event.content.
27
+ puts "Answer: #{event.answer}"
28
+ puts "Success: #{event.success?}"
29
+ puts "Error: #{event.error_message}" if event.error?
30
+
31
+ puts "\n--- Watch progress live (optional block) ---"
32
+ # Pass a block to stream each lifecycle event as it happens (user message, each
33
+ # tool request/result, final answer) — handy for a CLI spinner or a UI.
34
+ agent.ask('Repeat: streaming works') do |e|
35
+ puts " #{e.role}#{e.tool_name ? " (#{e.tool_name})" : ''}"
36
+ end
37
+
38
+ # `ask` makes a fresh session each call. To keep context across turns, create a
39
+ # session once and pass its id to each ask:
40
+ #
41
+ # session = agent.session_service.create_session(app_name: agent.name.to_s, user_id: 'demo')
42
+ # agent.ask('first question', session_id: session.id)
43
+ # agent.ask('a follow-up', session_id: session.id)
44
+ #
45
+ # See examples 02+ for multi-tool planning, custom tools, sessions, and more.
46
+
47
+ # `ask` does not auto-stop (it stays warm for more questions). Stop a long-lived
48
+ # agent when you're done to release any MCP connections.
49
+ agent.stop
50
+
51
+ puts "\n--- Quickstart Complete ---"
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # If running from project root: bundle exec ruby examples/01_simple_agent.rb
5
+ require_relative '../lib/legate'
6
+
7
+ # Load .env and map GEMINI_API_KEY -> GOOGLE_API_KEY (as the `legate` CLI does).
8
+ # The library never reads .env on its own; an application must opt in.
9
+ Legate.load_environment
10
+
11
+ puts '--- Simple Echo Agent Example (Session-Based) ---'
12
+
13
+ # 1. --- Agent Definition Setup ---
14
+ simple_echo_definition = Legate::AgentDefinition.new.define do |a|
15
+ a.name :simple_echo_agent # Agent name as a Symbol
16
+ a.description 'A simple agent that can echo messages'
17
+ a.instruction 'You are an echo agent. Your task is to repeat the user\'s input exactly.' # Instruction is required
18
+ a.use_tool :echo # Tool name as a Symbol, Legate::Tools::Echo should be globally discoverable
19
+ end
20
+
21
+ # Optional: Register with GlobalDefinitionRegistry if this definition needs to be found by name later
22
+ # Legate::GlobalDefinitionRegistry.register(simple_echo_definition)
23
+
24
+ # Ensure the tool is globally available (Legate::Tools::Echo should be by default)
25
+ # If Legate::Tools::Echo wasn't automatically registered, you might need:
26
+ # Legate::GlobalToolManager.register_tool(Legate::Tools::Echo)
27
+
28
+ # 2. --- Agent Instantiation ---
29
+ # Initialize the agent with the definition object
30
+ agent = Legate::Agent.new(definition: simple_echo_definition)
31
+
32
+ puts "\nAgent '#{agent.name}' created with tool: #{agent.tools.first&.name || 'none'}"
33
+
34
+ # 3. --- Start Agent ---
35
+ agent.start
36
+ puts "Agent started. Running: #{agent.running?}"
37
+
38
+ # 4. --- Session Setup ---
39
+ session_service = Legate::SessionService::InMemory.new
40
+ session = session_service.create_session(app_name: agent.name, user_id: 'example_user')
41
+ session_id = session.id
42
+ puts "\nCreated session: #{session_id}"
43
+
44
+ # 5. --- Task Execution ---
45
+ task = 'Hello, world!'
46
+ puts "\nExecuting task: '#{task}'"
47
+
48
+ begin
49
+ result_event = agent.run_task(
50
+ session_id: session_id,
51
+ user_input: task,
52
+ session_service: session_service
53
+ )
54
+ puts "Raw result event: #{result_event.inspect}"
55
+
56
+ # --- Updated Result Handling ---
57
+ puts "\nInterpreted Result:"
58
+ if result_event.is_a?(Legate::Event)
59
+ puts ' Status: Event Received'
60
+ puts " Role: #{result_event.role}"
61
+
62
+ content = result_event.content
63
+ if content.is_a?(Array)
64
+ puts ' Content Type: Multi-Step Plan Results'
65
+ any_errors = false
66
+ content.each_with_index do |step_hash, index|
67
+ print " Step #{index + 1}: "
68
+ if step_hash.is_a?(Hash) && step_hash[:status] == :success
69
+ puts "Success | Result: #{step_hash[:result]}"
70
+ elsif step_hash.is_a?(Hash) && step_hash[:status] == :error
71
+ puts "Error | Message: #{step_hash[:error_message]}"
72
+ any_errors = true
73
+ else
74
+ puts "Unknown Format | Data: #{step_hash.inspect}"
75
+ any_errors = true # Treat unexpected format as problematic
76
+ end
77
+ end
78
+ puts " Overall Plan Status: #{any_errors ? 'Completed with errors' : 'Completed successfully'}"
79
+ elsif content.is_a?(Hash) && content.key?(:status)
80
+ # Single step plan or a planning error
81
+ if content[:status] == :success
82
+ puts ' Content Type: Single Step Success'
83
+ puts " Result: #{content[:result]}"
84
+ else # status == :error or other
85
+ puts ' Content Type: Error (or Single Step Error)'
86
+ puts " Message: #{content[:error_message]}"
87
+ end
88
+ else
89
+ puts ' Content Type: String or Other Format'
90
+ puts " Content: #{content}"
91
+ end
92
+ else
93
+ puts ' Status: Unknown (Unexpected Format)'
94
+ puts " Raw Data: #{result_event.inspect}"
95
+ end
96
+ # --- End Updated Result Handling ---
97
+ rescue StandardError => e
98
+ puts "\nError executing task: #{e.class} - #{e.message}"
99
+ puts e.backtrace.first(5).join("\n")
100
+ end
101
+
102
+ # 6. --- Stop Agent ---
103
+ agent.stop
104
+ puts "\nAgent stopped. Running: #{agent.running?}"
105
+ puts "\n--- Example Complete ---"