Skip to main content
doc0blade47/mem0documentation home
Sign in

Python CLI Architecture

Last updated Oct 11, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

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 None

Sources: 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, config

Sources: 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:

PrecedenceSource LayerExamples
1 (Highest)CLI Flags--api-key, --base-url
2Environment VariablesMEM0_API_KEY, MEM0_BASE_URL, MEM0_USER_ID, MEM0_AGENT_ID, MEM0_APP_ID, MEM0_RUN_ID
3Configuration File~/.mem0/config.json
4 (Lowest)Hardcoded Defaultshttps://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 = 1

Sources: 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_DIR

Sources: 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 config

Sources: 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 True

Sources: 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 MethodReturn TypeKey Parameters
adddictcontent, messages, user_id, agent_id, app_id, run_id, metadata, immutable, infer, expires, custom_instructions, agent_custom_instructions, custom_categories, structured_data_schema, timestamp
searchlist[dict]query, top_k, threshold, rerank, keyword, filters, fields, show_expired, reference_date, latest_only
getdictmemory_id
list_memorieslist[dict]page, page_size, category, after, before, show_expired, latest_only
updatedictmemory_id, content, metadata, expiration_date, timestamp
deletedictmemory_id, all, delete_linked
delete_entitiesdictuser_id, agent_id, app_id, run_id
statusdict[str, Any]user_id, agent_id
entitieslist[dict]entity_type
list_eventslist[dict](none)
get_eventdictevent_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 NameValue / Format
AuthorizationToken {config.api_key}
Content-Typeapplication/json
X-Mem0-SourceCLI
X-Mem0-Clientmem0-cli-python/{__version__}
X-Mem0-Client-Languagepython
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:

  1. cmd_list invokes backend.list_memories and receives raw records.
  2. format_agent_envelope wraps the command name, duration, scope filters, and data.
  3. sanitize_agent_data processes the command category and project dataset items.
  4. pick filters 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:

  1. cmd_search queries the backend and measures execution time via _time.perf_counter().
  2. format_agent_envelope constructs a JSON envelope containing execution duration and scope parameters.
  3. sanitize_agent_data matches the search command type.
  4. pick extracts only id, memory, score, created_at, categories, and expiration_date fields 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 ConstantConsole 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 ConstantHex ValueSemantic Purpose
BRAND_COLOR#8b5cf6Primary purple branding, welcome panel borders, and info symbols
ACCENT_COLOR#a78bfaSecondary highlights, updated state icons, and table headers
SUCCESS_COLOR#22c55eSuccess indicators, added memory plus icons
ERROR_COLOR#ef4444Error messages and deleted memory minus symbols
WARNING_COLOR#f59e0bWarning indicators and processing states
DIM_COLOR#6b7280Muted 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 fancy

Sources: 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.

  1. cmd_entities_list invokes the backend to retrieve raw entity records and captures performance metrics via _time.perf_counter().
  2. format_agent_envelope wraps the resulting dataset inside a structured dictionary containing command metadata, counts, execution duration, and active scopes.
  3. sanitize_agent_data inspects the command string (entity list) and iterates over records to project them.
  4. pick extracts only the authorized keys (type, count, plus normalized name or id) 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

Was this page helpful?