{"slug": "subs-a-cloud-native-agent-harness", "title": "Subs, a cloud native agent harness", "summary": "Substructure AI released Subs, a pre-1.0 cloud-native agent harness that runs an unprivileged agent loop with no system access, using MCP servers for tools and supporting local or client-server operation. The tool manages durability, retries, timeouts, MCP connections, session state, session branching, AG-UI, Slack integration, LLM calls, subagents, and interrupts, with agents declared in a config file and customizable via HTTP webhooks. Subs is available via a curl installer and can be used with hosted engines or self-hosted servers.", "body_md": "Pre-1.0: APIs and the wire protocol can change between releases.\n\n`subs`\n\nis an agent harness for the cloud.\n\nIt runs an unprivileged agent loop with no system access. It uses MCP servers for tools. It runs locally or as a client and a server.\n\nDeclare your agents in a config file. To customize the loop, point an agent at an HTTP endpoint and answer a webhook.\n\n`subs`\n\nhandles durability, retries, timeouts, MCP connection management, session state, session branching, AG-UI, Slack connection, LLM calls, subagents, interrupts and more.\n\nTo turn a sandbox into an MCP server, see\n[mcpd](https://github.com/substructureai/mcpd).\n\n```\ncurl -fsSL https://subs.dev/cli.sh | bash\n```\n\nThe CLI is also the engine.\n\nCreate a `subs.toml`\n\n.\n\n```\nname = \"example\"\n\n[llm.openrouter]\ntype = \"openrouter\"\n\n[agent.teammate]\nllm = \"openrouter\"\nmodel = \"deepseek/deepseek-v4-flash-0731\"\nsystem = \"You are a helpful teammate.\"\n```\n\nSet your provider key and talk to the agent.\n\n```\nexport OPENROUTER_API_KEY=sk-or-...\nsubs chat teammate -c subs.toml\n```\n\nAdd a `[serve]`\n\nsection and a `[remote]`\n\nthat points at it.\n\n```\n[serve]\nport = 9999\nauth = false\n\n[remote]\nurl = \"http://localhost:9999\"\n```\n\nStart the server.\n\n```\nsubs serve -c subs.toml\n```\n\nIn another terminal, the same chat command now talks to it.\n\n```\nsubs chat teammate -c subs.toml\n```\n\nPoint `[remote]`\n\nat the hosted engine instead of your own.\n\n```\n[remote]\nurl = \"https://api.substructure.ai\"\n```\n\nCreate the project from the file, then upload your LLM key.\n\n```\nsubs apply\nsubs auth llm.openrouter\n```\n\nThe same chat command now runs the turn on the hosted engine.\n\n```\nsubs chat teammate -c subs.toml\n```\n\nSay which agent takes a DM and which one answers a mention.\n\n```\n[slack]\ndm = \"teammate\"\nmentions = \"teammate\"\n```\n\nApply the file again, then connect your workspace.\n\n```\nsubs apply\nsubs slack connect\n```\n\nMention the bot in a channel and it answers in the thread.\n\nDeclare the server and give it to an agent. Every user of the agent shares one credential.\n\n```\n[mcp.sentry]\nurl = \"https://mcp.sentry.dev/mcp\"\n\n[agent.teammate]\nllm = \"openrouter\"\nmodel = \"deepseek/deepseek-v4-flash-0731\"\nsystem = \"You are a helpful teammate.\"\nmcp = [\"mcp.sentry\"]\n```\n\nAuthorize the connection.\n\n```\nsubs auth mcp.sentry\n```\n\nSet `credential = \"user\"`\n\nand each user connects their own account. A\nuser-scoped connection works only in a one-on-one chat between the agent and\nthat user.\n\n```\n[mcp.linear]\nurl = \"https://mcp.linear.app/mcp\"\ncredential = \"user\"\n\n[agent.personal]\nllm = \"openrouter\"\nmodel = \"deepseek/deepseek-v4-flash-0731\"\nsystem = \"Help me with my Linear issues.\"\nmcp = [\"mcp.linear\"]\n\n[slack]\ndm = \"personal\"\nmentions = \"teammate\"\n```\n\nPoint an agent at a URL and the engine sends every decision for that agent to your code. This gives you full control of the loop, including how the agent behaves in Slack.\n\n```\n[agent.teammate]\nllm = \"openrouter\"\nmodel = \"deepseek/deepseek-v4-flash-0731\"\nsystem = \"You are a helpful teammate.\"\nmcp = [\"mcp.sentry\"]\nworker = \"https://example.com/agent\"\n```\n\nYour endpoint reads the engine's proposal and returns it, changing only the steps you care about. There is no SDK to install.\n\n``` python\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport type { DecisionRequest, DecisionResponse } from \"./protocol.ts\";\n\nfunction decide({ trigger, proposed }: DecisionRequest): DecisionResponse {\n    if (trigger.type === \"session.start\") {\n        return {\n            agent: {\n                ...proposed.agent,\n                tools: [{ name: \"current_time\", description: \"Get the current time\" }]\n            }\n        };\n    }\n\n    // Run our tool when the model calls it.\n    if (trigger.type === \"tool.execute\" && trigger.name === \"current_time\") {\n        return { actions: [{ type: \"tool.result\", result: new Date().toISOString() }] };\n    }\n\n    // Accept the engine's proposal for everything else.\n    return proposed;\n}\n\nconst app = new Hono();\napp.post(\"/\", async (c) => c.json(decide(await c.req.json())));\n\nserve({ fetch: app.fetch, port: 4444 });\n```\n\nOnly the agents that name a worker use one. The rest stay with the engine, in the same project and the same file.\n\nFull walkthrough in the [quick start](/substructureai/subs/blob/main/docs/10-quick-start.md). Docs:\n[Workers](/substructureai/subs/blob/main/docs/50-workers.md), [Connectors](/substructureai/subs/blob/main/docs/40-connectors.md),\n[Slack](/substructureai/subs/blob/main/docs/130-slack.md), [Local development](/substructureai/subs/blob/main/docs/160-local-development.md)\n\nMention the bot or DM it. The thread is the session. Route different channels to different agents.\n\nDocs: [Slack](/substructureai/subs/blob/main/docs/130-slack.md)\n\n`subs chat`\n\nholds one session open, streams the reply as it is written, and\nturns an approval prompt into a picker. The session is the same kind a Slack\nthread is.\n\nDocs: [Chat](/substructureai/subs/blob/main/docs/135-chat.md)\n\nExamples: [no-code-chat](/substructureai/subs/blob/main/examples/no-code-chat)\n\nAt each step the engine tells your code what it plans to do next. Accept the plan or do something else. A working agent is a few lines.\n\nDocs: [How it works](/substructureai/subs/blob/main/docs/20-how-it-works.md)\n\nYour agent is an HTTP endpoint. Generate typed bindings from the published JSON schema.\n\nDocs: [Typed bindings](/substructureai/subs/blob/main/docs/270-typed-bindings.md)\n\nExamples: [Go](/substructureai/subs/blob/main/examples/go-chat-with-tools), [Python](/substructureai/subs/blob/main/examples/python-fast-api-pydantic-chat-with-tools), [TypeScript](/substructureai/subs/blob/main/examples/node-hono-typescript-chat-with-tools), [Elixir](/substructureai/subs/blob/main/examples/elixir-plug-chat-with-tools)\n\nDeclare an MCP server and the engine handles the authorization, reads the tools it offers, and runs every call. Your code never holds a token.\n\nDocs: [Connectors](/substructureai/subs/blob/main/docs/40-connectors.md)\n\nExamples: [Node](/substructureai/subs/blob/main/examples/node-hono-connectors)\n\nPoint an agent at an [agent-plugins](https://agent-plugins.org) directory and it\ngets that plugin's skills and MCP servers.\n\nDocs: [Plugins](/substructureai/subs/blob/main/docs/45-plugins.md)\n\nThe engine calls Anthropic, OpenAI, or OpenRouter with your key. Or your worker makes the call and the engine never sees a key.\n\nDocs: [LLMs](/substructureai/subs/blob/main/docs/70-llms.md)\n\nExamples: [Anthropic](/substructureai/subs/blob/main/examples/node-hono-anthropic), [OpenAI](/substructureai/subs/blob/main/examples/node-hono-openai), [OpenRouter](/substructureai/subs/blob/main/examples/node-hono-openrouter)\n\nEvery step is saved before it runs. A run continues from where it stopped. The same message submitted twice runs once.\n\nDocs: [Durability](/substructureai/subs/blob/main/docs/200-durability.md)\n\nAn agent can stop and wait for a person to approve, then continue. A waiting agent uses no compute. In Slack this is a button.\n\nDocs: [Interrupts](/substructureai/subs/blob/main/docs/100-interrupts.md)\n\nA tool does not have to answer immediately. Accept the call, do the work on your own schedule, and report the result later.\n\nDocs: [Async tools](/substructureai/subs/blob/main/docs/110-async-tools.md)\n\nHistory, editing, regeneration, and branching belong to the engine. A user can edit an earlier message and go a new direction. The original branch stays.\n\nDocs: [Conversations](/substructureai/subs/blob/main/docs/120-conversations.md)\n\nThe engine streams AG-UI events, so assistant-ui and CopilotKit connect to it directly.\n\nDocs: [AG-UI](/substructureai/subs/blob/main/docs/140-ag-ui.md)\n\nExamples: [assistant-ui](/substructureai/subs/blob/main/examples/node-hono-assistant-ui), [CopilotKit](/substructureai/subs/blob/main/examples/node-hono-copilotkit)\n\nA tool can run in the user's browser instead of on your server. The run waits for the browser, then continues.\n\nDocs: [Client-side tools](/substructureai/subs/blob/main/docs/150-client-tools.md)\n\nExamples: [Node](/substructureai/subs/blob/main/examples/node-hono-client-tool)\n\nThe engine stores your agent's state with the conversation. Your code gets it on every request and writes changes back.\n\nDocs: [Agent state](/substructureai/subs/blob/main/docs/90-state.md)\n\nAn agent can give work to other agents. Each child runs in its own session. The parent's totals include each child's cost and token use.\n\nDocs: [Subagents](/substructureai/subs/blob/main/docs/80-subagents.md)\n\nGive a tool an input and output schema. The engine checks every call against it.\n\nDocs: [Tool calls](/substructureai/subs/blob/main/docs/60-tools.md)\n\nSet a policy on any tool or model call. The engine applies it, and keeps applying it after a restart.\n\nDocs: [Retries and timeouts](/substructureai/subs/blob/main/docs/210-retries.md)\n\nRun the engine on your own servers and hold every credential.\n\nDocs: [Self-hosting](/substructureai/subs/blob/main/docs/180-self-hosting.md)\n\n**Engine.** Runs the agent loop, in Rust. It calls the model, runs tools, saves each step, retries failures, streams events, and supervises subagents. Use the hosted version at[app.substructure.ai](https://app.substructure.ai), run it from the CLI, or embed it in your process.**Workers.** Your agent code. It receives a trigger and returns actions. It runs in your codebase with your dependencies.**Clients.** They send work and stream events back, from your backend or from the browser. Slack and AG-UI are clients.**CLI.** Set up, deploy, watch, and debug from the terminal. It also runs the engine locally.\n\n```\ncurl -fsSL https://subs.dev/cli.sh | bash\n```\n\nThe script verifies the release checksum and installs to `~/.local/bin`\n\n. Set\n`SUBS_INSTALL_DIR`\n\nto install elsewhere and `SUBS_VERSION`\n\nto pin a release. Or\ninstall from npm:\n\n```\nnpm i -g @substructure.ai/cli\n```\n\nFull documentation in [ docs/](/substructureai/subs/blob/main/docs).", "url": "https://wpnews.pro/news/subs-a-cloud-native-agent-harness", "canonical_source": "https://github.com/substructureai/subs", "published_at": "2026-08-29 02:35:34+00:00", "updated_at": "2026-08-29 02:48:12.446966+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Substructure AI", "Subs", "MCP", "OpenRouter", "Sentry", "Linear", "Slack", "Hono"], "alternates": {"html": "https://wpnews.pro/news/subs-a-cloud-native-agent-harness", "markdown": "https://wpnews.pro/news/subs-a-cloud-native-agent-harness.md", "text": "https://wpnews.pro/news/subs-a-cloud-native-agent-harness.txt", "jsonld": "https://wpnews.pro/news/subs-a-cloud-native-agent-harness.jsonld"}}