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.
@@ -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
 
@@ -46,12 +43,13 @@ module Agentf
46
43
  }
47
44
  }.freeze
48
45
 
49
- def initialize(global_root: Dir.home, local_root: Dir.pwd, dry_run: false, verbose: false, install_deps: true)
46
+ def initialize(global_root: Dir.home, local_root: Dir.pwd, dry_run: false, verbose: false, install_deps: true, opencode_runtime: "mcp")
50
47
  @global_root = global_root
51
48
  @local_root = local_root
52
49
  @dry_run = dry_run
53
50
  @verbose = verbose
54
51
  @install_deps = install_deps
52
+ @opencode_runtime = opencode_runtime.to_s
55
53
  end
56
54
 
57
55
  def install(
@@ -83,10 +81,11 @@ module Agentf
83
81
  writes.concat(write_agents(root: root, layout: layout, provider: provider, only_agents: only_agents))
84
82
  writes.concat(write_commands(root: root, layout: layout, provider: provider, only_commands: only_commands))
85
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"
86
85
  end
87
86
 
88
87
  # Optionally install dependencies for opencode helper package.json
89
- if provider.to_s == "opencode" && @install_deps
88
+ if provider.to_s == "opencode" && @install_deps && opencode_plugin_runtime?
90
89
  roots.each do |root|
91
90
  package_json_path = File.join(root, ".opencode/package.json")
92
91
  if @dry_run
@@ -171,23 +170,39 @@ module Agentf
171
170
  File.join(root, ".opencode/agents/agentf-orchestrator.md"),
172
171
  render_workflow_engine_manifest
173
172
  )
174
- writes << write_manifest(
175
- File.join(root, ".opencode/plugins/agentf-plugin.ts"),
176
- render_opencode_plugin
177
- )
178
- writes << write_manifest(
179
- File.join(root, ".opencode/tsconfig.json"),
180
- render_opencode_tsconfig
181
- )
182
- writes << write_package_json(root)
183
173
  writes << write_manifest(
184
174
  File.join(root, ".opencode/memory/agentf-redis-schema.md"),
185
175
  render_opencode_memory_schema
186
176
  )
187
177
  writes << write_opencode_json(root)
178
+ if opencode_plugin_runtime?
179
+ writes << write_manifest(
180
+ File.join(root, ".opencode/plugins/opencode-plugin.d.ts"),
181
+ render_opencode_plugin
182
+ )
183
+ writes << write_manifest(
184
+ File.join(root, ".opencode/plugins/agentf-plugin.ts"),
185
+ render_agentf_plugin
186
+ )
187
+ writes << write_manifest(
188
+ File.join(root, ".opencode/tsconfig.json"),
189
+ render_opencode_tsconfig
190
+ )
191
+ writes << write_package_json(root)
192
+ end
188
193
  writes
189
194
  end
190
195
 
196
+ def write_copilot_helpers(root:)
197
+ return [] unless root == @local_root
198
+
199
+ [write_copilot_mcp_json(root)]
200
+ end
201
+
202
+ def opencode_plugin_runtime?
203
+ @opencode_runtime == "plugin"
204
+ end
205
+
191
206
  def discover_agents
192
207
  Agentf::Agents.constants
193
208
  .map { |const| Agentf::Agents.const_get(const) }
@@ -223,46 +238,42 @@ module Agentf
223
238
  end
224
239
 
225
240
  def render_agent_manifest(klass, provider:)
226
- meta = {
227
- "name" => agent_identifier(klass),
228
- "description" => klass.description,
229
- "commands" => klass.commands,
230
- "memory" => klass.memory_concepts,
231
- "policy" => klass.policy_boundaries
232
- }
233
-
234
- <<~MARKDOWN
235
- #{meta.to_yaml}---
236
- #{klass.prompt}
241
+ # Emit a minimal, stable manifest that acts as a pointer to the runtime
242
+ # tool implemented by the plugin/CLI. Keep filename and `name` stable so
243
+ # upgrades remain compatible with existing installs.
244
+ tool_name = agent_identifier(klass)
237
245
 
238
- ## Core Mission
239
- #{klass.description}
246
+ # Build a short policy summary (guard nils and limit length)
247
+ pb = klass.respond_to?(:policy_boundaries) ? klass.policy_boundaries || {} : {}
248
+ always = Array(pb["always"]).join("; ")
249
+ ask_first = Array(pb["ask_first"]).join("; ")
250
+ never = Array(pb["never"]).join("; ")
240
251
 
241
- ## When To Use
242
- #{klass.when_to_use}
252
+ parts = []
253
+ parts << "Always: #{always}" unless always.to_s.strip.empty?
254
+ parts << "Ask first: #{ask_first}" unless ask_first.to_s.strip.empty?
255
+ parts << "Never: #{never}" unless never.to_s.strip.empty?
256
+ policy_summary = parts.join(" | ")
243
257
 
244
- ## Deliverables
245
- #{Array(klass.deliverables).map { |item| "- #{item}" }.join("\n")}
258
+ description = klass.respond_to?(:description) ? klass.description.to_s.strip : ""
246
259
 
247
- ## Working Style
248
- #{klass.working_style}
260
+ <<~MARKDOWN
261
+ ---
262
+ name: #{tool_name}
263
+ description: #{description}
264
+ ---
265
+ This manifest is a thin pointer. All runtime logic lives in the `#{tool_name}` tool.
249
266
 
250
- ## Memory Integration
251
- - Reads: #{Array(klass.memory_concepts["reads"]).join(", ")}
252
- - Writes: #{Array(klass.memory_concepts["writes"]).join(", ")}
253
- - Policy: #{klass.memory_concepts["policy"]}
267
+ IMPORTANT: Use the `#{tool_name}` tool for any filesystem, codebase, or memory actions.
268
+ The manifest contains only routing and a small policy summary — the tool is the
269
+ authoritative implementation.
254
270
 
255
- ## Memory Actions
256
- #{memory_actions_for(klass, provider: provider).join("\n")}
271
+ If the tool returns `confirmation_required: true`, ask the user whether to continue.
272
+ If they approve, rerun the same tool with `confirmedWrite=confirmed`. If they decline,
273
+ do not retry the write.
257
274
 
258
- ## Policy Boundaries
259
- - Always: #{Array(klass.policy_boundaries["always"]).join("; ")}
260
- - Ask first: #{Array(klass.policy_boundaries["ask_first"]).join("; ")}
261
- - Never: #{Array(klass.policy_boundaries["never"]).join("; ")}
262
- - Required inputs: #{Array(klass.policy_boundaries["required_inputs"]).join(", ")}
263
- - Required outputs: #{Array(klass.policy_boundaries["required_outputs"]).join(", ")}
275
+ Policy Summary: #{policy_summary}
264
276
 
265
- #{copilot_mcp_agent_section(provider: provider)}
266
277
  MARKDOWN
267
278
  end
268
279
 
@@ -319,20 +330,19 @@ module Agentf
319
330
  end
320
331
 
321
332
  def render_command_manifest(manifest, provider:)
322
- commands = Array(manifest.fetch("commands"))
323
- frontmatter = {
324
- "name" => command_identifier(manifest.fetch("name")),
325
- "description" => manifest.fetch("description"),
326
- "commands" => commands
327
- }
333
+ cmd_name = command_identifier(manifest.fetch("name"))
334
+ desc = manifest.fetch("description", "").to_s.strip
328
335
 
329
336
  <<~MARKDOWN
330
- #{frontmatter.to_yaml}---
331
- # Commands
337
+ ---
338
+ name: #{cmd_name}
339
+ description: #{desc}
340
+ ---
341
+ This is a thin command manifest that routes execution to the `#{cmd_name}` tool.
332
342
 
333
- #{commands.map { |command| "- #{command.fetch('name')}" }.join("\n")}
343
+ IMPORTANT: Do not embed runtime logic here. Invoke the `#{cmd_name}` tool to perform
344
+ any codebase or memory operations.
334
345
 
335
- #{copilot_mcp_command_section(manifest: manifest, provider: provider)}
336
346
  MARKDOWN
337
347
  end
338
348
 
@@ -345,8 +355,8 @@ module Agentf
345
355
  Copilot should call the local `agentf` MCP server tools for runtime actions.
346
356
 
347
357
  - Code discovery tools: `agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`
348
- - Memory read tools: `agentf-memory-recent`, `agentf-memory-search`
349
- - 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`
350
360
 
351
361
  MCP server is started via `agentf mcp-server` and runs locally over stdio.
352
362
  MARKDOWN
@@ -359,8 +369,8 @@ module Agentf
359
369
  recommended_tools = case command_name
360
370
  when "explorer"
361
371
  "`agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`"
362
- when "memory"
363
- "`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`"
364
374
  else
365
375
  "`agentf-code-glob`, `agentf-code-grep`, `agentf-memory-recent`, `agentf-memory-search`"
366
376
  end
@@ -395,7 +405,7 @@ module Agentf
395
405
  1. Build plan from provider adapter (`Agentf::Service::Providers::OpenCode` or `Agentf::Service::Providers::Copilot`)
396
406
  2. Enrich each agent step with brain context from Redis memory
397
407
  3. Persist feature intent at workflow start
398
- 4. Persist lessons/pitfalls from each agent execution
408
+ 4. Persist lessons and negative episodes from each agent execution
399
409
  5. Return full workflow state for manual review and future autonomous control
400
410
  6. Enforce workflow contract stages (`spec`, `plan`, `execute`, `review`, `finalize`) when enabled
401
411
 
@@ -417,13 +427,34 @@ module Agentf
417
427
  end
418
428
 
419
429
  def render_opencode_plugin
430
+ <<~'TYPESCRIPT'
431
+ declare module "@opencode-ai/plugin" {
432
+ export type Plugin = (input: any) => Promise<any>;
433
+
434
+ // Minimal `tool` factory type used by our plugin. Keep very loose to avoid
435
+ // coupling to the full SDK types in this repo.
436
+ export function tool(def: any): any;
437
+
438
+ export const schema: any;
439
+
440
+ export default {} as { tool: typeof tool; schema: any };
441
+ }
442
+ TYPESCRIPT
443
+ end
444
+
445
+ def render_agentf_plugin
420
446
  <<~'TYPESCRIPT'
421
447
  // tools:
422
448
  import { execFile } from "child_process";
423
449
  import { promisify } from "util";
424
- import path from "path";
425
- import { type Plugin, tool } from "@opencode-ai/plugin";
426
- import fs from "fs";
450
+ import * as path from "path";
451
+ // Avoid importing host SDK types directly to reduce coupling during local
452
+ // type-checks. Use a runtime require and loose `any` types here.
453
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
454
+ const _opencode_plugin: any = require("@opencode-ai/plugin");
455
+ const tool = _opencode_plugin.tool;
456
+ type Plugin = any;
457
+ import * as fs from "fs";
427
458
 
428
459
  const execFileAsync = promisify(execFile);
429
460
 
@@ -553,317 +584,373 @@ module Agentf
553
584
  });
554
585
 
555
586
  const text = stdout.toString().trim();
556
- return text || "{}";
587
+ if (!text) return {};
588
+
589
+ try {
590
+ return JSON.parse(text);
591
+ } catch (err) {
592
+ // If the CLI returned non-JSON, return a structured error object so callers
593
+ // can surface useful debugging info instead of crashing.
594
+ return { ok: false, _parse_error: String(err), _raw: text };
595
+ }
557
596
  }
558
597
 
559
- export const agentfPlugin: Plugin = async () => {
560
- await ensureAgentfPreflight(process.env.PWD || process.cwd());
598
+ // Lightweight frontmatter parser: extract YAML between leading `---` blocks
599
+ function parseFrontmatter(content: string): Record<string, string> {
600
+ const res: Record<string, string> = {};
601
+ const fmStart = content.indexOf("---");
602
+ if (fmStart === -1) return res;
603
+ const rest = content.slice(fmStart + 3);
604
+ const fmEndIdx = rest.indexOf("---");
605
+ if (fmEndIdx === -1) return res;
606
+ const block = rest.slice(0, fmEndIdx).trim();
607
+ const lines = block.split(/\r?\n/);
608
+ for (const line of lines) {
609
+ const m = line.match(/^\s*([A-Za-z0-9_\-]+)\s*:\s*(.+)\s*$/);
610
+ if (!m) continue;
611
+ let key = m[1];
612
+ let value = m[2];
613
+ // strip surrounding quotes
614
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
615
+ value = value.slice(1, -1);
616
+ }
617
+ res[key] = value;
618
+ }
619
+ return res;
620
+ }
561
621
 
562
- return {
563
- tool: {
564
- "agentf-code-glob": tool({
565
- description: "Find files using project glob patterns via Agentf code CLI.",
566
- args: {
567
- pattern: tool.schema.string().describe("Glob pattern, example: lib/**/*.rb"),
568
- types: tool.schema.array(tool.schema.string()).optional().describe("Optional file extensions"),
569
- },
570
- async execute(args, context) {
571
- const commandArgs = [];
572
- if (args.types?.length) {
573
- commandArgs.push(`--types=${args.types.join(",")}`);
574
- }
622
+ export const agentfPlugin = async (input: any) => {
623
+ const workspaceDir = input?.directory || process.env.PWD || process.cwd();
624
+ await ensureAgentfPreflight(workspaceDir);
625
+
626
+ // Build static tools map
627
+ const staticTools: Record<string, ReturnType<typeof tool>> = {
628
+ "agentf-code-glob": tool({
629
+ description: "Find files using project glob patterns via Agentf code CLI.",
630
+ args: {
631
+ pattern: tool.schema.string().describe("Glob pattern, example: lib/**/*.rb"),
632
+ types: tool.schema.array(tool.schema.string()).optional().describe("Optional file extensions"),
633
+ },
634
+ async execute(_args: any, context: any) {
635
+ const commandArgs = [];
636
+ if (_args.types?.length) {
637
+ commandArgs.push(`--types=${_args.types.join(",")}`);
638
+ }
639
+
640
+ return runAgentfCli(context.directory, "code", "glob", [_args.pattern, ...commandArgs]);
641
+ },
642
+ }),
643
+ "agentf-code-grep": tool({
644
+ description: "Search file contents via Agentf code CLI.",
645
+ args: {
646
+ pattern: tool.schema.string().describe("Regex/text to search"),
647
+ filePattern: tool.schema.string().optional().describe("Optional include pattern"),
648
+ context: tool.schema.number().int().min(0).max(20).optional().describe("Context lines"),
649
+ },
650
+ async execute(_args: any, context: any) {
651
+ const commandArgs = [];
652
+ if (_args.filePattern) commandArgs.push(`--file-pattern=${_args.filePattern}`);
653
+ if (Number.isInteger(_args.context)) commandArgs.push(`--context=${_args.context}`);
654
+
655
+ return runAgentfCli(context.directory, "code", "grep", [_args.pattern, ...commandArgs]);
656
+ },
657
+ }),
658
+ "agentf-code-tree": tool({
659
+ description: "Get directory tree data via Agentf code CLI.",
660
+ args: {
661
+ depth: tool.schema.number().int().min(1).max(10).optional().describe("Max traversal depth"),
662
+ },
663
+ async execute(_args: any, context: any) {
664
+ const depth = _args.depth ?? 3;
665
+ return runAgentfCli(context.directory, "code", "tree", [`--depth=${depth}`]);
666
+ },
667
+ }),
668
+ "agentf-code-related-files": tool({
669
+ description: "Find import and related file hints for a target file.",
670
+ args: {
671
+ targetFile: tool.schema.string().describe("Workspace-relative file path"),
672
+ },
673
+ async execute(_args: any, context: any) {
674
+ return runAgentfCli(context.directory, "code", "related", [_args.targetFile]);
675
+ },
676
+ }),
677
+ "agentf-memory-recent": tool({
678
+ description: "Get recent Agentf memories from Redis.",
679
+ args: {
680
+ limit: tool.schema.number().int().min(1).max(100).optional().describe("How many memories to return"),
681
+ },
682
+ async execute(_args: any, context: any) {
683
+ const limit = _args.limit ?? 10;
684
+ return runAgentfCli(context.directory, "memory", "recent", ["-n", String(limit)]);
685
+ },
686
+ }),
687
+ "agentf-memory-search": tool({
688
+ description: "Search Agentf memories by keyword.",
689
+ args: {
690
+ query: tool.schema.string().describe("Search query"),
691
+ limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
692
+ },
693
+ async execute(_args: any, context: any) {
694
+ const limit = _args.limit ?? 10;
695
+ return runAgentfCli(context.directory, "memory", "search", [_args.query, "-n", String(limit)]);
696
+ },
697
+ }),
698
+ "agentf-memory-by-agent": tool({
699
+ description: "Get Agentf memories by agent.",
700
+ args: {
701
+ agent: tool.schema.string().describe("Agent name"),
702
+ limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
703
+ },
704
+ async execute(_args: any, context: any) {
705
+ const limit = _args.limit ?? 10;
706
+ return runAgentfCli(context.directory, "memory", "by-agent", [_args.agent, "-n", String(limit)]);
707
+ },
708
+ }),
709
+ "agentf-memory-by-type": tool({
710
+ description: "Get Agentf memories by type.",
711
+ args: {
712
+ type: tool.schema.string().describe("Memory type (episode|lesson|playbook|business_intent|feature_intent|incident)"),
713
+ limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
714
+ },
715
+ async execute(_args: any, context: any) {
716
+ const limit = _args.limit ?? 10;
717
+ return runAgentfCli(context.directory, "memory", "by-type", [_args.type, "-n", String(limit)]);
718
+ },
719
+ }),
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(),
725
+ },
726
+ async execute(_args: any, context: any) {
727
+ const limit = _args.limit ?? 10;
728
+ const commandArgs = ["-n", String(limit)];
729
+ if (_args.outcome) commandArgs.push(`--outcome=${_args.outcome}`);
730
+ return runAgentfCli(context.directory, "memory", "episodes", commandArgs);
731
+ },
732
+ }),
733
+ "agentf-memory-lessons": tool({
734
+ description: "List lesson memories.",
735
+ args: { limit: tool.schema.number().int().min(1).max(100).optional() },
736
+ async execute(_args: any, context: any) {
737
+ const limit = _args.limit ?? 10;
738
+ return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
739
+ },
740
+ }),
741
+ "agentf-memory-intents": tool({
742
+ description: "List intents (business, feature or both).",
743
+ args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
744
+ async execute(_args: any, context: any) {
745
+ const limit = _args.limit ?? 10;
746
+ const kind = _args.kind ? String(_args.kind) : "";
747
+ const cmdArgs = kind ? [kind, "-n", String(limit)] : ["-n", String(limit)];
748
+ return runAgentfCli(context.directory, "memory", "intents", cmdArgs);
749
+ },
750
+ }),
751
+ "agentf-memory-business-intents": tool({
752
+ description: "List business intents.",
753
+ args: { limit: tool.schema.number().int().min(1).max(100).optional() },
754
+ async execute(_args: any, context: any) {
755
+ const limit = _args.limit ?? 10;
756
+ return runAgentfCli(context.directory, "memory", "business-intents", ["-n", String(limit)]);
757
+ },
758
+ }),
759
+ "agentf-memory-feature-intents": tool({
760
+ description: "List feature intents.",
761
+ args: { limit: tool.schema.number().int().min(1).max(100).optional() },
762
+ async execute(_args: any, context: any) {
763
+ const limit = _args.limit ?? 10;
764
+ return runAgentfCli(context.directory, "memory", "feature-intents", ["-n", String(limit)]);
765
+ },
766
+ }),
767
+ "agentf-memory-add-business-intent": tool({
768
+ description: "Store a business intent in Redis.",
769
+ args: {
770
+ title: tool.schema.string(),
771
+ description: tool.schema.string(),
772
+ constraints: tool.schema.array(tool.schema.string()).optional(),
773
+ priority: tool.schema.number().int().optional(),
774
+ },
775
+ async execute(_args: any, context: any) {
776
+ const commandArgs = [_args.title, _args.description];
777
+ if (_args.constraints?.length) commandArgs.push(`--constraints=${_args.constraints.join(";")}`);
778
+ if (Number.isInteger(_args.priority)) commandArgs.push(`--priority=${String(_args.priority)}`);
779
+ return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
780
+ },
781
+ }),
782
+ "agentf-memory-add-feature-intent": tool({
783
+ description: "Store a feature intent in Redis.",
784
+ args: {
785
+ title: tool.schema.string(),
786
+ description: tool.schema.string(),
787
+ acceptance: tool.schema.array(tool.schema.string()).optional(),
788
+ non_goals: tool.schema.array(tool.schema.string()).optional(),
789
+ related_task_id: tool.schema.string().optional(),
790
+ },
791
+ async execute(_args: any, context: any) {
792
+ const commandArgs = [_args.title, _args.description];
793
+ if (_args.acceptance?.length) commandArgs.push(`--acceptance=${_args.acceptance.join(";")}`);
794
+ if (_args.non_goals?.length) commandArgs.push(`--non-goals=${_args.non_goals.join(";")}`);
795
+ if (_args.related_task_id) commandArgs.push(`--task=${_args.related_task_id}`);
796
+ return runAgentfCli(context.directory, "memory", "add-feature-intent", commandArgs);
797
+ },
798
+ }),
799
+ "agentf-memory-neighbors": tool({
800
+ description: "Get neighboring memory nodes by edge traversal.",
801
+ args: {
802
+ node_id: tool.schema.string(),
803
+ relation: tool.schema.string().optional(),
804
+ depth: tool.schema.number().int().optional(),
805
+ limit: tool.schema.number().int().optional(),
806
+ },
807
+ async execute(_args: any, context: any) {
808
+ const commandArgs = [_args.node_id];
809
+ if (_args.relation) commandArgs.push(`--relation=${_args.relation}`);
810
+ if (Number.isInteger(_args.depth)) commandArgs.push(`--depth=${String(_args.depth)}`);
811
+ if (Number.isInteger(_args.limit)) commandArgs.push(`-n`, String(_args.limit));
812
+ return runAgentfCli(context.directory, "memory", "neighbors", commandArgs);
813
+ },
814
+ }),
815
+ "agentf-memory-subgraph": tool({
816
+ description: "Build a subgraph from seed ids.",
817
+ args: {
818
+ seed_ids: tool.schema.array(tool.schema.string()),
819
+ relation_filters: tool.schema.array(tool.schema.string()).optional(),
820
+ depth: tool.schema.number().int().optional(),
821
+ limit: tool.schema.number().int().optional(),
822
+ },
823
+ async execute(_args: any, context: any) {
824
+ const seeds = (_args.seed_ids || []).join(",");
825
+ const commandArgs = [seeds];
826
+ if (_args.relation_filters?.length) commandArgs.push(`--relation=${_args.relation_filters.join(",")}`);
827
+ if (Number.isInteger(_args.depth)) commandArgs.push(`--depth=${String(_args.depth)}`);
828
+ if (Number.isInteger(_args.limit)) commandArgs.push(`-n`, String(_args.limit));
829
+ return runAgentfCli(context.directory, "memory", "subgraph", commandArgs);
830
+ },
831
+ }),
832
+ "agentf-memory-add-lesson": tool({
833
+ description: "Store a lesson memory in Redis.",
834
+ args: {
835
+ title: tool.schema.string(),
836
+ description: tool.schema.string(),
837
+ agent: tool.schema.string().optional(),
838
+ context: tool.schema.string().optional(),
839
+ },
840
+ async execute(_args: any, context: any) {
841
+ const commandArgs = [_args.title, _args.description];
842
+ if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
843
+ if (_args.context) commandArgs.push(`--context=${_args.context}`);
844
+
845
+ return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
846
+ },
847
+ }),
848
+ "agentf-memory-add-playbook": tool({
849
+ description: "Store a playbook memory in Redis.",
850
+ args: {
851
+ title: tool.schema.string(),
852
+ description: tool.schema.string(),
853
+ agent: tool.schema.string().optional(),
854
+ steps: tool.schema.array(tool.schema.string()).optional(),
855
+ feature_area: tool.schema.string().optional(),
856
+ },
857
+ async execute(_args: any, context: any) {
858
+ const commandArgs = [_args.title, _args.description];
859
+ if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
860
+ if (_args.steps?.length) commandArgs.push(`--steps=${_args.steps.join(";")}`);
861
+ if (_args.feature_area) commandArgs.push(`--feature-area=${_args.feature_area}`);
862
+
863
+ return runAgentfCli(context.directory, "memory", "add-playbook", commandArgs);
864
+ },
865
+ }),
866
+ };
575
867
 
576
- return runAgentfCli(context.directory, "code", "glob", [args.pattern, ...commandArgs]);
577
- },
578
- }),
579
- "agentf-code-grep": tool({
580
- description: "Search file contents via Agentf code CLI.",
581
- args: {
582
- pattern: tool.schema.string().describe("Regex/text to search"),
583
- filePattern: tool.schema.string().optional().describe("Optional include pattern"),
584
- context: tool.schema.number().int().min(0).max(20).optional().describe("Context lines"),
585
- },
586
- async execute(args, context) {
587
- const commandArgs = [];
588
- if (args.filePattern) commandArgs.push(`--file-pattern=${args.filePattern}`);
589
- if (Number.isInteger(args.context)) commandArgs.push(`--context=${args.context}`);
868
+ const agentTools: Record<string, ReturnType<typeof tool>> = {};
869
+ const absDir = path.join(process.cwd(), ".opencode/agents");
590
870
 
591
- return runAgentfCli(context.directory, "code", "grep", [args.pattern, ...commandArgs]);
592
- },
593
- }),
594
- "agentf-code-tree": tool({
595
- description: "Get directory tree data via Agentf code CLI.",
596
- args: {
597
- depth: tool.schema.number().int().min(1).max(10).optional().describe("Max traversal depth"),
598
- },
599
- async execute(args, context) {
600
- const depth = args.depth ?? 3;
601
- return runAgentfCli(context.directory, "code", "tree", [`--depth=${depth}`]);
602
- },
603
- }),
604
- "agentf-code-related-files": tool({
605
- description: "Find import and related file hints for a target file.",
606
- args: {
607
- targetFile: tool.schema.string().describe("Workspace-relative file path"),
608
- },
609
- async execute(args, context) {
610
- return runAgentfCli(context.directory, "code", "related", [args.targetFile]);
611
- },
612
- }),
613
- "agentf-memory-recent": tool({
614
- description: "Get recent Agentf memories from Redis.",
615
- args: {
616
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many memories to return"),
617
- },
618
- async execute(args, context) {
619
- const limit = args.limit ?? 10;
620
- return runAgentfCli(context.directory, "memory", "recent", ["-n", String(limit)]);
621
- },
622
- }),
623
- "agentf-memory-search": tool({
624
- description: "Search Agentf memories by keyword.",
625
- args: {
626
- query: tool.schema.string().describe("Search query"),
627
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
628
- },
629
- async execute(args, context) {
630
- const limit = args.limit ?? 10;
631
- return runAgentfCli(context.directory, "memory", "search", [args.query, "-n", String(limit)]);
632
- },
633
- }),
634
- "agentf-memory-by-tag": tool({
635
- description: "Get Agentf memories by tag.",
636
- args: {
637
- tag: tool.schema.string().describe("Tag to filter by"),
638
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
639
- },
640
- async execute(args, context) {
641
- const limit = args.limit ?? 10;
642
- return runAgentfCli(context.directory, "memory", "by-tag", [args.tag, "-n", String(limit)]);
643
- },
644
- }),
645
- "agentf-memory-by-agent": tool({
646
- description: "Get Agentf memories by agent.",
647
- args: {
648
- agent: tool.schema.string().describe("Agent name"),
649
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
650
- },
651
- async execute(args, context) {
652
- const limit = args.limit ?? 10;
653
- return runAgentfCli(context.directory, "memory", "by-agent", [args.agent, "-n", String(limit)]);
654
- },
655
- }),
656
- "agentf-memory-by-type": tool({
657
- description: "Get Agentf memories by type.",
658
- args: {
659
- type: tool.schema.string().describe("Memory type (pitfall|lesson|success|business_intent|feature_intent)"),
660
- limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
661
- },
662
- async execute(args, context) {
663
- const limit = args.limit ?? 10;
664
- return runAgentfCli(context.directory, "memory", "by-type", [args.type, "-n", String(limit)]);
665
- },
666
- }),
667
- "agentf-memory-tags": tool({
668
- description: "List all unique memory tags.",
669
- args: {},
670
- async execute(_args, context) {
671
- return runAgentfCli(context.directory, "memory", "tags", []);
672
- },
673
- }),
674
- "agentf-memory-pitfalls": tool({
675
- description: "List pitfall memories.",
676
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
677
- async execute(args, context) {
678
- const limit = args.limit ?? 10;
679
- return runAgentfCli(context.directory, "memory", "pitfalls", ["-n", String(limit)]);
680
- },
681
- }),
682
- "agentf-memory-lessons": tool({
683
- description: "List lesson memories.",
684
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
685
- async execute(args, context) {
686
- const limit = args.limit ?? 10;
687
- return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
688
- },
689
- }),
690
- "agentf-memory-successes": tool({
691
- description: "List success memories.",
692
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
693
- async execute(args, context) {
694
- const limit = args.limit ?? 10;
695
- return runAgentfCli(context.directory, "memory", "successes", ["-n", String(limit)]);
696
- },
697
- }),
698
- "agentf-memory-intents": tool({
699
- description: "List intents (business, feature or both).",
700
- args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
701
- async execute(args, context) {
702
- const limit = args.limit ?? 10;
703
- const kind = args.kind ? String(args.kind) : "";
704
- const cmdArgs = kind ? [kind, "-n", String(limit)] : ["-n", String(limit)];
705
- return runAgentfCli(context.directory, "memory", "intents", cmdArgs);
706
- },
707
- }),
708
- "agentf-memory-business-intents": tool({
709
- description: "List business intents.",
710
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
711
- async execute(args, context) {
712
- const limit = args.limit ?? 10;
713
- return runAgentfCli(context.directory, "memory", "business-intents", ["-n", String(limit)]);
714
- },
715
- }),
716
- "agentf-memory-feature-intents": tool({
717
- description: "List feature intents.",
718
- args: { limit: tool.schema.number().int().min(1).max(100).optional() },
719
- async execute(args, context) {
720
- const limit = args.limit ?? 10;
721
- return runAgentfCli(context.directory, "memory", "feature-intents", ["-n", String(limit)]);
722
- },
723
- }),
724
- "agentf-memory-add-business-intent": tool({
725
- description: "Store a business intent in Redis.",
726
- args: {
727
- title: tool.schema.string(),
728
- description: tool.schema.string(),
729
- tags: tool.schema.array(tool.schema.string()).optional(),
730
- constraints: tool.schema.array(tool.schema.string()).optional(),
731
- priority: tool.schema.number().int().optional(),
732
- },
733
- async execute(args, context) {
734
- const commandArgs = [args.title, args.description];
735
- if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
736
- if (args.constraints?.length) commandArgs.push(`--constraints=${args.constraints.join(";")}`);
737
- if (Number.isInteger(args.priority)) commandArgs.push(`--priority=${String(args.priority)}`);
738
- return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
739
- },
740
- }),
741
- "agentf-memory-add-feature-intent": tool({
742
- description: "Store a feature intent in Redis.",
743
- args: {
744
- title: tool.schema.string(),
745
- description: tool.schema.string(),
746
- tags: tool.schema.array(tool.schema.string()).optional(),
747
- acceptance: tool.schema.array(tool.schema.string()).optional(),
748
- non_goals: tool.schema.array(tool.schema.string()).optional(),
749
- related_task_id: tool.schema.string().optional(),
750
- },
751
- async execute(args, context) {
752
- const commandArgs = [args.title, args.description];
753
- if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
754
- if (args.acceptance?.length) commandArgs.push(`--acceptance=${args.acceptance.join(";")}`);
755
- if (args.non_goals?.length) commandArgs.push(`--non-goals=${args.non_goals.join(";")}`);
756
- if (args.related_task_id) commandArgs.push(`--task=${args.related_task_id}`);
757
- return runAgentfCli(context.directory, "memory", "add-feature-intent", commandArgs);
758
- },
759
- }),
760
- "agentf-memory-neighbors": tool({
761
- description: "Get neighboring memory nodes by edge traversal.",
762
- args: {
763
- node_id: tool.schema.string(),
764
- relation: tool.schema.string().optional(),
765
- depth: tool.schema.number().int().optional(),
766
- limit: tool.schema.number().int().optional(),
767
- },
768
- async execute(args, context) {
769
- const commandArgs = [args.node_id];
770
- if (args.relation) commandArgs.push(`--relation=${args.relation}`);
771
- if (Number.isInteger(args.depth)) commandArgs.push(`--depth=${String(args.depth)}`);
772
- if (Number.isInteger(args.limit)) commandArgs.push(`-n`, String(args.limit));
773
- return runAgentfCli(context.directory, "memory", "neighbors", commandArgs);
774
- },
775
- }),
776
- "agentf-memory-subgraph": tool({
777
- description: "Build a subgraph from seed ids.",
778
- args: {
779
- seed_ids: tool.schema.array(tool.schema.string()),
780
- relation_filters: tool.schema.array(tool.schema.string()).optional(),
781
- depth: tool.schema.number().int().optional(),
782
- limit: tool.schema.number().int().optional(),
783
- },
784
- async execute(args, context) {
785
- const seeds = (args.seed_ids || []).join(",");
786
- const commandArgs = [seeds];
787
- if (args.relation_filters?.length) commandArgs.push(`--relation=${args.relation_filters.join(",")}`);
788
- if (Number.isInteger(args.depth)) commandArgs.push(`--depth=${String(args.depth)}`);
789
- if (Number.isInteger(args.limit)) commandArgs.push(`-n`, String(args.limit));
790
- return runAgentfCli(context.directory, "memory", "subgraph", commandArgs);
791
- },
792
- }),
793
- "agentf-memory-add-lesson": tool({
794
- description: "Store a lesson memory in Redis.",
795
- args: {
796
- title: tool.schema.string(),
797
- description: tool.schema.string(),
798
- agent: tool.schema.string().optional(),
799
- tags: tool.schema.array(tool.schema.string()).optional(),
800
- context: tool.schema.string().optional(),
801
- },
802
- async execute(args, context) {
803
- const commandArgs = [args.title, args.description];
804
- if (args.agent) commandArgs.push(`--agent=${args.agent}`);
805
- if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
806
- if (args.context) commandArgs.push(`--context=${args.context}`);
871
+ // Guard: agents directory may not exist in minimal workspaces (eg. tests).
872
+ if (fs.existsSync(absDir)) {
873
+ for (const file of fs.readdirSync(absDir)) {
874
+ const full = path.join(absDir, file);
875
+ if (!fs.statSync(full).isFile()) continue;
876
+ const content = fs.readFileSync(full, "utf8");
877
+ const fm = parseFrontmatter(content);
878
+ const toolName = fm["name"] || path.basename(file, path.extname(file));
807
879
 
808
- return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
809
- },
810
- }),
811
- "agentf-memory-add-success": tool({
812
- description: "Store a success memory in Redis.",
813
- args: {
814
- title: tool.schema.string(),
815
- description: tool.schema.string(),
816
- agent: tool.schema.string().optional(),
817
- tags: tool.schema.array(tool.schema.string()).optional(),
818
- context: tool.schema.string().optional(),
819
- },
820
- async execute(args, context) {
821
- const commandArgs = [args.title, args.description];
822
- if (args.agent) commandArgs.push(`--agent=${args.agent}`);
823
- if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
824
- if (args.context) commandArgs.push(`--context=${args.context}`);
880
+ if ((staticTools as any)[toolName]) continue;
825
881
 
826
- return runAgentfCli(context.directory, "memory", "add-success", commandArgs);
827
- },
828
- }),
829
- "agentf-memory-add-pitfall": tool({
830
- description: "Store a pitfall memory in Redis.",
882
+ const agentName = toolName.replace(/^agentf-/, "");
883
+
884
+ agentTools[toolName] = tool({
885
+ description: `Invoke agent ${agentName} via the agentf CLI. If the result includes confirmation_required=true, ask the user before retrying with confirmedWrite=confirmed.`,
831
886
  args: {
832
- title: tool.schema.string(),
833
- description: tool.schema.string(),
834
- agent: tool.schema.string().optional(),
835
- tags: tool.schema.array(tool.schema.string()).optional(),
836
- context: tool.schema.string().optional(),
887
+ input: tool.schema.string().optional().describe("Optional input prompt or payload"),
888
+ confirmedWrite: tool.schema.string().optional().describe("Continuation token for confirmed writes"),
837
889
  },
838
- async execute(args, context) {
839
- const commandArgs = [args.title, args.description];
840
- if (args.agent) commandArgs.push(`--agent=${args.agent}`);
841
- if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
842
- if (args.context) commandArgs.push(`--context=${args.context}`);
843
-
844
- return runAgentfCli(context.directory, "memory", "add-pitfall", commandArgs);
890
+ async execute(_args: any, context: any) {
891
+ const cmdArgs: string[] = [];
892
+ // Ensure complex payloads are passed as a single JSON argument so the
893
+ // Ruby CLI can parse structured tasks. Accept strings as-is but
894
+ // stringify objects to avoid `[object Object]` being sent.
895
+ if (_args.input !== undefined) {
896
+ if (typeof _args.input === "object") {
897
+ cmdArgs.push(JSON.stringify(_args.input));
898
+ } else {
899
+ cmdArgs.push(String(_args.input));
900
+ }
901
+ }
902
+ if (_args.confirmedWrite) cmdArgs.push(`--confirmed-write=${_args.confirmedWrite}`);
903
+ return runAgentfCli(context.directory, "agent", agentName, cmdArgs);
845
904
  },
846
- }),
847
- },
848
- };
905
+ });
906
+ }
907
+ }
908
+
909
+ const tools = { ...staticTools, ...agentTools };
910
+
911
+ // The plugin host expects a `tool` map (singular key) in the returned hooks.
912
+ return { tool: tools };
849
913
  };
850
914
 
851
915
  export default agentfPlugin;
852
916
  TYPESCRIPT
853
917
  end
854
918
 
855
- def render_opencode_json
856
- <<~JSON
857
- {
858
- "$schema": "https://opencode.ai/config.json",
859
- "plugin": ["./.opencode/plugins/agentf-plugin"]
919
+ def render_opencode_json(root)
920
+ JSON.pretty_generate(opencode_json_config(root))
921
+ end
922
+
923
+ def render_copilot_mcp_json(root)
924
+ JSON.pretty_generate(copilot_mcp_config(root))
925
+ end
926
+
927
+ def opencode_json_config(root)
928
+ base = {
929
+ "$schema" => "https://opencode.ai/config.json"
930
+ }
931
+
932
+ if opencode_plugin_runtime?
933
+ base["plugin"] = ["./.opencode/plugins/agentf-plugin"]
934
+ else
935
+ base["mcp"] = {
936
+ "agentf" => {
937
+ "type" => "local",
938
+ "enabled" => true,
939
+ "command" => opencode_mcp_command(root)
940
+ }
860
941
  }
861
- JSON
942
+ end
943
+
944
+ base
945
+ end
946
+
947
+ def opencode_mcp_command(root)
948
+ [File.join(root, "bin", "agentf"), "mcp-server"]
862
949
  end
863
950
 
864
951
  def write_opencode_json(root)
865
952
  path = File.join(root, "opencode.json")
866
- new_content = JSON.parse(render_opencode_json)
953
+ new_content = JSON.parse(render_opencode_json(root))
867
954
 
868
955
  return write_manifest(path, JSON.pretty_generate(new_content)) unless File.exist?(path)
869
956
 
@@ -875,8 +962,53 @@ module Agentf
875
962
  end
876
963
 
877
964
  merged = existing.dup
878
- merged_plugins = Array(existing["plugin"]) + Array(new_content["plugin"])
879
- merged["plugin"] = merged_plugins.uniq
965
+
966
+ if new_content["plugin"]
967
+ merged_plugins = Array(existing["plugin"]) + Array(new_content["plugin"])
968
+ merged["plugin"] = merged_plugins.uniq
969
+ elsif existing["plugin"]
970
+ filtered_plugins = Array(existing["plugin"]).reject { |entry| entry == "./.opencode/plugins/agentf-plugin" }
971
+ if filtered_plugins.empty?
972
+ merged.delete("plugin")
973
+ else
974
+ merged["plugin"] = filtered_plugins
975
+ end
976
+ end
977
+
978
+ if new_content["mcp"]
979
+ merged["mcp"] = (existing["mcp"] || {}).merge(new_content["mcp"])
980
+ end
981
+
982
+ write_manifest(path, JSON.pretty_generate(merged))
983
+ end
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"))
880
1012
 
881
1013
  write_manifest(path, JSON.pretty_generate(merged))
882
1014
  end
@@ -955,19 +1087,20 @@ module Agentf
955
1087
  - `agent`: string
956
1088
 
957
1089
  ### 2. Episodic Memory (`episodic:*`)
958
- Used for success, pitfall, lesson, and intent records.
1090
+ Used for episode, lesson, playbook, and intent records.
959
1091
 
960
1092
  **Search index**: `episodic:logs`
961
1093
 
962
1094
  **Schema fields**:
963
1095
  - `$.id`
964
1096
  - `$.type`
1097
+ - `$.outcome`
965
1098
  - `$.title`
966
1099
  - `$.description`
967
1100
  - `$.project`
968
1101
  - `$.context`
969
1102
  - `$.code_snippet`
970
- - `$.tags`
1103
+ - `$.embedding`
971
1104
  - `$.created_at`
972
1105
  - `$.agent`
973
1106
  - `$.related_task_id`
@@ -978,9 +1111,9 @@ module Agentf
978
1111
 
979
1112
  - Read recent: `agentf memory recent -n 10`
980
1113
  - Search: `agentf memory search "query" -n 10`
1114
+ - List episodes: `agentf memory episodes -n 10 --outcome=negative`
981
1115
  - Add lesson: `agentf memory add-lesson "<title>" "<description>" --agent=<AGENT>`
982
- - Add success: `agentf memory add-success "<title>" "<description>" --agent=<AGENT>`
983
- - Add pitfall: `agentf memory add-pitfall "<title>" "<description>" --agent=<AGENT>`
1116
+ - Add playbook: `agentf memory add-playbook "<title>" "<description>" --steps="<step1>;<step2>"`
984
1117
  MARKDOWN
985
1118
  end
986
1119
  end