Skip to main content

Platform for building CLI-native agent applications.

Project description

mashpy

Platform for building CLI-native agent applications.

MashPy gives you reusable pieces for interactive agent CLIs: a REPL, slash commands, memory, an LLM think/act loop, skills, runtime tools, BashTool, MCP integration, and telemetry.

Install

pip install mashpy

Or with uv:

uv add mashpy

Validate the installation:

mash --version

The PyPI package is framework-only and ships the mash library plus a thin mash CLI for install validation.

What MashPy Does

Each line typed in a Mash app follows one of two paths:

  1. Slash command path (/help, /session, /prefs, /app_data, etc.): command handlers run immediately in the CLI.
  2. Agent path (normal message): Mash builds context, runs the agent loop, executes tools, and persists/logs the turn.

This lets you combine deterministic CLI controls with model-driven tool execution in one interface.

High-Level Flow

flowchart LR
    U[User]

    subgraph MashApp[MashApp CLI]
        REPL[REPL]
        CMD[Commands]
        AGENT[Agent]
    end

    subgraph RUNTIME[Runtime Layer]
        LOOP[Agent Loop]
        SKILLS[Skills]
        RT[Runtime Tools]
        BASH[BashTool]
        MCP[Remote MCP Tools]
        LLM[LLM Provider]
    end

    subgraph TELEMETRY[Telemetry]
        LOG[Command Telemetry Events]
        TEL[Telemetry Server + Web UI]
        AGENT_LOG[Agent Telemetry Events]
    end

    subgraph MEMORY[Memory]
        MEM[Memory Store]
        SEARCH[Conversation History]
        PREFS[Preferences]
        APPD[App Data]
    end
    
    U --> REPL
    REPL --> CMD
    REPL --> AGENT

    AGENT --> LOOP
    LOOP <--> LLM
    LOOP --> SKILLS
    LOOP --> RT
    LOOP --> BASH
    LOOP --> MCP

    RT <--> MEMORY
    SEARCH --> MEM
    PREFS --> MEM
    APPD --> MEM

    RUNTIME --> TELEMETRY
    CMD --> TELEMETRY
    LOG --> TEL
    AGENT_LOG --> TEL

How to read this diagram:

  1. REPL receives user input and routes slash commands to Commands.
  2. Non-command messages go through Agent into the Agent Loop.
  3. The Agent Loop can call local Runtime Tools, BashTool, Remote MCP Tools and Skills.
  4. State is persisted in the Memory Store. This includes Conversation history, User preferences and App data
  5. Command, agent, and MCP events are written as JSONL and visualized in telemetry.

Core Modules

REPL

The REPL handles interactive input, command auto-completion, and history persistence. It is the entrypoint UX for every Mash app session.

Commands

Slash commands provide deterministic control without involving the model. MashApp registers these built-ins out of the box:

  • /help (/h, /?) - list available slash commands.
  • /exit (/quit, /q) - exit the application.
  • /clear (/cls) - clear terminal output.
  • /session - show app name, session id, model, max steps, and session token total.
  • /prefs - view preferences, or set/clear persistent preferences.
  • /app_data - list, get, set, or delete app-scoped JSON data.
  • /history [limit] - print saved conversation turns (optionally capped).
  • /compact - summarize conversation into a checkpoint turn.

Memory Store

SQLiteStore persists conversation turns, signals, preferences, and app data. This gives both commands and tools durable state across turns and sessions.

Agent Loop

The agent runs a bounded think/act/observe loop (max_steps) for non-command messages. It builds context from prompt + recent history, calls tools when needed, and writes final response metadata (including token usage).

Skills

Skills are discoverable instructions stored in local SKILL.md files and registered via SkillRegistry. When skills_enabled=True, Mash auto-injects a Skill tool so the model can load skill content at runtime.

Runtime Tools

MashApp auto-registers runtime tools for memory access (enabled by default):

  • search_conversations - Search conversation history at session or app scope.
  • get_full_turn_message - Fetch full user and assistant messages for one or more turns.
  • get_preferences - read stored user preferences.
  • set_preferences - update stored user preferences.
  • list_app_data - list stored app-scoped key/value entries.
  • set_app_data - persist app-scoped key/value data.

BashTool

BashTool is an opt-in execution tool for shell commands in a persistent bash session. Register it explicitly in your app when you want repository inspection or CLI automation from the agent.

  • bash - execute shell commands with timeout controls and output truncation safeguards.

Remote MCP Tools

MCPManager manages remote MCP server connections, optional tool allowlists, and tool invocation. Remote MCP tools are adapted into normal Mash tools so the agent can call them like local tools.

Telemetry

EventLogger writes structured JSONL events for commands, LLM calls, agent steps, MCP activity, and memory-search stages. The telemetry server exposes:

  • /api/logs for snapshots
  • /api/stream for live SSE tailing
  • /api/search for memory search (requires starting the server with --memory-db)
    • q - query DSL string (for example @user:billing issue or @agent:retry logic)
    • app_id - required app scope for memory search
    • session_id - optional session scope filter
    • limit - optional result limit (max 50)

Start the telemetry API server directly with:

python -m mash.telemetry --log /path/to/events.jsonl

Enable memory search in telemetry (/api/search) by also passing your memory DB:

python -m mash.telemetry \
  --log /path/to/events.jsonl \
  --memory-db /path/to/memory.db

Telemetry API endpoints (default --host 127.0.0.1 --port 8765):

  • Logs snapshot: http://127.0.0.1:8765/api/logs
  • Live stream (SSE): http://127.0.0.1:8765/api/stream
  • Search (requires --memory-db): http://127.0.0.1:8765/api/search

For local API + web UI development together, use:

make telemetry-dev \
  TELEMETRY_LOG=/path/to/events.jsonl \
  TELEMETRY_MEMORY_DB=/path/to/memory.db

Default dev UI endpoint:

  • Web UI: http://127.0.0.1:5173

How to Write a New App

Build apps by subclassing AbstractMashApp and implementing its required hooks:

  • get_app_id()
  • build_store()
  • build_tools()
  • build_skills()
  • build_llm()
  • build_agent_config()
  • get_log_destination()

Optional hooks:

  • register_commands() for custom slash commands
  • build_mcp_servers() for startup MCP connections
from mash.mcp import MCPServerConfig

def build_mcp_servers(self) -> list[MCPServerConfig]:
    return [
        MCPServerConfig(
            name="github",
            url="https://api.githubcopilot.com/mcp/",
            description="GitHub MCP tools",
            headers={"Authorization": "Bearer <token>"},
            allowed_tools=["list_issues", "issue_read"],
        )
    ]

Mash connects to each configured server at startup, fetches tool definitions, and registers them as callable tools in the agent runtime.

  • enable_runtime_tools() to disable built-in memory/runtime tools
  • on_startup() / on_shutdown() for app lifecycle hooks

1) Simple app

import sys
from pathlib import Path

from mash.cli.app import AbstractMashApp
from mash.cli.commands import Command
from mash.core.config import AgentConfig
from mash.core.llm import AnthropicProvider, LLMProvider
from mash.memory.store import MemoryStore, SQLiteStore
from mash.skills.registry import SkillRegistry
from mash.tools.bash import BashTool
from mash.tools.registry import ToolRegistry

APP_ID = "hello-mash"

class HelloMashApp(AbstractMashApp):
    def __init__(self) -> None:
        self._root = Path(".").resolve()
        super().__init__()

    def get_app_id(self) -> str:
        return APP_ID

    def build_store(self) -> MemoryStore:
        return SQLiteStore(self._root / ".mash" / "hello-mash.db")

    def build_tools(self) -> ToolRegistry:
        tools = ToolRegistry()
        tools.register(BashTool(working_dir=str(self._root)))
        return tools

    def build_skills(self) -> SkillRegistry:
        return SkillRegistry()

    def build_llm(self) -> LLMProvider:
        return AnthropicProvider(app_id=APP_ID)

    def build_agent_config(self) -> AgentConfig:
        return AgentConfig(
            app_id=APP_ID,
            system_prompt="You are a concise CLI assistant.",
            skills_enabled=False,
        )

    def get_log_destination(self) -> Path:
        return self._root / ".mash" / "logs" / "hello-mash.jsonl"

    def register_commands(self) -> None:
        super().register_commands()
        self.register_command(
            Command(
                name="ping",
                help="Check app responsiveness",
                handler=lambda ctx, _args: ctx.renderer.info("pong"),
            )
        )


def main() -> int:
    app = HelloMashApp()
    try:
        app.run()
        return 0
    except KeyboardInterrupt:
        return 0
    finally:
        app.cleanup()


if __name__ == "__main__":
    sys.exit(main())

Run it:

python my_app.py

2) App with skills enabled

Skill requirements:

  1. location must point to a folder containing SKILL.md.
  2. Set skills_enabled=True in AgentConfig.
  3. Register at least one skill in SkillRegistry.
import sys
from pathlib import Path

from mash.cli.app import AbstractMashApp
from mash.core.config import AgentConfig
from mash.core.llm import AnthropicProvider, LLMProvider
from mash.memory.store import MemoryStore, SQLiteStore
from mash.skills.base import Skill
from mash.skills.registry import SkillRegistry
from mash.tools.registry import ToolRegistry

APP_ID = "hello-skills"


class HelloSkillsApp(AbstractMashApp):
    def __init__(self) -> None:
        self._root = Path(".").resolve()
        super().__init__()

    def get_app_id(self) -> str:
        return APP_ID

    def build_store(self) -> MemoryStore:
        return SQLiteStore(self._root / ".mash" / "hello-skills.db")

    def build_tools(self) -> ToolRegistry:
        return ToolRegistry()

    def build_skills(self) -> SkillRegistry:
        skills = SkillRegistry()
        skills.register(
            Skill(
                type="custom",
                name="repo-audit",
                description="Checklist for auditing a repository",
                location=str((self._root / "skills" / "repo-audit").resolve()),
            )
        )
        return skills

    def build_llm(self) -> LLMProvider:
        return AnthropicProvider(app_id=APP_ID)

    def build_agent_config(self) -> AgentConfig:
        return AgentConfig(
            app_id=APP_ID,
            system_prompt="You are a CLI agent that can load skills when needed.",
            skills_enabled=True,
        )

    def get_log_destination(self) -> Path:
        return self._root / ".mash" / "logs" / "hello-skills.jsonl"


def main() -> int:
    app = HelloSkillsApp()
    try:
        app.run()
        return 0
    except KeyboardInterrupt:
        return 0
    finally:
        app.cleanup()


if __name__ == "__main__":
    sys.exit(main())

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mashpy-0.1.1.tar.gz (77.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mashpy-0.1.1-py3-none-any.whl (93.4 kB view details)

Uploaded Python 3

File details

Details for the file mashpy-0.1.1.tar.gz.

File metadata

  • Download URL: mashpy-0.1.1.tar.gz
  • Upload date:
  • Size: 77.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mashpy-0.1.1.tar.gz
Algorithm Hash digest
SHA256 65de47e9a529fc6380e3f15b960fa2f088d12ed03724086c3087fccedcf8360e
MD5 0cbdadd4f47973ce19b3c83082968c14
BLAKE2b-256 261b38be4fd11fa5eb5a476554b4cec86f948e496ea34cb55a19b1754f5e2de4

See more details on using hashes here.

Provenance

The following attestation bundles were made for mashpy-0.1.1.tar.gz:

Publisher: publish.yml on imsid/mashpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mashpy-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: mashpy-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 93.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mashpy-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e10c97c368a0bbec835539f229759e6d42cf44c691154f51c4d032edb8525d7f
MD5 7a145cca16039471eb74492c03df22c1
BLAKE2b-256 4156bc63ed6ee3c4e91d686969f214857ce421083a78c974a5c325787752edd3

See more details on using hashes here.

Provenance

The following attestation bundles were made for mashpy-0.1.1-py3-none-any.whl:

Publisher: publish.yml on imsid/mashpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page