agentf 0.4.6 → 0.4.7

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: 2cf35c1c3dd0c0b331de754c2805b3bbd27e9175a339ecb264509ab3ff5e843b
4
+ data.tar.gz: 1c552174bd8cfdfb0770f8a4c7ae19dd732c7c8e2d440f9197a5c210a0e55b1c
5
5
  SHA512:
6
- metadata.gz: 37efe8cb9847156d5d0e93ee981c108fddab0940eab62fe60bb609e6fc68f799c14fd84f08214263ec4e98d05b617b5be7cd8d035d8a9352d7c639b4737947bb
7
- data.tar.gz: c26f29b7059e1997f62b1a9b25c628c6ff25832309e8766ba2d076ebdf8333ca7ac3206145ae4e05f623c84aa6771c316970b5377e0b48de2980dff3e5c077df
6
+ metadata.gz: e60ee2faa826fe20c6845ab4250153838519456109893adcbba592e3a389a85520c00e3302a4bfb279e3b53b2c79c74afbe32a621a044837e904ca73dee37c54
7
+ data.tar.gz: d246b77664af5a645dc00afd17d0ea2799cf786d8c51f910212c7d24516e0f53a00bbeab81ec84b23a466d3a2185058bdbc89318aca932f20b7ef7c16be7e668
@@ -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"]
@@ -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"]
@@ -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 exploration breadcrumbs to memory"],
50
50
  "never" => ["Mutate project files during exploration"],
51
51
  "required_inputs" => [],
52
52
  "required_outputs" => ["files", "context_gathered"]
@@ -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"]
@@ -45,7 +45,7 @@ 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"]
@@ -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"]
data/lib/agentf/memory.rb CHANGED
@@ -45,7 +45,39 @@ module Agentf
45
45
  end
46
46
 
47
47
  def store_episode(type:, title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER,
48
- related_task_id: nil, metadata: {}, entity_ids: [], relationships: [], parent_episode_id: nil, causal_from: nil)
48
+ related_task_id: nil, metadata: {}, entity_ids: [], relationships: [], parent_episode_id: nil, causal_from: nil, confirm: nil)
49
+ # Determine persistence preference from the agent's policy boundaries.
50
+ # Precedence: never > always > ask_first > none.
51
+ auto_confirm = ENV['AGENTF_AUTO_CONFIRM_MEMORIES'] == 'true'
52
+ pref = persistence_preference_for(agent)
53
+
54
+ case pref
55
+ when :never
56
+ begin
57
+ puts "[MEMORY] Skipping persistence for #{agent}: policy forbids persisting memories"
58
+ rescue StandardError
59
+ end
60
+ return nil
61
+ when :always
62
+ # proceed without requiring explicit confirm
63
+ when :ask_first
64
+ unless agent == Agentf::AgentRoles::ORCHESTRATOR || confirm == true || auto_confirm
65
+ begin
66
+ puts "[MEMORY] Skipping persistence for #{agent}: confirmation required by policy"
67
+ rescue StandardError
68
+ end
69
+ return nil
70
+ end
71
+ else
72
+ # default conservative behavior: require explicit confirm (or env opt-in)
73
+ unless agent == Agentf::AgentRoles::ORCHESTRATOR || confirm == true || auto_confirm
74
+ begin
75
+ puts "[MEMORY] Skipping persistence for #{agent}: confirmation required"
76
+ rescue StandardError
77
+ end
78
+ return nil
79
+ end
80
+ end
49
81
  episode_id = "episode_#{SecureRandom.hex(4)}"
50
82
  normalized_metadata = enrich_metadata(
51
83
  metadata: metadata,
@@ -107,52 +139,56 @@ module Agentf
107
139
  episode_id
108
140
  end
109
141
 
110
- def store_success(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
111
- store_episode(
142
+ def store_success(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER, confirm: nil)
143
+ store_episode(
112
144
  type: "success",
113
145
  title: title,
114
146
  description: description,
115
147
  context: context,
116
148
  code_snippet: code_snippet,
117
149
  tags: tags,
118
- agent: agent
150
+ agent: agent,
151
+ confirm: confirm
119
152
  )
120
153
  end
121
154
 
122
- def store_pitfall(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
123
- store_episode(
155
+ def store_pitfall(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER, confirm: nil)
156
+ store_episode(
124
157
  type: "pitfall",
125
158
  title: title,
126
159
  description: description,
127
160
  context: context,
128
161
  code_snippet: code_snippet,
129
162
  tags: tags,
130
- agent: agent
163
+ agent: agent,
164
+ confirm: confirm
131
165
  )
132
166
  end
133
167
 
134
- def store_lesson(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
135
- store_episode(
168
+ def store_lesson(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER, confirm: nil)
169
+ store_episode(
136
170
  type: "lesson",
137
171
  title: title,
138
172
  description: description,
139
173
  context: context,
140
174
  code_snippet: code_snippet,
141
175
  tags: tags,
142
- agent: agent
176
+ agent: agent,
177
+ confirm: confirm
143
178
  )
144
179
  end
145
180
 
146
- def store_business_intent(title:, description:, constraints: [], tags: [], agent: Agentf::AgentRoles::ORCHESTRATOR, priority: 1)
181
+ def store_business_intent(title:, description:, constraints: [], tags: [], agent: Agentf::AgentRoles::ORCHESTRATOR, priority: 1, confirm: nil)
147
182
  context = constraints.any? ? "Constraints: #{constraints.join('; ')}" : ""
148
183
 
149
- store_episode(
184
+ store_episode(
150
185
  type: "business_intent",
151
186
  title: title,
152
187
  description: description,
153
188
  context: context,
154
189
  tags: tags,
155
- agent: agent,
190
+ agent: agent,
191
+ confirm: confirm,
156
192
  metadata: {
157
193
  "intent_kind" => "business",
158
194
  "constraints" => constraints,
@@ -162,18 +198,19 @@ module Agentf
162
198
  end
163
199
 
164
200
  def store_feature_intent(title:, description:, acceptance_criteria: [], non_goals: [], tags: [], agent: Agentf::AgentRoles::PLANNER,
165
- related_task_id: nil)
201
+ related_task_id: nil, confirm: nil)
166
202
  context_parts = []
167
203
  context_parts << "Acceptance: #{acceptance_criteria.join('; ')}" if acceptance_criteria.any?
168
204
  context_parts << "Non-goals: #{non_goals.join('; ')}" if non_goals.any?
169
205
 
170
- store_episode(
206
+ store_episode(
171
207
  type: "feature_intent",
172
208
  title: title,
173
209
  description: description,
174
210
  context: context_parts.join(" | "),
175
211
  tags: tags,
176
212
  agent: agent,
213
+ confirm: confirm,
177
214
  related_task_id: related_task_id,
178
215
  metadata: {
179
216
  "intent_kind" => "feature",
@@ -858,6 +895,54 @@ module Agentf
858
895
  base
859
896
  end
860
897
 
898
+ # Determine whether writes from the given agent should require explicit
899
+ # confirmation. We consider agents that declare "ask_first" policy
900
+ # boundaries to be conservative and require confirmation before persisting
901
+ # memories. Agent classes register policy boundaries on their class
902
+ # definitions (see agents/*). When agent is provided as a role string
903
+ # (eg. "ENGINEER") we try to match against known agent classes.
904
+ def agent_requires_confirmation?(agent)
905
+ begin
906
+ # If a developer passed an agent class-like string, try to map it to
907
+ # the loaded agent class and inspect its policy_boundaries.
908
+ candidate = Agentf::Agents.constants
909
+ .map { |c| Agentf::Agents.const_get(c) }
910
+ .find do |klass|
911
+ klass.is_a?(Class) && klass.respond_to?(:policy_boundaries) && klass.typed_name == agent
912
+ end
913
+
914
+ return false unless candidate
915
+
916
+ boundaries = candidate.policy_boundaries
917
+ ask_first = Array(boundaries["ask_first"]) rescue []
918
+ !ask_first.empty?
919
+ rescue StandardError
920
+ false
921
+ end
922
+ end
923
+
924
+ # Inspect loaded agent classes for explicit persistence preference.
925
+ # Returns one of: :always, :ask_first, :never, or nil when unknown.
926
+ def persistence_preference_for(agent)
927
+ begin
928
+ candidate = Agentf::Agents.constants
929
+ .map { |c| Agentf::Agents.const_get(c) }
930
+ .find do |klass|
931
+ klass.is_a?(Class) && klass.respond_to?(:policy_boundaries) && klass.typed_name == agent
932
+ end
933
+
934
+ return nil unless candidate
935
+
936
+ boundaries = candidate.policy_boundaries
937
+ return :never if Array(boundaries["never"]).any?
938
+ return :always if Array(boundaries["always"]).any? && Array(boundaries["ask_first"]).empty?
939
+ return :ask_first if Array(boundaries["ask_first"]).any?
940
+ nil
941
+ rescue StandardError
942
+ nil
943
+ end
944
+ end
945
+
861
946
  def infer_division(agent)
862
947
  case agent
863
948
  when Agentf::AgentRoles::PLANNER, Agentf::AgentRoles::ORCHESTRATOR, Agentf::AgentRoles::KNOWLEDGE_MANAGER
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Agentf
4
- VERSION = "0.4.6"
4
+ VERSION = "0.4.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agentf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neal Deters
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-10 00:00:00.000000000 Z
11
+ date: 2026-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis