Substrate

Portable tool execution service that reduces the burden on agent applications by managing environment complexity and eliminating repetitive tool implementation

GitHub

Substrate is an environment like a filesystem, with a worker that reads from an agent application in the background. Intuitively, Substrate's SDK intercepts your tool calls, writes them to the broker, which is often just a path in your filesystem. The worker will be listening on the broker, relaying the tool call events to the server for execution.

Substrate architecture — an agent app issues a tool call through a language SDK, which is routed either as a direct call or through a broker to a worker; both reach the Substrate server, which consults a policy and workspace resolver and is backed by stores for sessions, a sandbox, an effect ledger, and artifacts.
A tool call's path — the agent app calls in through a language SDK, gets routed directly or through the broker to a worker, and the Substrate server runs it against a resolved policy and workspace, persisting sessions, a sandbox, an effect ledger, and artifacts.
from pathlib import Path
from substrate import Environment


def open_session(root: Path, *, timeout_ms: int = 600_000):
    """Provision the environment once; it outlives any agent."""
    env = Environment.create(
        workspace={"kind": "existing", "root": str(root)},
        worker={"kind": "managed", "id": "synth-worker"},
        policy={
            "readRoots": ["/workspace"],
            "writeRoots": ["/workspace"],
            "network": {"enabled": False},
            "maxDurationMs": timeout_ms,
            "maxOutputBytes": 100_000,
        },
        submitTimeoutMs=timeout_ms,
    )
    return env.create_session()


def run(session, agent):
    """Drive an agent against an existing session."""
    while not agent.done:
        call = agent.next_tool_call()
        result = session.submit_tool(call.name, **call.arguments)
        agent.observe(result)
    return session.effects()  # what the agent applied


# Provisioned once; agents come and go against it.
session = open_session(Path("/data/run-42"))
effects = run(session, agent)

This project is primarily about separating tool execution from an agent application. This buys three things I find important: First, you do not have to redefine and reimplement tools for every language or project. Second, you can separate application control state from environment state. Third, you can separate the application lifecycle from the environment lifecycle. The tried and true metaphor for this concept makes it a bit more clear:

A waiter carries your order back to the kitchen, but they don't cook it at your table, and they don't drag the stove, the pantry, and the half-finished sauces out into the dining room. They hold only the order ticket, table four, salmon. That is the control state, it routes the waiter. The kitchen holds everything else: the ingredients, the equipment, the mise en place that took all morning to set up. Substrate is a kitchen that cooks every dish instantly, and can be dropped in the new restaurant you're opening tomorrow that is a completely different cousine with a waitstaff that rides around on unicycles.


WorkloadRequestsThroughputMedianP95
GET /health baseline5006,861 rps0.130 ms0.207 ms
Sequential Write250200 rps4.963 ms6.256 ms
Sequential Read2503,968 rps0.215 ms0.370 ms
Sequential Glob over 250 files1002,538 rps0.390 ms0.433 ms
Concurrent Write, 8 envs, 32 clients800320 rps97.273 ms141.030 ms

Measured on localhost against a release-built Substrate HTTP host. The run created real environments and sessions, then executed actual tool calls against temporary workspaces.