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
Mash stores agent memory in one of two ways:
- Postgres when
MASH_MEMORY_DATABASE_URLis set - otherwise SQLite at
<MASH_DATA_DIR>/<agent_id>/state.db
That memory store contains:
- conversation turns and signals
- memory-search data
- structured memory logs
If MASH_DATA_DIR is not set, the SQLite fallback root is /var/lib/mash.
Hosted runtime durability and observability events are separate and use Postgres via MASH_RUNTIME_DATABASE_URL.
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, subagent delegation, and workflow-only agents.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, including optional subagents and code-defined workflows.mash.apistarts the host and exposes HTTP endpoints for agent requests, sessions, session signals, history, workflows, 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 gets a memory_store
from AgentSpec.build_memory_store(), which defaults to:
- Postgres when
MASH_MEMORY_DATABASE_URLis set - otherwise SQLite at
<MASH_DATA_DIR>/<agent_id>/state.db
If MASH_DATA_DIR is not set, the SQLite fallback root is /var/lib/mash.
Agent Runtime
The core execution stack is:
- src/mash/runtime/spec.py
AgentSpecis the transport-agnostic contract app authors implement, includingbuild_memory_store(). - src/mash/runtime/host.py
MashAgentHostandMashAgentHostBuilderregister the primary agent, subagents, workflow-only agents, and workflows, start one runtime per agent, and wire host-managed delegation. - src/mash/runtime/runtime.py
MashAgentRuntimeis the async execution core for one agent: hosted request execution, per-session serialization, runtime event emission, replay-derived request state, recovery hooks, persistence orchestration, and trace fanout. - src/mash/runtime/execution
Runtime execution primitives:
RuntimeEvent,RuntimeStore, and the Postgres-backed runtime event log. - src/mash/runtime/server.py
MashAgentServeris the Starlette transport adapter over one runtime and exposes the H2A HTTP + SSE surface. - src/mash/core/agent.py
Agentprovides think/act/observe primitives used by the hosted runtime workflow loop. - src/mash/runtime/client.py
MashAgentClientis the async H2A 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 Server as MashAgentServer
participant Runtime as MashAgentRuntime
participant Workflow as DBOS Workflow
participant Agent as mash.core.Agent
participant Store as State Store
User->>API: POST /api/v1/agent/{agent_id}/request
API->>Host: get_client(agent_id)
Host-->>API: MashAgentClient
API->>Client: post_request(...) / stream_response(...)
Client->>Server: HTTP request + SSE stream
Server->>Runtime: submit_request(...) / stream_request_events(...)
Runtime->>Store: append runtime.request.accepted
Runtime->>Workflow: start_workflow(request_id)
Workflow->>Agent: think / act / observe primitives
Workflow->>Store: append RuntimeEvent progress
Runtime->>Store: save_turn(...)
Server-->>Client: request.accepted / request.waiting? / agent.trace / request.completed
Client-->>API: streamed runtime events
API-->>User: SSE / final payload
Important runtime properties:
- Each request is accepted immediately and started as a durable DBOS workflow.
- Requests for the same
session_idare serialized inside one runtime with a per-session lock. - Different sessions on the same agent may run concurrently up to the runtime concurrency limit.
request.waitingis emitted when an accepted request cannot start yet because the session is busy or the runtime concurrency limit is saturated.runtime_storeis the append-only canonical event log for request streaming, telemetry, replay, and debugging.- Runtime durability is implemented separately from the event log via DBOS workflows.
- Public request streaming is derived from persisted
RuntimeEvents inruntime_event_log. - Startup recovery resumes incomplete hosted requests through DBOS workflow recovery.
- Request lifecycle is streamed over SSE using stable event names:
request.accepted, optionalrequest.waiting,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. - Each runtime uses separate
memory_storeandruntime_storepersistence roles. - The runtime H2A surface is:
GET /health,POST /agent/{agent_id}/request,GET /agent/{agent_id}/request/{request_id}. - Host-level workflows are exposed through:
GET /api/v1/workflows,POST /api/v1/workflows/{workflow_id}/run, andGET /api/v1/workflows/{workflow_id}/runs/{run_id}.
Working on the SDK
The main SDK surface is:
AgentSpecin src/mash/runtime/spec.pyMashAgentHostin src/mash/runtime/host.pyMashAgentRuntimein src/mash/runtime/runtime.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 resolves subagent endpoints and the primary runtime injects subagent routing guidance plus
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 runtime server, runtime core, 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 Runtime as Subagent MashAgentRuntime
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->>Runtime: submit_request(...) / stream_request_events(...)
Runtime->>Runtime: RuntimeWorkflowExecutor.run(request_id)
Runtime->>Subagent: think / act / observe primitives
Runtime->>Runtime: append RuntimeEvent progress
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 primitives, tool execution, token aggregation, and trace metadata generation.
- src/mash/runtime/runtime.py Async hosted request lifecycle, per-session locking, runtime event append/read behavior, and persistence orchestration.
- src/mash/runtime/execution
RuntimeEvent, replay state, workflow execution, recovery, and runtime event storage. - src/mash/runtime/server.py H2A Starlette surface and SSE streaming for one runtime.
- src/mash/runtime/host.py Multi-agent composition, uvicorn server startup, and subagent endpoint wiring.
- src/mash/api/app.py
FastAPI composition for the public host API, telemetry endpoints, auth, and
/api/v1/agent/...routes. - 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. - Telemetry is a trace debugger backed by canonical runtime events; memory search remains turns-backed.
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.runtime-copilot: specialist forsrc/mash/runtime.
Run Pilot from the activated repo environment:
export OPENAI_API_KEY=...
export MASH_DATA_DIR=.mash
export MASH_RUNTIME_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mash_runtime
export MASH_MEMORY_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mash_memory
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:
Telemetry is intentionally narrow:
- request and trace inspection backed by
runtime_event_log - recent trace lookup
- live request streaming via the hosted SSE endpoints
- memory search as a separate turns-backed feature
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 Mash's built-in workflow-only trace processing worker, implemented in src/mash/agents/masher/spec.py.
Masher is not exposed as a user-invokable subagent. HostBuilder.enable_masher() registers Masher as a workflow-only runtime and registers Masher workflows. It is hidden from public agent listings and from InvokeSubagent, but workflow tasks can still call it through the host runtime client.
The built-in workflows each have one task backed by TaskSpec(agent_spec=masher_spec) and support two modes through workflow input.
masher-trace-digest is diagnostic:
- trace mode: digest one explicit
target_agent_id/session_id/trace_idand return the digest in completed workflow output - incremental mode: use workflow task state as a per-target checkpoint, digest traces after the last checkpoint timestamp, append digest rows to Masher's JSONL artifact, and return updated checkpoint state only
masher-online-eval-curation is dataset-focused:
- trace mode: curate one explicit
target_agent_id/session_id/trace_idinto a normalized eval row - incremental mode: use workflow task state as a per-target checkpoint, curate traces after the last checkpoint timestamp, append eval rows to Masher's JSONL artifact, and return updated checkpoint state only
Example trace-mode invocation through the REPL:
/workflow run masher-trace-digest --input '{"mode":"trace","target_agent_id":"primary","session_id":"...","trace_id":"..."}'
/workflow status masher-trace-digest <run_id>
/workflow run masher-online-eval-curation --input '{"mode":"trace","target_agent_id":"primary","session_id":"...","trace_id":"..."}'
/workflow status masher-online-eval-curation <run_id>
Example incremental invocation:
/workflow run masher-trace-digest daily-primary --input '{"mode":"incremental","target_agent_id":"primary"}'
/workflow run masher-online-eval-curation daily-primary-evals --input '{"mode":"incremental","target_agent_id":"primary"}'
Incremental artifacts are written to Masher's configured JSONL artifacts, which default to:
<MASH_DATA_DIR>/masher/trace-digests.jsonl
<MASH_DATA_DIR>/masher/online-evals.jsonl
Running the host API directly
You can start the API server either from Python or through mash host serve.
From Python:
import os
from mash.api import MashHostConfig, run_host
from my_app import build_host
os.environ["MASH_RUNTIME_DATABASE_URL"] = "postgresql://postgres:postgres@127.0.0.1:5432/mash_runtime"
os.environ["MASH_MEMORY_DATABASE_URL"] = "postgresql://postgres:postgres@127.0.0.1:5432/mash_memory"
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_RUNTIME_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mash_runtime \
MASH_MEMORY_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mash_memory \
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 \
-e MASH_RUNTIME_DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/mash_runtime \
-e MASH_MEMORY_DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/mash_memory \
-v $(pwd)/data:/var/lib/mash \
mashpy/pilot:latest
The container contract is:
MASH_HOST_APPpoints atmodule:build_hostMASH_DATA_DIRpoints at the SQLite fallback state rootMASH_RUNTIME_DATABASE_URLpoints at the Postgres database used for hosted runtime durability and runtime eventsMASH_MEMORY_DATABASE_URLpoints at the Postgres database used for agent memory when Postgres-backed memory is desired- 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.3.2.tar.gz.
File metadata
- Download URL: mashpy-0.3.2.tar.gz
- Upload date:
- Size: 198.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
745af7a2da7e29850a7bd4106be16e5a250bd769d97c2af1ce77d8e094c9d4b4
|
|
| MD5 |
f7910721336b7be41db94c32d91aaf21
|
|
| BLAKE2b-256 |
e8fa636c640478433825d8eff6ffb1f9826b9a0ac97a91d9ac2a667491881cd4
|
Provenance
The following attestation bundles were made for mashpy-0.3.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.3.2.tar.gz -
Subject digest:
745af7a2da7e29850a7bd4106be16e5a250bd769d97c2af1ce77d8e094c9d4b4 - Sigstore transparency entry: 1578538725
- Sigstore integration time:
-
Permalink:
imsid/mashpy@f7fdc99e7137134dcd9936f8a230280d84a45500 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7fdc99e7137134dcd9936f8a230280d84a45500 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mashpy-0.3.2-py3-none-any.whl.
File metadata
- Download URL: mashpy-0.3.2-py3-none-any.whl
- Upload date:
- Size: 232.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2464b32a4d7890265419791a14881cd744f4163fb62f06fa267c71a2ee6f09b7
|
|
| MD5 |
787f93041415eb45ddb6b6b987685b91
|
|
| BLAKE2b-256 |
7539b836ffa169872c0ed9521bc16e68eeae75d735e6dcebfc6be20e055d5d22
|
Provenance
The following attestation bundles were made for mashpy-0.3.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.3.2-py3-none-any.whl -
Subject digest:
2464b32a4d7890265419791a14881cd744f4163fb62f06fa267c71a2ee6f09b7 - Sigstore transparency entry: 1578538951
- Sigstore integration time:
-
Permalink:
imsid/mashpy@f7fdc99e7137134dcd9936f8a230280d84a45500 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/imsid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7fdc99e7137134dcd9936f8a230280d84a45500 -
Trigger Event:
release
-
Statement type: