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 +4 -4
- data/lib/agentf/agents/architect.rb +3 -3
- data/lib/agentf/agents/base.rb +8 -23
- data/lib/agentf/agents/debugger.rb +1 -2
- data/lib/agentf/agents/designer.rb +22 -7
- data/lib/agentf/agents/documenter.rb +2 -2
- data/lib/agentf/agents/explorer.rb +1 -2
- data/lib/agentf/agents/reviewer.rb +7 -7
- data/lib/agentf/agents/security.rb +11 -9
- data/lib/agentf/agents/specialist.rb +28 -12
- data/lib/agentf/agents/tester.rb +22 -7
- data/lib/agentf/cli/eval.rb +1 -1
- data/lib/agentf/cli/memory.rb +95 -92
- data/lib/agentf/cli/router.rb +1 -1
- data/lib/agentf/commands/memory_reviewer.rb +21 -55
- data/lib/agentf/commands/metrics.rb +4 -13
- data/lib/agentf/context_builder.rb +4 -14
- data/lib/agentf/embedding_provider.rb +35 -0
- data/lib/agentf/installer.rb +162 -82
- data/lib/agentf/mcp/server.rb +123 -177
- data/lib/agentf/memory/confirmation_handler.rb +24 -0
- data/lib/agentf/memory.rb +322 -169
- data/lib/agentf/version.rb +1 -1
- data/lib/agentf/workflow_engine.rb +15 -18
- data/lib/agentf.rb +2 -0
- metadata +4 -2
data/lib/agentf/cli/memory.rb
CHANGED
|
@@ -12,8 +12,9 @@ module Agentf
|
|
|
12
12
|
# - by_type accepts business_intent and feature_intent (finding #11)
|
|
13
13
|
class Memory
|
|
14
14
|
include ArgParser
|
|
15
|
+
include Agentf::Memory::ConfirmationHandler
|
|
15
16
|
|
|
16
|
-
VALID_EPISODE_TYPES = %w[
|
|
17
|
+
VALID_EPISODE_TYPES = %w[episode lesson playbook business_intent feature_intent incident].freeze
|
|
17
18
|
|
|
18
19
|
def initialize(reviewer: nil, memory: nil)
|
|
19
20
|
@reviewer = reviewer || Commands::MemoryReviewer.new
|
|
@@ -28,30 +29,28 @@ module Agentf
|
|
|
28
29
|
case command
|
|
29
30
|
when "recent", "list"
|
|
30
31
|
list_memories(args)
|
|
31
|
-
when "
|
|
32
|
-
|
|
32
|
+
when "episodes"
|
|
33
|
+
list_episodes(args)
|
|
33
34
|
when "lessons"
|
|
34
35
|
list_lessons(args)
|
|
35
|
-
when "successes"
|
|
36
|
-
list_successes(args)
|
|
37
36
|
when "intents"
|
|
38
37
|
list_intents(args)
|
|
39
38
|
when "business-intents"
|
|
40
39
|
list_business_intents(args)
|
|
41
40
|
when "feature-intents"
|
|
42
41
|
list_feature_intents(args)
|
|
42
|
+
when "add-intent"
|
|
43
|
+
add_intent(args)
|
|
44
|
+
when "add-episode"
|
|
45
|
+
add_episode_direct(args)
|
|
43
46
|
when "add-business-intent"
|
|
44
47
|
add_business_intent(args)
|
|
45
48
|
when "add-feature-intent"
|
|
46
49
|
add_feature_intent(args)
|
|
50
|
+
when "add-playbook"
|
|
51
|
+
add_playbook(args)
|
|
47
52
|
when "add-lesson"
|
|
48
53
|
add_episode("lesson", args)
|
|
49
|
-
when "add-success"
|
|
50
|
-
add_episode("success", args)
|
|
51
|
-
when "add-pitfall"
|
|
52
|
-
add_episode("pitfall", args)
|
|
53
|
-
when "tags"
|
|
54
|
-
list_tags
|
|
55
54
|
when "search"
|
|
56
55
|
search_memories(args)
|
|
57
56
|
when "delete"
|
|
@@ -62,8 +61,6 @@ module Agentf
|
|
|
62
61
|
subgraph(args)
|
|
63
62
|
when "summary", "stats"
|
|
64
63
|
show_summary
|
|
65
|
-
when "by-tag"
|
|
66
|
-
by_tag(args)
|
|
67
64
|
when "by-agent"
|
|
68
65
|
by_agent(args)
|
|
69
66
|
when "by-type"
|
|
@@ -86,9 +83,10 @@ module Agentf
|
|
|
86
83
|
output(result)
|
|
87
84
|
end
|
|
88
85
|
|
|
89
|
-
def
|
|
86
|
+
def list_episodes(args)
|
|
90
87
|
limit = extract_limit(args)
|
|
91
|
-
|
|
88
|
+
outcome = parse_single_option(args, "--outcome=")
|
|
89
|
+
result = @reviewer.get_episodes(limit: limit, outcome: outcome)
|
|
92
90
|
output(result)
|
|
93
91
|
end
|
|
94
92
|
|
|
@@ -98,12 +96,6 @@ module Agentf
|
|
|
98
96
|
output(result)
|
|
99
97
|
end
|
|
100
98
|
|
|
101
|
-
def list_successes(args)
|
|
102
|
-
limit = extract_limit(args)
|
|
103
|
-
result = @reviewer.get_successes(limit: limit)
|
|
104
|
-
output(result)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
99
|
def list_intents(args)
|
|
108
100
|
limit = extract_limit(args)
|
|
109
101
|
kind = args.shift
|
|
@@ -132,6 +124,24 @@ module Agentf
|
|
|
132
124
|
output(@reviewer.get_feature_intents(limit: limit))
|
|
133
125
|
end
|
|
134
126
|
|
|
127
|
+
def add_intent(args)
|
|
128
|
+
kind = args.shift.to_s.downcase
|
|
129
|
+
unless %w[business feature].include?(kind)
|
|
130
|
+
$stderr.puts "Error: add-intent requires kind (business|feature) as first argument"
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
kind == "business" ? add_business_intent(args) : add_feature_intent(args)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def add_episode_direct(args)
|
|
137
|
+
type = args.shift.to_s
|
|
138
|
+
unless VALID_EPISODE_TYPES.include?(type)
|
|
139
|
+
$stderr.puts "Error: add-episode requires type as first argument (#{VALID_EPISODE_TYPES.join('|')})"
|
|
140
|
+
exit 1
|
|
141
|
+
end
|
|
142
|
+
add_episode(type, args)
|
|
143
|
+
end
|
|
144
|
+
|
|
135
145
|
def add_business_intent(args)
|
|
136
146
|
title = args.shift
|
|
137
147
|
description = args.shift
|
|
@@ -141,16 +151,14 @@ module Agentf
|
|
|
141
151
|
exit 1
|
|
142
152
|
end
|
|
143
153
|
|
|
144
|
-
tags = parse_list_option(args, "--tags=")
|
|
145
154
|
constraints = parse_list_option(args, "--constraints=")
|
|
146
155
|
priority = parse_integer_option(args, "--priority=", default: 1)
|
|
147
156
|
|
|
148
157
|
id = nil
|
|
149
|
-
res =
|
|
158
|
+
res = safe_memory_write(@memory, attempted: { command: "add-business-intent", args: { title: title, description: description, constraints: constraints, priority: priority } }) do
|
|
150
159
|
id = @memory.store_business_intent(
|
|
151
160
|
title: title,
|
|
152
161
|
description: description,
|
|
153
|
-
tags: tags,
|
|
154
162
|
constraints: constraints,
|
|
155
163
|
priority: priority
|
|
156
164
|
)
|
|
@@ -181,17 +189,15 @@ module Agentf
|
|
|
181
189
|
exit 1
|
|
182
190
|
end
|
|
183
191
|
|
|
184
|
-
tags = parse_list_option(args, "--tags=")
|
|
185
192
|
acceptance_criteria = parse_list_option(args, "--acceptance=")
|
|
186
193
|
non_goals = parse_list_option(args, "--non-goals=")
|
|
187
194
|
related_task_id = parse_single_option(args, "--task=")
|
|
188
195
|
|
|
189
196
|
id = nil
|
|
190
|
-
res =
|
|
197
|
+
res = safe_memory_write(@memory, attempted: { command: "add-feature-intent", args: { title: title, description: description, acceptance: acceptance_criteria, non_goals: non_goals, related_task_id: related_task_id } }) do
|
|
191
198
|
id = @memory.store_feature_intent(
|
|
192
199
|
title: title,
|
|
193
200
|
description: description,
|
|
194
|
-
tags: tags,
|
|
195
201
|
acceptance_criteria: acceptance_criteria,
|
|
196
202
|
non_goals: non_goals,
|
|
197
203
|
related_task_id: related_task_id
|
|
@@ -214,6 +220,46 @@ module Agentf
|
|
|
214
220
|
end
|
|
215
221
|
end
|
|
216
222
|
|
|
223
|
+
def add_playbook(args)
|
|
224
|
+
title = args.shift
|
|
225
|
+
description = args.shift
|
|
226
|
+
|
|
227
|
+
if title.to_s.empty? || description.to_s.empty?
|
|
228
|
+
$stderr.puts "Error: add-playbook requires <title> <description>"
|
|
229
|
+
exit 1
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
steps = parse_list_option(args, "--steps=")
|
|
233
|
+
feature_area = parse_single_option(args, "--feature-area=")
|
|
234
|
+
agent = parse_single_option(args, "--agent=") || Agentf::AgentRoles::PLANNER
|
|
235
|
+
|
|
236
|
+
id = nil
|
|
237
|
+
res = safe_memory_write(@memory, attempted: { command: "add-playbook", args: { title: title, description: description, steps: steps, feature_area: feature_area, agent: agent } }) do
|
|
238
|
+
id = @memory.store_playbook(
|
|
239
|
+
title: title,
|
|
240
|
+
description: description,
|
|
241
|
+
steps: steps,
|
|
242
|
+
feature_area: feature_area,
|
|
243
|
+
agent: agent
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
248
|
+
if @json_output
|
|
249
|
+
puts JSON.generate(res)
|
|
250
|
+
else
|
|
251
|
+
$stderr.puts "Confirmation required to store playbook: #{res['confirmation_details'].inspect}"
|
|
252
|
+
end
|
|
253
|
+
return
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if @json_output
|
|
257
|
+
puts JSON.generate({ "id" => id, "type" => "playbook", "status" => "stored" })
|
|
258
|
+
else
|
|
259
|
+
puts "Stored playbook: #{id}"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
217
263
|
def add_episode(type, args)
|
|
218
264
|
title = args.shift
|
|
219
265
|
description = args.shift
|
|
@@ -223,21 +269,21 @@ module Agentf
|
|
|
223
269
|
exit 1
|
|
224
270
|
end
|
|
225
271
|
|
|
226
|
-
tags = parse_list_option(args, "--tags=")
|
|
227
272
|
context = parse_single_option(args, "--context=").to_s
|
|
228
273
|
agent = parse_single_option(args, "--agent=") || Agentf::AgentRoles::ENGINEER
|
|
229
274
|
code_snippet = parse_single_option(args, "--code=").to_s
|
|
275
|
+
outcome = parse_single_option(args, "--outcome=")
|
|
230
276
|
|
|
231
277
|
id = nil
|
|
232
|
-
res =
|
|
278
|
+
res = safe_memory_write(@memory, attempted: { command: "add-#{type}", args: { title: title, description: description, context: context, agent: agent, code: code_snippet, outcome: outcome } }) do
|
|
233
279
|
id = @memory.store_episode(
|
|
234
280
|
type: type,
|
|
235
281
|
title: title,
|
|
236
282
|
description: description,
|
|
237
283
|
context: context,
|
|
238
|
-
tags: tags,
|
|
239
284
|
agent: agent,
|
|
240
|
-
code_snippet: code_snippet
|
|
285
|
+
code_snippet: code_snippet,
|
|
286
|
+
outcome: outcome
|
|
241
287
|
)
|
|
242
288
|
end
|
|
243
289
|
|
|
@@ -257,37 +303,6 @@ module Agentf
|
|
|
257
303
|
end
|
|
258
304
|
end
|
|
259
305
|
|
|
260
|
-
# Helper to standardize CLI memory write confirmation handling.
|
|
261
|
-
def safe_cli_memory_write(memory, attempted: {})
|
|
262
|
-
begin
|
|
263
|
-
yield
|
|
264
|
-
nil
|
|
265
|
-
rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
|
|
266
|
-
{
|
|
267
|
-
"confirmation_required" => true,
|
|
268
|
-
"confirmation_details" => e.details,
|
|
269
|
-
"attempted" => attempted,
|
|
270
|
-
"confirmed_write_token" => "confirmed",
|
|
271
|
-
"confirmation_prompt" => "Ask the user whether to save this memory. If they approve, rerun the same command with confirmation enabled. If they decline, do not retry."
|
|
272
|
-
}
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def list_tags
|
|
277
|
-
result = @reviewer.get_all_tags
|
|
278
|
-
if @json_output
|
|
279
|
-
puts JSON.generate(result)
|
|
280
|
-
return
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
if result["tags"].empty?
|
|
284
|
-
puts "No tags found."
|
|
285
|
-
else
|
|
286
|
-
puts "Tags (#{result["count"]}):"
|
|
287
|
-
result["tags"].each { |tag| puts " - #{tag}" }
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
|
|
291
306
|
def search_memories(args)
|
|
292
307
|
# Extract limit BEFORE joining remaining args as query (fixes finding #7)
|
|
293
308
|
limit = extract_limit(args)
|
|
@@ -318,19 +333,12 @@ module Agentf
|
|
|
318
333
|
puts ""
|
|
319
334
|
puts "By agent:"
|
|
320
335
|
result["by_agent"].each { |agent, count| puts " #{agent}: #{count}" }
|
|
321
|
-
puts ""
|
|
322
|
-
puts "Unique tags: #{result["unique_tags"]}"
|
|
323
|
-
end
|
|
324
336
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
exit 1
|
|
337
|
+
if result["by_outcome"].is_a?(Hash)
|
|
338
|
+
puts ""
|
|
339
|
+
puts "By outcome:"
|
|
340
|
+
result["by_outcome"].each { |outcome, count| puts " #{outcome}: #{count}" }
|
|
330
341
|
end
|
|
331
|
-
limit = extract_limit(args)
|
|
332
|
-
result = @reviewer.get_by_tag(tag, limit: limit)
|
|
333
|
-
output(result)
|
|
334
342
|
end
|
|
335
343
|
|
|
336
344
|
def by_agent(args)
|
|
@@ -540,8 +548,8 @@ module Agentf
|
|
|
540
548
|
[#{mem["type"]&.upcase}] #{mem["title"]}
|
|
541
549
|
#{mem["created_at"]} by #{mem["agent"]}
|
|
542
550
|
#{mem["description"]}
|
|
551
|
+
#{"Outcome: #{mem['outcome']}" unless mem["outcome"].to_s.empty?}
|
|
543
552
|
#{format_code(mem["code_snippet"]) unless mem["code_snippet"].to_s.empty?}
|
|
544
|
-
Tags: #{mem["tags"]&.join(", ") || "none"}
|
|
545
553
|
OUTPUT
|
|
546
554
|
end
|
|
547
555
|
|
|
@@ -557,26 +565,20 @@ module Agentf
|
|
|
557
565
|
|
|
558
566
|
Commands:
|
|
559
567
|
recent, list List recent memories (default: 10)
|
|
560
|
-
|
|
568
|
+
episodes List episode memories
|
|
561
569
|
lessons List lessons learned
|
|
562
|
-
successes List successes
|
|
563
570
|
intents [kind] List intents (kind: business|feature)
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
add-
|
|
567
|
-
add-feature-intent Store feature intent
|
|
571
|
+
add-intent <kind> Store intent (kind: business|feature)
|
|
572
|
+
add-episode <type> Store episode (type: #{VALID_EPISODE_TYPES.join("|")}) with --outcome=
|
|
573
|
+
add-playbook Store playbook memory
|
|
568
574
|
add-lesson Store lesson memory
|
|
569
|
-
|
|
570
|
-
add-pitfall Store pitfall memory
|
|
571
|
-
tags List all unique tags
|
|
572
|
-
search <query> Search memories by keyword
|
|
575
|
+
search <query> Search memories semantically
|
|
573
576
|
delete id <memory_id> Delete one memory and related edges
|
|
574
577
|
delete last -n <count> Delete most recent memories
|
|
575
578
|
delete all Delete memories and graph/task keys
|
|
576
579
|
neighbors <id> Traverse graph edges from a memory id
|
|
577
580
|
subgraph <ids> Build graph from comma-separated seed ids
|
|
578
581
|
summary, stats Show summary statistics
|
|
579
|
-
by-tag <tag> Get memories with specific tag
|
|
580
582
|
by-agent <agent> Get memories from specific agent
|
|
581
583
|
by-type <type> Get memories by type (#{VALID_EPISODE_TYPES.join("|")})
|
|
582
584
|
|
|
@@ -586,18 +588,19 @@ module Agentf
|
|
|
586
588
|
|
|
587
589
|
Examples:
|
|
588
590
|
agentf memory recent -n 5
|
|
589
|
-
agentf memory
|
|
591
|
+
agentf memory episodes --outcome=negative
|
|
590
592
|
agentf memory intents business -n 5
|
|
591
|
-
agentf memory add-
|
|
592
|
-
agentf memory add-
|
|
593
|
-
agentf memory add-lesson "Refactor strategy" "Extracted adapter seam" --agent=PLANNER --
|
|
594
|
-
agentf memory add-
|
|
595
|
-
agentf memory
|
|
593
|
+
agentf memory add-intent business "Reliability" "Prioritize uptime" --constraints="No downtime;No vendor lock-in"
|
|
594
|
+
agentf memory add-intent feature "Agent handoff" "Improve continuity" --acceptance="Keeps context;Preserves task state"
|
|
595
|
+
agentf memory add-episode lesson "Refactor strategy" "Extracted adapter seam" --agent=PLANNER --outcome=positive
|
|
596
|
+
agentf memory add-episode incident "Auth regression" "JWT expiry not checked" --outcome=negative
|
|
597
|
+
agentf memory add-playbook "Release rollout" "Safe deploy sequence" --steps="deploy canary;monitor;promote"
|
|
598
|
+
agentf memory add-lesson "Refactor strategy" "Extracted adapter seam" --agent=PLANNER
|
|
599
|
+
agentf memory search "react" --type=lesson --outcome=positive
|
|
596
600
|
agentf memory delete id episode_abcd
|
|
597
601
|
agentf memory delete last -n 10 --scope=project
|
|
598
602
|
agentf memory delete all --scope=all --yes
|
|
599
603
|
agentf memory neighbors episode_abcd --depth=2
|
|
600
|
-
agentf memory by-tag "performance"
|
|
601
604
|
agentf memory summary
|
|
602
605
|
HELP
|
|
603
606
|
end
|
data/lib/agentf/cli/router.rb
CHANGED
|
@@ -75,7 +75,7 @@ module Agentf
|
|
|
75
75
|
Usage: agentf <command> [subcommand] [options]
|
|
76
76
|
|
|
77
77
|
Commands:
|
|
78
|
-
memory Manage agent memory (
|
|
78
|
+
memory Manage agent memory (episodes, lessons, playbooks, intents)
|
|
79
79
|
code Explore codebase (glob, grep, tree, related files)
|
|
80
80
|
metrics Show workflow success and provider parity metrics
|
|
81
81
|
architecture Analyze architecture layers and violations
|
|
@@ -12,16 +12,14 @@ module Agentf
|
|
|
12
12
|
def self.manifest
|
|
13
13
|
{
|
|
14
14
|
"name" => NAME,
|
|
15
|
-
"description" => "Review and query Redis-stored memories,
|
|
15
|
+
"description" => "Review and query Redis-stored memories, episodes, and learnings.",
|
|
16
16
|
"commands" => [
|
|
17
17
|
{ "name" => "get_recent_memories", "type" => "function" },
|
|
18
|
-
{ "name" => "
|
|
18
|
+
{ "name" => "get_episodes", "type" => "function" },
|
|
19
19
|
{ "name" => "get_lessons", "type" => "function" },
|
|
20
|
-
{ "name" => "get_successes", "type" => "function" },
|
|
21
|
-
{ "name" => "get_all_tags", "type" => "function" },
|
|
22
|
-
{ "name" => "get_by_tag", "type" => "function" },
|
|
23
20
|
{ "name" => "get_by_type", "type" => "function" },
|
|
24
21
|
{ "name" => "get_by_agent", "type" => "function" },
|
|
22
|
+
{ "name" => "get_intents", "type" => "function" },
|
|
25
23
|
{ "name" => "search", "type" => "function" },
|
|
26
24
|
{ "name" => "get_summary", "type" => "function" },
|
|
27
25
|
{ "name" => "neighbors", "type" => "function" },
|
|
@@ -44,10 +42,9 @@ module Agentf
|
|
|
44
42
|
{ "error" => e.message }
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
format_memories(pitfalls)
|
|
45
|
+
def get_episodes(limit: 10, outcome: nil)
|
|
46
|
+
episodes = @memory.get_episodes(limit: limit, outcome: outcome)
|
|
47
|
+
format_memories(episodes)
|
|
51
48
|
rescue => e
|
|
52
49
|
{ "error" => e.message }
|
|
53
50
|
end
|
|
@@ -60,14 +57,6 @@ module Agentf
|
|
|
60
57
|
{ "error" => e.message }
|
|
61
58
|
end
|
|
62
59
|
|
|
63
|
-
# Get all successes
|
|
64
|
-
def get_successes(limit: 10)
|
|
65
|
-
successes = @memory.get_memories_by_type(type: "success", limit: limit)
|
|
66
|
-
format_memories(successes)
|
|
67
|
-
rescue => e
|
|
68
|
-
{ "error" => e.message }
|
|
69
|
-
end
|
|
70
|
-
|
|
71
60
|
def get_business_intents(limit: 10)
|
|
72
61
|
intents = @memory.get_intents(kind: "business", limit: limit)
|
|
73
62
|
format_memories(intents)
|
|
@@ -89,51 +78,25 @@ module Agentf
|
|
|
89
78
|
{ "error" => e.message }
|
|
90
79
|
end
|
|
91
80
|
|
|
92
|
-
# Get all unique tags from memories
|
|
93
|
-
def get_all_tags
|
|
94
|
-
tags = @memory.get_all_tags
|
|
95
|
-
{ "tags" => tags.sort, "count" => tags.length }
|
|
96
|
-
rescue => e
|
|
97
|
-
{ "error" => e.message }
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Get memories by tag
|
|
101
|
-
def get_by_tag(tag, limit: 10)
|
|
102
|
-
memories = @memory.get_recent_memories(limit: 100)
|
|
103
|
-
filtered = memories.select { |m| m["tags"]&.include?(tag) }
|
|
104
|
-
format_memories(filtered.first(limit))
|
|
105
|
-
rescue => e
|
|
106
|
-
{ "error" => e.message }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
81
|
# Get memories by type (pitfall, lesson, success)
|
|
110
82
|
def get_by_type(type, limit: 10)
|
|
111
|
-
memories = @memory.
|
|
112
|
-
|
|
113
|
-
format_memories(filtered.first(limit))
|
|
83
|
+
memories = @memory.get_memories_by_type(type: type, limit: limit)
|
|
84
|
+
format_memories(memories)
|
|
114
85
|
rescue => e
|
|
115
86
|
{ "error" => e.message }
|
|
116
87
|
end
|
|
117
88
|
|
|
118
89
|
# Get memories by agent
|
|
119
90
|
def get_by_agent(agent, limit: 10)
|
|
120
|
-
memories = @memory.
|
|
121
|
-
|
|
122
|
-
format_memories(filtered.first(limit))
|
|
91
|
+
memories = @memory.get_memories_by_agent(agent: agent, limit: limit)
|
|
92
|
+
format_memories(memories)
|
|
123
93
|
rescue => e
|
|
124
94
|
{ "error" => e.message }
|
|
125
95
|
end
|
|
126
96
|
|
|
127
|
-
# Search memories
|
|
128
|
-
def search(query, limit: 10)
|
|
129
|
-
|
|
130
|
-
q = query.downcase
|
|
131
|
-
filtered = memories.select do |m|
|
|
132
|
-
m["title"]&.downcase&.include?(q) ||
|
|
133
|
-
m["description"]&.downcase&.include?(q) ||
|
|
134
|
-
m["context"]&.downcase&.include?(q)
|
|
135
|
-
end
|
|
136
|
-
format_memories(filtered.first(limit))
|
|
97
|
+
# Search memories semantically with optional type, agent, and outcome filters
|
|
98
|
+
def search(query, limit: 10, type: nil, agent: nil, outcome: nil)
|
|
99
|
+
format_memories(@memory.search_memories(query: query, limit: limit, type: type, agent: agent, outcome: outcome))
|
|
137
100
|
rescue => e
|
|
138
101
|
{ "error" => e.message }
|
|
139
102
|
end
|
|
@@ -141,19 +104,22 @@ module Agentf
|
|
|
141
104
|
# Get summary statistics
|
|
142
105
|
def get_summary
|
|
143
106
|
memories = @memory.get_recent_memories(limit: 100)
|
|
144
|
-
tags = @memory.get_all_tags
|
|
145
107
|
|
|
146
108
|
{
|
|
147
109
|
"total_memories" => memories.length,
|
|
148
110
|
"by_type" => {
|
|
149
|
-
"
|
|
111
|
+
"episode" => memories.count { |m| m["type"] == "episode" },
|
|
150
112
|
"lesson" => memories.count { |m| m["type"] == "lesson" },
|
|
151
|
-
"
|
|
113
|
+
"playbook" => memories.count { |m| m["type"] == "playbook" },
|
|
152
114
|
"business_intent" => memories.count { |m| m["type"] == "business_intent" },
|
|
153
115
|
"feature_intent" => memories.count { |m| m["type"] == "feature_intent" }
|
|
154
116
|
},
|
|
117
|
+
"by_outcome" => {
|
|
118
|
+
"positive" => memories.count { |m| m["outcome"] == "positive" },
|
|
119
|
+
"negative" => memories.count { |m| m["outcome"] == "negative" },
|
|
120
|
+
"neutral" => memories.count { |m| m["outcome"] == "neutral" }
|
|
121
|
+
},
|
|
155
122
|
"by_agent" => memories.each_with_object(Hash.new(0)) { |m, h| h[m["agent"]] += 1 },
|
|
156
|
-
"unique_tags" => tags.length,
|
|
157
123
|
"project" => @project
|
|
158
124
|
}
|
|
159
125
|
rescue => e
|
|
@@ -189,7 +155,7 @@ module Agentf
|
|
|
189
155
|
"description" => m["description"],
|
|
190
156
|
"context" => m["context"],
|
|
191
157
|
"code_snippet" => m["code_snippet"],
|
|
192
|
-
"
|
|
158
|
+
"outcome" => m["outcome"],
|
|
193
159
|
"agent" => m["agent"],
|
|
194
160
|
"metadata" => m["metadata"],
|
|
195
161
|
"entity_ids" => m["entity_ids"],
|
|
@@ -7,8 +7,6 @@ module Agentf
|
|
|
7
7
|
class Metrics
|
|
8
8
|
NAME = "metrics"
|
|
9
9
|
|
|
10
|
-
WORKFLOW_METRICS_TAG = "workflow_metric"
|
|
11
|
-
|
|
12
10
|
def self.manifest
|
|
13
11
|
{
|
|
14
12
|
"name" => NAME,
|
|
@@ -30,12 +28,13 @@ module Agentf
|
|
|
30
28
|
metrics = extract_metrics(workflow_state)
|
|
31
29
|
begin
|
|
32
30
|
@memory.store_episode(
|
|
33
|
-
type: "
|
|
31
|
+
type: "episode",
|
|
34
32
|
title: metric_title(metrics),
|
|
35
33
|
description: metric_description(metrics),
|
|
36
34
|
context: metric_context(metrics),
|
|
37
|
-
tags: metric_tags(metrics),
|
|
38
35
|
agent: Agentf::AgentRoles::ORCHESTRATOR,
|
|
36
|
+
outcome: "positive",
|
|
37
|
+
metadata: { "workflow_metric" => true },
|
|
39
38
|
code_snippet: ""
|
|
40
39
|
)
|
|
41
40
|
{ "status" => "recorded", "metrics" => metrics }
|
|
@@ -171,14 +170,6 @@ module Agentf
|
|
|
171
170
|
}.to_json
|
|
172
171
|
end
|
|
173
172
|
|
|
174
|
-
def metric_tags(metrics)
|
|
175
|
-
[
|
|
176
|
-
WORKFLOW_METRICS_TAG,
|
|
177
|
-
"provider:#{metrics['provider'].to_s.downcase}",
|
|
178
|
-
"workflow:#{metrics['workflow_type']}"
|
|
179
|
-
]
|
|
180
|
-
end
|
|
181
|
-
|
|
182
173
|
def top_contract_violations(records)
|
|
183
174
|
counts = Hash.new(0)
|
|
184
175
|
records.each do |record|
|
|
@@ -191,7 +182,7 @@ module Agentf
|
|
|
191
182
|
memories = @memory.get_recent_memories(limit: limit)
|
|
192
183
|
|
|
193
184
|
memories
|
|
194
|
-
.select { |m|
|
|
185
|
+
.select { |m| m.dig("metadata", "workflow_metric") == true }
|
|
195
186
|
.map do |m|
|
|
196
187
|
context = parse_context_json(m["context"])
|
|
197
188
|
context
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Agentf
|
|
4
4
|
class ContextBuilder
|
|
5
|
-
def initialize(memory:)
|
|
5
|
+
def initialize(memory:, embedding_provider: Agentf::EmbeddingProvider.new)
|
|
6
6
|
@memory = memory
|
|
7
|
+
@embedding_provider = embedding_provider
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def build(agent:, workflow_state:, limit: 8)
|
|
@@ -13,23 +14,12 @@ module Agentf
|
|
|
13
14
|
@memory.get_agent_context(
|
|
14
15
|
agent: agent,
|
|
15
16
|
task_type: task_type,
|
|
16
|
-
|
|
17
|
+
query_text: task,
|
|
18
|
+
query_embedding: @embedding_provider.embed(task),
|
|
17
19
|
limit: limit
|
|
18
20
|
)
|
|
19
21
|
rescue StandardError
|
|
20
22
|
{ "agent" => agent, "intent" => [], "memories" => [], "similar_tasks" => [] }
|
|
21
23
|
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def simple_embedding(text)
|
|
26
|
-
normalized = text.to_s.downcase
|
|
27
|
-
[
|
|
28
|
-
normalized.include?("fix") || normalized.include?("bug") ? 1.0 : 0.0,
|
|
29
|
-
normalized.include?("feature") || normalized.include?("add") ? 1.0 : 0.0,
|
|
30
|
-
normalized.include?("security") ? 1.0 : 0.0,
|
|
31
|
-
normalized.length.to_f / 100.0
|
|
32
|
-
]
|
|
33
|
-
end
|
|
34
24
|
end
|
|
35
25
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
class EmbeddingProvider
|
|
7
|
+
DIMENSIONS = 64
|
|
8
|
+
|
|
9
|
+
def initialize(dimensions: DIMENSIONS)
|
|
10
|
+
@dimensions = dimensions
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def embed(text)
|
|
14
|
+
tokens = tokenize(text)
|
|
15
|
+
return [] if tokens.empty?
|
|
16
|
+
|
|
17
|
+
vector = Array.new(@dimensions, 0.0)
|
|
18
|
+
tokens.each do |token|
|
|
19
|
+
hash = Digest::SHA256.hexdigest(token)[0, 8].to_i(16)
|
|
20
|
+
vector[hash % @dimensions] += 1.0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
magnitude = Math.sqrt(vector.sum { |value| value * value })
|
|
24
|
+
return vector if magnitude.zero?
|
|
25
|
+
|
|
26
|
+
vector.map { |value| (value / magnitude).round(8) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def tokenize(text)
|
|
32
|
+
text.to_s.downcase.scan(/[a-z0-9_]+/).reject { |token| token.length < 2 }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|