Substrate
- Agent Tooling
Portable tool execution service that reduces the burden on agent applications by managing environment complexity and eliminating repetitive tool implementation
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.
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)
import { Environment } from "@substrate/sdk";
async function openSession(root: string, timeoutMs = 600_000) {
// Provision the environment once; it outlives any agent.
const env = await Environment.create({
workspace: { kind: "existing", root },
worker: { kind: "managed", id: "synth-worker" },
policy: {
readRoots: ["/workspace"],
writeRoots: ["/workspace"],
network: { enabled: false },
maxDurationMs: timeoutMs,
maxOutputBytes: 100_000,
},
submitTimeoutMs: timeoutMs,
});
return env.createSession();
}
async function run(session, agent) {
// Drive an agent against an existing session.
while (!agent.done) {
const call = agent.nextToolCall();
const result = await session.submitTool(call.name, call.arguments);
agent.observe(result);
}
return session.effects(); // what the agent applied
}
// Provisioned once; agents come and go against it.
const session = await openSession("/data/run-42");
const effects = await run(session, agent);
Why I built this
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.
Performance snapshot
| Workload | Requests | Throughput | Median | P95 |
|---|---|---|---|---|
| GET /health baseline | 500 | 6,861 rps | 0.130 ms | 0.207 ms |
| Sequential Write | 250 | 200 rps | 4.963 ms | 6.256 ms |
| Sequential Read | 250 | 3,968 rps | 0.215 ms | 0.370 ms |
| Sequential Glob over 250 files | 100 | 2,538 rps | 0.390 ms | 0.433 ms |
| Concurrent Write, 8 envs, 32 clients | 800 | 320 rps | 97.273 ms | 141.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.