SDK for building hosted multi-agent Mash applications.
Project description
mashpy
This repository is the development workspace for Mash.
It contains three main components:
mash.runtimeThe managed runtime for an agent.mash.apiThe self-hosted API server insrc/mash/api.mash.cliThe bundled CLI insrc/mash/cli.
Repo layout
src/mash/ SDK, host API, and CLI
docs/rfcs/ Protocol and design RFCs
pilot/ Mash Pilot host built on Mash
tests/ Unified test suite
Dockerfile Base image for Mash host deployments
RFCs
Mental model
The architecture is:
- app developers use
mashpyto define one or moreAgentSpecs - an app exposes
build_host() -> MashAgentHost mash.apiloads that host and serves HTTPmash.apialso serves the built-in telemetry UI at/telemetrymashtalks to a running Mash API deployment- deployments are expected to run in a container
Persistence is runtime-level, not app-level.
Mash stores agent state under:
<MASH_DATA_DIR>/<agent_id>/state.db
state.db is the single per-agent SQLite store. It contains:
- conversation turns and signals
- preferences and app data
- structured runtime logs in the
logstable
If MASH_DATA_DIR is not set, the runtime falls back to /var/lib/mash.
Local setup
Create the repo virtualenv, install dependencies, and activate it:
uv venv
uv sync
source .venv/bin/activate
System Architecture
At a high level, mashpy is one distribution with three cooperating surfaces:
mash.coreThe agent loop itself: config, context, provider adapters, and think/act/observe execution.mash.runtimeHost-side orchestration:AgentSpec, runtime servers, host/client wiring, request streaming, session storage, and subagent delegation.mash.apiandmash.cliApp facing surfaces built on top of the runtime.mash.apiexposes the FastAPI host service and telemetry UI;mash.cliis the remote client.
The normal execution path is:
- An app defines one or more
AgentSpecs. MashAgentHostBuildercomposes those specs into aMashAgentHost.mash.apistarts the host and exposes HTTP endpoints for agent requests, sessions, history, and telemetry.mash.clitalks to that HTTP API as a remote client.
flowchart TD
A["`AgentSpec`"] --> B["`MashAgentHostBuilder`"]
B --> C["`MashAgentHost`"]
C --> D["`mash.api`"]
G["`mash.cli`"] --> D
D --> F["MashAgent"]
Persistence is runtime-level, not app-level. Each agent stores state under:
<MASH_DATA_DIR>/<agent_id>/state.db
Structured runtime events are stored inside that SQLite database in the logs
table rather than in a separate JSONL file.
If MASH_DATA_DIR is not set, the runtime falls back to /var/lib/mash.
Agent Runtime
The core execution stack is:
- src/mash/runtime/spec.py
AgentSpecis the transport-agnostic contract app authors implement. - src/mash/runtime/host.py
MashAgentHostandMashAgentHostBuilderregister the primary agent and subagents, start one runtime per agent, and wire host-managed delegation. - src/mash/runtime/server.py
MashAgentServerowns per-agent execution state, request buffering, session persistence, event logging, and the HTTP request worker. - src/mash/core/agent.py
Agent.run()executes the think/act/observe loop and emits trace + token metadata. - src/mash/runtime/client.py
MashAgentClientis the runtime-side HTTP client used by the host and by subagent delegation.
sequenceDiagram
participant User
participant API as mash.api
participant Host as MashAgentHost
participant Client as MashAgentClient
participant Runtime as MashAgentServer
participant Agent as mash.core.Agent
participant Store as State Store
User->>API: POST /api/v1/agents/{agent_id}/requests
API->>Host: get_client(agent_id)
Host-->>API: MashAgentClient
API->>Client: post_request(...) / stream(...)
Client->>Runtime: HTTP request + SSE stream
Runtime->>Agent: process_user_message(...)
Agent->>Agent: think -> act -> observe
Agent-->>Runtime: Response + token_usage + trace_id
Runtime->>Store: save_turn(...)
Runtime->>Store: collect_signals(...)
Runtime-->>Client: request.accepted / agent.trace / request.completed
Client-->>API: streamed runtime events
API-->>User: SSE / final payload
Important runtime properties:
- Each
MashAgentServeris single-flight per server: requests are queued and executed by one worker thread. - Request lifecycle is streamed over SSE using stable event names:
request.accepted,request.started,agent.trace,request.completed,request.error. - Token accounting is session-scoped inside each runtime.
session_total_tokensis computed from saved turn metadata and persisted with each turn. - Runtime logs are persisted in each agent's
MemoryStoreand also fanned out as live events for telemetry and remote clients.
Working on the SDK
The main SDK surface is:
AgentSpecin src/mash/runtime/spec.pyMashAgentHostin src/mash/runtime/host.pyMashAgentClientin src/mash/runtime/client.pyMashAgentServerin src/mash/runtime/server.py
The intended app shape is:
from mash.core.config import AgentConfig
from mash.core.llm import AnthropicProvider
from mash.runtime import AgentSpec, MashAgentHostBuilder
from mash.skills.registry import SkillRegistry
from mash.tools.registry import ToolRegistry
class PrimaryAgent(AgentSpec):
def get_agent_id(self) -> str:
return "primary"
def build_tools(self) -> ToolRegistry:
return ToolRegistry()
def build_skills(self) -> SkillRegistry:
return SkillRegistry()
def build_llm(self):
return AnthropicProvider(app_id="primary", api_key="...")
def build_agent_config(self) -> AgentConfig:
return AgentConfig(app_id="primary", system_prompt="You are helpful.")
def build_host():
return MashAgentHostBuilder().primary(PrimaryAgent()).build()
Subagent Invocation Flow
Subagent delegation is host-managed, not a special local shortcut.
- The host registers the primary agent and subagents in src/mash/runtime/host.py.
- On startup, the host injects subagent routing guidance into the primary system prompt and registers
InvokeSubagent. InvokeSubagentToolin src/mash/tools/subagent.py resolves the target subagent client and submits a normal streamed request.- The subagent request runs through that subagent’s own
MashAgentServer, persistence layer, and session namespace. - Streamed request events are forwarded back to the primary runtime as
subagent.*trace events for observability.
sequenceDiagram
participant Primary as Primary Agent
participant Tool as InvokeSubagentTool
participant Host as MashAgentHost
participant Client as MashAgentClient
participant Server as Subagent MashAgentServer
participant Subagent as Subagent mash.core.Agent
Primary->>Tool: InvokeSubagent(agent_id, prompt, opts)
Tool->>Host: get_client(agent_id)
Host-->>Tool: MashAgentClient
Tool->>Client: post_request(prompt, session_id=subagent_session_id)
Client->>Server: HTTP request + SSE stream
Server->>Subagent: process_user_message(...)
Subagent->>Subagent: think -> act -> observe
Subagent-->>Server: Response + token_usage + trace_id
Server-->>Client: request.accepted / agent.trace / request.completed
Client-->>Tool: streamed events
Tool-->>Primary: ToolResult(text + metadata)
Relevant implementation details:
- Subagent session ids are deterministic via src/mash/runtime/session.py using:
primary_app_id + primary_session_id + subagent_id. - The subagent keeps its own session history and token totals.
- The primary agent only owns its own turns and token usage; subagent execution is correlated, but not merged into the primary session’s token total.
Core Modules
When working on mashpy, these are the main files to orient around:
- src/mash/core/agent.py Core think/act/observe loop, tool execution, token aggregation, and trace emission.
- src/mash/runtime/server.py Session persistence, request queueing, SSE event buffering, and runtime event fan-out.
- src/mash/runtime/host.py Multi-agent composition and subagent tool wiring.
- src/mash/api/app.py FastAPI composition for the public host API, telemetry endpoints, and auth.
- src/mash/cli/main.py
Unified
mashCLI entrypoint for remote operations andmash host serve. - src/mash/cli/shell.py Remote REPL path, including streamed request handling and chain rendering.
Common contributor questions:
- If you are changing request or event shapes, update runtime tests, API tests, and CLI streaming behavior together.
- If you are changing token accounting or persistence, validate both
tests/mash/runtime/test_engine.pyandtests/mash/runtime/test_host_integration.py. - If you are changing telemetry behavior, remember there are two layers:
the API routes in
mash.api, and the bundled frontend assets undersrc/mash/api/web.
Mash Pilot
pilot/spec.py is the main in-repo agent app built on Mash.
Pilot uses standard Mash building blocks:
PilotSpecis the primaryAgentSpec.build_host()composes the primary pilot plus module-specific copilots withMashAgentHostBuilder.mash.apiserves that host over HTTP.mash.cliconnects to it as a remote client.
The Pilot host currently registers:
pilot: the primary codebase guide for shared Mash modules.cli-copilot: specialist forsrc/mash/cli.api-copilot: specialist forsrc/mash/api.mcp-copilot: specialist forsrc/mash/mcp.
Run Pilot from the activated repo environment:
export OPENAI_API_KEY=...
export MASH_DATA_DIR=.mash
python -m pilot.spec \
--workspace-root /Users/sid/Projects/mashpy \
--host 127.0.0.1 \
--port 8000 \
--api-key secret
Connect with the bundled CLI:
mash connect --api-base-url http://127.0.0.1:8000 --api-key secret --agent pilot
mash status
mash agents
mash repl
Open the built-in telemetry UI:
Pilot is useful as both:
- a real Mash app built on the same
AgentSpecandMashAgentHostBuildercontracts exposed to users - the canonical reference for how to build a multi-agent Mash host with specialized subagents
Masher
Masher is another agent built on Mash, implemented in src/mash/agents/masher/spec.py.
It is a built-in log-analysis specialist that uses the normal Mash runtime contracts:
MasherAgentSpecis a regularAgentSpec.- it uses Mash memory/store tools to resolve recent sessions and traces
- it reads structured event rows from the target agent's
MemoryStore - it can append normalized online-eval rows with
append_jsonl
Pilot enables Masher by default with .enable_masher(), but Masher can also be registered in any other Mash host that wants a log-analysis subagent.
Running the host API directly
You can start the API server either from Python or through mash host serve.
From Python:
from mash.api import MashHostConfig, run_host
from my_app import build_host
run_host(build_host(), config=MashHostConfig(bind_host="0.0.0.0", bind_port=8000))
From the CLI:
MASH_HOST_APP=my_app:build_host \
MASH_API_HOST=0.0.0.0 \
MASH_API_PORT=8000 \
MASH_DATA_DIR=/var/lib/mash \
mash host serve
For containerized deployments, the env-driven startup path is the intended one.
Docker workflow
The root Dockerfile is the base image for Mash host deployments.
Build it:
docker build -t mashpy/mash-host-base:latest .
The Pilot image is defined in pilot/Dockerfile.
Build and run it:
docker build -t mashpy/pilot:latest -f pilot/Dockerfile .
docker run \
-p 8000:8000 \
-e OPENAI_API_KEY=... \
-e MASH_HOST_APP=pilot.spec:build_host \
-e MASH_API_KEY=secret \
-e MASH_DATA_DIR=/var/lib/mash \
-v $(pwd)/data:/var/lib/mash \
mashpy/pilot:latest
The container contract is:
MASH_HOST_APPpoints atmodule:build_hostMASH_DATA_DIRpoints at the persistent state root- port
8000is exposed by default - operators mount persistent storage at
/var/lib/mash
Tests
Focused runtime and API tests:
PYTHONPATH=src \
pytest -q \
tests/mash/runtime/test_engine.py \
tests/mash/runtime/test_host_integration.py \
tests/mash/api/test_host_server.py
CLI-focused tests:
PYTHONPATH=src \
pytest -q \
tests/mash/cli/test_main.py \
tests/mash/cli/test_shell.py
When changing cross-surface behavior, set PYTHONPATH=src so tests resolve the unified workspace sources instead of stale installed copies.
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.2.0.tar.gz.
File metadata
- Download URL: mashpy-0.2.0.tar.gz
- Upload date:
- Size: 160.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54ef458fed8be3285139c683bfa70b6c4cecce0376997b6f49164559c9979089
|
|
| MD5 |
f22daca5b620ff1d1c13b167bab27066
|
|
| BLAKE2b-256 |
fd9437658a13e05dd90fa0c2a97f723a855cf8e5cf5d599154ecc571076f8f3d
|
Provenance
The following attestation bundles were made for mashpy-0.2.0.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.2.0.tar.gz -
Subject digest:
54ef458fed8be3285139c683bfa70b6c4cecce0376997b6f49164559c9979089 - Sigstore transparency entry: 1205264913
- Sigstore integration time:
-
Permalink:
imsid/mashpy@67a1b52e8b11a86243d1a2fd9252e67379c773dc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@67a1b52e8b11a86243d1a2fd9252e67379c773dc -
Trigger Event:
release
-
Statement type:
File details
Details for the file mashpy-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mashpy-0.2.0-py3-none-any.whl
- Upload date:
- Size: 184.8 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 |
2b0d69c0a337f9709a97dfc66174d48ec576207c63a60f1457b5c87246bb25a9
|
|
| MD5 |
11f6d953b913f0a1e1c55d96e883d536
|
|
| BLAKE2b-256 |
8fda5e75fdd202936a7ce0ffbfe1bb11c246e5ff47d8f867a8d7eddd522165cf
|
Provenance
The following attestation bundles were made for mashpy-0.2.0-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.2.0-py3-none-any.whl -
Subject digest:
2b0d69c0a337f9709a97dfc66174d48ec576207c63a60f1457b5c87246bb25a9 - Sigstore transparency entry: 1205264920
- Sigstore integration time:
-
Permalink:
imsid/mashpy@67a1b52e8b11a86243d1a2fd9252e67379c773dc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@67a1b52e8b11a86243d1a2fd9252e67379c773dc -
Trigger Event:
release
-
Statement type: