agentf 0.4.6 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a6b455ea4e8d08419665c731414589a39173932f0bdf70788b76cfbcc7c0380
4
- data.tar.gz: 4552007bea3fbb27e4940f09542f76e557bdac18f06391dfcb7db6914adc9017
3
+ metadata.gz: 6072a9a176fba8e2376b10cc125d78d450057c90c4aa5d95158797429504d1f5
4
+ data.tar.gz: f66dc58d0030e1ba289d3f7046811190b9ae818aca61021729c9da12d19e710c
5
5
  SHA512:
6
- metadata.gz: 37efe8cb9847156d5d0e93ee981c108fddab0940eab62fe60bb609e6fc68f799c14fd84f08214263ec4e98d05b617b5be7cd8d035d8a9352d7c639b4737947bb
7
- data.tar.gz: c26f29b7059e1997f62b1a9b25c628c6ff25832309e8766ba2d076ebdf8333ca7ac3206145ae4e05f623c84aa6771c316970b5377e0b48de2980dff3e5c077df
6
+ metadata.gz: '0397d2603cc060239303773b370f14cc9585b94b4ba5109207ac7f705b18aec244f83443478c15c1ed40be4650c20f9f13d669f224dfc916374e924da4ab6cb1'
7
+ data.tar.gz: a8f7562d3bc1f8bd43897b4692c18a4096509d2c4ce413eaa1c992c61c2a7fada7c0c52228c543d62387c9a56c37672819f1becfd39240bc83066491947678f8
@@ -78,6 +78,10 @@ module Agentf
78
78
 
79
79
  { "subtasks" => subtasks, "context" => context }
80
80
  end
81
+
82
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
83
+ plan_task(task)
84
+ end
81
85
  end
82
86
  end
83
87
  end
@@ -61,7 +61,15 @@ module Agentf
61
61
  )
62
62
  end
63
63
 
64
+ # Unified execution entrypoint for all agents. Concrete agents must
65
+ # implement `execute(task:, context:, agents:, commands:, logger:)`.
66
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
67
+ raise NotImplementedError, "#{self.class} must implement #execute"
68
+ end
69
+
64
70
  def log(message)
71
+ return if ENV["AGENTF_SUPPRESS_AGENT_LOGS"] == "true"
72
+
65
73
  puts "\n[#{@name}] #{message}"
66
74
  end
67
75
 
@@ -83,8 +91,28 @@ module Agentf
83
91
  result: result
84
92
  )
85
93
 
86
- result
94
+ result
95
+ end
96
+
97
+ # Helper to centralize memory write confirmation handling.
98
+ # Yields a block that performs the memory write. If the memory layer
99
+ # requires confirmation (ask_first policy) a structured hash is
100
+ # returned with confirmation details so agents can merge that into
101
+ # their own return payloads or let the orchestrator handle prompting.
102
+ def safe_memory_write(attempted: {})
103
+ begin
104
+ yield
105
+ rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
106
+ log "[MEMORY] Confirmation required: #{e.message} -- details=#{e.details.inspect}"
107
+ {
108
+ "confirmation_required" => true,
109
+ "confirmation_details" => e.details,
110
+ "attempted" => attempted,
111
+ "confirmed_write_token" => "confirmed",
112
+ "confirmation_prompt" => "Ask the user whether to save this memory. If they approve, rerun the same tool with confirmedWrite=confirmed. If they decline, do not retry."
113
+ }
87
114
  end
88
115
  end
116
+ end
89
117
  end
90
118
  end
@@ -45,8 +45,8 @@ module Agentf
45
45
 
46
46
  def self.policy_boundaries
47
47
  {
48
- "always" => ["Return analysis with root causes and suggested fix", "Persist debugging lesson"],
49
- "ask_first" => ["Applying speculative fixes without reproducible error"],
48
+ "always" => ["Return analysis with root causes and suggested fix"],
49
+ "ask_first" => ["Applying speculative fixes without reproducible error", "Persisting debugging lessons to memory"],
50
50
  "never" => ["Discard stack trace context when available"],
51
51
  "required_inputs" => ["error_text"],
52
52
  "required_outputs" => ["analysis", "success"]
@@ -66,14 +66,32 @@ module Agentf
66
66
 
67
67
  analysis = @commands.parse_error(error)
68
68
 
69
- memory.store_episode(
70
- type: "lesson",
71
- title: "Debugged: #{error[0..50]}...",
72
- description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
73
- context: context.to_s,
74
- tags: ["debugging", "error", "fix"],
75
- agent: name
76
- )
69
+ res = safe_memory_write(attempted: { action: "store_lesson", title: "Debugged: #{error[0..50]}...", tags: ["debugging", "error", "fix"], agent: name }) do
70
+ memory.store_episode(
71
+ type: "lesson",
72
+ title: "Debugged: #{error[0..50]}...",
73
+ description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
74
+ context: context.to_s,
75
+ tags: ["debugging", "error", "fix"],
76
+ agent: name
77
+ )
78
+ end
79
+
80
+ if res.is_a?(Hash) && res["confirmation_required"]
81
+ log "Root cause: #{analysis.possible_causes.first}"
82
+ log "Suggested fix: #{analysis.suggested_fix}"
83
+ return {
84
+ "success" => false,
85
+ "confirmation_required" => true,
86
+ "confirmation_details" => res["confirmation_details"],
87
+ "analysis" => {
88
+ "error_type" => analysis.error_type,
89
+ "possible_causes" => analysis.possible_causes,
90
+ "suggested_fix" => analysis.suggested_fix,
91
+ "stack_trace" => analysis.stack_trace
92
+ }
93
+ }
94
+ end
77
95
 
78
96
  log "Root cause: #{analysis.possible_causes.first}"
79
97
  log "Suggested fix: #{analysis.suggested_fix}"
@@ -90,6 +108,11 @@ module Agentf
90
108
  }
91
109
  end
92
110
  end
111
+
112
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
113
+ error_text = task.is_a?(String) ? task : context["error"]
114
+ diagnose(error_text, context: context)
115
+ end
93
116
  end
94
117
  end
95
118
  end
@@ -46,7 +46,7 @@ module Agentf
46
46
  def self.policy_boundaries
47
47
  {
48
48
  "always" => ["Return generated component details", "Persist successful implementation pattern"],
49
- "ask_first" => ["Changing primary UI framework"],
49
+ "ask_first" => ["Changing primary UI framework", "Persisting successful implementation patterns to memory"],
50
50
  "never" => ["Return empty generated code for successful design task"],
51
51
  "required_inputs" => ["design_spec"],
52
52
  "required_outputs" => ["component", "generated_code", "success"]
@@ -64,13 +64,19 @@ module Agentf
64
64
 
65
65
  spec = @commands.generate_component("GeneratedComponent", design_spec)
66
66
 
67
- memory.store_success(
68
- title: "Implemented design: #{design_spec}",
69
- description: "Created #{spec.name} in #{spec.framework}",
70
- context: "Framework: #{framework}",
71
- tags: ["design", "ui", framework],
72
- agent: name
73
- )
67
+ res = safe_memory_write(attempted: { action: "store_success", title: "Implemented design: #{design_spec}", tags: ["design", "ui", framework], agent: name }) do
68
+ memory.store_success(
69
+ title: "Implemented design: #{design_spec}",
70
+ description: "Created #{spec.name} in #{spec.framework}",
71
+ context: "Framework: #{framework}",
72
+ tags: ["design", "ui", framework],
73
+ agent: name
74
+ )
75
+ end
76
+
77
+ if res.is_a?(Hash) && res["confirmation_required"]
78
+ return { "design_spec" => design_spec, "component" => spec.name, "framework" => framework, "generated_code" => spec.code, "success" => true }.merge(res)
79
+ end
74
80
 
75
81
  log "Created component: #{spec.name}"
76
82
 
@@ -83,6 +89,11 @@ module Agentf
83
89
  }
84
90
  end
85
91
  end
92
+
93
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
94
+ spec = task.is_a?(String) ? task : context["design_spec"]
95
+ implement_design(spec, framework: context["framework"] || "react")
96
+ end
86
97
  end
87
98
  end
88
99
  end
@@ -69,6 +69,12 @@ module Agentf
69
69
  "total_memories" => memories.size
70
70
  }
71
71
  end
72
+
73
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
74
+ project = task.is_a?(String) ? task : (context["project_name"] || "project")
75
+ sync_docs(project)
76
+ end
77
+
72
78
  end
73
79
  end
74
80
  end
@@ -11,8 +11,8 @@ module Agentf
11
11
  COMMANDS = %w[glob grep read_file].freeze
12
12
  MEMORY_CONCEPTS = {
13
13
  "reads" => [],
14
- "writes" => ["store_episode"],
15
- "policy" => "Store exploration breadcrumbs as episodic memories."
14
+ "writes" => ["store_lesson"],
15
+ "policy" => "Store research findings as lessons after user confirmation."
16
16
  }.freeze
17
17
 
18
18
  def self.description
@@ -45,8 +45,8 @@ module Agentf
45
45
 
46
46
  def self.policy_boundaries
47
47
  {
48
- "always" => ["Return concrete file evidence", "Persist exploration breadcrumbs"],
49
- "ask_first" => ["Scanning outside configured base path"],
48
+ "always" => ["Return concrete file evidence"],
49
+ "ask_first" => ["Scanning outside configured base path", "Persisting research lessons to memory"],
50
50
  "never" => ["Mutate project files during exploration"],
51
51
  "required_inputs" => [],
52
52
  "required_outputs" => ["files", "context_gathered"]
@@ -63,19 +63,38 @@ module Agentf
63
63
 
64
64
  files = @commands.glob(query, file_types: nil)
65
65
 
66
- memory.store_episode(
67
- type: "exploration",
68
- title: "Explored: #{query}",
69
- description: "Found #{files.size} relevant files",
70
- context: "Search pattern: #{file_pattern || 'all files'}",
71
- tags: ["exploration", "context"],
72
- agent: name
73
- )
66
+ res = safe_memory_write(attempted: { action: "store_lesson", title: "Research finding: #{query}", tags: ["research", "exploration"], agent: name }) do
67
+ memory.store_lesson(
68
+ title: "Research finding: #{query}",
69
+ description: "Found #{files.size} relevant files during exploration",
70
+ context: "Search pattern: #{file_pattern || 'all files'}",
71
+ tags: ["research", "exploration"],
72
+ agent: name
73
+ )
74
+ end
75
+
76
+ if res.is_a?(Hash) && res["confirmation_required"]
77
+ log "Memory confirmation required during exploration: #{res['confirmation_details'].inspect}"
78
+ return {
79
+ "files" => files,
80
+ "context_gathered" => true,
81
+ "confirmation_required" => true,
82
+ "confirmation_details" => res["confirmation_details"],
83
+ "attempted" => res["attempted"],
84
+ "confirmed_write_token" => res["confirmed_write_token"],
85
+ "confirmation_prompt" => res["confirmation_prompt"]
86
+ }
87
+ end
74
88
 
75
89
  log "Found #{files.size} files"
76
90
 
77
91
  { "query" => query, "files" => files, "context_gathered" => true }
78
92
  end
93
+
94
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
95
+ query = context["explore_query"] || task || "*.rb"
96
+ explore(query, file_pattern: context["file_pattern"])
97
+ end
79
98
  end
80
99
  end
81
100
  end
@@ -77,6 +77,11 @@ module Agentf
77
77
  { "approved" => approved, "issues" => issues }
78
78
  end
79
79
  end
80
+
81
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
82
+ subtask = task.is_a?(Hash) ? task : context["execution"] || {}
83
+ review(subtask)
84
+ end
80
85
  end
81
86
  end
82
87
  end
@@ -45,8 +45,8 @@ module Agentf
45
45
 
46
46
  def self.policy_boundaries
47
47
  {
48
- "always" => ["Return issue list and best practices", "Persist outcome as success or pitfall"],
49
- "ask_first" => ["Allowing known secret patterns in context"],
48
+ "always" => ["Return issue list and best practices"],
49
+ "ask_first" => ["Allowing known secret patterns in context", "Persisting security scan findings to memory"],
50
50
  "never" => ["Echo raw secrets in output"],
51
51
  "required_inputs" => ["task"],
52
52
  "required_outputs" => ["issues", "best_practices"]
@@ -66,27 +66,37 @@ module Agentf
66
66
  summary = summarize_findings(findings)
67
67
 
68
68
  if findings["issues"].empty?
69
- memory.store_success(
70
- title: "Security review passed",
71
- description: summary,
72
- context: task,
73
- tags: ["security", "pass"],
74
- agent: name
75
- )
69
+ res = safe_memory_write(attempted: { action: "store_success", title: "Security review passed", tags: ["security", "pass"], agent: name }) do
70
+ memory.store_success(
71
+ title: "Security review passed",
72
+ description: summary,
73
+ context: task,
74
+ tags: ["security", "pass"],
75
+ agent: name
76
+ )
77
+ end
78
+ return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
76
79
  else
77
- memory.store_pitfall(
78
- title: "Security findings detected",
79
- description: summary,
80
- context: task,
81
- tags: ["security", "warning"],
82
- agent: name
83
- )
80
+ res = safe_memory_write(attempted: { action: "store_pitfall", title: "Security findings detected", tags: ["security", "warning"], agent: name }) do
81
+ memory.store_pitfall(
82
+ title: "Security findings detected",
83
+ description: summary,
84
+ context: task,
85
+ tags: ["security", "warning"],
86
+ agent: name
87
+ )
88
+ end
89
+ return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
84
90
  end
85
91
 
86
92
  findings.merge("best_practices" => @commands.best_practices)
87
93
  end
88
94
  end
89
95
 
96
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
97
+ assess(task: task, context: context)
98
+ end
99
+
90
100
  private
91
101
 
92
102
  def summarize_findings(findings)
@@ -45,14 +45,16 @@ module Agentf
45
45
  def self.policy_boundaries
46
46
  {
47
47
  "always" => ["Persist execution outcome", "Return deterministic success boolean"],
48
- "ask_first" => ["Applying architecture style changes across unrelated modules"],
48
+ "ask_first" => ["Applying architecture style changes across unrelated modules", "Persisting execution outcomes to memory (success/pitfall)"] ,
49
49
  "never" => ["Claim implementation complete without execution result"],
50
50
  "required_inputs" => ["description"],
51
51
  "required_outputs" => ["subtask_id", "success"]
52
52
  }
53
53
  end
54
54
 
55
- def execute(subtask)
55
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
56
+ subtask = task.is_a?(Hash) ? task : (context["current_subtask"] || { "description" => task })
57
+
56
58
  normalized_subtask = subtask.merge(
57
59
  "id" => subtask["id"] || "ad-hoc",
58
60
  "description" => subtask["description"] || "Execute implementation step"
@@ -64,23 +66,35 @@ module Agentf
64
66
  success = normalized_subtask.fetch("success", true)
65
67
 
66
68
  if success
67
- memory.store_success(
68
- title: "Completed: #{normalized_subtask['description']}",
69
- description: "Successfully executed subtask #{normalized_subtask['id']}",
70
- context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
71
- tags: ["implementation", normalized_subtask.fetch("language", "general")],
72
- agent: name
73
- )
74
- log "Stored success memory"
69
+ res = safe_memory_write(attempted: { action: "store_success", title: "Completed: #{normalized_subtask['description']}", tags: ["implementation", normalized_subtask.fetch("language", "general")], agent: name }) do
70
+ memory.store_success(
71
+ title: "Completed: #{normalized_subtask['description']}",
72
+ description: "Successfully executed subtask #{normalized_subtask['id']}",
73
+ context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
74
+ tags: ["implementation", normalized_subtask.fetch("language", "general")],
75
+ agent: name
76
+ )
77
+ end
78
+
79
+ if res.is_a?(Hash) && res["confirmation_required"]
80
+ log "Memory confirmation required when storing success: #{res['confirmation_details'].inspect}"
81
+ return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
82
+ end
75
83
  else
76
- memory.store_pitfall(
77
- title: "Failed: #{normalized_subtask['description']}",
78
- description: "Subtask #{normalized_subtask['id']} failed",
79
- context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
80
- tags: ["failure", "implementation"],
81
- agent: name
82
- )
83
- log "Stored pitfall memory"
84
+ res = safe_memory_write(attempted: { action: "store_pitfall", title: "Failed: #{normalized_subtask['description']}", tags: ["failure", "implementation"], agent: name }) do
85
+ memory.store_pitfall(
86
+ title: "Failed: #{normalized_subtask['description']}",
87
+ description: "Subtask #{normalized_subtask['id']} failed",
88
+ context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
89
+ tags: ["failure", "implementation"],
90
+ agent: name
91
+ )
92
+ end
93
+
94
+ if res.is_a?(Hash) && res["confirmation_required"]
95
+ log "Memory confirmation required when storing pitfall: #{res['confirmation_details'].inspect}"
96
+ return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
97
+ end
84
98
  end
85
99
 
86
100
  { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed" }
@@ -46,7 +46,7 @@ module Agentf
46
46
  def self.policy_boundaries
47
47
  {
48
48
  "always" => ["Produce framework-aware tests", "Verify red/green state when TDD enabled"],
49
- "ask_first" => ["Changing test framework conventions"],
49
+ "ask_first" => ["Changing test framework conventions", "Persisting test-generation outcomes to memory"],
50
50
  "never" => ["Mark passing when command output is uncertain"],
51
51
  "required_inputs" => [],
52
52
  "required_outputs" => ["test_file"]
@@ -63,13 +63,20 @@ module Agentf
63
63
 
64
64
  template = @commands.generate_unit_tests(code_file)
65
65
 
66
- memory.store_success(
67
- title: "Generated #{test_type} tests for #{code_file}",
68
- description: "Created #{template.test_file} with #{test_type} tests",
69
- context: "Test framework: #{template.framework}",
70
- tags: ["testing", test_type, code_file.split(".").last],
71
- agent: name
72
- )
66
+ res = safe_memory_write(attempted: { action: "store_success", title: "Generated #{test_type} tests for #{code_file}", tags: ["testing", test_type, code_file.split(".").last], agent: name }) do
67
+ memory.store_success(
68
+ title: "Generated #{test_type} tests for #{code_file}",
69
+ description: "Created #{template.test_file} with #{test_type} tests",
70
+ context: "Test framework: #{template.framework}",
71
+ tags: ["testing", test_type, code_file.split(".").last],
72
+ agent: name
73
+ )
74
+ end
75
+
76
+ if res.is_a?(Hash) && res["confirmation_required"]
77
+ log "Memory confirmation required when storing generated tests: #{res['confirmation_details'].inspect}"
78
+ return { "test_file" => template.test_file, "test_type" => test_type, "generated_code" => template.test_code, "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
79
+ end
73
80
 
74
81
  log "Created: #{template.test_file}"
75
82
 
@@ -90,6 +97,38 @@ module Agentf
90
97
 
91
98
  { "test_file" => test_file, "passed" => result["passed"] }
92
99
  end
100
+
101
+ def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
102
+ # Support provider-driven TDD red-phase: when context signals a red phase,
103
+ # generate tests via the tester commands (if provided) and return a
104
+ # simulated failing test signature so orchestrator flows can short-circuit.
105
+ if context.to_h["tdd_phase"] == "red"
106
+ tester_commands = if commands.respond_to?(:fetch)
107
+ commands.fetch("tester", nil)
108
+ else
109
+ commands["tester"]
110
+ end
111
+
112
+ begin
113
+ tester_commands&.generate_unit_tests(context.to_h["source_file"]) if tester_commands&.respond_to?(:generate_unit_tests)
114
+ rescue StandardError
115
+ # ignore command errors for the simulated red phase
116
+ end
117
+
118
+ return { "tdd_phase" => "red", "passed" => false, "failure_signature" => "expected-failure-#{context.to_h["source_file"] || 'unspecified'}" }
119
+ end
120
+
121
+ action = context["action"] || (task.is_a?(String) ? "generate_tests" : context["action"])
122
+ case action
123
+ when "generate_tests"
124
+ code_file = task.is_a?(String) ? task : context["code_file"]
125
+ generate_tests(code_file, test_type: context["test_type"] || "unit")
126
+ when "run_tests"
127
+ run_tests(context["test_file"] || task)
128
+ else
129
+ { "error" => "Unknown action for Tester: #{action}" }
130
+ end
131
+ end
93
132
  end
94
133
  end
95
134
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "arg_parser"
4
+ require_relative "../commands/registry"
5
+ require_relative "../commands"
6
+ require_relative "../agents"
7
+ require_relative "../memory"
8
+
9
+ module Agentf
10
+ module CLI
11
+ # CLI entry for running a single agent and returning JSON output.
12
+ class Agent
13
+ include ArgParser
14
+
15
+ def initialize
16
+ @memory = Agentf::Memory::RedisMemory.new
17
+ end
18
+
19
+ def run(args)
20
+ if args.empty? || args.include?("--help") || args.include?("help")
21
+ show_help
22
+ return
23
+ end
24
+
25
+ # Allow callers (like the TypeScript plugin) to append `--json` to
26
+ # request machine-readable output. Strip it here so it's not treated as
27
+ # part of the agent payload.
28
+ args = args.dup
29
+ json_output = !args.delete("--json").nil?
30
+ confirmed_write = parse_single_option(args, "--confirmed-write=")
31
+
32
+ agent_name = args.shift
33
+ payload = args.join(" ")
34
+
35
+ # Build command registry with default implementations
36
+ registry = Agentf::Commands::Registry.new
37
+ # Register known command providers
38
+ registry.register("explorer", Agentf::Commands::Explorer.new)
39
+ registry.register("tester", Agentf::Commands::Tester.new)
40
+ registry.register("debugger", Agentf::Commands::Debugger.new)
41
+ registry.register("designer", Agentf::Commands::Designer.new)
42
+ registry.register("security", Agentf::Commands::SecurityScanner.new)
43
+ registry.register("architecture", Agentf::Commands::Architecture.new)
44
+
45
+ # Load agents (classes already required via lib/agentf)
46
+ agents = {}
47
+ Agentf::Agents.constants.each do |const|
48
+ klass = Agentf::Agents.const_get(const)
49
+ next unless klass.is_a?(Class) && klass < Agentf::Agents::Base
50
+ agents[klass.typed_name] = klass.new(@memory)
51
+ end
52
+
53
+ agent = agents[agent_name.upcase]
54
+ unless agent
55
+ $stderr.puts JSON.generate({ ok: false, error: "Agent not found: #{agent_name}" })
56
+ exit 1
57
+ end
58
+
59
+ # Parse possible JSON payload
60
+ parsed = nil
61
+ begin
62
+ parsed = JSON.parse(payload) unless payload.strip.empty?
63
+ rescue StandardError
64
+ parsed = payload
65
+ end
66
+
67
+ previous = ENV["AGENTF_SUPPRESS_AGENT_LOGS"]
68
+ previous_auto_confirm = ENV["AGENTF_AUTO_CONFIRM_MEMORIES"]
69
+ ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = "true" if json_output
70
+ ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = "true" unless confirmed_write.to_s.empty?
71
+
72
+ result = agent.execute(
73
+ task: parsed || payload,
74
+ context: { "confirmed_write" => confirmed_write },
75
+ agents: agents,
76
+ commands: registry,
77
+ logger: json_output ? nil : method(:puts)
78
+ )
79
+
80
+ puts JSON.generate(result)
81
+ ensure
82
+ ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = previous if json_output
83
+ ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = previous_auto_confirm unless confirmed_write.to_s.empty?
84
+ end
85
+
86
+ def show_help
87
+ puts <<~HELP
88
+ Usage: agentf agent <AGENT_NAME> [payload] [--json] [--confirmed-write=<token>]
89
+
90
+ Runs a single agent and prints JSON result.
91
+ HELP
92
+ end
93
+ end
94
+ end
95
+ end