agentf 0.5.0 → 0.7.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: 6072a9a176fba8e2376b10cc125d78d450057c90c4aa5d95158797429504d1f5
4
- data.tar.gz: f66dc58d0030e1ba289d3f7046811190b9ae818aca61021729c9da12d19e710c
3
+ metadata.gz: 9d0041ebcd3b112183ddb349312e4cab8d30237ed1108dbb55d928c525dd59ae
4
+ data.tar.gz: 2577b0dc7af10255d02d2dde419aaa8e1135aea4e36dd67cc10b5d55b1866ba6
5
5
  SHA512:
6
- metadata.gz: '0397d2603cc060239303773b370f14cc9585b94b4ba5109207ac7f705b18aec244f83443478c15c1ed40be4650c20f9f13d669f224dfc916374e924da4ab6cb1'
7
- data.tar.gz: a8f7562d3bc1f8bd43897b4692c18a4096509d2c4ce413eaa1c992c61c2a7fada7c0c52228c543d62387c9a56c37672819f1becfd39240bc83066491947678f8
6
+ metadata.gz: ceae3f4d97e84e9934eae6591b7a2be4ea6ed56e31f4555110f998a2f3551f8231572a8c56dd790815988772ad8f4584f5d043d0283828665bcb69ad033d9ef2
7
+ data.tar.gz: 2ff0e3ece10105b6632ebd8e34c5b78c870dc6b8e105dabc41bc7f472502a8c33aee1dc7d10f14fa0ffaea513553c24f1353a9bda7a6647117f200cf8a43de88
@@ -9,7 +9,7 @@ module Agentf
9
9
  DESCRIPTION = "Strategy, task decomposition, and memory retrieval."
10
10
  COMMANDS = %w[glob read_file memory].freeze
11
11
  MEMORY_CONCEPTS = {
12
- "reads" => ["get_recent_memories", "get_pitfalls"],
12
+ "reads" => ["get_recent_memories", "get_episodes"],
13
13
  "writes" => [],
14
14
  "policy" => "Retrieve relevant memories before planning; do not duplicate runtime memory into static markdown."
15
15
  }.freeze
@@ -44,7 +44,7 @@ module Agentf
44
44
 
45
45
  def self.policy_boundaries
46
46
  {
47
- "always" => ["Capture constraints before decomposition", "Use recent memories and pitfalls in planning"],
47
+ "always" => ["Capture constraints before decomposition", "Use recent memories and negative episodes in planning"],
48
48
  "ask_first" => ["Changing architectural style from project defaults"],
49
49
  "never" => ["Skip task decomposition for non-trivial workflows"],
50
50
  "required_inputs" => [],
@@ -57,7 +57,7 @@ module Agentf
57
57
 
58
58
  # Retrieve relevant memories before planning
59
59
  recent = memory.get_recent_memories(limit: 5)
60
- pitfalls = memory.get_pitfalls(limit: 3)
60
+ pitfalls = memory.get_episodes(limit: 3, outcome: "negative")
61
61
 
62
62
  context = {
63
63
  "task" => task,
@@ -4,6 +4,7 @@ module Agentf
4
4
  module Agents
5
5
  # Base agent class
6
6
  class Base
7
+ include Agentf::Memory::ConfirmationHandler
7
8
  attr_reader :memory, :name
8
9
 
9
10
  def self.typed_name
@@ -32,8 +33,8 @@ module Agentf
32
33
 
33
34
  def self.memory_concepts
34
35
  {
35
- "reads" => ["RedisMemory#get_recent_memories", "RedisMemory#get_pitfalls"],
36
- "writes" => ["RedisMemory#store_lesson", "RedisMemory#store_success", "RedisMemory#store_pitfall"],
36
+ "reads" => ["RedisMemory#get_recent_memories", "RedisMemory#get_episodes"],
37
+ "writes" => ["RedisMemory#store_lesson", "RedisMemory#store_episode", "RedisMemory#store_playbook"],
37
38
  "policy" => "Memory is runtime state in Redis and should not be embedded as raw data in manifest markdown."
38
39
  }
39
40
  end
@@ -52,6 +53,10 @@ module Agentf
52
53
  }
53
54
  end
54
55
 
56
+ def self.writes_code?
57
+ false
58
+ end
59
+
55
60
  def initialize(memory)
56
61
  @memory = memory
57
62
  @name = self.class.typed_name
@@ -91,28 +96,8 @@ module Agentf
91
96
  result: result
92
97
  )
93
98
 
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
- }
99
+ result
114
100
  end
115
101
  end
116
- end
117
102
  end
118
103
  end
@@ -66,13 +66,12 @@ module Agentf
66
66
 
67
67
  analysis = @commands.parse_error(error)
68
68
 
69
- res = safe_memory_write(attempted: { action: "store_lesson", title: "Debugged: #{error[0..50]}...", tags: ["debugging", "error", "fix"], agent: name }) do
69
+ res = safe_memory_write(attempted: { action: "store_lesson", title: "Debugged: #{error[0..50]}...", agent: name }) do
70
70
  memory.store_episode(
71
71
  type: "lesson",
72
72
  title: "Debugged: #{error[0..50]}...",
73
73
  description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
74
74
  context: context.to_s,
75
- tags: ["debugging", "error", "fix"],
76
75
  agent: name
77
76
  )
78
77
  end
@@ -11,7 +11,7 @@ module Agentf
11
11
  COMMANDS = %w[generate_component validate_design_system].freeze
12
12
  MEMORY_CONCEPTS = {
13
13
  "reads" => [],
14
- "writes" => ["store_success"],
14
+ "writes" => ["store_episode"],
15
15
  "policy" => "Capture successful design implementation patterns."
16
16
  }.freeze
17
17
 
@@ -45,14 +45,28 @@ module Agentf
45
45
 
46
46
  def self.policy_boundaries
47
47
  {
48
- "always" => ["Return generated component details", "Persist successful implementation pattern"],
48
+ "always" => [
49
+ "Return generated component details",
50
+ "Persist successful implementation pattern",
51
+ "Write a failing spec before implementing any new component or function (red)",
52
+ "Run the test suite to confirm the spec fails before writing implementation",
53
+ "Run the test suite again after implementation to confirm green"
54
+ ],
49
55
  "ask_first" => ["Changing primary UI framework", "Persisting successful implementation patterns to memory"],
50
- "never" => ["Return empty generated code for successful design task"],
56
+ "never" => [
57
+ "Return empty generated code for successful design task",
58
+ "Create a new component or function without a corresponding spec file",
59
+ "Skip red/green verification when writing or modifying code"
60
+ ],
51
61
  "required_inputs" => ["design_spec"],
52
62
  "required_outputs" => ["component", "generated_code", "success"]
53
63
  }
54
64
  end
55
65
 
66
+ def self.writes_code?
67
+ true
68
+ end
69
+
56
70
  def initialize(memory, commands: nil)
57
71
  super(memory)
58
72
  @commands = commands || Agentf::Commands::Designer.new
@@ -64,13 +78,14 @@ module Agentf
64
78
 
65
79
  spec = @commands.generate_component("GeneratedComponent", design_spec)
66
80
 
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(
81
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Implemented design: #{design_spec}", outcome: "positive", agent: name }) do
82
+ memory.store_episode(
83
+ type: "episode",
69
84
  title: "Implemented design: #{design_spec}",
70
85
  description: "Created #{spec.name} in #{spec.framework}",
71
86
  context: "Framework: #{framework}",
72
- tags: ["design", "ui", framework],
73
- agent: name
87
+ agent: name,
88
+ outcome: "positive"
74
89
  )
75
90
  end
76
91
 
@@ -57,8 +57,8 @@ module Agentf
57
57
 
58
58
  memories = memory.get_recent_memories(limit: 20)
59
59
 
60
- successes = memories.select { |m| m["type"] == "success" }
61
- pitfalls = memories.select { |m| m["type"] == "pitfall" }
60
+ successes = memories.select { |m| m["type"] == "episode" && m["outcome"] == "positive" }
61
+ pitfalls = memories.select { |m| m["type"] == "episode" && m["outcome"] == "negative" }
62
62
 
63
63
  log "Found #{successes.size} successes"
64
64
  log "Found #{pitfalls.size} pitfalls"
@@ -63,12 +63,11 @@ module Agentf
63
63
 
64
64
  files = @commands.glob(query, file_types: nil)
65
65
 
66
- res = safe_memory_write(attempted: { action: "store_lesson", title: "Research finding: #{query}", tags: ["research", "exploration"], agent: name }) do
66
+ res = safe_memory_write(attempted: { action: "store_lesson", title: "Research finding: #{query}", agent: name }) do
67
67
  memory.store_lesson(
68
68
  title: "Research finding: #{query}",
69
69
  description: "Found #{files.size} relevant files during exploration",
70
70
  context: "Search pattern: #{file_pattern || 'all files'}",
71
- tags: ["research", "exploration"],
72
71
  agent: name
73
72
  )
74
73
  end
@@ -9,9 +9,9 @@ module Agentf
9
9
  DESCRIPTION = "Quality assurance and regression checking against memory."
10
10
  COMMANDS = %w[read_file memory].freeze
11
11
  MEMORY_CONCEPTS = {
12
- "reads" => ["get_pitfalls", "get_recent_memories"],
12
+ "reads" => ["get_episodes", "get_recent_memories"],
13
13
  "writes" => [],
14
- "policy" => "Validate outputs against known pitfalls before approval."
14
+ "policy" => "Validate outputs against known negative episodes before approval."
15
15
  }.freeze
16
16
 
17
17
  def self.description
@@ -56,14 +56,14 @@ module Agentf
56
56
  execute_with_contract(context: { "execution" => subtask_result }) do
57
57
  log "Reviewing subtask #{subtask_result['subtask_id']}"
58
58
 
59
- pitfalls = memory.get_pitfalls(limit: 5)
60
- memories = memory.get_recent_memories(limit: 5)
59
+ pitfalls = memory.get_episodes(limit: 5, outcome: "negative")
60
+ memories = memory.get_recent_memories(limit: 5)
61
61
 
62
62
  issues = []
63
63
 
64
- pitfalls.each do |pitfall|
65
- issues << "Warning: Known pitfall - #{pitfall['title']}" if pitfall["type"] == "pitfall"
66
- end
64
+ pitfalls.each do |pitfall|
65
+ issues << "Warning: Known negative episode - #{pitfall['title']}" if pitfall["type"] == "episode"
66
+ end
67
67
 
68
68
  approved = issues.empty?
69
69
 
@@ -11,7 +11,7 @@ module Agentf
11
11
  COMMANDS = %w[scan best_practices].freeze
12
12
  MEMORY_CONCEPTS = {
13
13
  "reads" => [],
14
- "writes" => ["store_success", "store_pitfall"],
14
+ "writes" => ["store_episode"],
15
15
  "policy" => "Record findings while redacting sensitive values."
16
16
  }.freeze
17
17
 
@@ -66,24 +66,26 @@ module Agentf
66
66
  summary = summarize_findings(findings)
67
67
 
68
68
  if findings["issues"].empty?
69
- res = safe_memory_write(attempted: { action: "store_success", title: "Security review passed", tags: ["security", "pass"], agent: name }) do
70
- memory.store_success(
69
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Security review passed", outcome: "positive", agent: name }) do
70
+ memory.store_episode(
71
+ type: "episode",
71
72
  title: "Security review passed",
72
73
  description: summary,
73
74
  context: task,
74
- tags: ["security", "pass"],
75
- agent: name
75
+ agent: name,
76
+ outcome: "positive"
76
77
  )
77
78
  end
78
79
  return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
79
80
  else
80
- res = safe_memory_write(attempted: { action: "store_pitfall", title: "Security findings detected", tags: ["security", "warning"], agent: name }) do
81
- memory.store_pitfall(
81
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Security findings detected", outcome: "negative", agent: name }) do
82
+ memory.store_episode(
83
+ type: "episode",
82
84
  title: "Security findings detected",
83
85
  description: summary,
84
86
  context: task,
85
- tags: ["security", "warning"],
86
- agent: name
87
+ agent: name,
88
+ outcome: "negative"
87
89
  )
88
90
  end
89
91
  return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
@@ -10,7 +10,7 @@ module Agentf
10
10
  COMMANDS = %w[read_file write_file run_command].freeze
11
11
  MEMORY_CONCEPTS = {
12
12
  "reads" => [],
13
- "writes" => ["store_success", "store_pitfall"],
13
+ "writes" => ["store_episode"],
14
14
  "policy" => "Persist execution outcomes as lessons for downstream agents."
15
15
  }.freeze
16
16
 
@@ -44,14 +44,28 @@ module Agentf
44
44
 
45
45
  def self.policy_boundaries
46
46
  {
47
- "always" => ["Persist execution outcome", "Return deterministic success boolean"],
48
- "ask_first" => ["Applying architecture style changes across unrelated modules", "Persisting execution outcomes to memory (success/pitfall)"] ,
49
- "never" => ["Claim implementation complete without execution result"],
47
+ "always" => [
48
+ "Persist execution outcome",
49
+ "Return deterministic success boolean",
50
+ "Write a failing spec before adding any new class, method, or module (red)",
51
+ "Run the test suite to confirm the spec fails before writing implementation",
52
+ "Run the test suite again after implementation to confirm green"
53
+ ],
54
+ "ask_first" => ["Applying architecture style changes across unrelated modules", "Persisting execution outcomes to memory (success/pitfall)"],
55
+ "never" => [
56
+ "Claim implementation complete without execution result",
57
+ "Create a new class, method, or module without a corresponding spec file",
58
+ "Skip red/green verification when writing or modifying code"
59
+ ],
50
60
  "required_inputs" => ["description"],
51
61
  "required_outputs" => ["subtask_id", "success"]
52
62
  }
53
63
  end
54
64
 
65
+ def self.writes_code?
66
+ true
67
+ end
68
+
55
69
  def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
56
70
  subtask = task.is_a?(Hash) ? task : (context["current_subtask"] || { "description" => task })
57
71
 
@@ -66,13 +80,14 @@ module Agentf
66
80
  success = normalized_subtask.fetch("success", true)
67
81
 
68
82
  if success
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(
83
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Completed: #{normalized_subtask['description']}", outcome: "positive", agent: name }) do
84
+ memory.store_episode(
85
+ type: "episode",
71
86
  title: "Completed: #{normalized_subtask['description']}",
72
87
  description: "Successfully executed subtask #{normalized_subtask['id']}",
73
88
  context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
74
- tags: ["implementation", normalized_subtask.fetch("language", "general")],
75
- agent: name
89
+ agent: name,
90
+ outcome: "positive"
76
91
  )
77
92
  end
78
93
 
@@ -81,13 +96,14 @@ module Agentf
81
96
  return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
82
97
  end
83
98
  else
84
- res = safe_memory_write(attempted: { action: "store_pitfall", title: "Failed: #{normalized_subtask['description']}", tags: ["failure", "implementation"], agent: name }) do
85
- memory.store_pitfall(
99
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Failed: #{normalized_subtask['description']}", outcome: "negative", agent: name }) do
100
+ memory.store_episode(
101
+ type: "episode",
86
102
  title: "Failed: #{normalized_subtask['description']}",
87
103
  description: "Subtask #{normalized_subtask['id']} failed",
88
104
  context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
89
- tags: ["failure", "implementation"],
90
- agent: name
105
+ agent: name,
106
+ outcome: "negative"
91
107
  )
92
108
  end
93
109
 
@@ -11,7 +11,7 @@ module Agentf
11
11
  COMMANDS = %w[detect_framework generate_unit_tests run_tests].freeze
12
12
  MEMORY_CONCEPTS = {
13
13
  "reads" => [],
14
- "writes" => ["store_success"],
14
+ "writes" => ["store_episode"],
15
15
  "policy" => "Persist test generation outcomes for future reuse."
16
16
  }.freeze
17
17
 
@@ -45,14 +45,28 @@ module Agentf
45
45
 
46
46
  def self.policy_boundaries
47
47
  {
48
- "always" => ["Produce framework-aware tests", "Verify red/green state when TDD enabled"],
48
+ "always" => [
49
+ "Produce framework-aware tests",
50
+ "Verify red/green state when TDD enabled",
51
+ "Write a failing spec before adding any new test helper, fixture, or shared example (red)",
52
+ "Run the test suite to confirm the spec fails before writing implementation",
53
+ "Run the test suite again after implementation to confirm green"
54
+ ],
49
55
  "ask_first" => ["Changing test framework conventions", "Persisting test-generation outcomes to memory"],
50
- "never" => ["Mark passing when command output is uncertain"],
56
+ "never" => [
57
+ "Mark passing when command output is uncertain",
58
+ "Create a new test helper or shared example without a corresponding spec file",
59
+ "Skip red/green verification when writing or modifying test infrastructure code"
60
+ ],
51
61
  "required_inputs" => [],
52
62
  "required_outputs" => ["test_file"]
53
63
  }
54
64
  end
55
65
 
66
+ def self.writes_code?
67
+ true
68
+ end
69
+
56
70
  def initialize(memory, commands: nil)
57
71
  super(memory)
58
72
  @commands = commands || Agentf::Commands::Tester.new
@@ -63,13 +77,14 @@ module Agentf
63
77
 
64
78
  template = @commands.generate_unit_tests(code_file)
65
79
 
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(
80
+ res = safe_memory_write(attempted: { action: "store_episode", title: "Generated #{test_type} tests for #{code_file}", outcome: "positive", agent: name }) do
81
+ memory.store_episode(
82
+ type: "episode",
68
83
  title: "Generated #{test_type} tests for #{code_file}",
69
84
  description: "Created #{template.test_file} with #{test_type} tests",
70
85
  context: "Test framework: #{template.framework}",
71
- tags: ["testing", test_type, code_file.split(".").last],
72
- agent: name
86
+ agent: name,
87
+ outcome: "positive"
73
88
  )
74
89
  end
75
90
 
@@ -113,7 +113,7 @@ module Agentf
113
113
 
114
114
  Examples:
115
115
  agentf eval list
116
- agentf eval run engineer_store_success
116
+ agentf eval run engineer_episode_positive
117
117
  agentf eval report
118
118
  agentf eval run all --json
119
119
  HELP