memo-agent
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Fail
- exec() — Shell command execution in __tests__/db.test.ts
- rm -rf — Recursive force deletion command in __tests__/guard.test.ts
- os.homedir — User home directory access in __tests__/guard.test.ts
- process.env — Environment variable access in __tests__/pathUtils.test.ts
- os.homedir — User home directory access in src/config/loader.ts
- process.env — Environment variable access in src/config/loader.ts
Permissions Pass
- Permissions — No dangerous permissions requested
This is a terminal-based AI assistant built with TypeScript that connects to OpenAI-compatible APIs. It provides persistent memory, MCP tool extensions, and intelligent context compression directly in your command line.
Security Assessment
Risk: Medium. The tool explicitly executes shell commands and accesses the user's home directory to load configurations. It also reads environment variables to retrieve API keys. While it includes a built-in permission guard and sandboxing features designed to restrict dangerous operations and protect secrets from child processes, the underlying execution capabilities require caution. No hardcoded secrets were detected, but it does make network requests to external APIs and web search engines. The most concerning code flags (like `rm -rf` and shell execution) were found within test files rather than core runtime code.
Quality Assessment
The project uses the standard MIT license and has a clear description. It appears actively maintained with very recent repository updates. However, it currently has low community visibility with only 5 GitHub stars, which means it hasn't been broadly peer-reviewed or battle-tested. The documentation is thorough, detailing features like session archiving, slash commands, and tool caching, which reflects a mature approach to feature design.
Verdict
Use with caution. It offers well-designed safety features, but low community adoption and inherent shell execution capabilities mean you should review the sandbox configuration closely before deployment.
memo-agent is a terminal-based AI assistant application (Hermes Agent simplified version), built with TypeScript + React + Ink. It connects directly to OpenAI-compatible APIs and features persistent memory, MCP tool extensions, and intelligent context compression.
memo-agent
memo-agent is a terminal-based AI assistant application (Hermes Agent simplified version), built with TypeScript + React + Ink. It connects directly to OpenAI-compatible APIs and features persistent memory, MCP tool extensions, and intelligent context compression.
Features
- Persistent Memory —
NOTES.mdretains context across sessions and is automatically injected into the system prompt every round; whenauto_updateis enabled, it automatically evaluates and writes at the end of each round - Session Chain Archiving — When context compression is triggered, a new session is created and linked to the old session via
parent_session_id, ensuring history is never lost - Three-Zone Context Compression — Automatically archives the middle history for extra-long conversations, preserving the first round and the most recent ~20k tokens, so conversations are never truncated
- Slash Commands —
/notes,/history,/search,/compact,/cost,/plan, and more; use/helpto see all available commands - Plan-Execute-Reflect Loop —
/plan <goal>triggers a three-phase agentic loop: planning (task breakdown), execution (per-task tool loop in dependency order), and reflection (read-only review); progress is streamed live to the UI - Task Persistence — Tasks created by the agent are stored in SQLite, scoped to the session, and survive
/resume - Recipes System — Custom
.mdtemplate files; invoke with/recipe-name [args]in one click; supportswatchPathsto auto-recommend related recipes when matching files are modified - MCP Tool Extensions — Connect to external tool servers via the Model Context Protocol
- Web Search — Built-in
WebSearchtool powered by Brave Search; configuresearch.apiKeyto let the agent search the web autonomously - Tool Result Cache — Read-only tool results are cached for 30 seconds; cleared automatically when files are modified
- Session Persistence — SQLite stores all history;
/resumerestores any historical session; supports full-text search - Profile Isolation — Multiple profiles with independent configurations, memory, and session data, completely isolated from each other
- Permission Guard —
ask/automodes; dangerous commands (e.g.,rm -rf) force confirmation; path safety restrictions; supportsdisabledToolsto completely block specified tools - RunCommand Sandbox — When
permissions.sandbox.enabled: true, child processes only inherit an explicit env-var allowlist, keeping API keys and secrets out of subprocess scope - Rich Text UI — Rendered with React + Ink, streaming output, status bar displays token usage and cost in real time
- Enhanced Input — Cursor positioning (←/→ to move, edit mid-line), history navigation (↑/↓), queued input during streaming without loss
Installation
Via npm (recommended)
npm install -g memo-agent
Then configure your API key (choose one method):
Option A — Environment variable:
export MODEL_API_KEY=sk-...
export MODEL_BASE_URL=https://api.openai.com/v1 # optional, defaults to OpenAI
export MODEL_NAME=gpt-4o # optional, defaults to gpt-4o
memo
Option B — Config file:
mkdir -p ~/.memo-agent
cat > ~/.memo-agent/config.yaml << 'EOF'
model:
base_url: "https://api.openai.com/v1"
api_key: "sk-..."
name: gpt-4o
EOF
memo
Via npx (no install)
MODEL_API_KEY=sk-... npx memo-agent
Quick Start (from source)
Install Dependencies
npm install
Configuration
Copy the example configuration file and fill in your API details:
cp .env.example .env
MODEL_BASE_URL=https://api.openai.com/v1
MODEL_API_KEY=sk-...
MODEL_NAME=gpt-4o
Or create ~/.memo-agent/config.yaml (see Configuration File).
Launch
# Development mode (tsx hot reload)
npm run dev
# Build and run
npm run build
npm start
# Global installation from source
npm install -g .
memo
CLI Arguments
memo [options]
OPTIONS
--profile <name> Use the specified profile (default: "default")
--model <name> Override the model name in config
--resume <session-id> Resume a specific historical session
--auto Start in auto permission mode (no confirmation)
--version, -v Print version number
--help, -h Print help
Examples:
memo --profile work
memo --model gpt-4o-mini
memo --resume abc12345
memo --auto
Terminal Input Operations
| Operation | Effect |
|---|---|
← / → |
Move cursor within the input line; supports mid-line insertion or deletion |
↑ / ↓ |
Switch through input history (up to 50 entries); ↓ returns to current editing content |
Backspace / Delete |
Delete character to the left of the cursor |
| Paste / Multi-character input | Cursor correctly jumps to the end of all characters |
| Typing during streaming | Characters are queued (gray + (queued)); can continue editing after idle |
Ctrl+C (during streaming) |
Interrupt the request; already streamed content remains on screen (marked [interrupted]) |
Ctrl+C (idle, press twice) |
Exit |
Status Bar
The bottom status bar displays in real time:
● memo-agent │ gpt-4o tokens: 1234/128k (15%) │ $0.0042 │ mode:ask │ profile:default
| Field | Description |
|---|---|
● / ○ |
Streaming / idle |
tokens |
Session used tokens / model limit; turns yellow above 70%, red above 85% |
$X.XXXX |
Estimated session cost (USD) |
mode |
Current permission mode (ask / auto) |
profile |
Current profile name |
Slash Commands
Enter the following commands during a conversation:
| Command | Description |
|---|---|
/help |
Display all available commands and recipes |
/plan <goal> |
Run Plan-Execute-Reflect agentic loop for a goal |
/notes [show|clear] |
View or clear persistent notes (NOTES.md) |
/history [n] |
Show the last n sessions (default 10) |
/search <keyword> |
Full-text search all historical messages |
/compact [focus description] |
Manually trigger context archival compression |
/model [name] |
View or switch the current model |
/cost |
Show current session token consumption and estimated cost |
/clear |
Clear the current session context (memory is preserved) |
/resume [session-id] |
Prompt to resume a session using the --resume argument |
/profile [name] |
View or switch profile |
/recipes |
List installed recipes |
/mode [ask|auto] |
Switch tool execution permission mode |
/exit |
Exit memo-agent (alias: /quit) |
Agentic Plan Loop (/plan)
/plan <goal> triggers a three-phase autonomous loop:
- Plan — The agent calls
CreateTaskto break the goal into 3–7 tasks with explicit dependencies. No other actions occur in this phase. - Execute — Tasks are executed in topological order (blocked tasks wait for their prerequisites). Each task runs a full tool-call loop with the complete tool set.
- Reflect — After all tasks finish, the agent reviews results with read-only tools and writes a summary.
Progress events are streamed to the UI in real time:
● Planning tasks...
● Task 1/3: Set up project structure
✓ Set up project structure
● Task 2/3: Implement core logic
✓ Implement core logic
● Reflecting on results...
Recipes System
A recipe is a reusable prompt template stored as a .md file.
Storage Locations
- Global:
~/.memo-agent/recipes/ - Project-level (priority):
.memo-agent/recipes/
Recipe File Format
---
name: review
description: Perform a code review on current changes
allowedTools: [ReadFile, SearchCode, ListFiles]
---
Please perform a code review on the following changes, focusing on security, performance, and maintainability.
$ARGUMENTS
| Field | Description |
|---|---|
name |
Invocation name (lowercase letters + hyphens) |
description |
Description shown in the /recipes list |
allowedTools |
Tools pre-authorized during recipe execution (skips permission confirmation) |
watchPaths |
Auto-recommend this recipe when file paths match (optional) |
$ARGUMENTS |
Placeholder for arguments passed during invocation |
Invoking a Recipe
/review src/main.ts
/fix-types
/summarize-pr
Persistent Memory
NOTES.md — Working Notes (Read/Write)
Path: ~/.memo-agent/memory/NOTES.md (or under the profile directory)
- The agent can append notes via the
WriteNotestool - When
memory.auto_update: trueis enabled, it automatically evaluates whether information is worth retaining and writes it at the end of each round; saved content is displayed in the terminal upon writing - Automatically injected into the system prompt at the start of each session
/notes showto view,/notes clearto clear
PROFILE.md — User Preferences (Read-Only)
Path: ~/.memo-agent/memory/PROFILE.md
Only editable by the user; the agent will not modify it. Suitable for placing:
I am a backend engineer, primarily using Go and TypeScript.
Code style: functional-first, avoid over-abstraction.
Please respond in Chinese; code comments in English.
Context Compression
Conversation context is managed in three zones:
┌──────────────────────────────────┐
│ HEAD (Anchor Zone) │ system prompt + first round, never compressed
├──────────────────────────────────┤
│ MIDDLE (Archive Zone) │ Replaced with LLM-generated summary when threshold exceeded
├──────────────────────────────────┤
│ TAIL (Active Zone) │ Most recent ~20k tokens, fully preserved
└──────────────────────────────────┘
Trigger thresholds (adjustable in config):
- 70% context usage → Status bar warning (yellow)
- 85% context usage → Auto-trigger archival
- Manual trigger:
/compact [focus description]
Tool System
Built-in Tools
| Tool | Description | Permission |
|---|---|---|
ReadFile |
Read file content, supports line range (limited to cwd / profile directory) | Read-only |
WriteFile |
Create or overwrite files (limited to cwd / profile directory) | Write |
EditFile |
Precise string replacement; supports replace_all: true for global replacement |
Write |
ListFiles |
List files using glob patterns | Read-only |
SearchCode |
Regex search file content (prefers rg, falls back to grep), global result limit | Read-only |
RunCommand |
Execute shell commands, 30s timeout | High-risk |
WriteNotes |
Append content to NOTES.md | Write |
ReadNotes |
Read current NOTES.md | Read-only |
CreateTask |
Create in-session tasks (IDs start from 1, reset after /clear) |
Write |
UpdateTask |
Update task status, blockedBy, blocks |
Write |
ListTasks |
List all tasks in the current session | Read-only |
GetTask |
Get task details (including dependencies) | Read-only |
SearchHistory |
Full-text search historical messages (across all sessions) | Read-only |
ListSessions |
List historical sessions (including session chain parent-child relationships) | Read-only |
WebSearch |
Search the web via Brave Search; requires search.apiKey in config |
Read-only |
MCP Tool Extensions
Configure MCP servers in config.yaml; tools are auto-registered as mcp__<server_name>__<tool_name>:
mcp_servers:
github:
type: stdio
command: npx
args: ["@modelcontextprotocol/server-github"]
filesystem:
type: stdio
command: npx
args: ["@modelcontextprotocol/server-filesystem", "/tmp"]
MCP servers connect in the background in parallel without blocking startup.
Permission System
Modes
| Mode | Behavior |
|---|---|
ask (default) |
Prompt for confirmation on write operations and shell commands |
auto |
Auto-execute (dangerous commands still require confirmation) |
Switch modes: /mode auto or start with --auto.
Permission Confirmation Actions
When a permission dialog appears:
Enter/y— Allow this time (default)a— Always allow for this sessionn— Deny
Safe Project Directory Auto-Approval
When cwd (current working directory) is not a core/sensitive directory, WriteFile and EditFile operations within the directory are auto-approved without confirmation.
Core Directories (still require confirmation):
- Home directory itself
~(but~/projects/foois safe) - Filesystem root
/and system trees (/etc,/usr,/bin, etc.) - Sensitive subdirectories in home (
~/.ssh,~/.aws,~/.config,~/.kube, etc.)
RunCommand is not affected by this rule and always follows the original ask/auto logic.
Dangerous Command Force Confirmation
Regardless of mode, the following commands always trigger confirmation:
rm -rf, git push --force, git reset --hard, sudo, dd if=, mkfs, shutdown, kill -9, etc.
Config Allow/Deny Rules
permissions:
mode: ask
allow:
- ReadFile
- ListFiles
- SearchCode
- "RunCommand(git status)"
deny:
- "RunCommand(rm *)"
disabled_tools:
- RunCommand # Completely hidden from the model
Configuration File
File Locations
| Path | Purpose |
|---|---|
~/.memo-agent/config.yaml |
Global default config |
~/.memo-agent/profiles/<name>/config.yaml |
Config for a specific profile |
.env |
Environment variables in the project root (highest priority) |
Full Configuration Example
# Main model configuration
model:
provider: openai # openai | custom
base_url: "${MODEL_BASE_URL}"
api_key: "${MODEL_API_KEY}"
name: gpt-4o
timeout_ms: 60000
max_tokens: 8192 # Max tokens the model may generate per response
# Auxiliary model (used for context archival compression, recommend a cheaper model)
# If omitted, the main model is used for compression as well
auxiliary:
provider: openai
base_url: "${AUX_BASE_URL}"
api_key: "${AUX_API_KEY}"
name: gpt-4o-mini
timeout_ms: 60000
max_tokens: 4096
# Persistent memory
memory:
auto_update: true # Auto-evaluate and write to NOTES.md at end of each round (default on)
max_inject_tokens: 4000 # Max tokens to inject into system prompt
# Context compression thresholds
context:
warn_threshold: 0.70 # Warning at 70%
compress_threshold: 0.85 # Auto-archive at 85%
tail_tokens: 20000 # Tokens to preserve in the active zone
# Permission control
permissions:
mode: ask # ask | auto
allow:
- ReadFile
- ListFiles
- SearchCode
- ReadNotes
- ListTasks
- GetTask
- SearchHistory
- ListSessions
deny: []
disabled_tools: [] # List of completely hidden tools, e.g. [RunCommand]
sandbox:
enabled: false # When true, child processes only see allowed_env_vars
allowed_env_vars:
- PATH
- HOME
- LANG
- TERM
- USER
- SHELL
- TZ
# Web search (optional — Brave Search)
search:
provider: brave
api_key: "${BRAVE_API_KEY}"
max_results: 5 # Results per query (1–10)
# MCP servers (optional)
mcp_servers:
github:
type: stdio
command: npx
args: ["@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
filesystem:
type: stdio
command: npx
args: ["@modelcontextprotocol/server-filesystem", "/tmp"]
Profile Isolation
Use independent configurations, memory, and sessions for different scenarios:
~/.memo-agent/ # default profile
config.yaml
memory/
NOTES.md
PROFILE.md
sessions.db
recipes/
~/.memo-agent/profiles/work/ # work profile
config.yaml # Can use a different model, API key
memory/
sessions.db
recipes/
Switch profiles:
memo --profile work
memo --profile research
Session Management
# View last 10 sessions
/history
# View last 20 sessions
/history 20
# Full-text search historical messages (/search command used by humans)
/search "sqlite WAL mode"
# Resume a historical session (first use /history to get the session ID)
memo --resume abc12345
# Clear the current session (does not affect NOTES.md)
/clear
The model can also proactively query history via tools:
SearchHistory— Full-text search all historical messages, great for "what did we discuss before"ListSessions— List historical sessions, including session chain parent-child relationships (formed automatically after compression/archival)
Directory Structure
memo-agent/
├── src/
│ ├── cli/
│ │ └── index.ts # Entry: argument parsing, startup flow
│ ├── engine/
│ │ ├── conversationEngine.ts # Core: multi-round session loop, tool invocation
│ │ └── commandRouter.ts # Slash command routing (pure functions)
│ ├── model/
│ │ ├── client.ts # OpenAI client factory
│ │ └── streaming.ts # Streaming response async generator
│ ├── context/
│ │ ├── compressor.ts # Three-zone archival compression
│ │ ├── tokenBudget.ts # Token budget tracking and estimation
│ │ └── promptBuilder.ts # Dynamic system prompt construction
│ ├── memory/
│ │ ├── notesManager.ts # NOTES.md read/write
│ │ └── profileReader.ts # PROFILE.md read-only
│ ├── tools/
│ │ ├── registry.ts # Tool registry (supports disableTools)
│ │ ├── pathUtils.ts # Shared path security validation
│ │ ├── searchHistory.ts # Historical message full-text search tool
│ │ ├── listSessions.ts # Historical session list tool
│ │ └── *.ts # Individual tool implementations (self-registering)
│ ├── recipes/
│ │ └── recipeRegistry.ts # Recipe loading and template expansion
│ ├── session/
│ │ └── db.ts # SQLite session storage (WAL + FTS5)
│ ├── permissions/
│ │ └── guard.ts # Centralized permission decisions
│ ├── mcp/
│ │ └── mcpBridge.ts # MCP protocol tool bridge
│ ├── config/
│ │ └── loader.ts # Configuration loading and merging
│ ├── ui/
│ │ ├── App.tsx # Main UI (state machine)
│ │ ├── MessageList.tsx # Message list rendering
│ │ └── StatusBar.tsx # Bottom status bar
│ └── types/
│ ├── messages.ts # ChatMessage, StreamEvent
│ ├── config.ts # MemoAgentConfig
│ ├── errors.ts # MemoAgentError discriminated union
│ ├── tool.ts # Tool interface
│ └── session.ts # SessionRow, MessageRow
├── .memo-agent/
│ └── recipes/ # Project-level recipe files
├── .env.example
├── package.json
├── tsconfig.json
└── prd.md
Tech Stack
| Layer | Technology |
|---|---|
| Language | TypeScript 5 (strict + ESM) |
| Terminal UI | React 18 + Ink 5 |
| Database | better-sqlite3 (WAL mode + FTS5 full-text index) |
| Model SDK | openai (OpenAI-compatible API) |
| Tool Protocol | @modelcontextprotocol/sdk |
| Config Parsing | js-yaml + dotenv |
| Build | tsx (dev) / tsc (production) |
Development
# Type check
npm run typecheck
# Development mode (tsx, no build needed)
npm run dev
# Production build
npm run build
# Run the built artifact
npm start
Adding Custom Tools
- Create
myTool.tsundersrc/tools/ - Implement the
Toolinterface and callregisterTool(myTool)at the end of the file - Add
import "./myTool.js"insrc/tools/index.ts
import type { Tool, ToolContext, ToolResult } from "../types/tool.js";
import { registerTool } from "./registry.js";
const myTool: Tool = {
name: "MyTool",
description: "Describe the tool's purpose",
inputSchema: {
type: "object",
properties: {
param: { type: "string", description: "Parameter description" },
},
required: ["param"],
},
maxResultChars: 10_000,
isReadOnly(): boolean { return true; },
isEnabled(): boolean { return true; },
async call(input: Record<string, unknown>, _ctx: ToolContext): Promise<ToolResult> {
const param = input["param"] as string;
return { content: `Result: ${param}` };
},
};
registerTool(myTool);
Security Notes
- Path Restriction:
ReadFile,WriteFile,EditFileonly allow operating on files within the current working directory or profile directory; out-of-bounds access returns an error - Safe Project Directory: When working in non-core directories (project directories), file write operations are auto-approved; the home directory itself, system paths, and sensitive hidden directories still require confirmation (see Safe Project Directory Auto-Approval)
- Injection Scanning:
NOTES.md,PROFILE.md, and recipe files are automatically scanned for prompt injection signatures before injection; if detected, injection is skipped and a warning is displayed in the UI - Command Interception:
RunCommanddangerous command blacklist forces confirmation in any mode - FTS5 Security: Search queries are automatically escaped to prevent FTS5 syntax injection
- Tool Masking: Specified tools can be completely removed from the model's view via
permissions.disabled_tools - Log Sanitization: Runtime warnings are output via UI event stream or stderr without interfering with terminal UI rendering
- RunCommand Sandbox: When
permissions.sandbox.enabled: true, spawned processes inherit only the explicitly listed env vars — API keys and other secrets are stripped from the subprocess environment
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found