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:
- Slash command path (
/help,/session,/prefs,/app_data, etc.): command handlers run immediately in the CLI. - 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:
REPLreceives user input and routes slash commands toCommands.- Non-command messages go through
Agentinto theAgent Loop. - The
Agent Loopcan call localRuntime Tools,BashTool,Remote MCP ToolsandSkills. - State is persisted in the
Memory Store. This includes Conversation history, User preferences and App data - 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, orset/clearpersistent preferences./app_data-list,get,set, ordeleteapp-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 statusGET /api/v1/logs- log snapshot (limitoptional, max 20000)GET /api/v1/stream- live SSE tailGET /api/v1/search- memory search (requires--memory-db)qrequired query DSL (for example@user:billing issue)app_idrequired app scopesession_idoptional session scopelimitoptional 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 optionalmash-telemetry-webpackage is installedon: require UI package and fail startup if unavailableoff: 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 commandsbuild_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 toolson_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:
locationmust point to a folder containingSKILL.md.- Set
skills_enabled=TrueinAgentConfig. - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e68e8dc27d995d9fdcd59b78d77ffa93a37587f29cbe84fc03a7db64225b9bc5
|
|
| MD5 |
7e3c41974de0040accf1ea01a50df7fc
|
|
| BLAKE2b-256 |
66d6b3bf2573d59e97263bfe38f52da05c84315916d53b919d8d9c438d08c47b
|
Provenance
The following attestation bundles were made for mashpy-0.1.2.tar.gz:
Publisher:
publish.yml on imsid/mashpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mashpy-0.1.2.tar.gz -
Subject digest:
e68e8dc27d995d9fdcd59b78d77ffa93a37587f29cbe84fc03a7db64225b9bc5 - Sigstore transparency entry: 1005606221
- Sigstore integration time:
-
Permalink:
imsid/mashpy@7b3148fa3ee9c550eaa0add2e3e0838fe49e43dc -
Branch / Tag:
- Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7b3148fa3ee9c550eaa0add2e3e0838fe49e43dc -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c4a04b9c380d8ea2b48ee4ca873f05eaa2ccd8d617e766797db693fed39babd
|
|
| MD5 |
78ce2dc11d582b2ea357417fbcd453cb
|
|
| BLAKE2b-256 |
e51786ec88116919b2c3dbaba70a2c2a6fc4c5649b52da316302f44fc128bddd
|
Provenance
The following attestation bundles were made for mashpy-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on imsid/mashpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mashpy-0.1.2-py3-none-any.whl -
Subject digest:
7c4a04b9c380d8ea2b48ee4ca873f05eaa2ccd8d617e766797db693fed39babd - Sigstore transparency entry: 1005606235
- Sigstore integration time:
-
Permalink:
imsid/mashpy@7b3148fa3ee9c550eaa0add2e3e0838fe49e43dc -
Branch / Tag:
- Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7b3148fa3ee9c550eaa0add2e3e0838fe49e43dc -
Trigger Event:
release
-
Statement type: