Skip to main content

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.runtime The managed runtime for an agent.
  • mash.api The self-hosted API server in src/mash/api.
  • mash.cli The bundled CLI in src/mash/cli.

Repo layout

src/mash/                  SDK, host API, and CLI
examples/                  Canonical example app and example container
tests/                     Unified test suite
Dockerfile                 Base image for Mash host deployments

Mental model

The architecture is:

  • app developers use mashpy to define one or more AgentSpecs
  • an app exposes build_host() -> MashAgentHost
  • mash.api loads that host and serves HTTP
  • mash.api also serves the built-in telemetry UI at /telemetry
  • mash talks 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
  • <MASH_DATA_DIR>/<agent_id>/logs/events.jsonl

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.core The agent loop itself: config, context, provider adapters, and think/act/observe execution.
  • mash.runtime Host-side orchestration: AgentSpec, runtime servers, host/client wiring, request streaming, session storage, and subagent delegation.
  • mash.api and mash.cli App facing surfaces built on top of the runtime. mash.api exposes the FastAPI host service and telemetry UI; mash.cli is the remote client.

The normal execution path is:

  1. An app defines one or more AgentSpecs.
  2. MashAgentHostBuilder composes those specs into a MashAgentHost.
  3. mash.api starts the host and exposes HTTP endpoints for agent requests, sessions, history, and telemetry.
  4. mash.cli talks to that HTTP API as a remote client.
flowchart TD
    A["`AgentSpec`"] --> B["`MashAgentHostBuilder`"]
    B --> C["`MashAgentHost`"]
    C --> D["`mash.api`"]
    D --> E["HTTP API"]
    D --> F["Telemetry UI"]
    G["`mash.cli`"] --> E

Persistence is runtime-level, not app-level. Each agent stores state under:

  • <MASH_DATA_DIR>/<agent_id>/state.db
  • <MASH_DATA_DIR>/<agent_id>/logs/events.jsonl

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 AgentSpec is the transport-agnostic contract app authors implement.
  • src/mash/runtime/host.py MashAgentHost and MashAgentHostBuilder register the primary agent and subagents, start one runtime per agent, and wire host-managed delegation.
  • src/mash/runtime/server.py MashAgentServer owns 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 MashAgentClient is 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-->>Client: request.accepted / agent.trace / request.completed
    Client-->>API: streamed runtime events
    API-->>User: SSE / final payload

Important runtime properties:

  • Each MashAgentServer is 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_tokens is computed from saved turn metadata and persisted with each turn.
  • Runtime logs are written to JSONL and also fanned out as live events for telemetry and remote clients.

Working on the SDK

The main SDK surface is:

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.

  1. The host registers the primary agent and subagents in src/mash/runtime/host.py.
  2. On startup, the host injects subagent routing guidance into the primary system prompt and registers InvokeSubagent.
  3. InvokeSubagentTool in src/mash/tools/subagent.py resolves the target subagent client and submits a normal streamed request.
  4. The subagent request runs through that subagent’s own MashAgentServer, persistence layer, and session namespace.
  5. 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:

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.py and tests/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 under src/mash/api/web.

Running the canonical example

The repo has one canonical example app in examples/example_app.py.

Set MASH_DATA_DIR in examples/.env before starting the app. Example:

echo 'MASH_DATA_DIR=.mash' >> examples/.env

Start the example host from the activated repo environment:

python -m examples.example_app \
  --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
mash status
mash agents
mash repl --agent primary

Open the built-in telemetry UI:

The example app:

  • defines one primary agent
  • defines one research subagent
  • exposes build_host()
  • uses the same MASH_DATA_DIR storage contract as any real app
  • auto-loads env from repo .env and examples/.env

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 example app image is defined in examples/Dockerfile.

Build and run it:

docker build -t mashpy/example-app:latest -f examples/Dockerfile .
docker run \
  -p 8000:8000 \
  -e ANTHROPIC_API_KEY=... \
  -e MASH_API_KEY=secret \
  -e MASH_DATA_DIR=/var/lib/mash \
  -v $(pwd)/data:/var/lib/mash \
  mashpy/example-app:latest

The container contract is:

  • MASH_HOST_APP points at module:build_host
  • MASH_DATA_DIR points at the persistent state root
  • port 8000 is 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


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.5.tar.gz (147.8 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.5-py3-none-any.whl (167.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mashpy-0.1.5.tar.gz
  • Upload date:
  • Size: 147.8 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.5.tar.gz
Algorithm Hash digest
SHA256 0e7e52cf2acfa993cd61bf8dc58ea12c901d52feb95d54585635887ef0ded737
MD5 2025ccb6b54914cbe66412c5526e4495
BLAKE2b-256 4050cc3201e3e0cd3bf421b7d3288b4b0222a8809599936670afe8001081fbc0

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: mashpy-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 167.5 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 a9321b035ef45ba959f424286255d6ac9b7e5e1ed85edef2afd10b053a33e56f
MD5 7d62d432c585303c8f5811a0352c5acb
BLAKE2b-256 78489d2bac147334c5f3f453648f2de1e3ff6b4d1c1ca3f32fc2df43bb4c236c

See more details on using hashes here.

Provenance

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