Skip to content

popoto.integrations.cli

popoto.integrations.cli

popoto-memory -- the single console entry point for harness memory.

Four subcommands:

hook Read a harness hook payload on stdin, write the harness's response on stdout. One command string serves Claude Code and Codex, because both send hook_event_name and this command dispatches on it. mcp Serve the discretionary memory tools over stdio MCP. Requires pip install popoto[mcp]. doctor Print resolved configuration, Redis reachability, effective retrieval mode, record count, failure counters, and a measured hook round trip. This is the user-visible error surface; a hook has no console. demo Seed a few memories, retrieve them, capture a turn, and report an outcome, against local Redis with no harness and no API keys.

Startup latency is the reason this module imports almost nothing at module scope. The read hook is synchronous and on the critical path of every turn, so popoto itself, redis, and the mcp SDK are all imported inside the subcommand that needs them.

build_parser()

Construct the argument parser.

Returns:

Type Description
ArgumentParser

The configured :class:argparse.ArgumentParser.

Source code in src/popoto/integrations/cli.py
def build_parser() -> argparse.ArgumentParser:
    """Construct the argument parser.

    Returns:
        The configured :class:`argparse.ArgumentParser`.
    """
    parser = argparse.ArgumentParser(
        prog="popoto-memory",
        description=(
            "Subconscious memory for agent harnesses, backed by your own "
            "Redis or Valkey. No API keys."
        ),
        epilog=USAGE_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    sub = parser.add_subparsers(dest="command")

    hook = sub.add_parser(
        "hook",
        help="handle one harness hook event on stdin",
        description=(
            "Reads one JSON hook payload on stdin and writes the harness's "
            "response on stdout. Always exits 0."
        ),
    )
    hook.add_argument(
        "--event",
        default=None,
        help=(
            "override hook_event_name, for harnesses that do not include it "
            "in the payload"
        ),
    )

    sub.add_parser(
        "mcp",
        help="serve the memory tools over stdio MCP",
        description="Runs the stdio MCP server. Requires popoto[mcp].",
    )

    doctor = sub.add_parser(
        "doctor",
        help="print resolved config and live state",
        description=(
            "Prints resolved configuration, Redis reachability, effective "
            "retrieval mode, record count, failure counters, and a measured "
            "hook round trip."
        ),
    )
    doctor.add_argument(
        "--json", action="store_true", help="emit machine-readable JSON instead"
    )
    doctor.add_argument(
        "--no-latency",
        action="store_true",
        help="skip the hook round-trip measurement",
    )

    demo = sub.add_parser(
        "demo",
        help="exercise the full loop against local Redis, no harness",
        description=(
            "Seeds memories, assembles context for a query, captures a turn, "
            "and reports an outcome. Zero API keys."
        ),
    )
    demo.add_argument(
        "--agent-id",
        default="popoto-memory-demo",
        help="agent id to seed and query (default: popoto-memory-demo)",
    )
    demo.add_argument(
        "--keep",
        action="store_true",
        help="leave the seeded records in Redis when finished",
    )
    return parser

main(argv=None)

Entry point for the popoto-memory console script.

Parameters:

Name Type Description Default
argv Optional[List[str]]

Argument list, defaulting to sys.argv[1:].

None

Returns:

Type Description
int

A process exit code. hook always returns 0, whatever happened,

int

because a memory failure must not fail the user's turn.

Source code in src/popoto/integrations/cli.py
def main(argv: Optional[List[str]] = None) -> int:
    """Entry point for the ``popoto-memory`` console script.

    Args:
        argv: Argument list, defaulting to ``sys.argv[1:]``.

    Returns:
        A process exit code. ``hook`` always returns 0, whatever happened,
        because a memory failure must not fail the user's turn.
    """
    parser = build_parser()
    args = parser.parse_args(argv)

    if args.command is None:
        parser.print_help()
        return 0
    if args.command == "hook":
        return _cmd_hook(args)
    if args.command == "mcp":
        return _cmd_mcp()
    if args.command == "doctor":
        return _cmd_doctor(args)
    if args.command == "demo":
        return _cmd_demo(args)
    parser.print_help()
    return 0