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.
@@ -20,6 +20,7 @@ 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
+ include Agentf::Memory::ConfirmationHandler
23
24
  ToolDefinition = Struct.new(:name, :description, :arguments, :handler, keyword_init: true)
24
25
 
25
26
  class ToolBuilder
@@ -176,6 +177,7 @@ module Agentf
176
177
  end
177
178
 
178
179
  KNOWN_TOOLS = %w[
180
+ agentf-mcp-list-tools
179
181
  agentf-code-glob
180
182
  agentf-code-grep
181
183
  agentf-code-tree
@@ -183,31 +185,25 @@ module Agentf
183
185
  agentf-architecture-analyze-layers
184
186
  agentf-memory-recent
185
187
  agentf-memory-search
186
- agentf-memory-by-tag
187
188
  agentf-memory-by-agent
188
189
  agentf-memory-by-type
189
- agentf-memory-tags
190
- agentf-memory-pitfalls
190
+ agentf-memory-episodes
191
191
  agentf-memory-lessons
192
- agentf-memory-successes
193
192
  agentf-memory-intents
194
- agentf-memory-business-intents
195
- agentf-memory-feature-intents
193
+ agentf-memory-summary
196
194
  agentf-memory-neighbors
197
195
  agentf-memory-subgraph
196
+ agentf-memory-add-intent
197
+ agentf-memory-add-episode
198
+ agentf-memory-add-playbook
198
199
  agentf-memory-add-lesson
199
- agentf-memory-add-success
200
- agentf-memory-add-pitfall
201
- agentf-memory-add-business-intent
202
- agentf-memory-add-feature-intent
203
200
  ].freeze
204
201
 
205
202
  WRITE_TOOLS = Set.new(%w[
203
+ agentf-memory-add-intent
204
+ agentf-memory-add-episode
205
+ agentf-memory-add-playbook
206
206
  agentf-memory-add-lesson
207
- agentf-memory-add-success
208
- agentf-memory-add-pitfall
209
- agentf-memory-add-business-intent
210
- agentf-memory-add-feature-intent
211
207
  ]).freeze
212
208
 
213
209
  attr_reader :server, :guardrails
@@ -221,25 +217,6 @@ module Agentf
221
217
  @server = build_server
222
218
  end
223
219
 
224
- # Helper to centralize confirmation handling for MCP server write tools.
225
- # Yields the block that performs the memory write and returns either the
226
- # block result or the normalized confirmation hash produced by
227
- # Agentf::Agents::Base#safe_memory_write.
228
- def safe_mcp_memory_write(memory, attempted: {})
229
- begin
230
- yield
231
- nil
232
- rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
233
- {
234
- "confirmation_required" => true,
235
- "confirmation_details" => e.details,
236
- "attempted" => attempted,
237
- "confirmed_write_token" => "confirmed",
238
- "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."
239
- }
240
- end
241
- end
242
-
243
220
  # Start the stdio read loop (blocks until stdin closes).
244
221
  def run
245
222
  @server.run
@@ -392,23 +369,26 @@ module Agentf
392
369
  end
393
370
 
394
371
  s.tool("agentf-memory-search") do
395
- description "Search memories by keyword."
372
+ description "Search memories semantically. Supports optional filters for type, agent, and outcome."
396
373
  argument :query, String, required: true, description: "Search query"
397
374
  argument :limit, Integer, required: false, description: "How many results to return (1-100)"
375
+ argument :type, String, required: false, description: "Filter by type: episode|lesson|playbook|business_intent|feature_intent|incident"
376
+ argument :agent, String, required: false, description: "Filter by agent name"
377
+ argument :outcome, String, required: false, description: "Filter by outcome: positive|negative|neutral"
398
378
  call do |args|
399
379
  mcp_server.send(:guard!, "agentf-memory-search", **args)
400
- result = reviewer.search(args[:query], limit: args[:limit] || 10)
380
+ result = reviewer.search(args[:query], limit: args[:limit] || 10, type: args[:type], agent: args[:agent], outcome: args[:outcome])
401
381
  JSON.generate(result)
402
382
  end
403
383
  end
404
384
 
405
- s.tool("agentf-memory-by-tag") do
406
- description "Get memories by tag."
407
- argument :tag, String, required: true, description: "Tag to filter"
385
+ s.tool("agentf-memory-episodes") do
386
+ description "List episode memories with optional outcome filter."
387
+ argument :outcome, String, required: false, description: "Optional outcome filter: positive|negative|neutral"
408
388
  argument :limit, Integer, required: false, description: "How many results to return (1-100)"
409
389
  call do |args|
410
- mcp_server.send(:guard!, "agentf-memory-by-tag", **args)
411
- result = reviewer.get_by_tag(args[:tag], limit: args[:limit] || 10)
390
+ mcp_server.send(:guard!, "agentf-memory-episodes", **args)
391
+ result = reviewer.get_episodes(limit: args[:limit] || 10, outcome: args[:outcome])
412
392
  JSON.generate(result)
413
393
  end
414
394
  end
@@ -435,25 +415,6 @@ module Agentf
435
415
  end
436
416
  end
437
417
 
438
- s.tool("agentf-memory-tags") do
439
- description "List all unique memory tags."
440
- call do |args|
441
- mcp_server.send(:guard!, "agentf-memory-tags", **args)
442
- result = reviewer.get_all_tags
443
- JSON.generate(result)
444
- end
445
- end
446
-
447
- s.tool("agentf-memory-pitfalls") do
448
- description "List pitfall memories."
449
- argument :limit, Integer, required: false, description: "How many results to return (1-100)"
450
- call do |args|
451
- mcp_server.send(:guard!, "agentf-memory-pitfalls", **args)
452
- result = reviewer.get_pitfalls(limit: args[:limit] || 10)
453
- JSON.generate(result)
454
- end
455
- end
456
-
457
418
  s.tool("agentf-memory-lessons") do
458
419
  description "List lesson memories."
459
420
  argument :limit, Integer, required: false, description: "How many results to return (1-100)"
@@ -464,18 +425,8 @@ module Agentf
464
425
  end
465
426
  end
466
427
 
467
- s.tool("agentf-memory-successes") do
468
- description "List success memories."
469
- argument :limit, Integer, required: false, description: "How many results to return (1-100)"
470
- call do |args|
471
- mcp_server.send(:guard!, "agentf-memory-successes", **args)
472
- result = reviewer.get_successes(limit: args[:limit] || 10)
473
- JSON.generate(result)
474
- end
475
- end
476
-
477
428
  s.tool("agentf-memory-intents") do
478
- description "List intents (business|feature)."
429
+ description "List intents (business|feature). Pass kind to filter."
479
430
  argument :kind, String, required: false, description: "Optional: business|feature"
480
431
  argument :limit, Integer, required: false, description: "How many results to return (1-100)"
481
432
  call do |args|
@@ -514,65 +465,85 @@ module Agentf
514
465
  end
515
466
  end
516
467
 
517
- s.tool("agentf-memory-add-business-intent") do
518
- description "Store a business intent in Redis."
468
+ s.tool("agentf-memory-add-intent") do
469
+ description "Store a business or feature intent. Pass kind: business or feature."
470
+ argument :kind, String, required: true, description: "Intent type: business|feature"
519
471
  argument :title, String, required: true, description: "Intent title"
520
472
  argument :description, String, required: true, description: "Intent description"
521
- argument :tags, Array, required: false, items: String, description: "Tags"
522
- argument :constraints, Array, required: false, items: String, description: "Constraints"
523
- argument :priority, Integer, required: false, description: "Priority"
473
+ argument :constraints, Array, required: false, items: String, description: "Business intent constraints"
474
+ argument :priority, Integer, required: false, description: "Business intent priority"
475
+ argument :acceptance, Array, required: false, items: String, description: "Feature intent acceptance criteria"
476
+ argument :non_goals, Array, required: false, items: String, description: "Feature intent non-goals"
477
+ argument :related_task_id, String, required: false, description: "Related task id (feature intents)"
524
478
  call do |args|
525
- mcp_server.send(:guard!, "agentf-memory-add-business-intent", **args)
526
- begin
527
- id = nil
528
- res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-business-intent", args: args }) do
529
- id = memory.store_business_intent(
530
- title: args[:title],
531
- description: args[:description],
532
- tags: args[:tags] || [],
533
- constraints: args[:constraints] || [],
534
- priority: args[:priority] || 1
535
- )
536
- end
537
-
538
- if res.is_a?(Hash) && res["confirmation_required"]
539
- JSON.generate(confirmation_required: true, confirmation_details: res["confirmation_details"], attempted: res["attempted"])
540
- else
541
- JSON.generate(id: id, type: "business_intent", status: "stored")
542
- end
543
- end
479
+ mcp_server.send(:guard!, "agentf-memory-add-intent", **args)
480
+ id = nil
481
+ kind = args[:kind].to_s.downcase
482
+ res = mcp_server.send(:safe_memory_write, memory, attempted: { tool: "agentf-memory-add-intent", args: args }) do
483
+ id = case kind
484
+ when "business"
485
+ memory.store_business_intent(
486
+ title: args[:title],
487
+ description: args[:description],
488
+ constraints: args[:constraints] || [],
489
+ priority: args[:priority] || 1
490
+ )
491
+ when "feature"
492
+ memory.store_feature_intent(
493
+ title: args[:title],
494
+ description: args[:description],
495
+ acceptance_criteria: args[:acceptance] || [],
496
+ non_goals: args[:non_goals] || [],
497
+ related_task_id: args[:related_task_id]
498
+ )
499
+ else
500
+ raise ArgumentError, "kind must be business or feature, got: #{kind}"
501
+ end
502
+ end
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: "#{kind}_intent", status: "stored")
507
+ end
544
508
  end
545
509
  end
546
510
 
547
- s.tool("agentf-memory-add-feature-intent") do
548
- description "Store a feature intent in Redis."
549
- argument :title, String, required: true, description: "Intent title"
550
- argument :description, String, required: true, description: "Intent description"
551
- argument :tags, Array, required: false, items: String, description: "Tags"
552
- argument :acceptance, Array, required: false, items: String, description: "Acceptance criteria"
553
- argument :non_goals, Array, required: false, items: String, description: "Non-goals"
554
- argument :related_task_id, String, required: false, description: "Related task id"
511
+ s.tool("agentf-memory-add-episode") do
512
+ description "Store a memory episode with type and outcome (type: episode|lesson|incident|playbook, outcome: positive|negative|neutral)."
513
+ argument :type, String, required: true, description: "Episode type: episode|lesson|incident|playbook"
514
+ argument :title, String, required: true, description: "Episode title"
515
+ argument :description, String, required: true, description: "Episode description"
516
+ argument :outcome, String, required: false, description: "Outcome: positive|negative|neutral"
517
+ argument :agent, String, required: false, description: "Agent name"
518
+ argument :context, String, required: false, description: "Additional context"
519
+ argument :code_snippet, String, required: false, description: "Code snippet"
555
520
  call do |args|
556
- mcp_server.send(:guard!, "agentf-memory-add-feature-intent", **args)
557
- begin
558
- id = nil
559
- res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-feature-intent", args: args }) do
560
- id = memory.store_feature_intent(
561
- title: args[:title],
562
- description: args[:description],
563
- tags: args[:tags] || [],
564
- acceptance_criteria: args[:acceptance] || [],
565
- non_goals: args[:non_goals] || [],
566
- related_task_id: args[:related_task_id]
567
- )
568
- end
521
+ mcp_server.send(:guard!, "agentf-memory-add-episode", **args)
522
+ id = nil
523
+ res = mcp_server.send(:safe_memory_write, memory, attempted: { tool: "agentf-memory-add-episode", args: args }) do
524
+ id = memory.store_episode(
525
+ type: args[:type],
526
+ title: args[:title],
527
+ description: args[:description],
528
+ outcome: args[:outcome],
529
+ agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
530
+ context: args[:context].to_s,
531
+ code_snippet: args[:code_snippet].to_s
532
+ )
533
+ end
534
+ if res.is_a?(Hash) && res["confirmation_required"]
535
+ JSON.generate(confirmation_required: true, confirmation_details: res["confirmation_details"], attempted: res["attempted"])
536
+ else
537
+ JSON.generate(id: id, type: args[:type], status: "stored")
538
+ end
539
+ end
540
+ end
569
541
 
570
- if res.is_a?(Hash) && res["confirmation_required"]
571
- JSON.generate(confirmation_required: true, confirmation_details: res["confirmation_details"], attempted: res["attempted"])
572
- else
573
- JSON.generate(id: id, type: "feature_intent", status: "stored")
574
- end
575
- end
542
+ s.tool("agentf-memory-summary") do
543
+ description "Get summary statistics: counts of memories by type, agent, and outcome."
544
+ call do |_args|
545
+ mcp_server.send(:guard!, "agentf-memory-summary")
546
+ JSON.generate(reviewer.get_summary)
576
547
  end
577
548
  end
578
549
 
@@ -612,24 +583,39 @@ module Agentf
612
583
  end
613
584
  end
614
585
 
586
+ s.tool("agentf-mcp-list-tools") do
587
+ description "List MCP tools and current guardrail status."
588
+ call do |_args|
589
+ # Use guard to ensure the caller is allowed to invoke tools
590
+ mcp_server.send(:guard!, "agentf-mcp-list-tools", **{})
591
+
592
+ tools = s.list_tools
593
+ guard = {
594
+ allowed_tools: mcp_server.guardrails[:allowed_tools].to_a,
595
+ allow_writes: mcp_server.guardrails[:allow_writes],
596
+ max_arg_length: mcp_server.guardrails[:max_arg_length]
597
+ }
598
+
599
+ JSON.generate({ tools: tools, guardrails: guard })
600
+ end
601
+ end
602
+
615
603
  s.tool("agentf-memory-add-lesson") do
616
604
  description "Store a lesson memory in Redis."
617
605
  argument :title, String, required: true, description: "Lesson title"
618
606
  argument :description, String, required: true, description: "Lesson description"
619
607
  argument :agent, String, required: false, description: "Agent name"
620
- argument :tags, Array, required: false, items: String, description: "Tags"
621
608
  argument :context, String, required: false, description: "Context"
622
609
  call do |args|
623
610
  mcp_server.send(:guard!, "agentf-memory-add-lesson", **args)
624
611
  begin
625
612
  id = nil
626
- res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-lesson", args: args }) do
613
+ res = mcp_server.send(:safe_memory_write, memory, attempted: { tool: "agentf-memory-add-lesson", args: args }) do
627
614
  id = memory.store_episode(
628
615
  type: "lesson",
629
616
  title: args[:title],
630
617
  description: args[:description],
631
618
  agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
632
- tags: args[:tags] || [],
633
619
  context: args[:context].to_s,
634
620
  code_snippet: ""
635
621
  )
@@ -650,64 +636,24 @@ module Agentf
650
636
  end
651
637
  end
652
638
 
653
- s.tool("agentf-memory-add-success") do
654
- description "Store a success memory in Redis."
655
- argument :title, String, required: true, description: "Success title"
656
- argument :description, String, required: true, description: "Success description"
657
- argument :agent, String, required: false, description: "Agent name"
658
- argument :tags, Array, required: false, items: String, description: "Tags"
659
- argument :context, String, required: false, description: "Context"
660
- call do |args|
661
- mcp_server.send(:guard!, "agentf-memory-add-success", **args)
662
- begin
663
- id = nil
664
- res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-success", args: args }) do
665
- id = memory.store_episode(
666
- type: "success",
667
- title: args[:title],
668
- description: args[:description],
669
- agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
670
- tags: args[:tags] || [],
671
- context: args[:context].to_s,
672
- code_snippet: ""
673
- )
674
- end
675
-
676
- if res.is_a?(Hash) && res["confirmation_required"]
677
- JSON.generate(
678
- confirmation_required: true,
679
- confirmation_details: res["confirmation_details"],
680
- attempted: res["attempted"],
681
- confirmed_write_token: res["confirmed_write_token"],
682
- confirmation_prompt: res["confirmation_prompt"]
683
- )
684
- else
685
- JSON.generate(id: id, type: "success", status: "stored")
686
- end
687
- end
688
- end
689
- end
690
-
691
- s.tool("agentf-memory-add-pitfall") do
692
- description "Store a pitfall memory in Redis."
693
- argument :title, String, required: true, description: "Pitfall title"
694
- argument :description, String, required: true, description: "Pitfall description"
639
+ s.tool("agentf-memory-add-playbook") do
640
+ description "Store a playbook memory in Redis."
641
+ argument :title, String, required: true, description: "Playbook title"
642
+ argument :description, String, required: true, description: "Playbook description"
695
643
  argument :agent, String, required: false, description: "Agent name"
696
- argument :tags, Array, required: false, items: String, description: "Tags"
697
- argument :context, String, required: false, description: "Context"
644
+ argument :steps, Array, required: false, items: String, description: "Ordered playbook steps"
645
+ argument :feature_area, String, required: false, description: "Feature area"
698
646
  call do |args|
699
- mcp_server.send(:guard!, "agentf-memory-add-pitfall", **args)
647
+ mcp_server.send(:guard!, "agentf-memory-add-playbook", **args)
700
648
  begin
701
649
  id = nil
702
- res = mcp_server.send(:safe_mcp_memory_write, memory, attempted: { tool: "agentf-memory-add-pitfall", args: args }) do
703
- id = memory.store_episode(
704
- type: "pitfall",
650
+ res = mcp_server.send(:safe_memory_write, memory, attempted: { tool: "agentf-memory-add-playbook", args: args }) do
651
+ id = memory.store_playbook(
705
652
  title: args[:title],
706
653
  description: args[:description],
707
- agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
708
- tags: args[:tags] || [],
709
- context: args[:context].to_s,
710
- code_snippet: ""
654
+ agent: args[:agent] || Agentf::AgentRoles::PLANNER,
655
+ steps: args[:steps] || [],
656
+ feature_area: args[:feature_area]
711
657
  )
712
658
  end
713
659
 
@@ -720,7 +666,7 @@ module Agentf
720
666
  confirmation_prompt: res["confirmation_prompt"]
721
667
  )
722
668
  else
723
- JSON.generate(id: id, type: "pitfall", status: "stored")
669
+ JSON.generate(id: id, type: "playbook", status: "stored")
724
670
  end
725
671
  end
726
672
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentf
4
+ module Memory
5
+ module ConfirmationHandler
6
+ # Wraps a memory write block and normalizes ConfirmationRequired into a
7
+ # structured hash so callers (MCP server, CLI, agents) can handle it
8
+ # uniformly. The optional `_memory` arg is accepted for call-site
9
+ # readability but is not used by this method.
10
+ def safe_memory_write(_memory = nil, attempted: {})
11
+ yield
12
+ nil
13
+ rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
14
+ {
15
+ "confirmation_required" => true,
16
+ "confirmation_details" => e.details,
17
+ "attempted" => attempted,
18
+ "confirmed_write_token" => "confirmed",
19
+ "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."
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end