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.
- 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
npm install -g memo-agentThen 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
memoOption 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
memoMODEL_API_KEY=sk-... npx memo-agentnpm installCopy the example configuration file and fill in your API details:
cp .env.example .envMODEL_BASE_URL=https://api.openai.com/v1
MODEL_API_KEY=sk-...
MODEL_NAME=gpt-4oOr create ~/.memo-agent/config.yaml (see Configuration File).
# Development mode (tsx hot reload)
npm run dev
# Build and run
npm run build
npm start
# Global installation from source
npm install -g .
memomemo [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| 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 |
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 |
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) |
/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...
A recipe is a reusable prompt template stored as a .md file.
- Global:
~/.memo-agent/recipes/ - Project-level (priority):
.memo-agent/recipes/
---
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 |
/review src/main.ts
/fix-types
/summarize-pr
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
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.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 | 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 |
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.
| 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.
When a permission dialog appears:
Enter/y— Allow this time (default)a— Always allow for this sessionn— Deny
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.
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.
permissions:
mode: ask
allow:
- ReadFile
- ListFiles
- SearchCode
- "RunCommand(git status)"
deny:
- "RunCommand(rm *)"
disabled_tools:
- RunCommand # Completely hidden from the model| 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) |
# 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"]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# 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)
/clearThe 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)
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
| 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) |
# 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- 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);- 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