Relevant source files
The following files were used as context for generating this wiki page:
- cli/python/src/mem0_cli/app.py
- integrations/openclaw/cli/commands.ts
- cli/python/src/mem0_cli/backend/platform.py
- cli/node/src/index.ts
- cli/python/src/mem0_cli/commands/memory.py
- cli/python/src/mem0_cli/backend/base.py
- integrations/mem0-agent-plugin/core/memory_cli.py
- cli/python/src/mem0_cli/output.py
- integrations/kimi-plugin/core/memory_cli.py
- integrations/codex-plugin/core/memory_cli.py
- integrations/antigravity-plugin/core/memory_cli.py
- integrations/agent-plugin-core/python/memory_cli.py
- integrations/claude-code-plugin/core/memory_cli.py
- integrations/cursor-plugin/core/memory_cli.py
- cli/python/src/mem0_cli/branding.py
- cli/python/src/mem0_cli/commands/events_cmd.py
- cli/python/src/mem0_cli/backend/init.py
- cli/python/src/mem0_cli/commands/utils.py
- cli/python/src/mem0_cli/commands/init.py
- cli/node/src/output.ts
- scripts/oss-to-platform-migrate.sh
- cli/node/src/backend/platform.ts
- cli/node/src/backend/base.ts
- cli/python/src/mem0_cli/main.py
- cli/python/src/mem0_cli/config.py
- cli/python/src/mem0_cli/commands/entities.py
Overview
The Python CLI architecture of the Mem0 CLI provides a Typer-powered command-line interface designed for managing AI agent memory layers and platform integrations. It coordinates argument parsing, subcommand routing, piped input handling, and secure configuration persistence while abstracting communication through a pluggable backend layer. By supporting multiple output modes—including human-readable Rich console rendering, tabular displays, and structured JSON envelopes—the CLI serves both interactive operators and programmatic AI agents.
Sources: cli/python/src/mem0_cli/app.py:1-32, cli/python/src/mem0_cli/config.py:1-8, cli/python/src/mem0_cli/config.py:147-150, cli/python/src/mem0_cli/output.py:207-218, cli/python/src/mem0_cli/output.py:338-342
Entrypoint and Dispatch Architecture
Entrypoint and Execution Dispatch
The Mem0 CLI is initialized through cli/python/src/mem0_cli/__main__.py, which directly invokes the main() function exported by the core application module. Command execution relies on Typer to manage subcommands, arguments, and global options while orchestrating execution dispatch.
"""Allow running with `python -m mem0_cli`."""
from mem0_cli.app import main
main()Sources: cli/python/src/mem0_cli/main.py:1-5
The main Typer application instance (app) is configured with specific formatting settings, help behaviors, and version metadata upon startup.
app = typer.Typer(
name="mem0",
help=f"◆ Mem0 CLI v{__version__} · Python SDK\n\n The Memory Layer for AI Agents",
no_args_is_help=True,
rich_markup_mode="rich",
pretty_exceptions_enable=False,
add_completion=False,
subcommand_metavar="<command> [options]",
options_metavar="",
)Sources: cli/python/src/mem0_cli/app.py:23-32
Global Options and Argument Resolution
Global arguments and options—such as version flags and agent output formatting—are processed via the primary application callback (main_callback). Sub-groups for configuration (config_app), entity operations (entity_app), and event inspection (event_app) are defined independently and later registered onto the primary app instance to govern help menu ordering and isolate telemetry triggers.
config_app = typer.Typer(
name="config",
help="Manage mem0 configuration.",
no_args_is_help=True,
rich_markup_mode="rich",
)
entity_app = typer.Typer(
name="entity",
help="Manage entities.",
no_args_is_help=True,
rich_markup_mode="rich",
)
event_app = typer.Typer(
name="event",
help="Inspect background processing events.",
no_args_is_help=True,
rich_markup_mode="rich",
)Sources: cli/python/src/mem0_cli/app.py:36-55
Entity IDs are dynamically resolved through _resolve_ids, which prioritizes explicit CLI arguments over configuration file defaults unless no explicit identifiers are specified.
def _resolve_ids(
config,
*,
user_id: str | None = None,
agent_id: str | None = None,
app_id: str | None = None,
run_id: str | None = None,
):
"""Resolve entity IDs: CLI flag > config default > None.
If any explicit ID is provided, only use explicit IDs (don't mix
in defaults for other entity types which would over-filter).
If no explicit IDs, fall back to all configured defaults.
"""
has_explicit = any([user_id, agent_id, app_id, run_id])
if has_explicit:
return {
"user_id": user_id or None,
"agent_id": agent_id or None,
"app_id": app_id or None,
"run_id": run_id or None,
}
return {
"user_id": config.defaults.user_id or None,
"agent_id": config.defaults.agent_id or None,
"app_id": config.defaults.app_id or None,
"run_id": config.defaults.run_id or None,
}Sources: cli/python/src/mem0_cli/app.py:164-191
Note
When any explicit entity ID (user_id, agent_id, app_id, or run_id) is passed on the command line, _resolve_ids completely suppresses fallback defaults for other entity scopes to prevent unintended cross-filtering.
Piped Input Handling
The CLI inspects stream conditions to support piping text directly into memory creation or search commands from standard input. The input handler verifies both file descriptor status and runtime execution context using _stdin_is_piped and _read_stdin.
def _stdin_is_piped() -> bool:
"""Return True only when stdin is an actual pipe or file redirect — not a bare open fd."""
from mem0_cli.state import is_agent_mode
if is_agent_mode():
return False
try:
mode = os.fstat(sys.stdin.fileno()).st_mode
return _stat_mod.S_ISFIFO(mode) or _stat_mod.S_ISREG(mode)
except Exception:
return False
def _read_stdin() -> str | None:
"""Read from stdin if it is an actual pipe or file redirect (not a TTY, not agent mode)."""
if _stdin_is_piped():
return sys.stdin.read().strip() or None
return NoneSources: cli/python/src/mem0_cli/app.py:194-211
Warning
Piped input extraction via _stdin_is_piped() automatically returns False if agent mode is active (is_agent_mode() == True), preventing programmatic subprocess hangs when standard input is redirected by autonomous agent frameworks.
Backend Validation and Telemetry Flow
Before dispatching commands that require network connectivity, _get_backend_and_config initializes the platform client, verifies API tokens against the /v1/ping/ endpoint with a strict 5-second timeout, and tracks user identifiers for telemetry.
def _get_backend_and_config(
api_key: str | None = None,
base_url: str | None = None,
):
"""Build and return the Platform backend plus the loaded config.
Validates the API key upfront via ``/v1/ping/`` and caches the
resolved user email for telemetry.
"""
global _validated_user_email
from mem0_cli.backend import get_backend
from mem0_cli.backend.platform import AuthError
from mem0_cli.config import load_config, save_config
config = load_config()
if api_key:
config.platform.api_key = api_key
if base_url:
config.platform.base_url = base_url
if not config.platform.api_key:
print_error(
err_console,
"No API key configured.",
hint="Run 'mem0 init' or set MEM0_API_KEY environment variable.",
)
raise typer.Exit(1)
backend = get_backend(config)
# Validate the API key upfront with a fast timeout
try:
ping_data = backend.ping(timeout=5.0)
email = ping_data.get("user_email") if isinstance(ping_data, dict) else None
if email:
_validated_user_email = email
if config.platform.user_email != email:
config.platform.user_email = email
with contextlib.suppress(Exception):
save_config(config)
except AuthError:
print_error(
err_console,
"Invalid or expired API key.",
hint="Run 'mem0 init' or set MEM0_API_KEY environment variable.",
)
raise typer.Exit(1) from None
except Exception:
print_warning(err_console, "Could not validate API key (network issue). Proceeding anyway.")
return backend, configSources: cli/python/src/mem0_cli/app.py:100-152
Configuration Model and State Management
Configuration Precedence and Directory Structure
The Mem0 Python CLI manages persistent user options, authentication keys, default scopes, and telemetry identifiers through a hierarchical configuration store located at ~/.mem0/config.json. Configuration values and runtime settings are resolved following a strict precedence order where higher-priority inputs override lower-priority defaults:
| Precedence | Source Layer | Examples |
|---|---|---|
| 1 (Highest) | CLI Flags | --api-key, --base-url |
| 2 | Environment Variables | MEM0_API_KEY, MEM0_BASE_URL, MEM0_USER_ID, MEM0_AGENT_ID, MEM0_APP_ID, MEM0_RUN_ID |
| 3 | Configuration File | ~/.mem0/config.json |
| 4 (Lowest) | Hardcoded Defaults | https://api.mem0.ai, version 1, empty strings |
Sources: cli/python/src/mem0_cli/config.py:1-8
When the configuration directory or file is initialized or updated, ensure_config_dir guarantees that ~/.mem0 is created with strict file permissions (0700, restricting access exclusively to the file owner), while saved configuration files are explicitly secured with user-only read and write permissions (0600).
CONFIG_DIR = Path.home() / ".mem0"
CONFIG_FILE = CONFIG_DIR / "config.json"
DEFAULT_BASE_URL = "https://api.mem0.ai"
CONFIG_VERSION = 1Sources: cli/python/src/mem0_cli/config.py:19-23
def ensure_config_dir() -> Path:
"""Create ~/.mem0 directory with secure permissions if it doesn't exist."""
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
os.chmod(CONFIG_DIR, stat.S_IRWXU) # 0700
return CONFIG_DIRSources: cli/python/src/mem0_cli/config.py:81-85
with open(CONFIG_FILE, "w") as f:
json.dump(data, f, indent=2)
os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 0600
Sources: cli/python/src/mem0_cli/config.py:177-181
Caution
Both ensure_config_dir and save_config execute explicit os.chmod calls enforcing 0700 on the .mem0 directory and 0600 on config.json. Unprivileged environments or container filesystems lacking POSIX permission support may fail to restrict file modes, though configuration serialization proceeds regardless.
Dataclass Schemas and Aliases
The underlying data model is structured around typed dataclasses that represent distinct domains: PlatformConfig, DefaultsConfig, TelemetryConfig, and AgentRushConfig, all aggregated inside Mem0Config. Short-form CLI configuration keys map directly to nested attributes via SHORT_KEY_ALIASES.
@dataclass
class PlatformConfig:
api_key: str = ""
base_url: str = DEFAULT_BASE_URL
user_email: str = ""
# Agent Mode (unclaimed-shadow signup)
agent_mode: bool = False # True while the key is an unclaimed agent-mode key
created_via: str = "" # "agent_mode" | "email" | "api_key" | "existing_key"
agent_caller: str = (
"" # canonical agent name when created_via == "agent_mode" (e.g. "claude-code")
)
claimed_at: str = "" # ISO timestamp once the agent has been claimed by a human
default_user_id: str = "" # `user_<slug>` returned by bootstrap; used as auto-default
@dataclass
class DefaultsConfig:
user_id: str = ""
agent_id: str = ""
app_id: str = ""
run_id: str = ""
@dataclass
class TelemetryConfig:
anonymous_id: str = ""
@dataclass
class AgentRushConfig:
# ISO timestamp the human acknowledged the "memories are public" warning.
# Empty until first interactive `mem0 agent-rush add`.
acknowledged_at: str = ""
@dataclass
class Mem0Config:
version: int = CONFIG_VERSION
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
platform: PlatformConfig = field(default_factory=PlatformConfig)
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
agent_rush: AgentRushConfig = field(default_factory=AgentRushConfig)
SHORT_KEY_ALIASES: dict[str, str] = {
"api_key": "platform.api_key",
"base_url": "platform.base_url",
"user_email": "platform.user_email",
"user_id": "defaults.user_id",
"agent_id": "defaults.agent_id",
"app_id": "defaults.app_id",
"run_id": "defaults.run_id",
}Sources: cli/python/src/mem0_cli/config.py:26-78
Note
When save_config serializes active options to disk, it also invokes plugin_sync.sync_api_key(config.platform.api_key) as an idempotent, best-effort operation to propagate API keys into ecosystem touchpoints like shell RC files and plugin environment files, catching any IOError to ensure config.json remains the authoritative write target.
Nested Loading and Type Coercion
Configuration loading is implemented by load_config, which instantiates a base Mem0Config, parses JSON data from disk if present, and layers environment variable overrides on top.
def load_config() -> Mem0Config:
"""Load config from file, applying env var overrides."""
config = Mem0Config()
if CONFIG_FILE.exists():
with open(CONFIG_FILE) as f:
data = json.load(f)
config.version = data.get("version", CONFIG_VERSION)
plat = data.get("platform", {})
config.platform.api_key = plat.get("api_key", "")
config.platform.base_url = plat.get("base_url", DEFAULT_BASE_URL)
config.platform.user_email = plat.get("user_email", "")
config.platform.agent_mode = bool(plat.get("agent_mode", False))
config.platform.created_via = plat.get("created_via", "")
config.platform.agent_caller = plat.get("agent_caller", "")
config.platform.claimed_at = plat.get("claimed_at", "")
config.platform.default_user_id = plat.get("default_user_id", "")
defaults = data.get("defaults", {})
config.defaults.user_id = defaults.get("user_id", "")
config.defaults.agent_id = defaults.get("agent_id", "")
config.defaults.app_id = defaults.get("app_id", "")
config.defaults.run_id = defaults.get("run_id", "")
telemetry = data.get("telemetry", {})
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
agent_rush = data.get("agent_rush", {})
config.agent_rush.acknowledged_at = agent_rush.get("acknowledged_at", "")
# Environment variable overrides
env_key = os.environ.get("MEM0_API_KEY")
if env_key:
config.platform.api_key = env_key
env_base = os.environ.get("MEM0_BASE_URL")
if env_base:
config.platform.base_url = env_base
env_user_id = os.environ.get("MEM0_USER_ID")
if env_user_id:
config.defaults.user_id = env_user_id
env_agent_id = os.environ.get("MEM0_AGENT_ID")
if env_agent_id:
config.defaults.agent_id = env_agent_id
env_app_id = os.environ.get("MEM0_APP_ID")
if env_app_id:
config.defaults.app_id = env_app_id
env_run_id = os.environ.get("MEM0_RUN_ID")
if env_run_id:
config.defaults.run_id = env_run_id
return configSources: cli/python/src/mem0_cli/config.py:88-144
Property getters and setters (get_nested_value and set_nested_value) support dotted path strings (such as platform.api_key or defaults.user_id) along with short aliases. The setter performs automatic type coercion based on the existing attribute type, converting boolean representations and integers safely.
def get_nested_value(config: Mem0Config, dotted_key: str) -> Any:
"""Get a config value by dotted path, e.g. 'platform.api_key' or short form 'api_key'."""
dotted_key = SHORT_KEY_ALIASES.get(dotted_key, dotted_key)
parts = dotted_key.split(".")
obj: Any = config
for part in parts:
if hasattr(obj, part):
obj = getattr(obj, part)
else:
return None
return obj
def set_nested_value(config: Mem0Config, dotted_key: str, value: str) -> bool:
"""Set a config value by dotted path. Returns True on success."""
dotted_key = SHORT_KEY_ALIASES.get(dotted_key, dotted_key)
parts = dotted_key.split(".")
obj: Any = config
for part in parts[:-1]:
if hasattr(obj, part):
obj = getattr(obj, part)
else:
return False
final_key = parts[-1]
if not hasattr(obj, final_key):
return False
current = getattr(obj, final_key)
# Type coercion
if isinstance(current, bool):
value = value.lower() in ("true", "1", "yes") # type: ignore[assignment]
elif isinstance(current, int):
try:
value = int(value) # type: ignore[assignment]
except ValueError:
return False
setattr(obj, final_key, value)
return TrueSources: cli/python/src/mem0_cli/config.py:205-244
Backend Abstraction and Factory Selection
Abstract Interface Definition
The Backend abstract base class, defined in cli/python/src/mem0_cli/backend/base.py, establishes the core contract that any storage or platform backend must fulfill. It inherits from ABC and declares eleven abstract methods covering memory creation, semantic and keyword search, record retrieval, paginated listing, updates, deletion, entity management, status checks, and event inspection.
class Backend(ABC):
"""Abstract interface for mem0 backends."""
@abstractmethod
def add(
self,
content: str | None = None,
messages: list[dict] | None = None,
*,
user_id: str | None = None,
agent_id: str | None = None,
app_id: str | None = None,
run_id: str | None = None,
metadata: dict | None = None,
immutable: bool = False,
infer: bool = True,
expires: str | None = None,
custom_instructions: str | None = None,
agent_custom_instructions: str | None = None,
custom_categories: list[dict] | None = None,
structured_data_schema: dict | None = None,
timestamp: int | None = None,
) -> dict: ...
@abstractmethod
def search(
self,
query: str,
*,
user_id: str | None = None,
agent_id: str | None = None,
app_id: str | None = None,
run_id: str | None = None,
top_k: int = 10,
threshold: float = 0.3,
rerank: bool = False,
keyword: bool = False,
filters: dict | None = None,
fields: list[str] | None = None,
show_expired: bool = False,
reference_date: str | None = None,
latest_only: bool = False,
) -> list[dict]: ...
@abstractmethod
def get(self, memory_id: str) -> dict: ...
@abstractmethod
def list_memories(
self,
*,
user_id: str | None = None,
agent_id: str | None = None,
app_id: str | None = None,
run_id: str | None = None,
page: int = 1,
page_size: int = 100,
category: str | None = None,
after: str | None = None,
before: str | None = None,
show_expired: bool = False,
latest_only: bool = False,
) -> list[dict]: ...
@abstractmethod
def update(
self,
memory_id: str,
content: str | None = None,
metadata: dict | None = None,
*,
expiration_date: str | None = None,
timestamp: int | None = None,
) -> dict: ...
@abstractmethod
def delete(
self,
memory_id: str | None = None,
*,Sources: cli/python/src/mem0_cli/backend/base.py:11-90
Every method enforces explicit keyword arguments and positional scopes such as user_id, agent_id, app_id, and run_id. The contract ensures uniform method signatures across different backend targets.
| Abstract Method | Return Type | Key Parameters |
|---|---|---|
add | dict | content, messages, user_id, agent_id, app_id, run_id, metadata, immutable, infer, expires, custom_instructions, agent_custom_instructions, custom_categories, structured_data_schema, timestamp |
search | list[dict] | query, top_k, threshold, rerank, keyword, filters, fields, show_expired, reference_date, latest_only |
get | dict | memory_id |
list_memories | list[dict] | page, page_size, category, after, before, show_expired, latest_only |
update | dict | memory_id, content, metadata, expiration_date, timestamp |
delete | dict | memory_id, all, delete_linked |
delete_entities | dict | user_id, agent_id, app_id, run_id |
status | dict[str, Any] | user_id, agent_id |
entities | list[dict] | entity_type |
list_events | list[dict] | (none) |
get_event | dict | event_id |
Sources: cli/python/src/mem0_cli/backend/base.py:15-124
Note
The abstract interface does not prescribe storage or serialization details; it defines pure method boundaries so that command handlers interact with memory collections without knowing whether data resides locally or on a remote SaaS cluster.
Platform Backend Instantiation and Lifecycle
The PlatformBackend concrete class implements the Backend interface by communicating directly with the Mem0 Platform API (api.mem0.ai). Its constructor accepts a PlatformConfig instance, strips trailing slashes from base_url, and configures a persistent httpx.Client instance with request headers.
class PlatformBackend(Backend):
"""Backend that talks to the mem0 Platform API."""
def __init__(self, config: PlatformConfig) -> None:
self.config = config
self.base_url = config.base_url.rstrip("/")
self._client = httpx.Client(
base_url=self.base_url,
headers={
"Authorization": f"Token {config.api_key}",
"Content-Type": "application/json",
"X-Mem0-Source": "CLI",
"X-Mem0-Client": f"mem0-cli-python/{__version__}",
"X-Mem0-Client-Language": "python",
"X-Mem0-Client-Version": __version__,
},
timeout=30.0,
)Sources: cli/python/src/mem0_cli/backend/platform.py:19-36
The HTTP client injects authentication and telemetry headers into every outbound request:
| Header Name | Value / Format |
|---|---|
Authorization | Token {config.api_key} |
Content-Type | application/json |
X-Mem0-Source | CLI |
X-Mem0-Client | mem0-cli-python/{__version__} |
X-Mem0-Client-Language | python |
X-Mem0-Client-Version | __version__ |
Sources: cli/python/src/mem0_cli/backend/platform.py:27-34
The client enforces a default timeout of 30.0 seconds. Path segments are encoded using _encode_path_segment, which wraps urllib.parse.quote with safe="" to prevent path traversal or malformed URL injection.
def _encode_path_segment(value: Any) -> str:
return quote(str(value), safe="")Sources: cli/python/src/mem0_cli/backend/platform.py:15-16
Factory Dispatch
Backend creation is centralized in the get_backend factory function. It accepts a Mem0Config instance and instantiates PlatformBackend using config.platform.
def get_backend(config: Mem0Config) -> Backend:
"""Return the Platform backend."""
from mem0_cli.backend.platform import PlatformBackend
Sources: cli/python/src/mem0_cli/backend/base.py:127-130
Tip
get_backend performs a local import of PlatformBackend inside the function body rather than at the module level. This lazy import prevents circular dependencies between configuration modules, state managers, and platform client definitions during CLI startup.
Memory Command Workflows and Execution
Memory Operations and Execution Flows
The memory command layer handles user requests by validating input parameters, invoking backend methods within timed status contexts, and formatting responses based on terminal flags or autonomous agent mode. Memory creation, search, retrieval, listing, import, and background event inspection are orchestrated through dedicated command handlers.
When searching memories using cmd_search, the CLI validates --top-k against a minimum value of 1 and ensures --threshold falls within the inclusive range 0.0 to 1.0. Any supplied filter JSON is parsed via json.loads before invoking backend.search. Similarly, cmd_list enforces boundary checks on page and page_size parameters prior to calling backend.list_memories.
if top_k < 1:
print_error(err_console, "--top-k must be >= 1.")
raise typer.Exit(1)
if not (0.0 <= threshold <= 1.0):
print_error(err_console, "--threshold must be between 0.0 and 1.0.")
raise typer.Exit(1)
Sources: cli/python/src/mem0_cli/commands/memory.py:277-283
Warning
Failing to provide valid JSON to --filter during a search or passing non-positive integer values to pagination flags results in an immediate termination via typer.Exit(1) after writing an error description to the standard error console.
Execution Call Chains and Agent Envelopes
Command execution pipelines leverage shared formatting functions when operating under --agent mode or structured JSON output. For list and search operations, raw backend records pass through envelope generation and field projection before rendering.
The execution call chain for listing memories proceeds as follows:
cmd_listinvokesbackend.list_memoriesand receives raw records.format_agent_envelopewraps the command name, duration, scope filters, and data.sanitize_agent_dataprocesses the command category and project dataset items.pickfilters dictionary keys down to the minimal required subset.
results = backend.list_memories(
user_id=user_id,
agent_id=agent_id,
app_id=app_id,
run_id=run_id,
page=page,
page_size=page_size,
category=category,
after=after,
before=before,
show_expired=show_expired,
latest_only=latest_only,
)
…
def sanitize_agent_data(command: str, data: Any) -> Any:
"""Project API response data to minimal relevant fields for agent consumption."""
def pick(obj: dict, keys: list) -> dict:
return {k: obj[k] for k in keys if k in obj}
if data is None:
return data
if command == "add":
items = data if isinstance(data, list) else [data]
result = []
for item in items:
if item.get("status") == "PENDING":
result.append(pick(item, ["status", "event_id"]))
else:
result.append(pick(item, ["id", "memory", "event"]))
return result
if command == "search":
return [
pick(r, ["id", "memory", "score", "created_at", "categories", "expiration_date"])
for r in data
]
if command == "list":
return [
pick(r, ["id", "memory", "created_at", "categories", "expiration_date"]) for r in data
]
Sources: cli/python/src/mem0_cli/commands/memory.py:408-420, cli/python/src/mem0_cli/output.py:245-274, cli/python/src/mem0_cli/output.py:329-361
Similarly, the execution call chain for searching memories proceeds as:
cmd_searchqueries the backend and measures execution time via_time.perf_counter().format_agent_envelopeconstructs a JSON envelope containing execution duration and scope parameters.sanitize_agent_datamatches thesearchcommand type.pickextracts onlyid,memory,score,created_at,categories, andexpiration_datefields from each result object.
results = backend.search(
query,
user_id=user_id,
agent_id=agent_id,
app_id=app_id,
run_id=run_id,
top_k=top_k,
threshold=threshold,
rerank=rerank,
keyword=keyword,
filters=filters,
fields=field_list,
show_expired=show_expired,
reference_date=reference_date,
latest_only=latest_only,
)
…
def format_agent_envelope(
console: Console,
*,
command: str,
data: Any,
duration_ms: int | None = None,
scope: dict | None = None,
count: int | None = None,
) -> None:
"""Output structured JSON envelope for agent/programmatic use (--json/--agent mode)."""
envelope: dict[str, Any] = {
"status": "success",
"command": command,
}
if duration_ms is not None:
envelope["duration_ms"] = duration_ms
if scope:
filtered = {k: v for k, v in scope.items() if v}
if filtered:
envelope["scope"] = filtered
if count is not None:
envelope["count"] = count
envelope["data"] = sanitize_agent_data(command, data)
# Surface the unclaimed-Agent-Mode notice (if any) in the envelope so an
# agent reading the JSON output sees it without inspecting HTTP headers.
from mem0_cli.state import take_notice
notice = take_notice()
if notice:
envelope["mem0_notice"] = notice
console.print_json(json.dumps(envelope, default=str))
…
def sanitize_agent_data(command: str, data: Any) -> Any:
"""Project API response data to minimal relevant fields for agent consumption."""
def pick(obj: dict, keys: list) -> dict:
return {k: obj[k] for k in keys if k in obj}
if data is None:
return data
if command == "add":
items = data if isinstance(data, list) else [data]
result = []
for item in items:
if item.get("status") == "PENDING":
result.append(pick(item, ["status", "event_id"]))
else:
result.append(pick(item, ["id", "memory", "event"]))
return result
if command == "search":
return [
pick(r, ["id", "memory", "score", "created_at", "categories", "expiration_date"])
for r in data
]Sources: cli/python/src/mem0_cli/commands/memory.py:287-302, cli/python/src/mem0_cli/output.py:245-268, cli/python/src/mem0_cli/output.py:329-361
Sources: cli/python/src/mem0_cli/commands/memory.py:408-420, cli/python/src/mem0_cli/output.py:245-361
Background Event Inspection and Status Mapping
Asynchronous processing workflows on the platform generate background events that can be inspected via event commands. The events_cmd module maps event states to rich terminal styles and formats detailed execution summaries.
_STATUS_STYLE = {
"SUCCEEDED": f"[{SUCCESS_COLOR}]SUCCEEDED[/]",
"PENDING": f"[{ACCENT_COLOR}]PENDING[/]",
"FAILED": f"[{ERROR_COLOR}]FAILED[/]",
"PROCESSING": f"[{WARNING_COLOR}]PROCESSING[/]",
}Sources: cli/python/src/mem0_cli/commands/events_cmd.py:26-31
| Status Constant | Console Style / Color Mapping |
|---|---|
SUCCEEDED | [{SUCCESS_COLOR}]SUCCEEDED[/] |
PENDING | [{ACCENT_COLOR}]PENDING[/] |
FAILED | [{ERROR_COLOR}]FAILED[/] |
PROCESSING | [{WARNING_COLOR}]PROCESSING[/] |
Sources: cli/python/src/mem0_cli/commands/events_cmd.py:26-31
When executing cmd_event_status, backend event details are retrieved and formatted into a Rich panel containing metadata, latency figures, creation and update timestamps, and nested result items.
with timed_status(err_console, "Fetching event...") as _ts:
try:
ev = backend.get_event(event_id)
…
panel = Panel(
content,
title=f"[{BRAND_COLOR}]Event Status[/]",
title_align="left",
border_style=BRAND_COLOR,
padding=(1, 1),
)
console.print()
console.print(panel)Sources: cli/python/src/mem0_cli/commands/events_cmd.py:114-116, cli/python/src/mem0_cli/commands/events_cmd.py:167-175
Tip
When background memory ingestion returns PENDING status entries from the platform API, the output formatter automatically deduplicates events sharing the same event_id and prompts the user with status checking instructions.
Multi-Format Output and Terminal Rendering
Terminal Styling and Branding
The CLI utilizes the Rich library to render decorative console components, ASCII banners, and styled text blocks. Color palettes are centrally defined in the branding module to maintain visual consistency across interactive shells.
BRAND_COLOR = "#8b5cf6" # Purple
ACCENT_COLOR = "#a78bfa"
SUCCESS_COLOR = "#22c55e"
ERROR_COLOR = "#ef4444"
WARNING_COLOR = "#f59e0b"
DIM_COLOR = "#6b7280"Sources: cli/python/src/mem0_cli/branding.py:29-34
| Color Constant | Hex Value | Semantic Purpose |
|---|---|---|
BRAND_COLOR | #8b5cf6 | Primary purple branding, welcome panel borders, and info symbols |
ACCENT_COLOR | #a78bfa | Secondary highlights, updated state icons, and table headers |
SUCCESS_COLOR | #22c55e | Success indicators, added memory plus icons |
ERROR_COLOR | #ef4444 | Error messages and deleted memory minus symbols |
WARNING_COLOR | #f59e0b | Warning indicators and processing states |
DIM_COLOR | #6b7280 | Muted text, timestamps, scopes, and no-change symbols |
Sources: cli/python/src/mem0_cli/branding.py:29-34
ASCII symbols adapt dynamically based on terminal capabilities and environment flags. The helper function _sym evaluates whether sys.stdout.isatty() is true and ensures NO_COLOR is absent from environment variables before returning rich Unicode glyphs; otherwise, it falls back to plain ASCII text equivalents.
def _sym(fancy: str, plain: str) -> str:
"""Return *fancy* when stdout is a TTY with colour, else *plain*."""
if not sys.stdout.isatty() or os.environ.get("NO_COLOR") is not None:
return plain
return fancySources: cli/python/src/mem0_cli/branding.py:37-41
Welcome banners, constructed using a multi-line ASCII logo and a Python SDK version subtitle fetched via _get_version(), are suppressed entirely when agent mode is active.
from mem0_cli.state import is_agent_mode
if is_agent_mode():
return
logo_text = Text(LOGO, style=f"bold {BRAND_COLOR}")
tagline = Text(f" {TAGLINE}\n", style=f"{ACCENT_COLOR}")
content = Text()
content.append_text(logo_text)
content.append_text(tagline)
panel = Panel(
content,
border_style=BRAND_COLOR,
padding=(0, 2),
subtitle=f"[{DIM_COLOR}]Python SDK · v{_get_version()}[/]",
subtitle_align="right",
)
console.print(panel)Sources: cli/python/src/mem0_cli/branding.py:46-64
Note
The timed_status context manager directs all spinner animations and execution timing messages to standard error (_err = Console(stderr=True)) rather than standard output. This guarantees that background activity indicators never contaminate machine-readable JSON or agent data streams.
Sources: cli/python/src/mem0_cli/branding.py:14-124
Agent Envelope Sanitation Call Chain
When formatting outputs for programmatic consumers or AI agent workflows, data structures pass through strict projection functions to filter out extraneous database fields. The entity listing workflow follows a precise execution chain.
cmd_entities_listinvokes the backend to retrieve raw entity records and captures performance metrics via_time.perf_counter().format_agent_envelopewraps the resulting dataset inside a structured dictionary containing command metadata, counts, execution duration, and active scopes.sanitize_agent_datainspects the command string (entity list) and iterates over records to project them.pickextracts only the authorized keys (type,count, plus normalizednameorid) for each item.
def cmd_entities_list(backend: Backend, entity_type: str, *, output: str) -> None:
"""List entities of a given type."""
from mem0_cli.state import is_agent_mode, set_current_command
set_current_command("entity list")
if is_agent_mode():
output = "agent"
valid_types = {"users", "agents", "apps", "runs"}
if entity_type not in valid_types:
print_error(
err_console, f"Invalid entity type: {entity_type}. Use: {', '.join(valid_types)}"
)
raise typer.Exit(1)
_start = _time.perf_counter()
with timed_status(err_console, f"Fetching {entity_type}...") as _ts:
try:
results = backend.entities(entity_type)
except Exception as e:
print_error(err_console, str(e), hint="This feature may require the mem0 Platform.")
raise typer.Exit(1) from None
_elapsed = _time.perf_counter() - _start
if output == "agent":
format_agent_envelope(
console,
command="entity list",
data=results,
count=len(results),
duration_ms=int(_elapsed * 1000),
)
return
…
def format_agent_envelope(
console: Console,
*,
command: str,
data: Any,
duration_ms: int | None = None,
scope: dict | None = None,
count: int | None = None,
) -> None:
"""Output structured JSON envelope for agent/programmatic use (--json/--agent mode)."""
envelope: dict[str, Any] = {
"status": "success",
"command": command,
}
if duration_ms is not None:
envelope["duration_ms"] = duration_ms
if scope:
filtered = {k: v for k, v in scope.items() if v}
if filtered:
envelope["scope"] = filtered
if count is not None:
envelope["count"] = count
envelope["data"] = sanitize_agent_data(command, data)
# Surface the unclaimed-Agent-Mode notice (if any) in the envelope so an
# agent reading the JSON output sees it without inspecting HTTP headers.
from mem0_cli.state import take_notice
notice = take_notice()
if notice:
envelope["mem0_notice"] = notice
console.print_json(json.dumps(envelope, default=str))
…
def sanitize_agent_data(command: str, data: Any) -> Any:
"""Project API response data to minimal relevant fields for agent consumption."""
def pick(obj: dict, keys: list) -> dict:
return {k: obj[k] for k in keys if k in obj}
if data is None:
return data
if command == "add":
items = data if isinstance(data, list) else [data]
result = []
for item in items:Sources: cli/python/src/mem0_cli/commands/entities.py:27-58, cli/python/src/mem0_cli/output.py:245-301, cli/python/src/mem0_cli/output.py:329-361
Sources: cli/python/src/mem0_cli/commands/entities.py:27-58, cli/python/src/mem0_cli/output.py:245-361
Warning
If an unclaimed Agent Mode account triggers a platform notice during execution, both format_json_envelope and format_agent_envelope retrieve it via take_notice() and inject it directly into the mem0_notice JSON key. Consuming agents must inspect this field to discover authentication states without parsing HTTP headers.
Sources: cli/python/src/mem0_cli/output.py:233-242, cli/python/src/mem0_cli/output.py:353-360
Related
On This Page
OverviewEntrypoint and Dispatch ArchitectureEntrypoint and Execution DispatchGlobal Options and Argument ResolutionPiped Input HandlingBackend Validation and Telemetry FlowConfiguration Model and State ManagementConfiguration Precedence and Directory StructureDataclass Schemas and AliasesNested Loading and Type CoercionBackend Abstraction and Factory SelectionAbstract Interface DefinitionPlatform Backend Instantiation and LifecycleFactory DispatchMemory Command Workflows and ExecutionMemory Operations and Execution FlowsExecution Call Chains and Agent EnvelopesBackground Event Inspection and Status MappingMulti-Format Output and Terminal RenderingTerminal Styling and BrandingAgent Envelope Sanitation Call ChainRelated