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.
@@ -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
@@ -152,7 +150,7 @@ module Agentf
152
150
 
153
151
  classes.map do |klass|
154
152
  target = File.join(root, layout.fetch("agents_dir"), layout.fetch("agent_filename").call(klass))
155
- write_manifest(target, render_agent_manifest(klass, provider: provider))
153
+ write_manifest(target, render_agent_manifest(klass))
156
154
  end
157
155
  end
158
156
 
@@ -162,7 +160,7 @@ module Agentf
162
160
 
163
161
  manifests.map do |manifest|
164
162
  target = File.join(root, layout.fetch("commands_dir"), layout.fetch("command_filename").call(manifest))
165
- write_manifest(target, render_command_manifest(manifest, provider: provider))
163
+ write_manifest(target, render_command_manifest(manifest))
166
164
  end
167
165
  end
168
166
 
@@ -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
@@ -233,7 +237,7 @@ module Agentf
233
237
  end
234
238
  end
235
239
 
236
- def render_agent_manifest(klass, provider:)
240
+ def render_agent_manifest(klass)
237
241
  # Emit a minimal, stable manifest that acts as a pointer to the runtime
238
242
  # tool implemented by the plugin/CLI. Keep filename and `name` stable so
239
243
  # upgrades remain compatible with existing installs.
@@ -253,6 +257,9 @@ module Agentf
253
257
 
254
258
  description = klass.respond_to?(:description) ? klass.description.to_s.strip : ""
255
259
 
260
+ tdd_section = klass.respond_to?(:writes_code?) && klass.writes_code? ? tdd_requirement_section : ""
261
+ fallback = cli_fallback_section(klass)
262
+
256
263
  <<~MARKDOWN
257
264
  ---
258
265
  name: #{tool_name}
@@ -269,7 +276,50 @@ module Agentf
269
276
  do not retry the write.
270
277
 
271
278
  Policy Summary: #{policy_summary}
279
+ #{tdd_section}#{fallback}
280
+ MARKDOWN
281
+ end
282
+
283
+ def tdd_requirement_section
284
+ <<~MARKDOWN
285
+
286
+ ## TDD Requirement
287
+
288
+ This agent writes code. Every implementation MUST follow red/green discipline:
289
+
290
+ 1. **Write the spec first** — create a failing test before any implementation code
291
+ 2. **Run tests to confirm red** — verify the spec fails (`bundle exec rspec <spec_file>`)
292
+ 3. **Implement the code** — write only enough to make the spec pass
293
+ 4. **Run tests to confirm green** — verify all specs pass before reporting done
294
+ 5. **Never skip** — do not create a class, method, or module without a corresponding spec file
272
295
 
296
+ Showing test output (red then green) is mandatory evidence of completion.
297
+ MARKDOWN
298
+ end
299
+
300
+ def cli_fallback_section(klass)
301
+ agent_name = klass.typed_name.downcase
302
+ read_cmds = READ_ACTIONS.values_at("get_recent_memories", "search").map { |a| "- `#{a[:cli]}`" }.join("\n")
303
+
304
+ write_actions = Array(klass.respond_to?(:memory_concepts) ? klass.memory_concepts["writes"] : [])
305
+ .map { |item| item.to_s.split("#").last }
306
+ .filter_map { |key| WRITE_ACTIONS[key] }
307
+ .map { |a| "- `#{a[:cli].gsub("<AGENT>", agent_name.upcase)}`" }
308
+ .join("\n")
309
+
310
+ write_section = write_actions.empty? ? "" : "\n**Memory writes**:\n#{write_actions}\n"
311
+
312
+ <<~MARKDOWN
313
+
314
+ ## CLI Fallback
315
+
316
+ If the `agentf` MCP server is unavailable, run equivalent commands directly in the terminal:
317
+
318
+ **Run this agent**: `agentf agent #{agent_name} "<input>"`
319
+
320
+ **Memory reads**:
321
+ #{read_cmds}
322
+ #{write_section}
273
323
  MARKDOWN
274
324
  end
275
325
 
@@ -325,9 +375,10 @@ module Agentf
325
375
  "agentf-#{name.to_s.downcase}"
326
376
  end
327
377
 
328
- def render_command_manifest(manifest, provider:)
378
+ def render_command_manifest(manifest)
329
379
  cmd_name = command_identifier(manifest.fetch("name"))
330
380
  desc = manifest.fetch("description", "").to_s.strip
381
+ fallback = command_cli_fallback_section(manifest)
331
382
 
332
383
  <<~MARKDOWN
333
384
  ---
@@ -338,7 +389,45 @@ module Agentf
338
389
 
339
390
  IMPORTANT: Do not embed runtime logic here. Invoke the `#{cmd_name}` tool to perform
340
391
  any codebase or memory operations.
392
+ #{fallback}
393
+ MARKDOWN
394
+ end
395
+
396
+ def command_cli_fallback_section(manifest)
397
+ name = manifest.fetch("name")
398
+ cli_lines = case name
399
+ when "explorer"
400
+ [
401
+ "`agentf code glob \"<pattern>\"`",
402
+ "`agentf code grep \"<pattern>\"`",
403
+ "`agentf code tree --depth=3`",
404
+ "`agentf code related <file>`"
405
+ ]
406
+ when "memory"
407
+ [
408
+ "`#{READ_ACTIONS.fetch('get_recent_memories')[:cli]}`",
409
+ "`#{READ_ACTIONS.fetch('search')[:cli]}`",
410
+ "`#{READ_ACTIONS.fetch('get_episodes')[:cli]}`",
411
+ "`#{WRITE_ACTIONS.fetch('store_lesson')[:cli]}`",
412
+ "`#{WRITE_ACTIONS.fetch('store_episode')[:cli]}`"
413
+ ]
414
+ else
415
+ [
416
+ "`agentf code glob \"<pattern>\"`",
417
+ "`#{READ_ACTIONS.fetch('get_recent_memories')[:cli]}`",
418
+ "`#{READ_ACTIONS.fetch('search')[:cli]}`"
419
+ ]
420
+ end
421
+
422
+ tool_lines = cli_lines.map { |cmd| "- #{cmd}" }.join("\n")
423
+
424
+ <<~MARKDOWN
425
+
426
+ ## CLI Fallback
427
+
428
+ If the `agentf` MCP server is unavailable, run equivalent commands directly in the terminal:
341
429
 
430
+ #{tool_lines}
342
431
  MARKDOWN
343
432
  end
344
433
 
@@ -351,8 +440,8 @@ module Agentf
351
440
  Copilot should call the local `agentf` MCP server tools for runtime actions.
352
441
 
353
442
  - 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`
443
+ - Memory read tools: `agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-episodes`
444
+ - Memory write tools (if enabled): `agentf-memory-add-lesson`, `agentf-memory-add-playbook`
356
445
 
357
446
  MCP server is started via `agentf mcp-server` and runs locally over stdio.
358
447
  MARKDOWN
@@ -365,8 +454,8 @@ module Agentf
365
454
  recommended_tools = case command_name
366
455
  when "explorer"
367
456
  "`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`"
457
+ when "memory"
458
+ "`agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-episodes`, `agentf-memory-add-lesson`, `agentf-memory-add-playbook`"
370
459
  else
371
460
  "`agentf-code-glob`, `agentf-code-grep`, `agentf-memory-recent`, `agentf-memory-search`"
372
461
  end
@@ -401,7 +490,7 @@ module Agentf
401
490
  1. Build plan from provider adapter (`Agentf::Service::Providers::OpenCode` or `Agentf::Service::Providers::Copilot`)
402
491
  2. Enrich each agent step with brain context from Redis memory
403
492
  3. Persist feature intent at workflow start
404
- 4. Persist lessons/pitfalls from each agent execution
493
+ 4. Persist lessons and negative episodes from each agent execution
405
494
  5. Return full workflow state for manual review and future autonomous control
406
495
  6. Enforce workflow contract stages (`spec`, `plan`, `execute`, `review`, `finalize`) when enabled
407
496
 
@@ -691,17 +780,6 @@ module Agentf
691
780
  return runAgentfCli(context.directory, "memory", "search", [_args.query, "-n", String(limit)]);
692
781
  },
693
782
  }),
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
783
  "agentf-memory-by-agent": tool({
706
784
  description: "Get Agentf memories by agent.",
707
785
  args: {
@@ -716,7 +794,7 @@ module Agentf
716
794
  "agentf-memory-by-type": tool({
717
795
  description: "Get Agentf memories by type.",
718
796
  args: {
719
- type: tool.schema.string().describe("Memory type (pitfall|lesson|success|business_intent|feature_intent)"),
797
+ type: tool.schema.string().describe("Memory type (episode|lesson|playbook|business_intent|feature_intent|incident)"),
720
798
  limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
721
799
  },
722
800
  async execute(_args: any, context: any) {
@@ -724,19 +802,17 @@ module Agentf
724
802
  return runAgentfCli(context.directory, "memory", "by-type", [_args.type, "-n", String(limit)]);
725
803
  },
726
804
  }),
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", []);
805
+ "agentf-memory-episodes": tool({
806
+ description: "List episode memories.",
807
+ args: {
808
+ outcome: tool.schema.string().optional().describe("Optional outcome filter (positive|negative|neutral)"),
809
+ limit: tool.schema.number().int().min(1).max(100).optional(),
732
810
  },
733
- }),
734
- "agentf-memory-pitfalls": tool({
735
- description: "List pitfall memories.",
736
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
737
811
  async execute(_args: any, context: any) {
738
812
  const limit = _args.limit ?? 10;
739
- return runAgentfCli(context.directory, "memory", "pitfalls", ["-n", String(limit)]);
813
+ const commandArgs = ["-n", String(limit)];
814
+ if (_args.outcome) commandArgs.push(`--outcome=${_args.outcome}`);
815
+ return runAgentfCli(context.directory, "memory", "episodes", commandArgs);
740
816
  },
741
817
  }),
742
818
  "agentf-memory-lessons": tool({
@@ -747,14 +823,6 @@ module Agentf
747
823
  return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
748
824
  },
749
825
  }),
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
826
  "agentf-memory-intents": tool({
759
827
  description: "List intents (business, feature or both).",
760
828
  args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
@@ -786,13 +854,11 @@ module Agentf
786
854
  args: {
787
855
  title: tool.schema.string(),
788
856
  description: tool.schema.string(),
789
- tags: tool.schema.array(tool.schema.string()).optional(),
790
857
  constraints: tool.schema.array(tool.schema.string()).optional(),
791
858
  priority: tool.schema.number().int().optional(),
792
859
  },
793
860
  async execute(_args: any, context: any) {
794
861
  const commandArgs = [_args.title, _args.description];
795
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
796
862
  if (_args.constraints?.length) commandArgs.push(`--constraints=${_args.constraints.join(";")}`);
797
863
  if (Number.isInteger(_args.priority)) commandArgs.push(`--priority=${String(_args.priority)}`);
798
864
  return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
@@ -803,14 +869,12 @@ module Agentf
803
869
  args: {
804
870
  title: tool.schema.string(),
805
871
  description: tool.schema.string(),
806
- tags: tool.schema.array(tool.schema.string()).optional(),
807
872
  acceptance: tool.schema.array(tool.schema.string()).optional(),
808
873
  non_goals: tool.schema.array(tool.schema.string()).optional(),
809
874
  related_task_id: tool.schema.string().optional(),
810
875
  },
811
876
  async execute(_args: any, context: any) {
812
877
  const commandArgs = [_args.title, _args.description];
813
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
814
878
  if (_args.acceptance?.length) commandArgs.push(`--acceptance=${_args.acceptance.join(";")}`);
815
879
  if (_args.non_goals?.length) commandArgs.push(`--non-goals=${_args.non_goals.join(";")}`);
816
880
  if (_args.related_task_id) commandArgs.push(`--task=${_args.related_task_id}`);
@@ -856,52 +920,32 @@ module Agentf
856
920
  title: tool.schema.string(),
857
921
  description: tool.schema.string(),
858
922
  agent: tool.schema.string().optional(),
859
- tags: tool.schema.array(tool.schema.string()).optional(),
860
923
  context: tool.schema.string().optional(),
861
924
  },
862
925
  async execute(_args: any, context: any) {
863
926
  const commandArgs = [_args.title, _args.description];
864
927
  if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
865
- if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
866
928
  if (_args.context) commandArgs.push(`--context=${_args.context}`);
867
929
 
868
930
  return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
869
931
  },
870
932
  }),
871
- "agentf-memory-add-success": tool({
872
- description: "Store a success memory in Redis.",
933
+ "agentf-memory-add-playbook": tool({
934
+ description: "Store a playbook memory in Redis.",
873
935
  args: {
874
936
  title: tool.schema.string(),
875
937
  description: tool.schema.string(),
876
938
  agent: tool.schema.string().optional(),
877
- tags: tool.schema.array(tool.schema.string()).optional(),
878
- context: tool.schema.string().optional(),
939
+ steps: tool.schema.array(tool.schema.string()).optional(),
940
+ feature_area: tool.schema.string().optional(),
879
941
  },
880
942
  async execute(_args: any, context: any) {
881
943
  const commandArgs = [_args.title, _args.description];
882
944
  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}`);
945
+ if (_args.steps?.length) commandArgs.push(`--steps=${_args.steps.join(";")}`);
946
+ if (_args.feature_area) commandArgs.push(`--feature-area=${_args.feature_area}`);
885
947
 
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.",
891
- args: {
892
- title: tool.schema.string(),
893
- description: tool.schema.string(),
894
- agent: tool.schema.string().optional(),
895
- tags: tool.schema.array(tool.schema.string()).optional(),
896
- context: tool.schema.string().optional(),
897
- },
898
- async execute(_args: any, context: any) {
899
- const commandArgs = [_args.title, _args.description];
900
- 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}`);
903
-
904
- return runAgentfCli(context.directory, "memory", "add-pitfall", commandArgs);
948
+ return runAgentfCli(context.directory, "memory", "add-playbook", commandArgs);
905
949
  },
906
950
  }),
907
951
  };
@@ -961,6 +1005,10 @@ module Agentf
961
1005
  JSON.pretty_generate(opencode_json_config(root))
962
1006
  end
963
1007
 
1008
+ def render_copilot_mcp_json(root)
1009
+ JSON.pretty_generate(copilot_mcp_config(root))
1010
+ end
1011
+
964
1012
  def opencode_json_config(root)
965
1013
  base = {
966
1014
  "$schema" => "https://opencode.ai/config.json"
@@ -1019,6 +1067,37 @@ module Agentf
1019
1067
  write_manifest(path, JSON.pretty_generate(merged))
1020
1068
  end
1021
1069
 
1070
+ def copilot_mcp_config(root)
1071
+ {
1072
+ "servers" => {
1073
+ "agentf" => {
1074
+ "type" => "stdio",
1075
+ "command" => "agentf",
1076
+ "args" => ["mcp-server"]
1077
+ }
1078
+ }
1079
+ }
1080
+ end
1081
+
1082
+ def write_copilot_mcp_json(root)
1083
+ path = File.join(root, ".vscode", "mcp.json")
1084
+ new_content = JSON.parse(render_copilot_mcp_json(root))
1085
+
1086
+ return write_manifest(path, JSON.pretty_generate(new_content)) unless File.exist?(path)
1087
+
1088
+ begin
1089
+ existing = JSON.parse(File.read(path))
1090
+ rescue StandardError => e
1091
+ warn "Failed to parse existing #{path}: #{e.message}"
1092
+ return write_manifest(path, JSON.pretty_generate(new_content))
1093
+ end
1094
+
1095
+ merged = existing.dup
1096
+ merged["servers"] = (existing["servers"] || {}).merge(new_content.fetch("servers"))
1097
+
1098
+ write_manifest(path, JSON.pretty_generate(merged))
1099
+ end
1100
+
1022
1101
  def render_opencode_tsconfig
1023
1102
  <<~JSON
1024
1103
  {
@@ -1093,19 +1172,20 @@ module Agentf
1093
1172
  - `agent`: string
1094
1173
 
1095
1174
  ### 2. Episodic Memory (`episodic:*`)
1096
- Used for success, pitfall, lesson, and intent records.
1175
+ Used for episode, lesson, playbook, and intent records.
1097
1176
 
1098
1177
  **Search index**: `episodic:logs`
1099
1178
 
1100
1179
  **Schema fields**:
1101
1180
  - `$.id`
1102
1181
  - `$.type`
1182
+ - `$.outcome`
1103
1183
  - `$.title`
1104
1184
  - `$.description`
1105
1185
  - `$.project`
1106
1186
  - `$.context`
1107
1187
  - `$.code_snippet`
1108
- - `$.tags`
1188
+ - `$.embedding`
1109
1189
  - `$.created_at`
1110
1190
  - `$.agent`
1111
1191
  - `$.related_task_id`
@@ -1116,9 +1196,9 @@ module Agentf
1116
1196
 
1117
1197
  - Read recent: `agentf memory recent -n 10`
1118
1198
  - Search: `agentf memory search "query" -n 10`
1199
+ - List episodes: `agentf memory episodes -n 10 --outcome=negative`
1119
1200
  - 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>`
1201
+ - Add playbook: `agentf memory add-playbook "<title>" "<description>" --steps="<step1>;<step2>"`
1122
1202
  MARKDOWN
1123
1203
  end
1124
1204
  end