Local development
Run the engine on your machine. Iterate on an agent before it goes live.
Everything works locally: Slack, MCP connections, workers, and sub-agents. The engine keeps its state in a SQLite file beside your config.
Set up a file
Write substructure.toml in your project root.
name = "oncall-bot"
[llm.openrouter]
type = "openrouter"
[agent.oncall]
llm = "openrouter"
model = "anthropic/claude-sonnet-4-5"
system = "You are the on-call assistant."
[serve]
port = 8080[serve] runs the engine here. Name a [remote] instead to point the CLI at a
deployment. See Config for every section.
Run one turn
export OPENROUTER_API_KEY=sk-or-...
subs run --agent oncall -o pretty "hi"The reply streams to your terminal. -o pretty shows the turn as text. The
default, ag-ui, streams protocol events.
Put the flags in the file so you stop repeating them.
[run]
agent = "oncall"
output = "pretty"subs run "hi"Continue a session
After every run the CLI prints the command to continue it.
continue this session with:
subs run --agent oncall --session <session-id> '...'subs run --session <session-id> "what was my first question?"The agent remembers. The engine saves the whole session in substructure.db.
Stop everything, come back tomorrow, and the session continues.
Run a server
subs serve --no-authThis serves the REST API and the AG-UI
endpoints on 127.0.0.1:8080. Point a frontend at it.
--no-auth turns off client and worker authentication. Use it only for a server
nothing off this machine can reach.
Develop a worker
Run your worker and point an agent at it.
[agent.oncall]
llm = "openrouter"
model = "anthropic/claude-sonnet-4-5"
worker = "http://localhost:4444"node server.mjs
subs run "hi"Every decision now POSTs to your code. See Workers.
A local engine signs decisions only when the agent names
signing_secret_env. Leave it off while you develop.
Connect Slack locally
A local engine uses a Slack app you own, over Socket Mode. See Self-hosting.
export SLACK_APP_TOKEN=xapp-...
export SLACK_BOT_TOKEN=xoxb-...
subs serve --no-auth --slack-agent my-agentConnect MCP servers locally
subs mcp login sentryThe engine on this machine runs the OAuth flow. The credential goes into that
environment's db.
That database now holds credentials. Add *.db* to .gitignore.
Two environments
A second environment is a second file. Each one gets its own database.
subs run -c substructure.dev.toml "hi"
subs serve -c substructure.dev.tomldb defaults to the file's name. substructure.toml uses substructure.db.
substructure.dev.toml uses substructure.dev.db.
Develop against a cloud project
One file can do both. Keep the engine keys and a [remote] section together.
subs serve # run it here
subs apply # deploy the same declarationsubs run and subs serve read api_key_env and signing_secret_env. subs apply strips them.
Logs
log = "info"log takes RUST_LOG syntax: a level on its own, or per-target directives such
as substructure_core=debug,warn. $RUST_LOG wins over it. Without it, subs run shows errors and subs serve shows info.
Next
- Workers: the code the engine calls.
- CLI: every command and flag.
- Self-hosting: run the engine for other people.
- Cloud: deploy the same file.