Skip to content
Hoody.com

An AI agent framework on its own gives the agent no way to act. The model can generate code but has nowhere to run it, can plan a deployment but cannot execute one, and can describe a test without being able to open a browser. A human has to carry out every step it proposes.

Screenshot Coming Soon AI agent session: chat window with AI writing code, terminal showing live execution, and file browser reflecting changes in real time
An agent session: chat, terminal, and file browser updating live

A Hoody container closes that gap.

Every service in a container (terminal, filesystem, database, browser, display, process manager) is an HTTPS endpoint, so an agent needs no special adapter, plugin, or permission negotiation: it makes HTTP requests, which agents already know how to do. Every process the agent spawns gets a URL, and every tool it uses is one. The built-in hoody-agent service adds a single conversational interface over all of it, so the agent can manage itself.

That is the design rather than a coincidence: infrastructure that exposes everything over HTTP is infrastructure an AI can operate directly. And it all runs on servers you own, every agent with a complete machine of its own.


Consider what an autonomous agent needs:

CapabilityTraditional stackHoody
Execute commandsSSH + credentials + firewall rulesPOST terminal-1.../api/v1/terminal/execute
Read/write filesSFTP + mount points + permissionsGET/POST files.../api/v1/files/...
Query databasesConnection strings + drivers + ORMPOST sqlite-1.../api/v1/sqlite/db
Automate browsersPuppeteer setup + Chromium installGET browser-1.../screenshot, GET browser-1.../browse
Spawn more agentsInfrastructure provisioningPOST /api/v1/projects/{id}/containers
Observe everythingLogging infrastructureEvery HTTP request is observable

Every capability is one HTTP call, authenticated by nothing more than a bearer token. There is no SDK to install and no driver to manage.


Give your AI agent its own computer:

Terminal window
# Create a container for your agent
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "ai-agent-alpha" \
--container-image "debian/13" \
--hoody-kit

That container is a complete computer. The agent has all 18 HTTP services at its disposal without further setup.


The hoody-agent service is a full autonomous coding agent accessible entirely through HTTP, across 100+ endpoints:

Terminal window
# The `hoody agent` CLI has subcommands (sessions, tools, ...). Prompts run via `hoody agent sessions prompt-sync` / `prompt-stream`.
# You can also drive it directly via HTTP to the agent service URL.
# Prompts are session-scoped: create a session first, then dispatch turns into it.
SESSION_ID=$(curl -s -X POST "https://$PROJECT_ID-$AGENT_ID-agent-1.$SERVER.containers.hoody.com/api/v1/agent/sessions" \
-H "Content-Type: application/json" -d '{}' | jq -r '.id')
# ?policy=auto_approve adopts the headless posture so confirm gates auto-approve.
curl -X POST "https://$PROJECT_ID-$AGENT_ID-agent-1.$SERVER.containers.hoody.com/api/v1/agent/sessions/$SESSION_ID/prompt:sync?policy=auto_approve" \
-H "Content-Type: application/json" \
-d '{
"text": "Set up a Node.js REST API with user authentication, SQLite database, and automated tests. Deploy it as a daemon process."
}'

The agent now works autonomously. It reads files, writes code, executes commands, queries databases, and deploys services, all through the same HTTP services available to any human user.

Stream agent output in real time with the non-sync prompt endpoint, which returns Server-Sent Events:

// Stream real-time updates via SSE (session-scoped: dispatch into an existing session)
const response = await fetch(
// ?policy=auto_approve adopts the headless posture so confirm gates auto-approve.
`https://${PROJECT_ID}-${AGENT_ID}-agent-1.${SERVER}.containers.hoody.com/api/v1/agent/sessions/${SESSION_ID}/prompt:stream?policy=auto_approve`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: 'Run the test suite and report results',
}),
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
// Streams tool calls, tool results, and assistant messages as they happen
}

Or list the agent’s sessions, then tail one’s live event stream:

# List sessions
https://PROJECT_ID-AGENT_ID-agent-1.SERVER.containers.hoody.com/api/v1/agent/sessions
# Live event stream for one session
https://PROJECT_ID-AGENT_ID-agent-1.SERVER.containers.hoody.com/api/v1/agent/sessions/SESSION_ID/stream

You can also reach the agent directly. Its HTTP API, with browsable OpenAPI docs, lives at the agent service URL, and you can drive it interactively through the hoody CLI, the container’s web terminal, or SSH:

https://PROJECT_ID-AGENT_ID-agent-1.SERVER.containers.hoody.com

An agent in a Hoody container has access to everything a human developer would:

Terminal window
# Agent can execute any shell command.
# ephemeral=true gives an isolated one-shot PTY; without it, terminal_id is required.
curl -X POST "https://$PROJECT_ID-$AGENT_ID-terminal-1.$SERVER.containers.hoody.com/api/v1/terminal/execute?ephemeral=true" \
-H "Content-Type: application/json" \
-d '{"command": "git clone https://github.com/user/repo && cd repo && npm install && npm test"}'
Terminal window
# Agent can read and write any file. Upload is PUT /api/v1/files/{path} with the file content as the body
curl -X PUT "https://$PROJECT_ID-$AGENT_ID-files-1.$SERVER.containers.hoody.com/api/v1/files/app/config.json" \
-H "Content-Type: application/octet-stream" \
--data-binary '{"port": 3000, "env": "production"}'
Terminal window
# Agent can query and modify databases; the `db` query parameter is required
curl -X POST "https://$PROJECT_ID-$AGENT_ID-sqlite-1.$SERVER.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db&create_db_if_missing=true" \
-H "Content-Type: application/json" \
-d '{"transaction": [{"query": "CREATE TABLE metrics (id INTEGER PRIMARY KEY, name TEXT, value REAL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"}]}'
Terminal window
# Agent can navigate websites, take screenshots, interact with UI.
# /screenshot navigates to the URL and captures it in one call (PNG by default).
# browser_id (the 0-based instance index) is required.
curl "https://$PROJECT_ID-$AGENT_ID-browser-1.$SERVER.containers.hoody.com/screenshot?browser_id=0&url=http://localhost:3000&start=true" \
--output page.png

Because containers are peers connected by HTTP, agents can orchestrate other agents.

import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Agent A: The Orchestrator
// Creates a specialized container for Agent B
const workerContainer = await client.api.containers.create(PROJECT_ID, {
name: 'ai-worker-backend',
server_id: SERVER_ID,
container_image: 'debian/13',
hoody_kit: true,
});
// Agent A assigns a task to Agent B via the agent service URL.
// Prompts are session-scoped: open a session on Agent B, then dispatch a turn.
// prompt:sync waits for completion before returning.
const workerBase = `https://${PROJECT_ID}-${workerContainer.data.id}-agent-1.${SERVER}.containers.hoody.com`;
const workerSession = await fetch(`${workerBase}/api/v1/agent/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
}).then((r) => r.json());
const taskResponse = await fetch(
// ?policy=auto_approve adopts the headless posture so confirm gates auto-approve.
`${workerBase}/api/v1/agent/sessions/${workerSession.id}/prompt:sync?policy=auto_approve`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: 'Implement the payment processing module with Stripe integration. Write tests. Do not deploy until all tests pass.',
}),
}
);
const result = await taskResponse.json();
// result.status is already final ('done', 'error', 'canceled', or 'quit'), so no polling is needed
// Agent A inspects Agent B's work by reading its files directly via kit services
const containerClient = await client.withContainer({
id: workerContainer.data.id,
project_id: PROJECT_ID,
server: SERVER,
});
const code = await containerClient.files.get('/app/src/payments.ts');
// Agent A runs Agent B's tests from Agent B's terminal (ephemeral one-shot PTY).
// command is the request body; ephemeral is a query-string option (second arg).
const testResult = await containerClient.terminal.execution.execute(
{ command: 'cd /app && bun test payments' },
{ ephemeral: true },
);

All of this is ordinary HTTP between containers: Agent A controls Agent B’s terminal, reads its files, and queries its database. There is no message queue and no coordinator service, only URLs calling URLs.

┌─────────────────────┐
│ ORCHESTRATOR (A) │
│ Plans architecture │
│ Assigns tasks │
│ Reviews work │
└──────────┬──────────┘
│ HTTP
┌─────┴─────┐
v v
┌──────────┐ ┌──────────┐
│ WORKER B │ │ WORKER C │
│ Backend │ │ Frontend │
│ code │ │ code │
└──────────┘ └──────────┘
│ │
│ HTTP │ HTTP
v v
┌──────────┐ ┌──────────┐
│ WORKER D │ │ WORKER E │
│ Tests │ │ Design │
│ & QA │ │ review │
└──────────┘ └──────────┘

Each worker is an isolated container with its own agent, terminal, files, and database. The orchestrator coordinates via HTTP, and workers can spawn sub-workers of their own.


Hoody Agent includes an MCP (Model Context Protocol) client that connects to external MCP servers and discovers their tools at runtime. It is built on the official Go SDK and speaks every spec revision from 2024-11-05 through the current 2026-07-28, negotiating with each server automatically, over stdio, Streamable HTTP, or the legacy HTTP+SSE transport.

Servers are configured per session, and you manage them without touching a file: the Agents ▸ MCP servers mode in the TUI adds, imports, enables, disables, and reconnects them, and the same surface is scriptable under /api/v1/agent/mcp/…. Paste a config block from Claude, Cursor, or VS Code and it is understood as-is; imported servers land disabled so you review before anything runs. Changes reach sessions that are already open: removing or disabling a server cuts it off immediately, mid-turn.

Add a server over HTTP. Writes are two-step: mint a single-use nonce, then present it with expect_hash, the content hash you last read, so a concurrent edit is a conflict rather than a silent overwrite. Both fields are required on every write route. Writing into a settings file that does not exist yet is no exception: the read returns the empty-array hash for a missing file, and you pass that back.

Terminal window
AGENT=https://$PROJECT_ID-$AGENT_ID-agent-1.$SERVER.containers.hoody.com
# 1. Read the current config, the layer files behind it, and each server's live state.
# Credentials come back as key NAMES only, never values.
curl -s "$AGENT/api/v1/agent/mcp/servers?session_id=$SESSION_ID"
# 2. Mint the write nonce (op: upsert | delete | set_enabled | import).
curl -s -X POST "$AGENT/api/v1/agent/mcp/write-intents" \
-H "Content-Type: application/json" \
-d "{\"session_id\":\"$SESSION_ID\",\"op\":\"upsert\",\"scope\":\"user\"}"
# 3. Write. Fields you omit keep their stored value. A removed or re-pointed
# server is revoked in every live session before the response returns. The
# named session's reconnect is awaited but not guaranteed: a server that
# fails to start still returns 200, so read servers[].connected in the
# reply. Other live sessions reconnect in the background.
curl -s -X PUT "$AGENT/api/v1/agent/mcp/servers" \
-H "Content-Type: application/json" \
-d "{
\"session_id\": \"$SESSION_ID\",
\"nonce\": \"$NONCE\",
\"scope\": \"user\",
\"expect_hash\": \"$HASH\",
\"server\": {
\"name\": \"github\",
\"command\": \"docker\",
\"args\": [\"run\", \"-i\", \"--rm\", \"-e\", \"GITHUB_PERSONAL_ACCESS_TOKEN\", \"ghcr.io/github/github-mcp-server\"],
\"env\": { \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"\${GITHUB_TOKEN}\" }
}
}"

POST /mcp/parse previews what a pasted document would import without writing anything, and POST /mcp/reconnect re-reads the settings layers after a hand edit. POST /mcp/probe, which tries a candidate config and reports what it offers without saving anything, is human-only and returns 403 to a machine caller, because probing starts a process or makes an outbound request to a caller-chosen URL.

Once a session is live, list the mcp__* tools it exposes:

Terminal window
# MCP servers are configured in agent settings; list a live session's MCP tools.
hoody agent tools list-session-mcp --id $SESSION_ID --realm global -o json

With MCP servers configured, the agent discovers their tools and merges them with its built-in capabilities. Connect GitHub, Slack, Jira, custom APIs, or any MCP-compatible server, all orchestrated through the same HTTP interface.


Here is a real-world pattern: an AI agent that deploys your application end-to-end.

import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Step 1: Snapshot before deployment (safety net)
// Capture the snapshot's machine name (e.g. snap-20250115-103000) so the
// rollback can target the exact snapshot; the alias is only a friendly label.
const snapshot = await client.api.containers.createSnapshot(PRODUCTION_ID, {
alias: `pre-deploy-${Date.now()}`,
});
const snapshotName = snapshot.data.snapshot.name;
// Step 2: Agent pulls latest code and deploys via agent service URL.
// Prompts are session-scoped: open a session, then dispatch the turn.
// prompt:sync waits for the agent to finish before returning.
const prodBase = `https://${PROJECT_ID}-${PRODUCTION_ID}-agent-1.${SERVER}.containers.hoody.com`;
const deploySession = await fetch(`${prodBase}/api/v1/agent/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
}).then((r) => r.json());
const deployResponse = await fetch(
// ?policy=auto_approve adopts the headless posture so confirm gates auto-approve.
`${prodBase}/api/v1/agent/sessions/${deploySession.id}/prompt:sync?policy=auto_approve`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `
1. Pull latest code from main branch
2. Install dependencies
3. Run full test suite -- stop if any test fails
4. Build production assets
5. Restart the daemon process
6. Run smoke tests against the live URL
7. Report status via notification
`,
}),
}
);
const result = await deployResponse.json();
// prompt:sync is synchronous: result.status is already final.
// Roll back on anything other than a clean 'done' (covers 'error', 'canceled', 'quit').
if (result.status !== 'done') {
// Rollback: restore the snapshot created in Step 1 (by machine name, not alias)
await client.api.containers.restoreSnapshot(PRODUCTION_ID, snapshotName);
// Notify the team via kit notifications service
const containerClient = await client.withContainer({
id: PRODUCTION_ID,
project_id: PROJECT_ID,
server: SERVER,
});
await containerClient.notifications.notify.trigger({
summary: 'Deployment Failed',
body: `Rolled back to pre-deploy snapshot. Status: ${result.status ?? 'unknown'}${result.text ? `${result.text}` : ''}`,
display: '1',
});
}

The agent handles the entire deployment pipeline, and if anything goes wrong the snapshot restore puts the container back in seconds. There is no CI/CD platform and no YAML in the loop: an agent, HTTP calls, and a snapshot to fall back on.


AI agents are powerful but unpredictable, so snapshot before every run:

Terminal window
# Always snapshot before letting an agent run
hoody snapshots create -c $AGENT_ID \
--alias "before-agent-experiment"
# Let the agent work via direct HTTP to agent service (session-scoped)
SESSION_ID=$(curl -s -X POST "https://$PROJECT_ID-$AGENT_ID-agent-1.$SERVER.containers.hoody.com/api/v1/agent/sessions" \
-H "Content-Type: application/json" -d '{}' | jq -r '.id')
# ?policy=auto_approve adopts the headless posture so confirm gates auto-approve.
curl -X POST "https://$PROJECT_ID-$AGENT_ID-agent-1.$SERVER.containers.hoody.com/api/v1/agent/sessions/$SESSION_ID/prompt:sync?policy=auto_approve" \
-H "Content-Type: application/json" \
-d '{"text": "Refactor the entire codebase to use TypeScript 5 features"}'
# If the agent breaks something, restore by the snapshot's name: the ID from
# `list`, not the alias (`create` doesn't print it, so look it up first):
SNAP_NAME=$(hoody snapshots list -c $AGENT_ID -o json \
| jq -r '.snapshots[] | select(.alias=="before-agent-experiment") | .name' | head -n1)
hoody snapshots restore -c $AGENT_ID --name "$SNAP_NAME" -y

The danger with AI agent mistakes is not the mistake itself but how far it can propagate. Container isolation stops it at the container boundary:

  • Per-agent containers: one agent cannot access another’s filesystem, processes, or network unless explicitly connected via HTTP
  • Sandboxed experiments: an agent testing a destructive migration cannot touch your production data
  • Contained failures: if an agent installs malicious packages or runs a fork bomb, only its container is affected
  • Preserved evidence: snapshot the container after an agent run to audit exactly what it did

This is why bare metal plus containers is the architecture for AI: not because it is more convenient, but because the containment is physical, on hardware that is yours alone.


Any AI that can fetch a URL can read @hoody.com and receive a Skill: a machine-readable map of your infrastructure’s HTTP surface. That includes ChatGPT, Claude, Claude Code, Cline, Roo Code, and Codex, with no adapter, plugin, or custom integration for any of them. The agent fetches a URL and learns your API. From that point on it can spawn containers, execute code, read files, and query databases (everything covered in this guide) from any platform that can make an HTTP request.

You are also not locked into a single AI provider. Hoody supports 300+ models from 15+ gateway inference providers, among them Anthropic, OpenAI, Google, Mistral, Deepseek, and xAI, plus any OpenAI-compatible endpoint you point it at (Groq, local Ollama, Azure OpenAI, your own fine-tuned Llama). Swapping models is a config change, and A/B testing Claude against GPT-4o is two containers. The agents you build here work with all of them. See Hoody AI for the full provider list.