agentf 0.4.7 → 0.6.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 +7 -3
- data/lib/agentf/agents/base.rb +31 -3
- data/lib/agentf/agents/debugger.rb +30 -8
- data/lib/agentf/agents/designer.rb +20 -8
- data/lib/agentf/agents/documenter.rb +8 -2
- data/lib/agentf/agents/explorer.rb +29 -11
- data/lib/agentf/agents/reviewer.rb +12 -7
- data/lib/agentf/agents/security.rb +27 -15
- data/lib/agentf/agents/specialist.rb +34 -18
- data/lib/agentf/agents/tester.rb +48 -8
- data/lib/agentf/cli/agent.rb +95 -0
- data/lib/agentf/cli/eval.rb +203 -0
- data/lib/agentf/cli/install.rb +7 -0
- data/lib/agentf/cli/memory.rb +138 -90
- data/lib/agentf/cli/router.rb +16 -4
- data/lib/agentf/cli/update.rb +9 -2
- data/lib/agentf/commands/memory_reviewer.rb +22 -48
- data/lib/agentf/commands/metrics.rb +18 -25
- data/lib/agentf/commands/registry.rb +28 -0
- data/lib/agentf/context_builder.rb +4 -14
- data/lib/agentf/embedding_provider.rb +35 -0
- data/lib/agentf/evals/report.rb +134 -0
- data/lib/agentf/evals/runner.rb +771 -0
- data/lib/agentf/evals/scenario.rb +211 -0
- data/lib/agentf/installer.rb +498 -365
- data/lib/agentf/mcp/server.rb +294 -114
- data/lib/agentf/memory.rb +354 -214
- data/lib/agentf/service/providers.rb +10 -62
- data/lib/agentf/version.rb +1 -1
- data/lib/agentf/workflow_engine.rb +205 -77
- data/lib/agentf.rb +10 -3
- metadata +9 -3
- data/lib/agentf/packs.rb +0 -74
data/lib/agentf/mcp/server.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
begin
|
|
4
4
|
require "mcp"
|
|
5
5
|
rescue LoadError
|
|
6
|
-
require_relative "stub"
|
|
7
6
|
end
|
|
8
7
|
require "json"
|
|
8
|
+
require "set"
|
|
9
9
|
|
|
10
10
|
module Agentf
|
|
11
11
|
module MCP
|
|
@@ -20,7 +20,163 @@ module Agentf
|
|
|
20
20
|
# AGENTF_MCP_ALLOW_WRITES - true/false, controls memory write tools
|
|
21
21
|
# AGENTF_MCP_MAX_ARG_LENGTH - max length per string argument
|
|
22
22
|
class Server
|
|
23
|
+
ToolDefinition = Struct.new(:name, :description, :arguments, :handler, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
class ToolBuilder
|
|
26
|
+
attr_reader :arguments, :handler
|
|
27
|
+
|
|
28
|
+
def initialize(name)
|
|
29
|
+
@name = name
|
|
30
|
+
@arguments = {}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def description(value = nil)
|
|
34
|
+
@description = value unless value.nil?
|
|
35
|
+
@description
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def argument(name, type, required: false, description:, items: nil, **_opts)
|
|
39
|
+
@arguments[name] = {
|
|
40
|
+
type: type,
|
|
41
|
+
items: items,
|
|
42
|
+
required: required,
|
|
43
|
+
schema: {
|
|
44
|
+
type: schema_type(type),
|
|
45
|
+
description: description
|
|
46
|
+
}.tap do |schema|
|
|
47
|
+
schema[:items] = { type: schema_type(items) } if type == Array && items
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def call(&block)
|
|
53
|
+
@handler = block
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def schema_type(type)
|
|
59
|
+
case type&.name
|
|
60
|
+
when "String" then "string"
|
|
61
|
+
when "Integer" then "integer"
|
|
62
|
+
when "Float" then "number"
|
|
63
|
+
when "TrueClass", "FalseClass" then "boolean"
|
|
64
|
+
when "Array" then "array"
|
|
65
|
+
when "Hash" then "object"
|
|
66
|
+
else
|
|
67
|
+
"string"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class RegistryAdapter
|
|
73
|
+
def initialize(name:, version:)
|
|
74
|
+
@name = name
|
|
75
|
+
@version = version
|
|
76
|
+
@tools = {}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def tool(name, &block)
|
|
80
|
+
builder = ToolBuilder.new(name)
|
|
81
|
+
builder.instance_eval(&block)
|
|
82
|
+
@tools[name] = ToolDefinition.new(
|
|
83
|
+
name: name,
|
|
84
|
+
description: builder.description,
|
|
85
|
+
arguments: builder.arguments,
|
|
86
|
+
handler: builder.handler
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def list_tools
|
|
91
|
+
@tools.values.map do |tool|
|
|
92
|
+
{
|
|
93
|
+
name: tool.name,
|
|
94
|
+
description: tool.description.to_s,
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: tool.arguments.transform_values { |arg| arg[:schema] },
|
|
98
|
+
required: tool.arguments.select { |_k, arg| arg[:required] }.keys.map(&:to_s)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def call_tool(name, **args)
|
|
105
|
+
tool = @tools[name]
|
|
106
|
+
raise "Unknown tool: #{name}" unless tool
|
|
107
|
+
|
|
108
|
+
tool.handler.call(args)
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
e.message
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run
|
|
114
|
+
runtime_server = build_runtime_server
|
|
115
|
+
transport = ::MCP::Server::Transports::StdioTransport.new(runtime_server)
|
|
116
|
+
runtime_server.transport = transport if runtime_server.respond_to?(:transport=)
|
|
117
|
+
transport.open
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def build_runtime_server
|
|
123
|
+
ensure_runtime_support!
|
|
124
|
+
|
|
125
|
+
runtime_server = ::MCP::Server.new(name: @name, version: @version)
|
|
126
|
+
adapter = self
|
|
127
|
+
@tools.each_value do |tool|
|
|
128
|
+
runtime_server.define_tool(
|
|
129
|
+
name: tool.name,
|
|
130
|
+
description: tool.description,
|
|
131
|
+
input_schema: runtime_input_schema(tool.arguments)
|
|
132
|
+
) do |**kwargs|
|
|
133
|
+
result = tool.handler.call(kwargs)
|
|
134
|
+
adapter.send(:build_response, result)
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
::MCP::Tool::Response.new([::MCP::Content::Text.new(e.message).to_h], error: true)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
runtime_server
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def ensure_runtime_support!
|
|
143
|
+
return if defined?(::MCP::Server::Transports::StdioTransport) && defined?(::MCP::Tool::Response) && defined?(::MCP::Content::Text)
|
|
144
|
+
|
|
145
|
+
raise "Installed MCP runtime does not support stdio server transport"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def runtime_input_schema(arguments)
|
|
149
|
+
schema = {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: arguments.transform_keys(&:to_s).transform_values { |arg| arg[:schema].transform_keys(&:to_s) }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
required = arguments.select { |_k, arg| arg[:required] }.keys.map(&:to_s)
|
|
155
|
+
schema[:required] = required unless required.empty?
|
|
156
|
+
schema
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def build_response(result)
|
|
160
|
+
return result if result.is_a?(::MCP::Tool::Response)
|
|
161
|
+
|
|
162
|
+
text = result.is_a?(String) ? result : JSON.generate(result)
|
|
163
|
+
structured = parse_structured_content(result)
|
|
164
|
+
::MCP::Tool::Response.new([::MCP::Content::Text.new(text).to_h], structured_content: structured)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def parse_structured_content(result)
|
|
168
|
+
return result if result.is_a?(Hash)
|
|
169
|
+
return JSON.parse(result) if result.is_a?(String)
|
|
170
|
+
return result.as_json if result.respond_to?(:as_json)
|
|
171
|
+
|
|
172
|
+
nil
|
|
173
|
+
rescue JSON::ParserError
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
23
178
|
KNOWN_TOOLS = %w[
|
|
179
|
+
agentf-mcp-list-tools
|
|
24
180
|
agentf-code-glob
|
|
25
181
|
agentf-code-grep
|
|
26
182
|
agentf-code-tree
|
|
@@ -28,29 +184,24 @@ module Agentf
|
|
|
28
184
|
agentf-architecture-analyze-layers
|
|
29
185
|
agentf-memory-recent
|
|
30
186
|
agentf-memory-search
|
|
31
|
-
agentf-memory-by-tag
|
|
32
187
|
agentf-memory-by-agent
|
|
33
188
|
agentf-memory-by-type
|
|
34
|
-
agentf-memory-
|
|
35
|
-
agentf-memory-pitfalls
|
|
189
|
+
agentf-memory-episodes
|
|
36
190
|
agentf-memory-lessons
|
|
37
|
-
agentf-memory-successes
|
|
38
191
|
agentf-memory-intents
|
|
39
192
|
agentf-memory-business-intents
|
|
40
193
|
agentf-memory-feature-intents
|
|
41
194
|
agentf-memory-neighbors
|
|
42
195
|
agentf-memory-subgraph
|
|
196
|
+
agentf-memory-add-playbook
|
|
43
197
|
agentf-memory-add-lesson
|
|
44
|
-
agentf-memory-add-success
|
|
45
|
-
agentf-memory-add-pitfall
|
|
46
198
|
agentf-memory-add-business-intent
|
|
47
199
|
agentf-memory-add-feature-intent
|
|
48
200
|
].freeze
|
|
49
201
|
|
|
50
202
|
WRITE_TOOLS = Set.new(%w[
|
|
203
|
+
agentf-memory-add-playbook
|
|
51
204
|
agentf-memory-add-lesson
|
|
52
|
-
agentf-memory-add-success
|
|
53
|
-
agentf-memory-add-pitfall
|
|
54
205
|
agentf-memory-add-business-intent
|
|
55
206
|
agentf-memory-add-feature-intent
|
|
56
207
|
]).freeze
|
|
@@ -66,6 +217,25 @@ module Agentf
|
|
|
66
217
|
@server = build_server
|
|
67
218
|
end
|
|
68
219
|
|
|
220
|
+
# Helper to centralize confirmation handling for MCP server write tools.
|
|
221
|
+
# Yields the block that performs the memory write and returns either the
|
|
222
|
+
# block result or the normalized confirmation hash produced by
|
|
223
|
+
# Agentf::Agents::Base#safe_memory_write.
|
|
224
|
+
def safe_mcp_memory_write(memory, attempted: {})
|
|
225
|
+
begin
|
|
226
|
+
yield
|
|
227
|
+
nil
|
|
228
|
+
rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
|
|
229
|
+
{
|
|
230
|
+
"confirmation_required" => true,
|
|
231
|
+
"confirmation_details" => e.details,
|
|
232
|
+
"attempted" => attempted,
|
|
233
|
+
"confirmed_write_token" => "confirmed",
|
|
234
|
+
"confirmation_prompt" => "Ask the user whether to save this memory. If they approve, call the same tool again after confirmation. If they decline, do not retry."
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
69
239
|
# Start the stdio read loop (blocks until stdin closes).
|
|
70
240
|
def run
|
|
71
241
|
@server.run
|
|
@@ -139,7 +309,7 @@ module Agentf
|
|
|
139
309
|
# ── Server construction ─────────────────────────────────────
|
|
140
310
|
|
|
141
311
|
def build_server
|
|
142
|
-
s =
|
|
312
|
+
s = RegistryAdapter.new(name: "agentf", version: Agentf::VERSION)
|
|
143
313
|
register_code_tools(s)
|
|
144
314
|
register_memory_tools(s)
|
|
145
315
|
register_architecture_tools(s)
|
|
@@ -218,7 +388,7 @@ module Agentf
|
|
|
218
388
|
end
|
|
219
389
|
|
|
220
390
|
s.tool("agentf-memory-search") do
|
|
221
|
-
description "Search memories
|
|
391
|
+
description "Search memories semantically."
|
|
222
392
|
argument :query, String, required: true, description: "Search query"
|
|
223
393
|
argument :limit, Integer, required: false, description: "How many results to return (1-100)"
|
|
224
394
|
call do |args|
|
|
@@ -228,13 +398,13 @@ module Agentf
|
|
|
228
398
|
end
|
|
229
399
|
end
|
|
230
400
|
|
|
231
|
-
s.tool("agentf-memory-
|
|
232
|
-
description "
|
|
233
|
-
argument :
|
|
401
|
+
s.tool("agentf-memory-episodes") do
|
|
402
|
+
description "List episode memories with optional outcome filter."
|
|
403
|
+
argument :outcome, String, required: false, description: "Optional outcome filter: positive|negative|neutral"
|
|
234
404
|
argument :limit, Integer, required: false, description: "How many results to return (1-100)"
|
|
235
405
|
call do |args|
|
|
236
|
-
mcp_server.send(:guard!, "agentf-memory-
|
|
237
|
-
result = reviewer.
|
|
406
|
+
mcp_server.send(:guard!, "agentf-memory-episodes", **args)
|
|
407
|
+
result = reviewer.get_episodes(limit: args[:limit] || 10, outcome: args[:outcome])
|
|
238
408
|
JSON.generate(result)
|
|
239
409
|
end
|
|
240
410
|
end
|
|
@@ -261,25 +431,6 @@ module Agentf
|
|
|
261
431
|
end
|
|
262
432
|
end
|
|
263
433
|
|
|
264
|
-
s.tool("agentf-memory-tags") do
|
|
265
|
-
description "List all unique memory tags."
|
|
266
|
-
call do |args|
|
|
267
|
-
mcp_server.send(:guard!, "agentf-memory-tags", **args)
|
|
268
|
-
result = reviewer.get_all_tags
|
|
269
|
-
JSON.generate(result)
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
s.tool("agentf-memory-pitfalls") do
|
|
274
|
-
description "List pitfall memories."
|
|
275
|
-
argument :limit, Integer, required: false, description: "How many results to return (1-100)"
|
|
276
|
-
call do |args|
|
|
277
|
-
mcp_server.send(:guard!, "agentf-memory-pitfalls", **args)
|
|
278
|
-
result = reviewer.get_pitfalls(limit: args[:limit] || 10)
|
|
279
|
-
JSON.generate(result)
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
|
|
283
434
|
s.tool("agentf-memory-lessons") do
|
|
284
435
|
description "List lesson memories."
|
|
285
436
|
argument :limit, Integer, required: false, description: "How many results to return (1-100)"
|
|
@@ -290,16 +441,6 @@ module Agentf
|
|
|
290
441
|
end
|
|
291
442
|
end
|
|
292
443
|
|
|
293
|
-
s.tool("agentf-memory-successes") do
|
|
294
|
-
description "List success memories."
|
|
295
|
-
argument :limit, Integer, required: false, description: "How many results to return (1-100)"
|
|
296
|
-
call do |args|
|
|
297
|
-
mcp_server.send(:guard!, "agentf-memory-successes", **args)
|
|
298
|
-
result = reviewer.get_successes(limit: args[:limit] || 10)
|
|
299
|
-
JSON.generate(result)
|
|
300
|
-
end
|
|
301
|
-
end
|
|
302
|
-
|
|
303
444
|
s.tool("agentf-memory-intents") do
|
|
304
445
|
description "List intents (business|feature)."
|
|
305
446
|
argument :kind, String, required: false, description: "Optional: business|feature"
|
|
@@ -344,19 +485,27 @@ module Agentf
|
|
|
344
485
|
description "Store a business intent in Redis."
|
|
345
486
|
argument :title, String, required: true, description: "Intent title"
|
|
346
487
|
argument :description, String, required: true, description: "Intent description"
|
|
347
|
-
argument :tags, Array, required: false, items: String, description: "Tags"
|
|
348
488
|
argument :constraints, Array, required: false, items: String, description: "Constraints"
|
|
349
489
|
argument :priority, Integer, required: false, description: "Priority"
|
|
350
490
|
call do |args|
|
|
351
491
|
mcp_server.send(:guard!, "agentf-memory-add-business-intent", **args)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
492
|
+
begin
|
|
493
|
+
id = nil
|
|
494
|
+
res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-business-intent", args: args }) do
|
|
495
|
+
id = memory.store_business_intent(
|
|
496
|
+
title: args[:title],
|
|
497
|
+
description: args[:description],
|
|
498
|
+
constraints: args[:constraints] || [],
|
|
499
|
+
priority: args[:priority] || 1
|
|
500
|
+
)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
504
|
+
JSON.generate(confirmation_required: true, confirmation_details: res["confirmation_details"], attempted: res["attempted"])
|
|
505
|
+
else
|
|
506
|
+
JSON.generate(id: id, type: "business_intent", status: "stored")
|
|
507
|
+
end
|
|
508
|
+
end
|
|
360
509
|
end
|
|
361
510
|
end
|
|
362
511
|
|
|
@@ -364,21 +513,29 @@ module Agentf
|
|
|
364
513
|
description "Store a feature intent in Redis."
|
|
365
514
|
argument :title, String, required: true, description: "Intent title"
|
|
366
515
|
argument :description, String, required: true, description: "Intent description"
|
|
367
|
-
argument :tags, Array, required: false, items: String, description: "Tags"
|
|
368
516
|
argument :acceptance, Array, required: false, items: String, description: "Acceptance criteria"
|
|
369
517
|
argument :non_goals, Array, required: false, items: String, description: "Non-goals"
|
|
370
518
|
argument :related_task_id, String, required: false, description: "Related task id"
|
|
371
519
|
call do |args|
|
|
372
520
|
mcp_server.send(:guard!, "agentf-memory-add-feature-intent", **args)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
521
|
+
begin
|
|
522
|
+
id = nil
|
|
523
|
+
res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-feature-intent", args: args }) do
|
|
524
|
+
id = memory.store_feature_intent(
|
|
525
|
+
title: args[:title],
|
|
526
|
+
description: args[:description],
|
|
527
|
+
acceptance_criteria: args[:acceptance] || [],
|
|
528
|
+
non_goals: args[:non_goals] || [],
|
|
529
|
+
related_task_id: args[:related_task_id]
|
|
530
|
+
)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
534
|
+
JSON.generate(confirmation_required: true, confirmation_details: res["confirmation_details"], attempted: res["attempted"])
|
|
535
|
+
else
|
|
536
|
+
JSON.generate(id: id, type: "feature_intent", status: "stored")
|
|
537
|
+
end
|
|
538
|
+
end
|
|
382
539
|
end
|
|
383
540
|
end
|
|
384
541
|
|
|
@@ -418,69 +575,92 @@ module Agentf
|
|
|
418
575
|
end
|
|
419
576
|
end
|
|
420
577
|
|
|
578
|
+
s.tool("agentf-mcp-list-tools") do
|
|
579
|
+
description "List MCP tools and current guardrail status."
|
|
580
|
+
call do |_args|
|
|
581
|
+
# Use guard to ensure the caller is allowed to invoke tools
|
|
582
|
+
mcp_server.send(:guard!, "agentf-mcp-list-tools", **{})
|
|
583
|
+
|
|
584
|
+
tools = s.list_tools
|
|
585
|
+
guard = {
|
|
586
|
+
allowed_tools: mcp_server.guardrails[:allowed_tools].to_a,
|
|
587
|
+
allow_writes: mcp_server.guardrails[:allow_writes],
|
|
588
|
+
max_arg_length: mcp_server.guardrails[:max_arg_length]
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
JSON.generate({ tools: tools, guardrails: guard })
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
421
595
|
s.tool("agentf-memory-add-lesson") do
|
|
422
596
|
description "Store a lesson memory in Redis."
|
|
423
597
|
argument :title, String, required: true, description: "Lesson title"
|
|
424
598
|
argument :description, String, required: true, description: "Lesson description"
|
|
425
599
|
argument :agent, String, required: false, description: "Agent name"
|
|
426
|
-
argument :tags, Array, required: false, items: String, description: "Tags"
|
|
427
600
|
argument :context, String, required: false, description: "Context"
|
|
428
601
|
call do |args|
|
|
429
602
|
mcp_server.send(:guard!, "agentf-memory-add-lesson", **args)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
description: args[:description],
|
|
456
|
-
agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
|
|
457
|
-
tags: args[:tags] || [],
|
|
458
|
-
context: args[:context].to_s,
|
|
459
|
-
code_snippet: ""
|
|
460
|
-
)
|
|
461
|
-
JSON.generate(id: id, type: "success", status: "stored")
|
|
603
|
+
begin
|
|
604
|
+
id = nil
|
|
605
|
+
res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-lesson", args: args }) do
|
|
606
|
+
id = memory.store_episode(
|
|
607
|
+
type: "lesson",
|
|
608
|
+
title: args[:title],
|
|
609
|
+
description: args[:description],
|
|
610
|
+
agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
|
|
611
|
+
context: args[:context].to_s,
|
|
612
|
+
code_snippet: ""
|
|
613
|
+
)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
617
|
+
JSON.generate(
|
|
618
|
+
confirmation_required: true,
|
|
619
|
+
confirmation_details: res["confirmation_details"],
|
|
620
|
+
attempted: res["attempted"],
|
|
621
|
+
confirmed_write_token: res["confirmed_write_token"],
|
|
622
|
+
confirmation_prompt: res["confirmation_prompt"]
|
|
623
|
+
)
|
|
624
|
+
else
|
|
625
|
+
JSON.generate(id: id, type: "lesson", status: "stored")
|
|
626
|
+
end
|
|
627
|
+
end
|
|
462
628
|
end
|
|
463
629
|
end
|
|
464
630
|
|
|
465
|
-
s.tool("agentf-memory-add-
|
|
466
|
-
description "Store a
|
|
467
|
-
argument :title, String, required: true, description: "
|
|
468
|
-
argument :description, String, required: true, description: "
|
|
631
|
+
s.tool("agentf-memory-add-playbook") do
|
|
632
|
+
description "Store a playbook memory in Redis."
|
|
633
|
+
argument :title, String, required: true, description: "Playbook title"
|
|
634
|
+
argument :description, String, required: true, description: "Playbook description"
|
|
469
635
|
argument :agent, String, required: false, description: "Agent name"
|
|
470
|
-
argument :
|
|
471
|
-
argument :
|
|
636
|
+
argument :steps, Array, required: false, items: String, description: "Ordered playbook steps"
|
|
637
|
+
argument :feature_area, String, required: false, description: "Feature area"
|
|
472
638
|
call do |args|
|
|
473
|
-
mcp_server.send(:guard!, "agentf-memory-add-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
639
|
+
mcp_server.send(:guard!, "agentf-memory-add-playbook", **args)
|
|
640
|
+
begin
|
|
641
|
+
id = nil
|
|
642
|
+
res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-playbook", args: args }) do
|
|
643
|
+
id = memory.store_playbook(
|
|
644
|
+
title: args[:title],
|
|
645
|
+
description: args[:description],
|
|
646
|
+
agent: args[:agent] || Agentf::AgentRoles::PLANNER,
|
|
647
|
+
steps: args[:steps] || [],
|
|
648
|
+
feature_area: args[:feature_area]
|
|
649
|
+
)
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
653
|
+
JSON.generate(
|
|
654
|
+
confirmation_required: true,
|
|
655
|
+
confirmation_details: res["confirmation_details"],
|
|
656
|
+
attempted: res["attempted"],
|
|
657
|
+
confirmed_write_token: res["confirmed_write_token"],
|
|
658
|
+
confirmation_prompt: res["confirmation_prompt"]
|
|
659
|
+
)
|
|
660
|
+
else
|
|
661
|
+
JSON.generate(id: id, type: "playbook", status: "stored")
|
|
662
|
+
end
|
|
663
|
+
end
|
|
484
664
|
end
|
|
485
665
|
end
|
|
486
666
|
end
|