Guide
Self-hosted AI agent: what you take on
Self-hosting an AI agent is a constraint decision, not a preference. You move the runtime inside your own boundary and, in the same move, take over the work a vendor was doing: process supervision, backups, upgrades, and the security boundary itself. If none of those constraints binds, hosted is the cheaper buy.
Every claim below carries a file and line from our own implementation, so you can check it rather than take it.
Start with the constraint, not the deployment model
Three constraints can force self-hosting: the data path may not leave your network, the agent has to operate systems a vendor cannot reach, or the invoice grows with every user you add. Each one is checkable before you install anything.
One thing self-hosting the runtime does not buy you: local inference. The agent process runs on your machine, but the model and embedding calls still go to an endpoint you configure. In this codebase that endpoint is environment configuration, not code: the chat model reads DEEPSEEK_BASE_URL and the embedding adapter reads OPENAI_BASE_URL (README.md:112-124, backend/graph/embedding.py:22). If your constraint is that no data leaves at all, you also need a local model server, and that is a second decision with its own hardware budget.
Decision rule: if the data may leave and the agent only needs public APIs, self-hosting buys control of the code, not confidentiality of the data. Price it that way.
Three decision points
1. Data boundary: what stays on disk
Under self-hosting, the state that matters is a directory, not a service. Conversations are JSON files written under backend/sessions/ (backend/graph/session_manager.py:27-33, :55-62), the memory index is rebuilt into backend/storage/memory_index (backend/graph/memory_indexer.py:21-22), and API keys are written to backend/.env (backend/api/config_api.py:33-34).
That is the whole data story: copy those paths, stop the process, and you have a backup. The flip side is that no vendor is checking whether your copy succeeded.
2. Integration depth: what the agent has to touch
An agent that only answers questions can live in someone else's cloud. An agent that has to read your files, run your scripts or query an internal system cannot. This workbench ships tools that execute a shell, run Python, fetch URLs and read files (backend/tools/terminal_tool.py:12-24, backend/tools/read_file_tool.py), which is the class of capability a hosted product will not grant you.
The same argument applies to skills: they are Markdown files you edit in place, and a scanner compiles them into the system prompt at startup (backend/tools/skills_scanner.py:10-16, backend/app.py:23). Your runbooks stay in your repository.
3. Operations: what you now own
Self-hosting moves four jobs to your team: run the processes, keep the keys, rebuild the index, and take backups. Here the process count is two, a FastAPI backend and a Next.js front end (README.md:89-99), the index is rebuilt in-process at startup (backend/app.py:27-28), and configuration is a JSON file shipped next to the code (backend/config.py:7).
There is no database server, no migration step and no container image in the repository (README.md:157). That lowers the operational floor; it does not remove it. Someone still has to notice when the process is down.
What that looks like in the code
Six excerpts, chosen because each one is a decision you inherit the moment you run it.
backend/app.py36-42app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)The backend trusts its network. CORS is open and no authentication middleware is registered anywhere under backend/api/, so the deployment assumes loopback or a trusted LAN. Bind it to 127.0.0.1, or put your own auth in front, before it sees a public address. The repository says the same in its own limitations list (README.md:144-148).
backend/graph/session_manager.py27-28 self._sessions_dir = base_dir / "sessions"
self._sessions_dir.mkdir(exist_ok=True)State is a directory next to the code. Backup and restore are file operations, and the per-session JSON files are named from sanitized ids at :33. There is no dump, no schema and no migration.
backend/graph/memory_indexer.py45-49 current_hash = self._get_file_hash()
stored_hash = self._get_stored_hash()
if current_hash and current_hash != stored_hash:
self.rebuild_index()The index is derived state keyed on the MD5 of MEMORY.md, rebuilt only when that file changes (and once at startup, backend/app.py:27-28). Embedding calls cost money on every rebuild (README.md:154), so a restart after editing memory is not free.
backend/api/config_api.py33-34MANAGED_KEYS = ["DEEPSEEK_API_KEY", "OPENAI_API_KEY", "OPENAI_BASE_URL", "TAVILY_API_KEY"]
ENV_PATH = Path(__file__).resolve().parent.parent / ".env"Keys are plain environment variables in backend/.env, editable from the UI and masked on read. They are not encrypted at rest; file permissions are the control.
backend/graph/embedding.py17-23 def _client(self):
from openai import OpenAI
return OpenAI(
api_key=os.getenv("OPENAI_API_KEY") or "not-set",
base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)Any OpenAI-compatible endpoint works for embeddings, including vLLM or Ollama you host yourself. This is the seam where a fully local deployment starts, and the file header says why the adapter exists at all (backend/graph/embedding.py:1-4).
backend/tools/terminal_tool.py12-24BLACKLISTED_COMMANDS = [
"rm -rf /",
"rm -rf /*",
"mkfs",
"dd if=",
":(){:|:&};:",
"chmod -R 777 /",
"shutdown",
"reboot",
"halt",
"poweroff",
"format c:",
"del /f /s /q c:",
]This is a command blacklist, checked at :45, not a sandbox. It stops the classic destructive commands and nothing else: the subprocess still runs with your user's permissions. Handing it a shell is a real grant of authority.
When you should not self-host
- You need multi-user access or a public URL. There is no auth in this codebase (backend/app.py:36-42, README.md:144-148). Multi-tenancy would be a feature you build, not a setting you flip.
- You need isolation between a prompt-injected tool call and the host. The terminal tool blocks a list of destructive commands (backend/tools/terminal_tool.py:12-24); the Python REPL and the file reader inherit whatever the process can do.
- You cannot staff the pager. Self-hosting means the 02:00 alert is yours. There is no vendor on the other end, and the repository ships no container image, CI or migrations to lean on (README.md:157).
- Your data may leave anyway. If model and embedding calls still go to a hosted provider (backend/graph/embedding.py:22), self-hosting gives you control of the runtime, not confidentiality of the content.
- You want a managed SLA or someone else's upgrade path. A hosted agent is cheaper than the engineer-hours you will spend owning a directory of JSON files.
None of that makes self-hosting wrong. It makes it a trade with a price, and the price is operations.
See the whole implementation
OpenClaw Replicate is the workbench these excerpts come from: a full source implementation you can read end to end before you decide anything.