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

Optional telemetry observer UI package:

pip install "mashpy[telemetry-web]"
# or
uv add "mashpy[telemetry-web]"

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.

Telemetry now uses a versioned API contract under /api/v1:

  • GET /api/v1/health - server/runtime capability status
  • GET /api/v1/logs - log snapshot (limit optional, max 20000)
  • GET /api/v1/stream - live SSE tail
  • GET /api/v1/search - memory search (requires --memory-db)
    • q required query DSL (for example @user:billing issue)
    • app_id required app scope
    • session_id optional session scope
    • limit optional result limit (max 50)

Start telemetry API-only mode:

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

Enable memory search by also passing your memory DB:

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

Run the optional generic observer UI at /:

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

--ui modes:

  • auto (default): serve UI if optional mash-telemetry-web package is installed
  • on: require UI package and fail startup if unavailable
  • off: API-only mode

Default API endpoints (--host 127.0.0.1 --port 8765):

  • Health: http://127.0.0.1:8765/api/v1/health
  • Logs: http://127.0.0.1:8765/api/v1/logs
  • Stream: http://127.0.0.1:8765/api/v1/stream
  • Search: http://127.0.0.1:8765/api/v1/search

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.2.tar.gz (81.2 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.2-py3-none-any.whl (98.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mashpy-0.1.2.tar.gz
  • Upload date:
  • Size: 81.2 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.2.tar.gz
Algorithm Hash digest
SHA256 e68e8dc27d995d9fdcd59b78d77ffa93a37587f29cbe84fc03a7db64225b9bc5
MD5 7e3c41974de0040accf1ea01a50df7fc
BLAKE2b-256 66d6b3bf2573d59e97263bfe38f52da05c84315916d53b919d8d9c438d08c47b

See more details on using hashes here.

Provenance

The following attestation bundles were made for mashpy-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: mashpy-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 98.0 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7c4a04b9c380d8ea2b48ee4ca873f05eaa2ccd8d617e766797db693fed39babd
MD5 78ce2dc11d582b2ea357417fbcd453cb
BLAKE2b-256 e51786ec88116919b2c3dbaba70a2c2a6fc4c5649b52da316302f44fc128bddd

See more details on using hashes here.

Provenance

The following attestation bundles were made for mashpy-0.1.2-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