Skip to content

feat: add memory management commands to CLI#567

Open
yanurag-dev wants to merge 7 commits intotruffle-ai:mainfrom
yanurag-dev:feat/memory-command
Open

feat: add memory management commands to CLI#567
yanurag-dev wants to merge 7 commits intotruffle-ai:mainfrom
yanurag-dev:feat/memory-command

Conversation

@yanurag-dev
Copy link
Copy Markdown
Contributor

@yanurag-dev yanurag-dev commented Jan 30, 2026

  • Introduced memory command to manage agent memory, allowing users to list, add, and remove memory entries.
  • Implemented functionality to discover and create agent instruction files for storing memory entries.
  • Updated command parser to recognize memory commands prefixed with '#' for adding entries.
  • Enhanced user feedback with appropriate messages for memory operations.

Release Note

  • No release needed (docs/chore/test-only/private package)
  • Changeset added via pnpm changeset (select packages + bump)
    • Bump type: Patch / Minor / Major (choose patch for all if unsure)
    • Packages: ...

Summary by CodeRabbit

  • New Features
    • Added interactive memory management via a new /memory command (subcommands: show/list, add, remove).
    • Quick-add using a # prefix to insert memory entries into instruction files.
    • Memory entries stored under a dedicated "Memory" section in project and global instruction files.
    • New interactive wizards/overlays guide scope selection and entry add/remove workflows.

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 30, 2026

@yanurag-dev is attempting to deploy a commit to the Shaunak's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds interactive memory management to the CLI: new /memory command and overlays, memory utilities for AGENTS.md-style instruction files, Ink UI wizards (add/remove/manager), wiring into containers/state, and an exported discoverAgentInstructionFile API.

Changes

Cohort / File(s) Summary
Versioning & Changeset
/.changeset/memory-management-commands.md
New changeset documenting memory commands and package bumps.
Agent Management Exports
packages/agent-management/src/config/index.ts, packages/agent-management/src/index.ts
Re-exported discoverAgentInstructionFile, making it part of the public API.
CLI Command Integration
packages/cli/src/cli/commands/interactive-commands/commands.ts, packages/cli/src/cli/commands/interactive-commands/memory-command.ts
Added memoryCommand and handleMemoryAdd; integrated memory-add execution path into command dispatch.
Memory Utilities
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts
New module: add/list/remove memory entries for project/global scopes; discovers/creates instruction files and parses/writes Memory sections.
Ink UI Overlays
packages/cli/src/cli/ink-cli/components/overlays/MemoryManager.tsx, .../MemoryAddWizard.tsx, .../MemoryRemoveWizard.tsx
Three new Ink React overlays: manager, add-wizard, remove-wizard, each exposing imperative handles and public types for overlay interactions.
Container & Input Wiring
packages/cli/src/cli/ink-cli/containers/InputContainer.tsx, packages/cli/src/cli/ink-cli/containers/OverlayContainer.tsx
Handle __triggerOverlay results, route input to memory overlays, implement handlers for add/remove completions, and mount memory overlays with refs.
State & Types
packages/cli/src/cli/ink-cli/state/types.ts, .../initialState.ts, .../hooks/useCLIState.ts
Extended OverlayType with memory-manager, memory-add-wizard, memory-remove-wizard; added MemoryAddScope, MemoryAddWizardState, MemoryRemoveWizardState; initialized new UI state fields.
Overlay Mapping
packages/cli/src/cli/ink-cli/utils/commandOverlays.ts
Added memorymemory-manager to NO_ARGS_OVERLAY so /memory without args opens the manager overlay.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI Commands
    participant Container as OverlayContainer
    participant MemoryUI as MemoryManager UI
    participant MemoryUtils as Memory Utils
    participant FileSystem as File System

    User->>CLI: Input /memory
    CLI->>Container: Command result with __triggerOverlay: memory-manager
    Container->>MemoryUI: Show MemoryManager overlay
    MemoryUI->>User: Display add/show/remove options
    User->>MemoryUI: Select 'add'
    MemoryUI->>Container: handleMemoryManagerAction('add')
    Container->>MemoryUI: Show MemoryAddWizard
    MemoryUI->>User: Step 1: Select scope
    User->>MemoryUI: Choose scope
    MemoryUI->>User: Step 2: Enter content
    User->>MemoryUI: Input memory text and submit
    MemoryUI->>Container: onComplete(content, scope)
    Container->>MemoryUtils: addMemoryEntry(content, scope)
    MemoryUtils->>FileSystem: Read/Create instruction file & parse Memory section
    MemoryUtils->>FileSystem: Append entry and write file
    FileSystem-->>MemoryUtils: Write success
    MemoryUtils-->>Container: Success message
    Container->>User: Display success message
Loading
sequenceDiagram
    participant User
    participant CLI as CLI Commands
    participant Container as OverlayContainer
    participant RemoveUI as MemoryRemoveWizard
    participant MemoryUtils as Memory Utils
    participant FileSystem as File System

    User->>CLI: Input /memory remove
    CLI->>Container: Command result with __triggerOverlay: memory-remove-wizard
    Container->>MemoryUtils: listMemoryEntries()
    MemoryUtils->>FileSystem: Read project & global instruction files
    FileSystem-->>MemoryUtils: Return entries
    MemoryUtils-->>Container: Entries by scope
    Container->>RemoveUI: Show MemoryRemoveWizard with entries
    RemoveUI->>User: Select scope then entry
    User->>RemoveUI: Choose entry index
    RemoveUI->>Container: onComplete(index, scope)
    Container->>MemoryUtils: removeMemoryEntry(index, scope)
    MemoryUtils->>FileSystem: Read, modify, write file
    FileSystem-->>MemoryUtils: Write success
    MemoryUtils-->>Container: Success message
    Container->>User: Display success message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

A rabbit hops and takes a note,
I tuck it in the agent's coat,
"/memory add" — a little cheer,
"/memory show" — the entries appear,
Remove a line, and hop away! 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add memory management commands to CLI' clearly and concisely summarizes the main change: introducing new memory management functionality to the CLI with list, add, and remove operations.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-command.ts`:
- Around line 93-97: The handler function's parameter list contains an unused
parameter named agent which triggers ESLint; update the handler signature in
memory-command.ts to either rename agent to _agent (e.g., handler: async (args:
string[], _agent: DextoAgent, _ctx: CommandContext) => ...) or remove the
parameter entirely if the type system permits, and ensure all internal
references (if any) are updated accordingly so the lint warning is resolved.

In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts`:
- Around line 182-236: The removal logic in removeMemoryEntry currently looks
for an exact string `- ${entryToRemove}` which can fail if bullets have extra
whitespace; update removeMemoryEntry to match bullets by comparing the parsed
bullet content rather than raw line equality: when scanning lines inside the
MEMORY_SECTION_HEADER block, normalize each bullet line (trim leading '-' and
whitespace) and compare to entryToRemove (or use a regex that allows arbitrary
spaces like /^\s*-\s*(.+)$/ and compare captured group) and only skip/write the
line when a match is found; set a removed flag and only call writeFileSync if
removed is true, and if removed remains false return { success: false, filePath,
error: 'Entry not found' }; reference functions/constants: parseMemoryEntries,
MEMORY_SECTION_HEADER, entryToRemove, removeMemoryEntry.
- Around line 14-37: findInstructionFile() currently checks
AGENT_INSTRUCTION_FILES with existsSync (case-sensitive) causing divergence;
replace its body to call the shared discoverAgentInstructionFile() and return
its result (string | null) so behavior matches the shared helper; ensure
getOrCreateInstructionFile() continues to use findInstructionFile() (or call
discoverAgentInstructionFile() directly) and preserve return types; update any
references to AGENT_INSTRUCTION_FILES only if still needed.
🧹 Nitpick comments (2)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)

89-176: Consider standard Result helpers for these utilities.

These helpers return bespoke { success, error } objects. In packages/cli we standardize on ok() / fail() helpers for consistent error handling and composition. Consider switching these utilities (and callers) to the Result helpers pattern for alignment. As per coding guidelines, Use standardized Result helpers from packages/core/src/utils/result.ts: ok(data, issues?), fail(issues), hasErrors(issues), splitIssues(issues), zodToIssues(zodError).

packages/cli/src/cli/commands/interactive-commands/command-parser.ts (1)

92-106: Consider narrowing the # trigger to avoid hijacking normal prompts.

Currently any input starting with # becomes a memory-add command. That blocks sending prompts that begin with markdown headings or code (e.g., #include). You could require # (or other delimiter) to reduce accidental captures.

💡 Example tweak
-    if (trimmed.startsWith('#')) {
-        const memoryContent = trimmed.slice(1).trim();
+    const memoryMatch = trimmed.match(/^#\s+(.*)$/);
+    if (memoryMatch) {
+        const memoryContent = memoryMatch[1];
         return {
             type: 'command',
             command: 'memory-add',
             args: [memoryContent],
             rawInput: trimmed,
         };
     }

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts`:
- Around line 170-246: The removal currently matches bullet content which breaks
on duplicate entries; in removeMemoryEntry track a bulletCounter while scanning
the MEMORY_SECTION_HEADER section instead of comparing content to entryToRemove,
increment the counter whenever a bulletRegex match is found, and when
bulletCounter === index skip that specific bullet line (set removed = true) so
the exact indexed entry is removed; keep the existing index bounds check (using
entries.length), keep writeFileSync only when removed, and remove the
content-based equality check for deletion.
🧹 Nitpick comments (1)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)

77-81: Consider using standardized Result helpers for these exports.

Aligning add/list/remove with ok/fail keeps error handling consistent across CLI utilities and reduces bespoke result shapes.
As per coding guidelines: packages/{core,server,cli}/src/**/*.ts: Use standardized Result helpers from packages/core/src/utils/result.ts: ok(data, issues?), fail(issues), hasErrors(issues), splitIssues(issues), zodToIssues(zodError).

Comment on lines +170 to +246
export function removeMemoryEntry(index: number): {
success: boolean;
filePath: string;
error?: string;
} {
try {
const filePath = findInstructionFile();

if (!filePath || !existsSync(filePath)) {
return { success: false, filePath: '', error: 'No instruction file found' };
}

const content = readFileSync(filePath, 'utf-8');
const entries = parseMemoryEntries(content);

if (index < 0 || index >= entries.length) {
return { success: false, filePath, error: 'Invalid entry index' };
}

// Remove the entry
const entryToRemove = entries[index];
if (!entryToRemove) {
return { success: false, filePath, error: 'Entry not found' };
}

const lines = content.split('\n');
const newLines: string[] = [];
let inMemorySection = false;
let removed = false;

// Regex to extract bullet content: matches "- content" with any whitespace
const bulletRegex = /^\s*-\s*(.+)$/;

for (const line of lines) {
const trimmed = line.trim();

if (trimmed === MEMORY_SECTION_HEADER) {
inMemorySection = true;
newLines.push(line);
continue;
}

if (inMemorySection && trimmed.startsWith('##')) {
inMemorySection = false;
}

// Check if this line is a bullet point that matches the entry to remove
if (inMemorySection && !removed) {
const match = trimmed.match(bulletRegex);
if (match) {
const bulletContent = match[1]?.trim();
if (bulletContent === entryToRemove.trim()) {
removed = true;
continue; // Skip this line
}
}
}

newLines.push(line);
}

// Only write to file if we actually removed something
if (!removed) {
return { success: false, filePath, error: 'Entry not found' };
}

writeFileSync(filePath, newLines.join('\n'), 'utf-8');

return { success: true, filePath };
} catch (error) {
return {
success: false,
filePath: '',
error: error instanceof Error ? error.message : String(error),
};
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove by index to avoid duplicate-entry mismatches.

Currently removal matches the first bullet with matching content, so if entries are duplicated, removing by index can delete the wrong one. Track bullet index while scanning the Memory section and remove the exact target index.

🛠️ Proposed fix (index-based removal)
-        // Remove the entry
-        const entryToRemove = entries[index];
-        if (!entryToRemove) {
-            return { success: false, filePath, error: 'Entry not found' };
-        }
+        const targetIndex = index;
@@
-        let removed = false;
+        let removed = false;
+        let currentIndex = -1;
@@
-            if (inMemorySection && !removed) {
-                const match = trimmed.match(bulletRegex);
-                if (match) {
-                    const bulletContent = match[1]?.trim();
-                    if (bulletContent === entryToRemove.trim()) {
-                        removed = true;
-                        continue; // Skip this line
-                    }
-                }
-            }
+            if (inMemorySection && !removed) {
+                const match = trimmed.match(bulletRegex);
+                if (match) {
+                    currentIndex += 1;
+                    if (currentIndex === targetIndex) {
+                        removed = true;
+                        continue; // Skip this line
+                    }
+                }
+            }
🤖 Prompt for AI Agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts` around
lines 170 - 246, The removal currently matches bullet content which breaks on
duplicate entries; in removeMemoryEntry track a bulletCounter while scanning the
MEMORY_SECTION_HEADER section instead of comparing content to entryToRemove,
increment the counter whenever a bulletRegex match is found, and when
bulletCounter === index skip that specific bullet line (set removed = true) so
the exact indexed entry is removed; keep the existing index bounds check (using
entries.length), keep writeFileSync only when removed, and remove the
content-based equality check for deletion.

@shaunak99
Copy link
Copy Markdown
Collaborator

Nice! I think this is slightly different from our core memory layer but since adding memory into .md is also useful, let's add this version too.

Can we add options for creating memory with user/project scope? Currently this always adds it to the project scope only. It would be helpful if after creating the memory, it prompts the user if the scope should be project or user/global. (Refer /plugin command)

- Introduced memory command to manage agent memory, allowing users to list, add, and remove memory entries.
- Implemented functionality to discover and create agent instruction files for storing memory entries.
- Updated command parser to recognize memory commands prefixed with '#' for adding entries.
- Enhanced user feedback with appropriate messages for memory operations.
- Updated memory command handler to use a placeholder for the agent parameter.
- Refactored instruction file discovery logic to utilize shared functionality for consistent case-insensitive matching.
- Enhanced the removeMemoryEntry function to accurately identify and remove bullet points, ensuring only valid entries are processed.
…izards

- Renamed the memory list command to show for clarity.
- Improved the remove command to support both project and global memory entries with an interactive wizard.
- Updated memory command usage documentation and user feedback messages.
- Added MemoryRemoveWizard component for guided memory entry removal.
- Refactored memory command handlers to accommodate new functionality.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-command.ts`:
- Around line 1-7: Remove the unused imports causing ESLint warnings by deleting
the `import * as path from 'path';` line and the `import {
discoverAgentInstructionFile } from '@dexto/agent-management';` line in
memory-command.ts; verify there are no references to `path` or
`discoverAgentInstructionFile` anywhere in this file (e.g., inside functions
that call them) and if references exist, either remove or replace those usages
accordingly so the module builds cleanly.

In `@packages/cli/src/cli/ink-cli/containers/InputContainer.tsx`:
- Around line 792-794: Add the missing overlay key 'memory-remove-wizard' to the
overlaysWithOwnInput array in InputContainer.tsx so the main input is disabled
when the remove wizard (which uses BaseSelector and manages its own text input)
is active; edit the overlaysWithOwnInput constant (where 'memory-manager' and
'memory-add-wizard' are listed) to include 'memory-remove-wizard' so input
conflicts are prevented.
🧹 Nitpick comments (5)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)

181-184: Consider clarifying filePath when no project file exists.

When projectPath is null, result.project.filePath is set to a path that doesn't exist yet. This could mislead callers expecting the path to reference an actual file. Consider setting it to null or clearly documenting that the path is the default location where a file would be created.

     const result: MemoryListResult = {
-        project: { entries: [], filePath: projectPath || path.join(process.cwd(), 'AGENTS.md') },
+        project: { entries: [], filePath: projectPath },
         global: { entries: [], filePath: globalPath },
     };
packages/cli/src/cli/ink-cli/components/overlays/MemoryAddWizard.tsx (1)

68-71: Provide feedback when content is empty on submit.

When the user presses Enter with empty content, the submission is silently ignored. Consider providing visual feedback (e.g., a brief error message or highlighting) so users understand why nothing happened.

packages/cli/src/cli/commands/interactive-commands/memory-command.ts (1)

65-68: Consider formalizing the overlay trigger return type.

Multiple locations use as any to return overlay trigger objects ({ __triggerOverlay: '...' }). This bypasses type safety. Consider extending CommandHandlerResult to include an overlay trigger variant, or create a helper function that returns a properly typed result.

// Example: Add to command-parser.ts
export interface OverlayTrigger {
    __triggerOverlay: OverlayType;
    args?: string[];
}

export type CommandHandlerResult = boolean | string | StyledOutput | SendMessageMarker | OverlayTrigger;

Also applies to: 161-164, 168-170

packages/cli/src/cli/ink-cli/containers/OverlayContainer.tsx (1)

2698-2710: Duplicate listMemoryEntries() calls in render.

listMemoryEntries() is called twice on lines 2704 and 2705, causing redundant computation on every render when the overlay is visible.

♻️ Proposed fix to compute once
                 {/* Memory Remove Wizard */}
-                {ui.activeOverlay === 'memory-remove-wizard' && (
+                {ui.activeOverlay === 'memory-remove-wizard' && (() => {
+                    const memoryData = listMemoryEntries();
+                    return (
                     <MemoryRemoveWizard
                         ref={memoryRemoveWizardRef}
                         isVisible={true}
                         state={ui.memoryRemoveWizard}
-                        projectEntries={listMemoryEntries().project.entries}
-                        globalEntries={listMemoryEntries().global.entries}
+                        projectEntries={memoryData.project.entries}
+                        globalEntries={memoryData.global.entries}
                         onUpdateState={handleMemoryRemoveWizardUpdate}
                         onComplete={handleMemoryRemoveComplete}
                         onClose={handleClose}
                     />
-                )}
+                    );
+                })()}

Alternatively, consider using useMemo if the overlay is frequently toggled.

packages/cli/src/cli/ink-cli/components/overlays/MemoryRemoveWizard.tsx (1)

71-74: Consider using the index parameter directly from onSelect.

The entry parameter is unused because entryIndex is used instead. This works but could be cleaner if BaseSelector.onSelect provided the index, or by renaming the parameter to _entry to signal intentional non-use.

💡 Optional: Mark unused parameter
-        const handleEntrySelect = (entry: string) => {
+        const handleEntrySelect = (_entry: string) => {
             if (!state?.scope) return;
             onComplete(entryIndex, state.scope);
         };

- Removed unused imports from memory command file for better clarity.
- Added 'memory-remove-wizard' to the list of overlays in InputContainer for improved user interaction.
@@ -0,0 +1,13 @@
---
'@dexto/agent-management': patch
'dexto': minor
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to patch version

Comment on lines +53 to +60
* Handler for /memory remove with power user shortcuts
* Syntax:
* /memory remove → Interactive wizard
* /memory remove <number> → Remove from project
* /memory remove <number> --global → Remove from global
* /memory remove global <number> → Remove from global
*/
async function handleRemoveCommand(args: string[]): Promise<CommandHandlerResult> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need power user shortcuts like /memory remove, let interactive command handle all of this

can remove any unnecessary code from these files

}

/**
* Handler for # <content> - DEPRECATED: Now handled via /memory add
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove if unused

Comment on lines +14 to +23
function findProjectInstructionFile(): string | null {
return discoverAgentInstructionFile();
}

/**
* Gets the global instruction file path (~/.dexto/AGENTS.md)
*/
function getGlobalInstructionFilePath(): string {
return getDextoGlobalPath('', 'AGENTS.md');
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for 1 line functions, just call the function directly

/**
* Add a memory entry to the instruction file for a given scope
*/
export function addMemoryEntry(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe internal memories are stored in the database and need to be enabled, can you check MemoryManager class

Not sure if we should be adding to AGENTS.md here, it kind of breaks the pattern we have in core

do other CLI tools write to AGENTS.md?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants