From d6a03be140e7d8789fca8bc471d71f5d8c954b58 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 21:14:02 +0000 Subject: [PATCH] feat(agentic): add brain recall CLI command Co-Authored-By: Virgil --- pkg/agentic/commands.go | 92 ++++++++++++++++++++++++++++++++++++ pkg/agentic/commands_test.go | 77 ++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index 61a604f..7a25e8b 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -39,6 +39,8 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) { c.Command("agentic:mirror", core.Command{Description: "Mirror Forge repos to GitHub", Action: s.cmdMirror}) c.Command("brain/ingest", core.Command{Description: "Bulk ingest memories into OpenBrain", Action: s.cmdBrainIngest}) c.Command("brain:ingest", core.Command{Description: "Bulk ingest memories into OpenBrain", Action: s.cmdBrainIngest}) + c.Command("brain/recall", core.Command{Description: "Recall memories from OpenBrain", Action: s.cmdBrainRecall}) + c.Command("brain:recall", core.Command{Description: "Recall memories from OpenBrain", Action: s.cmdBrainRecall}) c.Command("brain/seed-memory", core.Command{Description: "Import markdown memories into OpenBrain from a project memory directory", Action: s.cmdBrainSeedMemory}) c.Command("brain:seed-memory", core.Command{Description: "Import markdown memories into OpenBrain from a project memory directory", Action: s.cmdBrainSeedMemory}) c.Command("brain/list", core.Command{Description: "List memories in OpenBrain", Action: s.cmdBrainList}) @@ -463,6 +465,59 @@ func (s *PrepSubsystem) cmdBrainList(options core.Options) core.Result { return core.Result{Value: output, OK: true} } +// result := c.Command("brain/recall").Run(ctx, core.NewOptions( +// +// core.Option{Key: "query", Value: "workspace handoff context"}, +// +// )) +func (s *PrepSubsystem) cmdBrainRecall(options core.Options) core.Result { + query := optionStringValue(options, "query", "_arg") + if query == "" { + core.Print(nil, "usage: core-agent brain recall [--top-k=10] [--project=agent] [--type=architecture] [--agent=virgil] [--min-confidence=0.7]") + return core.Result{Value: core.E("agentic.cmdBrainRecall", "query is required", nil), OK: false} + } + + result := s.Core().Action("brain.recall").Run(s.commandContext(), core.NewOptions( + core.Option{Key: "query", Value: query}, + core.Option{Key: "top_k", Value: optionIntValue(options, "top_k", "top-k")}, + core.Option{Key: "project", Value: optionStringValue(options, "project")}, + core.Option{Key: "type", Value: optionStringValue(options, "type")}, + core.Option{Key: "agent_id", Value: optionStringValue(options, "agent_id", "agent")}, + core.Option{Key: "min_confidence", Value: optionStringValue(options, "min_confidence", "min-confidence")}, + )) + if !result.OK { + err := commandResultError("agentic.cmdBrainRecall", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := brainRecallOutputFromResult(result.Value) + if !ok { + err := core.E("agentic.cmdBrainRecall", "invalid brain recall output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "count: %d", output.Count) + if len(output.Memories) == 0 { + core.Print(nil, "no memories") + return core.Result{Value: output, OK: true} + } + + for _, memory := range output.Memories { + if memory.Project != "" || memory.AgentID != "" || memory.Confidence != 0 { + core.Print(nil, " %s %-12s %s %s %.2f", memory.ID, memory.Type, memory.Project, memory.AgentID, memory.Confidence) + } else { + core.Print(nil, " %s %-12s", memory.ID, memory.Type) + } + if memory.Content != "" { + core.Print(nil, " %s", memory.Content) + } + } + + return core.Result{Value: output, OK: true} +} + // result := c.Command("brain/forget").Run(ctx, core.NewOptions(core.Option{Key: "_arg", Value: "mem-1"})) func (s *PrepSubsystem) cmdBrainForget(options core.Options) core.Result { id := optionStringValue(options, "id", "_arg") @@ -488,6 +543,43 @@ func (s *PrepSubsystem) cmdBrainForget(options core.Options) core.Result { return core.Result{Value: result.Value, OK: true} } +type brainRecallOutput struct { + Count int `json:"count"` + Memories []brainRecallMemory `json:"memories"` +} + +type brainRecallMemory struct { + ID string `json:"id"` + Type string `json:"type"` + Content string `json:"content"` + Project string `json:"project"` + AgentID string `json:"agent_id"` + Confidence float64 `json:"confidence"` + Tags []string `json:"tags"` +} + +func brainRecallOutputFromResult(value any) (brainRecallOutput, bool) { + switch typed := value.(type) { + case brainRecallOutput: + return typed, true + case *brainRecallOutput: + if typed == nil { + return brainRecallOutput{}, false + } + return *typed, true + default: + jsonResult := core.JSONMarshalString(value) + if jsonResult == "" { + return brainRecallOutput{}, false + } + var output brainRecallOutput + if parseResult := core.JSONUnmarshalString(jsonResult, &output); !parseResult.OK { + return brainRecallOutput{}, false + } + return output, true + } +} + func (s *PrepSubsystem) cmdStatus(options core.Options) core.Result { workspaceRoot := WorkspaceRoot() filesystem := s.Core().Fs() diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index 88636e6..7d5817d 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -225,6 +225,15 @@ func TestCommandsforge_CmdIssueCreate_Good_WithLabelsAndMilestone(t *testing.T) assert.True(t, r.OK) } +func TestCommands_RegisterCommands_Good_BrainRecall(t *testing.T) { + s, c := testPrepWithCore(t, nil) + + s.registerCommands(context.Background()) + + assert.Contains(t, c.Commands(), "brain/recall") + assert.Contains(t, c.Commands(), "brain:recall") +} + func TestCommands_CmdBrainList_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.list", func(_ context.Context, options core.Options) core.Result { @@ -287,6 +296,74 @@ func TestCommands_CmdBrainList_Ugly_InvalidOutput(t *testing.T) { assert.Contains(t, err.Error(), "invalid brain list output") } +func TestCommands_CmdBrainRecall_Good(t *testing.T) { + s, c := testPrepWithCore(t, nil) + c.Action("brain.recall", func(_ context.Context, options core.Options) core.Result { + assert.Equal(t, "workspace handoff context", options.String("query")) + assert.Equal(t, 3, options.Int("top_k")) + assert.Equal(t, "agent", options.String("project")) + assert.Equal(t, "architecture", options.String("type")) + assert.Equal(t, "virgil", options.String("agent_id")) + assert.Equal(t, "0.75", options.String("min_confidence")) + return core.Result{Value: map[string]any{ + "success": true, + "count": 1, + "memories": []any{ + map[string]any{ + "id": "mem-1", + "type": "architecture", + "content": "Use named actions.", + "project": "agent", + "agent_id": "virgil", + "confidence": 0.75, + "tags": []any{"architecture", "convention"}, + }, + }, + }, OK: true} + }) + + output := captureStdout(t, func() { + result := s.cmdBrainRecall(core.NewOptions( + core.Option{Key: "_arg", Value: "workspace handoff context"}, + core.Option{Key: "top_k", Value: 3}, + core.Option{Key: "project", Value: "agent"}, + core.Option{Key: "type", Value: "architecture"}, + core.Option{Key: "agent", Value: "virgil"}, + core.Option{Key: "min_confidence", Value: "0.75"}, + )) + require.True(t, result.OK) + }) + + assert.Contains(t, output, "count: 1") + assert.Contains(t, output, "mem-1 architecture") + assert.Contains(t, output, "Use named actions.") +} + +func TestCommands_CmdBrainRecall_Bad_MissingQuery(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + + result := s.cmdBrainRecall(core.NewOptions()) + + require.False(t, result.OK) + err, ok := result.Value.(error) + require.True(t, ok) + assert.Contains(t, err.Error(), "query is required") +} + +func TestCommands_CmdBrainRecall_Ugly_InvalidOutput(t *testing.T) { + s, c := testPrepWithCore(t, nil) + c.Action("brain.recall", func(_ context.Context, _ core.Options) core.Result { + return core.Result{Value: 123, OK: true} + }) + + result := s.cmdBrainRecall(core.NewOptions(core.Option{Key: "_arg", Value: "workspace handoff context"})) + + require.False(t, result.OK) + err, ok := result.Value.(error) + require.True(t, ok) + assert.Contains(t, err.Error(), "invalid brain recall output") +} + func TestCommands_CmdBrainForget_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.forget", func(_ context.Context, options core.Options) core.Result {