agentf 0.5.0 → 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.
@@ -12,14 +12,11 @@ module Agentf
12
12
  def self.manifest
13
13
  {
14
14
  "name" => NAME,
15
- "description" => "Review and query Redis-stored memories, pitfalls, and learnings.",
15
+ "description" => "Review and query Redis-stored memories, episodes, and learnings.",
16
16
  "commands" => [
17
17
  { "name" => "get_recent_memories", "type" => "function" },
18
- { "name" => "get_pitfalls", "type" => "function" },
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" },
25
22
  { "name" => "search", "type" => "function" },
@@ -44,10 +41,9 @@ module Agentf
44
41
  { "error" => e.message }
45
42
  end
46
43
 
47
- # Get all pitfalls (things that went wrong)
48
- def get_pitfalls(limit: 10)
49
- pitfalls = @memory.get_pitfalls(limit: limit)
50
- format_memories(pitfalls)
44
+ def get_episodes(limit: 10, outcome: nil)
45
+ episodes = @memory.get_episodes(limit: limit, outcome: outcome)
46
+ format_memories(episodes)
51
47
  rescue => e
52
48
  { "error" => e.message }
53
49
  end
@@ -60,14 +56,6 @@ module Agentf
60
56
  { "error" => e.message }
61
57
  end
62
58
 
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
59
  def get_business_intents(limit: 10)
72
60
  intents = @memory.get_intents(kind: "business", limit: limit)
73
61
  format_memories(intents)
@@ -89,28 +77,10 @@ module Agentf
89
77
  { "error" => e.message }
90
78
  end
91
79
 
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
80
  # Get memories by type (pitfall, lesson, success)
110
81
  def get_by_type(type, limit: 10)
111
- memories = @memory.get_recent_memories(limit: 100)
112
- filtered = memories.select { |m| m["type"] == type }
113
- format_memories(filtered.first(limit))
82
+ memories = @memory.get_memories_by_type(type: type, limit: limit)
83
+ format_memories(memories)
114
84
  rescue => e
115
85
  { "error" => e.message }
116
86
  end
@@ -126,14 +96,7 @@ module Agentf
126
96
 
127
97
  # Search memories by keyword in title or description
128
98
  def search(query, limit: 10)
129
- memories = @memory.get_recent_memories(limit: 100)
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))
99
+ format_memories(@memory.search_memories(query: query, limit: limit))
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
- "pitfall" => memories.count { |m| m["type"] == "pitfall" },
111
+ "episode" => memories.count { |m| m["type"] == "episode" },
150
112
  "lesson" => memories.count { |m| m["type"] == "lesson" },
151
- "success" => memories.count { |m| m["type"] == "success" },
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
- "tags" => m["tags"],
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: "success",
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| Array(m["tags"]).include?(WORKFLOW_METRICS_TAG) }
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
- query_embedding: simple_embedding(task),
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
@@ -9,12 +9,9 @@ module Agentf
9
9
  class Installer
10
10
  READ_ACTIONS = {
11
11
  "get_recent_memories" => { cli: "agentf memory recent -n 10", tool: "agentf-memory-recent" },
12
- "get_pitfalls" => { cli: "agentf memory pitfalls -n 10", tool: "agentf-memory-recent" },
12
+ "get_episodes" => { cli: "agentf memory episodes -n 10", tool: "agentf-memory-episodes" },
13
13
  "get_lessons" => { cli: "agentf memory lessons -n 10", tool: "agentf-memory-recent" },
14
- "get_successes" => { cli: "agentf memory successes -n 10", tool: "agentf-memory-recent" },
15
14
  "get_intents" => { cli: "agentf memory intents", tool: "agentf-memory-recent" },
16
- "get_all_tags" => { cli: "agentf memory tags", tool: "agentf-memory-recent" },
17
- "get_by_tag" => { cli: "agentf memory by-tag <tag> -n 10", tool: "agentf-memory-search" },
18
15
  "get_by_type" => { cli: "agentf memory by-type <type> -n 10", tool: "agentf-memory-search" },
19
16
  "get_by_agent" => { cli: "agentf memory by-agent <agent> -n 10", tool: "agentf-memory-search" },
20
17
  "search" => { cli: "agentf memory search \"<query>\" -n 10", tool: "agentf-memory-search" },
@@ -22,10 +19,10 @@ module Agentf
22
19
  }.freeze
23
20
 
24
21
  WRITE_ACTIONS = {
25
- "store_lesson" => { cli: "agentf memory add-lesson \"<title>\" \"<description>\" --agent=<AGENT> --tags=learning", tool: "agentf-memory-add-lesson" },
26
- "store_success" => { cli: "agentf memory add-success \"<title>\" \"<description>\" --agent=<AGENT> --tags=success", tool: "agentf-memory-add-success" },
27
- "store_pitfall" => { cli: "agentf memory add-pitfall \"<title>\" \"<description>\" --agent=<AGENT> --tags=pitfall", tool: "agentf-memory-add-pitfall" },
28
- "store_business_intent" => { cli: "agentf memory add-business-intent \"<title>\" \"<description>\" --tags=strategy", tool: "agentf-memory-add-business-intent" },
22
+ "store_lesson" => { cli: "agentf memory add-lesson \"<title>\" \"<description>\" --agent=<AGENT>", tool: "agentf-memory-add-lesson" },
23
+ "store_episode" => { cli: "agentf memory add-episode \"<title>\" \"<description>\" --outcome=positive --agent=<AGENT>", tool: "agentf-memory-episodes" },
24
+ "store_playbook" => { cli: "agentf memory add-playbook \"<title>\" \"<description>\" --steps=\"<step1>;<step2>\"", tool: "agentf-memory-add-playbook" },
25
+ "store_business_intent" => { cli: "agentf memory add-business-intent \"<title>\" \"<description>\"", tool: "agentf-memory-add-business-intent" },
29
26
  "store_feature_intent" => { cli: "agentf memory add-feature-intent \"<title>\" \"<description>\" --acceptance=\"<criteria>\"", tool: "agentf-memory-add-feature-intent" }
30
27
  }.freeze
31
28
 
@@ -84,6 +81,7 @@ module Agentf
84
81
  writes.concat(write_agents(root: root, layout: layout, provider: provider, only_agents: only_agents))
85
82
  writes.concat(write_commands(root: root, layout: layout, provider: provider, only_commands: only_commands))
86
83
  writes.concat(write_opencode_helpers(root: root)) if provider.to_s == "opencode"
84
+ writes.concat(write_copilot_helpers(root: root)) if provider.to_s == "copilot"
87
85
  end
88
86
 
89
87
  # Optionally install dependencies for opencode helper package.json
@@ -195,6 +193,12 @@ module Agentf
195
193
  writes
196
194
  end
197
195
 
196
+ def write_copilot_helpers(root:)
197
+ return [] unless root == @local_root
198
+
199
+ [write_copilot_mcp_json(root)]
200
+ end
201
+
198
202
  def opencode_plugin_runtime?
199
203
  @opencode_runtime == "plugin"
200
204
  end
@@ -351,8 +355,8 @@ module Agentf
351
355
  Copilot should call the local `agentf` MCP server tools for runtime actions.
352
356
 
353
357
  - Code discovery tools: `agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`
354
- - Memory read tools: `agentf-memory-recent`, `agentf-memory-search`
355
- - Memory write tools (if enabled): `agentf-memory-add-lesson`, `agentf-memory-add-success`, `agentf-memory-add-pitfall`
358
+ - Memory read tools: `agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-episodes`
359
+ - Memory write tools (if enabled): `agentf-memory-add-lesson`, `agentf-memory-add-playbook`
356
360
 
357
361
  MCP server is started via `agentf mcp-server` and runs locally over stdio.
358
362
  MARKDOWN
@@ -365,8 +369,8 @@ module Agentf
365
369
  recommended_tools = case command_name
366
370
  when "explorer"
367
371
  "`agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`"
368
- when "memory"
369
- "`agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-add-lesson`, `agentf-memory-add-success`, `agentf-memory-add-pitfall`"
372
+ when "memory"
373
+ "`agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-episodes`, `agentf-memory-add-lesson`, `agentf-memory-add-playbook`"
370
374
  else
371
375
  "`agentf-code-glob`, `agentf-code-grep`, `agentf-memory-recent`, `agentf-memory-search`"
372
376
  end
@@ -401,7 +405,7 @@ module Agentf
401
405
  1. Build plan from provider adapter (`Agentf::Service::Providers::OpenCode` or `Agentf::Service::Providers::Copilot`)
402
406
  2. Enrich each agent step with brain context from Redis memory
403
407
  3. Persist feature intent at workflow start
404
- 4. Persist lessons/pitfalls from each agent execution
408
+ 4. Persist lessons and negative episodes from each agent execution
405
409
  5. Return full workflow state for manual review and future autonomous control
406
410
  6. Enforce workflow contract stages (`spec`, `plan`, `execute`, `review`, `finalize`) when enabled
407
411
 
@@ -691,17 +695,6 @@ module Agentf
691
695
  return runAgentfCli(context.directory, "memory", "search", [_args.query, "-n", String(limit)]);
692
696
  },
693
697
  }),
694
- "agentf-memory-by-tag": tool({
695
- description: "Get Agentf memories by tag.",
696
- args: {
697
- tag: tool.schema.string().describe("Tag to filter by"),
698
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
699
- },
700
- async execute(_args: any, context: any) {
701
- const limit = _args.limit ?? 10;
702
- return runAgentfCli(context.directory, "memory", "by-tag", [_args.tag, "-n", String(limit)]);
703
- },
704
- }),
705
698
  "agentf-memory-by-agent": tool({
706
699
  description: "Get Agentf memories by agent.",
707
700
  args: {
@@ -716,7 +709,7 @@ module Agentf
716
709
  "agentf-memory-by-type": tool({
717
710
  description: "Get Agentf memories by type.",
718
711
  args: {
719
- type: tool.schema.string().describe("Memory type (pitfall|lesson|success|business_intent|feature_intent)"),
712
+ type: tool.schema.string().describe("Memory type (episode|lesson|playbook|business_intent|feature_intent|incident)"),
720
713
  limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
721
714
  },
722
715
  async execute(_args: any, context: any) {
@@ -724,19 +717,17 @@ module Agentf
724
717
  return runAgentfCli(context.directory, "memory", "by-type", [_args.type, "-n", String(limit)]);
725
718
  },
726
719
  }),
727
- "agentf-memory-tags": tool({
728
- description: "List all unique memory tags.",
729
- args: {},
730
- async execute(_args: any, context: any) {
731
- return runAgentfCli(context.directory, "memory", "tags", []);
720
+ "agentf-memory-episodes": tool({
721
+ description: "List episode memories.",
722
+ args: {
723
+ outcome: tool.schema.string().optional().describe("Optional outcome filter (positive|negative|neutral)"),
724
+ limit: tool.schema.number().int().min(1).max(100).optional(),
732
725
  },
733
- }),
734
- "agentf-memory-pitfalls": tool({
735
- description: "List pitfall memories.",
736
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
737
726
  async execute(_args: any, context: any) {
738
727
  const limit = _args.limit ?? 10;
739
- return runAgentfCli(context.directory, "memory", "pitfalls", ["-n", String(limit)]);
728
+ const commandArgs = ["-n", String(limit)];
729
+ if (_args.outcome) commandArgs.push(`--outcome=${_args.outcome}`);
730
+ return runAgentfCli(context.directory, "memory", "episodes", commandArgs);
740
731
  },
741
732
  }),
742
733
  "agentf-memory-lessons": tool({
@@ -747,14 +738,6 @@ module Agentf
747
738
  return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
748
739
  },
749
740
  }),
750
- "agentf-memory-successes": tool({
751
- description: "List success memories.",
752
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
753
- async execute(_args: any, context: any) {
754
- const limit = _args.limit ?? 10;
755
- return runAgentfCli(context.directory, "memory", "successes", ["-n", String(limit)]);
756
- },
757
- }),
758
741
  "agentf-memory-intents": tool({
759
742
  description: "List intents (business, feature or both).",
760
743
  args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
@@ -786,13 +769,11 @@ module Agentf
786
769
  args: {
787
770
  title: tool.schema.string(),
788
771
  description: tool.schema.string(),
789
- tags: tool.schema.array(tool.schema.string()).optional(),
790
772
  constraints: tool.schema.array(tool.schema.string()).optional(),
791
773
  priority: tool.schema.number().int().optional(),
792
774
  },
793
775
  async execute(_args: any, context: any) {
794
776
  const commandArgs = [_args.title, _args.description];
795
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
796
777
  if (_args.constraints?.length) commandArgs.push(`--constraints=${_args.constraints.join(";")}`);
797
778
  if (Number.isInteger(_args.priority)) commandArgs.push(`--priority=${String(_args.priority)}`);
798
779
  return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
@@ -803,14 +784,12 @@ module Agentf
803
784
  args: {
804
785
  title: tool.schema.string(),
805
786
  description: tool.schema.string(),
806
- tags: tool.schema.array(tool.schema.string()).optional(),
807
787
  acceptance: tool.schema.array(tool.schema.string()).optional(),
808
788
  non_goals: tool.schema.array(tool.schema.string()).optional(),
809
789
  related_task_id: tool.schema.string().optional(),
810
790
  },
811
791
  async execute(_args: any, context: any) {
812
792
  const commandArgs = [_args.title, _args.description];
813
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
814
793
  if (_args.acceptance?.length) commandArgs.push(`--acceptance=${_args.acceptance.join(";")}`);
815
794
  if (_args.non_goals?.length) commandArgs.push(`--non-goals=${_args.non_goals.join(";")}`);
816
795
  if (_args.related_task_id) commandArgs.push(`--task=${_args.related_task_id}`);
@@ -856,52 +835,32 @@ module Agentf
856
835
  title: tool.schema.string(),
857
836
  description: tool.schema.string(),
858
837
  agent: tool.schema.string().optional(),
859
- tags: tool.schema.array(tool.schema.string()).optional(),
860
838
  context: tool.schema.string().optional(),
861
839
  },
862
840
  async execute(_args: any, context: any) {
863
841
  const commandArgs = [_args.title, _args.description];
864
842
  if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
865
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
866
843
  if (_args.context) commandArgs.push(`--context=${_args.context}`);
867
844
 
868
845
  return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
869
846
  },
870
847
  }),
871
- "agentf-memory-add-success": tool({
872
- description: "Store a success memory in Redis.",
873
- args: {
874
- title: tool.schema.string(),
875
- description: tool.schema.string(),
876
- agent: tool.schema.string().optional(),
877
- tags: tool.schema.array(tool.schema.string()).optional(),
878
- context: tool.schema.string().optional(),
879
- },
880
- async execute(_args: any, context: any) {
881
- const commandArgs = [_args.title, _args.description];
882
- if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
883
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
884
- if (_args.context) commandArgs.push(`--context=${_args.context}`);
885
-
886
- return runAgentfCli(context.directory, "memory", "add-success", commandArgs);
887
- },
888
- }),
889
- "agentf-memory-add-pitfall": tool({
890
- description: "Store a pitfall memory in Redis.",
848
+ "agentf-memory-add-playbook": tool({
849
+ description: "Store a playbook memory in Redis.",
891
850
  args: {
892
851
  title: tool.schema.string(),
893
852
  description: tool.schema.string(),
894
853
  agent: tool.schema.string().optional(),
895
- tags: tool.schema.array(tool.schema.string()).optional(),
896
- context: tool.schema.string().optional(),
854
+ steps: tool.schema.array(tool.schema.string()).optional(),
855
+ feature_area: tool.schema.string().optional(),
897
856
  },
898
857
  async execute(_args: any, context: any) {
899
858
  const commandArgs = [_args.title, _args.description];
900
859
  if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
901
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
902
- if (_args.context) commandArgs.push(`--context=${_args.context}`);
860
+ if (_args.steps?.length) commandArgs.push(`--steps=${_args.steps.join(";")}`);
861
+ if (_args.feature_area) commandArgs.push(`--feature-area=${_args.feature_area}`);
903
862
 
904
- return runAgentfCli(context.directory, "memory", "add-pitfall", commandArgs);
863
+ return runAgentfCli(context.directory, "memory", "add-playbook", commandArgs);
905
864
  },
906
865
  }),
907
866
  };
@@ -961,6 +920,10 @@ module Agentf
961
920
  JSON.pretty_generate(opencode_json_config(root))
962
921
  end
963
922
 
923
+ def render_copilot_mcp_json(root)
924
+ JSON.pretty_generate(copilot_mcp_config(root))
925
+ end
926
+
964
927
  def opencode_json_config(root)
965
928
  base = {
966
929
  "$schema" => "https://opencode.ai/config.json"
@@ -1019,6 +982,37 @@ module Agentf
1019
982
  write_manifest(path, JSON.pretty_generate(merged))
1020
983
  end
1021
984
 
985
+ def copilot_mcp_config(root)
986
+ {
987
+ "servers" => {
988
+ "agentf" => {
989
+ "type" => "stdio",
990
+ "command" => "agentf",
991
+ "args" => ["mcp-server"]
992
+ }
993
+ }
994
+ }
995
+ end
996
+
997
+ def write_copilot_mcp_json(root)
998
+ path = File.join(root, ".vscode", "mcp.json")
999
+ new_content = JSON.parse(render_copilot_mcp_json(root))
1000
+
1001
+ return write_manifest(path, JSON.pretty_generate(new_content)) unless File.exist?(path)
1002
+
1003
+ begin
1004
+ existing = JSON.parse(File.read(path))
1005
+ rescue StandardError => e
1006
+ warn "Failed to parse existing #{path}: #{e.message}"
1007
+ return write_manifest(path, JSON.pretty_generate(new_content))
1008
+ end
1009
+
1010
+ merged = existing.dup
1011
+ merged["servers"] = (existing["servers"] || {}).merge(new_content.fetch("servers"))
1012
+
1013
+ write_manifest(path, JSON.pretty_generate(merged))
1014
+ end
1015
+
1022
1016
  def render_opencode_tsconfig
1023
1017
  <<~JSON
1024
1018
  {
@@ -1093,19 +1087,20 @@ module Agentf
1093
1087
  - `agent`: string
1094
1088
 
1095
1089
  ### 2. Episodic Memory (`episodic:*`)
1096
- Used for success, pitfall, lesson, and intent records.
1090
+ Used for episode, lesson, playbook, and intent records.
1097
1091
 
1098
1092
  **Search index**: `episodic:logs`
1099
1093
 
1100
1094
  **Schema fields**:
1101
1095
  - `$.id`
1102
1096
  - `$.type`
1097
+ - `$.outcome`
1103
1098
  - `$.title`
1104
1099
  - `$.description`
1105
1100
  - `$.project`
1106
1101
  - `$.context`
1107
1102
  - `$.code_snippet`
1108
- - `$.tags`
1103
+ - `$.embedding`
1109
1104
  - `$.created_at`
1110
1105
  - `$.agent`
1111
1106
  - `$.related_task_id`
@@ -1116,9 +1111,9 @@ module Agentf
1116
1111
 
1117
1112
  - Read recent: `agentf memory recent -n 10`
1118
1113
  - Search: `agentf memory search "query" -n 10`
1114
+ - List episodes: `agentf memory episodes -n 10 --outcome=negative`
1119
1115
  - Add lesson: `agentf memory add-lesson "<title>" "<description>" --agent=<AGENT>`
1120
- - Add success: `agentf memory add-success "<title>" "<description>" --agent=<AGENT>`
1121
- - Add pitfall: `agentf memory add-pitfall "<title>" "<description>" --agent=<AGENT>`
1116
+ - Add playbook: `agentf memory add-playbook "<title>" "<description>" --steps="<step1>;<step2>"`
1122
1117
  MARKDOWN
1123
1118
  end
1124
1119
  end