rcrewai 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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +108 -0
  3. data/LICENSE +21 -0
  4. data/README.md +328 -0
  5. data/Rakefile +130 -0
  6. data/bin/rcrewai +7 -0
  7. data/docs/_config.yml +59 -0
  8. data/docs/_layouts/api.html +16 -0
  9. data/docs/_layouts/default.html +78 -0
  10. data/docs/_layouts/example.html +24 -0
  11. data/docs/_layouts/tutorial.html +33 -0
  12. data/docs/api/configuration.md +327 -0
  13. data/docs/api/crew.md +345 -0
  14. data/docs/api/index.md +41 -0
  15. data/docs/api/tools.md +412 -0
  16. data/docs/assets/css/style.css +416 -0
  17. data/docs/examples/human-in-the-loop.md +382 -0
  18. data/docs/examples/index.md +78 -0
  19. data/docs/examples/production-ready-crew.md +485 -0
  20. data/docs/examples/simple-research-crew.md +297 -0
  21. data/docs/index.md +353 -0
  22. data/docs/tutorials/getting-started.md +341 -0
  23. data/examples/async_execution_example.rb +294 -0
  24. data/examples/hierarchical_crew_example.rb +193 -0
  25. data/examples/human_in_the_loop_example.rb +233 -0
  26. data/lib/rcrewai/agent.rb +636 -0
  27. data/lib/rcrewai/async_executor.rb +248 -0
  28. data/lib/rcrewai/cli.rb +39 -0
  29. data/lib/rcrewai/configuration.rb +100 -0
  30. data/lib/rcrewai/crew.rb +292 -0
  31. data/lib/rcrewai/human_input.rb +520 -0
  32. data/lib/rcrewai/llm_client.rb +41 -0
  33. data/lib/rcrewai/llm_clients/anthropic.rb +127 -0
  34. data/lib/rcrewai/llm_clients/azure.rb +158 -0
  35. data/lib/rcrewai/llm_clients/base.rb +82 -0
  36. data/lib/rcrewai/llm_clients/google.rb +158 -0
  37. data/lib/rcrewai/llm_clients/ollama.rb +199 -0
  38. data/lib/rcrewai/llm_clients/openai.rb +124 -0
  39. data/lib/rcrewai/memory.rb +194 -0
  40. data/lib/rcrewai/process.rb +421 -0
  41. data/lib/rcrewai/task.rb +376 -0
  42. data/lib/rcrewai/tools/base.rb +82 -0
  43. data/lib/rcrewai/tools/code_executor.rb +333 -0
  44. data/lib/rcrewai/tools/email_sender.rb +210 -0
  45. data/lib/rcrewai/tools/file_reader.rb +111 -0
  46. data/lib/rcrewai/tools/file_writer.rb +115 -0
  47. data/lib/rcrewai/tools/pdf_processor.rb +342 -0
  48. data/lib/rcrewai/tools/sql_database.rb +226 -0
  49. data/lib/rcrewai/tools/web_search.rb +131 -0
  50. data/lib/rcrewai/version.rb +5 -0
  51. data/lib/rcrewai.rb +36 -0
  52. data/rcrewai.gemspec +54 -0
  53. metadata +365 -0
@@ -0,0 +1,636 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require_relative 'llm_client'
5
+ require_relative 'memory'
6
+ require_relative 'tools/base'
7
+ require_relative 'human_input'
8
+
9
+ module RCrewAI
10
+ class Agent
11
+ include HumanInteractionExtensions
12
+ attr_reader :name, :role, :goal, :backstory, :tools, :memory, :llm_client
13
+ attr_accessor :verbose, :allow_delegation, :max_iterations, :max_execution_time, :manager
14
+
15
+ def initialize(name:, role:, goal:, backstory: nil, tools: [], **options)
16
+ @name = name
17
+ @role = role
18
+ @goal = goal
19
+ @backstory = backstory
20
+ @tools = tools
21
+ @verbose = options.fetch(:verbose, false)
22
+ @allow_delegation = options.fetch(:allow_delegation, false)
23
+ @manager = options.fetch(:manager, false) # New manager flag
24
+ @max_iterations = options.fetch(:max_iterations, 10)
25
+ @max_execution_time = options.fetch(:max_execution_time, 300) # 5 minutes
26
+ @human_input_enabled = options.fetch(:human_input, false)
27
+ @require_approval_for_tools = options.fetch(:require_approval_for_tools, false)
28
+ @require_approval_for_final_answer = options.fetch(:require_approval_for_final_answer, false)
29
+ @logger = Logger.new($stdout)
30
+ @logger.level = verbose ? Logger::DEBUG : Logger::INFO
31
+ @memory = Memory.new
32
+ @llm_client = LLMClient.for_provider
33
+ @subordinates = [] # For manager agents
34
+ end
35
+
36
+ def execute_task(task)
37
+ @logger.info "Agent #{name} starting task: #{task.name}"
38
+ start_time = Time.now
39
+
40
+ begin
41
+ # Build context for the agent
42
+ context = build_context(task)
43
+
44
+ # Execute task with reasoning loop
45
+ result = reasoning_loop(task, context)
46
+
47
+ execution_time = Time.now - start_time
48
+ @logger.info "Task completed in #{execution_time.round(2)}s"
49
+
50
+ # Store in memory
51
+ memory.add_execution(task, result, execution_time)
52
+
53
+ task.result = result
54
+ result
55
+
56
+ rescue => e
57
+ @logger.error "Task execution failed: #{e.message}"
58
+ task.result = "Task failed: #{e.message}"
59
+ raise AgentError, "Agent #{name} failed to execute task: #{e.message}"
60
+ end
61
+ end
62
+
63
+ def available_tools_description
64
+ return "No tools available." if tools.empty?
65
+
66
+ tools.map do |tool|
67
+ "- #{tool.name}: #{tool.description}"
68
+ end.join("\n")
69
+ end
70
+
71
+ def use_tool(tool_name, **params)
72
+ tool = tools.find { |t| t.name == tool_name || t.class.name.split('::').last.downcase == tool_name.downcase }
73
+ raise ToolNotFoundError, "Tool '#{tool_name}' not found" unless tool
74
+
75
+ # Request human approval for tool usage if required
76
+ if @require_approval_for_tools && @human_input_enabled
77
+ approval_result = request_tool_approval(tool_name, params)
78
+ unless approval_result[:approved]
79
+ @logger.info "Tool usage rejected by human: #{tool_name}"
80
+ return "Tool usage was rejected by human reviewer: #{approval_result[:reason]}"
81
+ end
82
+ end
83
+
84
+ @logger.debug "Using tool: #{tool_name} with params: #{params}"
85
+
86
+ begin
87
+ result = tool.execute(**params)
88
+
89
+ # Store tool usage in memory
90
+ memory.add_tool_usage(tool_name, params, result)
91
+
92
+ result
93
+ rescue => e
94
+ @logger.error "Tool execution failed: #{e.message}"
95
+
96
+ # Offer human intervention if tool fails and human input is enabled
97
+ if @human_input_enabled
98
+ handle_tool_failure(tool_name, params, e)
99
+ else
100
+ raise e
101
+ end
102
+ end
103
+ end
104
+
105
+ # Manager-specific methods
106
+ def is_manager?
107
+ @manager
108
+ end
109
+
110
+ def add_subordinate(agent)
111
+ return unless is_manager?
112
+ @subordinates << agent unless @subordinates.include?(agent)
113
+ end
114
+
115
+ def subordinates
116
+ @subordinates
117
+ end
118
+
119
+ def delegate_task(task, target_agent)
120
+ return unless is_manager?
121
+ return unless @subordinates.include?(target_agent) || allow_delegation
122
+
123
+ @logger.info "Manager #{name} delegating task '#{task.name}' to #{target_agent.name}"
124
+
125
+ # Create delegation context
126
+ delegation_prompt = build_delegation_prompt(task, target_agent)
127
+
128
+ # Use LLM to create proper delegation
129
+ response = llm_client.chat(
130
+ messages: [{ role: 'user', content: delegation_prompt }],
131
+ temperature: 0.2,
132
+ max_tokens: 1000
133
+ )
134
+
135
+ delegation_instructions = response[:content]
136
+ @logger.debug "Delegation instructions: #{delegation_instructions}"
137
+
138
+ # Execute delegated task
139
+ target_agent.execute_delegated_task(task, delegation_instructions, self)
140
+ end
141
+
142
+ def execute_delegated_task(task, delegation_instructions, manager_agent)
143
+ @logger.info "Receiving delegation from manager #{manager_agent.name}"
144
+ @logger.debug "Delegation instructions: #{delegation_instructions}"
145
+
146
+ # Store delegation context in task
147
+ original_description = task.description
148
+ enhanced_description = "#{original_description}\n\nDelegation Instructions from #{manager_agent.name}:\n#{delegation_instructions}"
149
+
150
+ # Temporarily modify task
151
+ task.instance_variable_set(:@description, enhanced_description)
152
+ task.instance_variable_set(:@manager, manager_agent)
153
+
154
+ begin
155
+ result = execute_task(task)
156
+
157
+ # Report back to manager
158
+ report_to_manager(task, result, manager_agent)
159
+
160
+ result
161
+ ensure
162
+ # Restore original task description
163
+ task.instance_variable_set(:@description, original_description)
164
+ end
165
+ end
166
+
167
+ # Human input methods (public)
168
+ def enable_human_input(**options)
169
+ @human_input_enabled = true
170
+ @require_approval_for_tools = options.fetch(:require_approval_for_tools, false)
171
+ @require_approval_for_final_answer = options.fetch(:require_approval_for_final_answer, false)
172
+ @logger.info "Human input enabled for agent #{name}"
173
+ end
174
+
175
+ def disable_human_input
176
+ @human_input_enabled = false
177
+ @require_approval_for_tools = false
178
+ @require_approval_for_final_answer = false
179
+ @logger.info "Human input disabled for agent #{name}"
180
+ end
181
+
182
+ def human_input_enabled?
183
+ @human_input_enabled
184
+ end
185
+
186
+ private
187
+
188
+ def build_context(task)
189
+ context = {
190
+ agent_role: role,
191
+ agent_goal: goal,
192
+ agent_backstory: backstory,
193
+ task_description: task.description,
194
+ task_expected_output: task.expected_output,
195
+ available_tools: available_tools_description,
196
+ previous_executions: memory.relevant_executions(task),
197
+ context_data: task.context_data
198
+ }
199
+
200
+ # Add delegation capabilities if allowed
201
+ if allow_delegation
202
+ context[:delegation_note] = "You can delegate subtasks to other agents if needed."
203
+ end
204
+
205
+ context
206
+ end
207
+
208
+ def reasoning_loop(task, context)
209
+ iteration = 0
210
+ start_time = Time.now
211
+
212
+ loop do
213
+ iteration += 1
214
+ current_time = Time.now
215
+
216
+ # Check limits
217
+ if iteration > max_iterations
218
+ @logger.warn "Max iterations (#{max_iterations}) reached"
219
+ break
220
+ end
221
+
222
+ if current_time - start_time > max_execution_time
223
+ @logger.warn "Max execution time (#{max_execution_time}s) reached"
224
+ break
225
+ end
226
+
227
+ # Human review of reasoning at key points
228
+ if @human_input_enabled && (iteration == 1 || iteration % 3 == 0)
229
+ review_result = request_reasoning_review(task, context, iteration)
230
+ if review_result && review_result[:feedback]
231
+ context[:human_guidance] = review_result[:feedback]
232
+ @logger.info "Incorporating human guidance into reasoning"
233
+ end
234
+ end
235
+
236
+ # Generate reasoning prompt
237
+ prompt = build_reasoning_prompt(context, iteration)
238
+
239
+ # Get LLM response
240
+ @logger.debug "Iteration #{iteration}: Sending prompt to LLM"
241
+ response = llm_client.chat(
242
+ messages: [{ role: 'user', content: prompt }],
243
+ temperature: 0.1,
244
+ max_tokens: 2000
245
+ )
246
+
247
+ reasoning = response[:content]
248
+ @logger.debug "LLM Response: #{reasoning[0..200]}..." if verbose
249
+
250
+ # Parse and execute actions
251
+ action_result = parse_and_execute_actions(reasoning, task)
252
+
253
+ # Check if task is complete
254
+ if task_complete?(reasoning, action_result)
255
+ final_result = extract_final_result(reasoning, action_result)
256
+
257
+ # Human approval of final result if required
258
+ if @require_approval_for_final_answer && @human_input_enabled
259
+ final_result = request_final_answer_approval(final_result)
260
+ end
261
+
262
+ @logger.info "Task completed successfully in #{iteration} iterations"
263
+ return final_result
264
+ end
265
+
266
+ # Update context with new information
267
+ context[:previous_reasoning] = reasoning
268
+ context[:previous_result] = action_result
269
+ context[:iteration] = iteration
270
+ end
271
+
272
+ # If we exit the loop without completion, return best effort result
273
+ final_result = extract_final_result(context[:previous_reasoning], context[:previous_result]) ||
274
+ "Task execution reached limits without clear completion"
275
+
276
+ # Human approval even for incomplete results if required
277
+ if @require_approval_for_final_answer && @human_input_enabled
278
+ final_result = request_final_answer_approval(final_result)
279
+ end
280
+
281
+ final_result
282
+ end
283
+
284
+ def build_reasoning_prompt(context, iteration)
285
+ prompt = <<~PROMPT
286
+ You are #{context[:agent_role]}.
287
+
288
+ Your goal: #{context[:agent_goal]}
289
+
290
+ Background: #{context[:agent_backstory]}
291
+
292
+ Current Task: #{context[:task_description]}
293
+ Expected Output: #{context[:task_expected_output]}
294
+
295
+ Available Tools:
296
+ #{context[:available_tools]}
297
+
298
+ #{context[:delegation_note] if context[:delegation_note]}
299
+
300
+ #{build_context_section(context)}
301
+
302
+ This is iteration #{iteration}. Think step by step about how to approach this task.
303
+
304
+ You can:
305
+ 1. Use tools by writing: USE_TOOL[tool_name](param1=value1, param2=value2)
306
+ 2. Provide your final answer when ready: FINAL_ANSWER[your complete response]
307
+ 3. Continue reasoning if you need more information
308
+
309
+ What is your next step?
310
+ PROMPT
311
+
312
+ prompt
313
+ end
314
+
315
+ def build_context_section(context)
316
+ sections = []
317
+
318
+ if context[:context_data] && !context[:context_data].empty?
319
+ sections << "Additional Context:\n#{context[:context_data]}"
320
+ end
321
+
322
+ if context[:previous_executions] && !context[:previous_executions].empty?
323
+ sections << "Previous Similar Tasks:\n#{context[:previous_executions]}"
324
+ end
325
+
326
+ if context[:human_guidance]
327
+ sections << "Human Guidance:\n#{context[:human_guidance]}"
328
+ end
329
+
330
+ if context[:previous_reasoning]
331
+ sections << "Previous Reasoning:\n#{context[:previous_reasoning]}"
332
+ end
333
+
334
+ if context[:previous_result]
335
+ sections << "Previous Action Result:\n#{context[:previous_result]}"
336
+ end
337
+
338
+ sections.join("\n\n")
339
+ end
340
+
341
+ def parse_and_execute_actions(reasoning, task)
342
+ results = []
343
+
344
+ # Look for tool usage patterns
345
+ tool_matches = reasoning.scan(/USE_TOOL\[(\w+)\]\(([^)]*)\)/)
346
+ tool_matches.each do |tool_name, params_str|
347
+ begin
348
+ params = parse_tool_params(params_str)
349
+ result = use_tool(tool_name, **params)
350
+ results << "Tool #{tool_name} result: #{result}"
351
+ rescue => e
352
+ results << "Tool #{tool_name} failed: #{e.message}"
353
+ @logger.error "Tool execution failed: #{e.message}"
354
+ end
355
+ end
356
+
357
+ results.join("\n")
358
+ end
359
+
360
+ def parse_tool_params(params_str)
361
+ params = {}
362
+ return params if params_str.strip.empty?
363
+
364
+ param_pairs = params_str.split(',').map(&:strip)
365
+ param_pairs.each do |pair|
366
+ key, value = pair.split('=', 2).map(&:strip)
367
+ if key && value
368
+ # Remove quotes if present
369
+ value = value.gsub(/^["']|["']$/, '')
370
+ params[key.to_sym] = value
371
+ end
372
+ end
373
+
374
+ params
375
+ end
376
+
377
+ def task_complete?(reasoning, action_result)
378
+ reasoning.include?('FINAL_ANSWER[') ||
379
+ reasoning.downcase.include?('task complete') ||
380
+ reasoning.downcase.include?('finished')
381
+ end
382
+
383
+ def extract_final_result(reasoning, action_result)
384
+ # Look for FINAL_ANSWER pattern
385
+ if match = reasoning.match(/FINAL_ANSWER\[(.*?)\]$/m)
386
+ return match[1].strip
387
+ end
388
+
389
+ # Otherwise try to extract meaningful result from reasoning
390
+ lines = reasoning.split("\n").map(&:strip).reject(&:empty?)
391
+ final_lines = lines.last(3).join(" ")
392
+
393
+ return final_lines if final_lines.length > 20
394
+
395
+ # Fallback to action result
396
+ action_result
397
+ end
398
+
399
+ # Manager-specific private methods
400
+ def build_delegation_prompt(task, target_agent)
401
+ <<~PROMPT
402
+ You are #{name}, a #{role}.
403
+
404
+ You need to delegate the following task to #{target_agent.name} (#{target_agent.role}):
405
+
406
+ Task: #{task.name}
407
+ Description: #{task.description}
408
+ Expected Output: #{task.expected_output || 'Not specified'}
409
+
410
+ Target Agent Profile:
411
+ - Role: #{target_agent.role}
412
+ - Goal: #{target_agent.goal}
413
+ - Available Tools: #{target_agent.available_tools_description}
414
+
415
+ Create clear, specific delegation instructions that:
416
+ 1. Explain why this agent is the right choice for this task
417
+ 2. Provide any additional context or requirements
418
+ 3. Set clear expectations for deliverables
419
+ 4. Include any coordination notes with other team members
420
+
421
+ Keep instructions concise but comprehensive.
422
+ PROMPT
423
+ end
424
+
425
+ def report_to_manager(task, result, manager_agent)
426
+ @logger.info "Reporting task completion to manager #{manager_agent.name}"
427
+
428
+ # Store the delegation result in manager's memory
429
+ manager_agent.memory.add_execution(
430
+ task,
431
+ "Delegated to #{name}: #{result}",
432
+ task.execution_time || 0
433
+ )
434
+
435
+ # Could enhance with formal reporting mechanism
436
+ end
437
+
438
+ # Human interaction methods
439
+ def request_tool_approval(tool_name, params)
440
+ message = "Agent #{name} wants to use tool '#{tool_name}'"
441
+ context = "Parameters: #{params.inspect}"
442
+ consequences = "This will execute the #{tool_name} tool with the specified parameters."
443
+
444
+ request_human_approval(message,
445
+ context: context,
446
+ consequences: consequences,
447
+ timeout: 60
448
+ )
449
+ end
450
+
451
+ def handle_tool_failure(tool_name, params, error)
452
+ @logger.warn "Requesting human intervention for tool failure"
453
+
454
+ choices = [
455
+ "Retry with same parameters",
456
+ "Retry with different parameters",
457
+ "Skip this tool and continue",
458
+ "Abort task execution"
459
+ ]
460
+
461
+ choice_result = request_human_choice(
462
+ "Tool '#{tool_name}' failed with error: #{error.message}. How should I proceed?",
463
+ choices,
464
+ timeout: 120
465
+ )
466
+
467
+ case choice_result[:choice_index]
468
+ when 0
469
+ # Retry with same parameters
470
+ @logger.info "Human requested retry with same parameters"
471
+ tool = tools.find { |t| t.name == tool_name }
472
+ tool.execute(**params)
473
+ when 1
474
+ # Retry with different parameters
475
+ new_params_result = request_human_input(
476
+ "Please provide new parameters for #{tool_name} (JSON format):",
477
+ type: :json,
478
+ help_text: "Enter parameters as JSON, e.g. {\"param1\": \"value1\"}"
479
+ )
480
+
481
+ if new_params_result[:valid]
482
+ @logger.info "Human provided new parameters, retrying tool"
483
+ tool = tools.find { |t| t.name == tool_name }
484
+ tool.execute(**new_params_result[:processed_input])
485
+ else
486
+ "Invalid parameters provided: #{new_params_result[:reason]}"
487
+ end
488
+ when 2
489
+ # Skip tool
490
+ @logger.info "Human requested to skip failed tool"
491
+ "Tool execution skipped by human intervention"
492
+ else
493
+ # Abort
494
+ @logger.error "Human requested task abortion due to tool failure"
495
+ raise AgentError, "Task aborted by human due to tool failure: #{error.message}"
496
+ end
497
+ end
498
+
499
+ def request_final_answer_approval(proposed_answer)
500
+ return proposed_answer unless @require_approval_for_final_answer && @human_input_enabled
501
+
502
+ review_result = request_human_review(
503
+ proposed_answer,
504
+ review_criteria: ["Accuracy", "Completeness", "Clarity", "Relevance"],
505
+ timeout: 180
506
+ )
507
+
508
+ if review_result[:approved]
509
+ @logger.info "Final answer approved by human"
510
+ proposed_answer
511
+ else
512
+ @logger.info "Human provided feedback on final answer"
513
+
514
+ if review_result[:suggested_changes].any?
515
+ @logger.info "Suggested changes: #{review_result[:suggested_changes].join('; ')}"
516
+
517
+ # Ask human what to do with the feedback
518
+ choice_result = request_human_choice(
519
+ "How should I handle your feedback?",
520
+ [
521
+ "Revise the answer based on feedback",
522
+ "Use the answer as-is despite feedback",
523
+ "Let me provide a completely new answer"
524
+ ]
525
+ )
526
+
527
+ case choice_result[:choice_index]
528
+ when 0
529
+ # Revise based on feedback
530
+ revision_context = "Original answer: #{proposed_answer}\n\nFeedback: #{review_result[:feedback]}"
531
+ revise_answer_with_feedback(revision_context)
532
+ when 1
533
+ # Use as-is
534
+ proposed_answer
535
+ when 2
536
+ # Get new answer from human
537
+ new_answer_result = request_human_input(
538
+ "Please provide the final answer:",
539
+ help_text: "Provide the complete answer for this task"
540
+ )
541
+ new_answer_result[:input] || proposed_answer
542
+ else
543
+ proposed_answer
544
+ end
545
+ else
546
+ # Generic feedback without specific suggestions
547
+ revise_answer_with_feedback("Original answer: #{proposed_answer}\n\nFeedback: #{review_result[:feedback]}")
548
+ end
549
+ end
550
+ end
551
+
552
+ def revise_answer_with_feedback(feedback_context)
553
+ @logger.info "Revising answer based on human feedback"
554
+
555
+ revision_prompt = <<~PROMPT
556
+ You are #{name}, a #{role}.
557
+
558
+ You need to revise your previous answer based on human feedback.
559
+
560
+ #{feedback_context}
561
+
562
+ Please provide a revised answer that addresses the feedback while maintaining accuracy and completeness.
563
+
564
+ Revised answer:
565
+ PROMPT
566
+
567
+ response = llm_client.chat(
568
+ messages: [{ role: 'user', content: revision_prompt }],
569
+ temperature: 0.2,
570
+ max_tokens: 1000
571
+ )
572
+
573
+ revised_answer = response[:content]
574
+ @logger.debug "Revised answer based on feedback: #{revised_answer[0..100]}..."
575
+
576
+ revised_answer
577
+ end
578
+
579
+ def request_reasoning_review(task, context, iteration)
580
+ return nil unless @human_input_enabled
581
+
582
+ review_content = <<~CONTENT
583
+ Task: #{task.name}
584
+ Description: #{task.description}
585
+
586
+ Current Iteration: #{iteration}
587
+
588
+ Agent Analysis:
589
+ - Role: #{role}
590
+ - Current Progress: #{context[:previous_result] || 'Starting task'}
591
+ - Previous Reasoning: #{context[:previous_reasoning] || 'No previous reasoning'}
592
+
593
+ The agent is about to continue reasoning for this task.
594
+ CONTENT
595
+
596
+ request_human_review(
597
+ review_content,
598
+ review_criteria: ["Task approach", "Progress assessment", "Strategic guidance"],
599
+ timeout: 30,
600
+ optional: true
601
+ )
602
+ rescue => e
603
+ @logger.warn "Failed to get human reasoning review: #{e.message}"
604
+ nil
605
+ end
606
+
607
+ class CLI < Thor
608
+ desc "new NAME", "Create a new agent"
609
+ option :role, type: :string, required: true
610
+ option :goal, type: :string, required: true
611
+ option :backstory, type: :string
612
+ option :verbose, type: :boolean, default: false
613
+ def new(name)
614
+ agent = Agent.new(
615
+ name: name,
616
+ role: options[:role],
617
+ goal: options[:goal],
618
+ backstory: options[:backstory],
619
+ verbose: options[:verbose]
620
+ )
621
+ puts "Agent '#{name}' created with role: #{options[:role]}"
622
+ end
623
+
624
+ desc "list", "List all agents"
625
+ def list
626
+ puts "Available agents:"
627
+ puts " - researcher (Role: Research Specialist)"
628
+ puts " - writer (Role: Content Writer)"
629
+ puts " - analyst (Role: Data Analyst)"
630
+ end
631
+ end
632
+ end
633
+
634
+ class AgentError < Error; end
635
+ class ToolNotFoundError < AgentError; end
636
+ end