Sénia
An agent harness with a swappable model backend and a custom MCP server over SQLite. It runs in a container with read-only mounts and a non-root user, so the sandbox holds even against root inside the container.
Why build one
Agents are the part of this field I understood least by reading about it. The descriptions are all at the level of “the model decides which tool to call”, which tells you nothing about the parts that actually turn out to be hard: what a tool is allowed to do, what happens when the model asks for something ruinous, and where the boundary sits between the thing generating text and the machine it is generating text about.
So I built a harness. Sénia is a loop that talks to a model, exposes a set of tools to it, and runs on my own infrastructure. It is a work in progress, and it exists mostly so that I have to answer the awkward questions myself rather than take somebody’s word for the answers.
The container is the boundary
The important design decision is that the sandbox is not a convenience, it is the security model.
The agent runs inside a container as a non-root user. Everything it should not be able to modify is mounted read-only, which holds even if something inside the container manages to become root, because the restriction is enforced by the kernel outside the container rather than by permissions inside it. Elevated commands are limited to a specific allowlist rather than granted wholesale.
The reasoning is simple enough to state in one line: the model’s output is untrusted input. Not because the model is malicious, but because anything that reads text from the internet and then acts on it can be talked into acting badly, and the correct place to handle that is a boundary that does not depend on the model behaving.
Swappable models
The model sits behind an interface, so the harness does not care whether it is talking to a hosted API or something running locally on my own hardware. Swapping one for another is a configuration change, not a rewrite.
That was partly a portability decision and partly a cost one: iterating against a local model is free, and the parts of a harness that are genuinely difficult (tool dispatch, error recovery, keeping context coherent across a long session) do not need a frontier model to develop against.
The MCP server
Tools are exposed over the Model Context Protocol, and I wrote a server for the ones I wanted that did not exist. It keeps state in SQLite, with schema migrations tracked in the repository rather than applied by hand, because a database whose shape is only recorded in somebody’s memory stops being restorable very quickly.
Writing a server rather than only consuming other people’s is the part that taught me the most. Consuming a protocol lets you stay vague about it; implementing one does not.
Running it in the open
The harness is on my own network and I reach it from my phone, through a chat interface and through a private messaging server. That means it is a thing I actually use rather than a demo I run when someone is watching, and being a real user of it surfaces problems that a test suite does not.
The repository is public, which raises the obvious problem: a personal agent’s configuration is personal. Rather than maintaining a sanitised copy by hand, the private parts are a separate module and a setup script generates working placeholders in their absence. Anyone can clone it and get a running system with none of my data in it.
Known gaps
It is a work in progress and the loop is the least finished part of it. Recovering gracefully when a tool call fails partway, and keeping a long session coherent without simply accumulating context until it stops fitting, are both unsolved here rather than solved badly.
I would also not currently describe the sandbox as sufficient for running fully unattended against anything I could not afford to lose. It is a real boundary, not a complete one, and the difference matters.
- Language
- GoPythonSQL
- Agents
- MCPtool callingswappable model backends
- Sandbox
- Dockerread-only mountsnon-root executionscoped privileges
- Storage
- SQLitetracked migrations