In short. A coding agent can write a site in minutes, but the code then has to be hosted somewhere. Hosted "sites" and "artifacts" features handle that on someone else's servers. Flats is an open-source alternative where the code, the data and the link all stay on a machine you own.
It's one Go binary. Agents connect over MCP and save work as a Private Draft. Publishing, deploying, rolling back, deleting and changing visibility all wait for the operator to approve them in a web console. There is no hosted Flats control plane.
Four kinds of flat
Each thing Flats hosts is a flat. A flat keeps the type of its first save, and the type decides how its changes go live.
| Type | Made for | How changes go live |
|---|---|---|
| Website | Static sites and sandboxed JavaScript or WASI server apps | Draft, then an approved version, then an approved deploy |
| Page | One HTML document with bundled assets: reports, charts, explainers like this one | Same as websites, with no server code or data |
| Document | Collaborative Markdown that people and agents edit together | Immediately, with every save kept as a revision |
| Design | A canvas of HTML artboards: screens, posters, click-through prototypes | Immediately, with every change kept as a revision you can pin or roll back |
Websites and pages use the version model. Documents and designs use the revision model. All four start out Private, and changing visibility in either direction needs approval for every type.
One binary on your machine
Flats is a single Go binary with no CGO, for macOS and Linux on amd64 and arm64. One long-running process, flats serve, provides a web console, a CLI, an HTTP API and a Streamable HTTP MCP endpoint, all backed by the same core service. The installer runs it as a launchd agent on macOS or a systemd user service on Linux, without sudo. A container image is published too.
flats worker child processes, which re-execute the same binary.Everything is stored in one data directory (~/Library/Application Support/Flats on macOS): config.json, the SQLite metadata database, immutable version files, each flat's live data, and the secret.key that encrypts flat secrets. The host locks that directory when it starts, so two hosts can never use it at the same time.
Websites and pages: the version model
The core rule is that saving, creating a version and deploying are three separate steps. Agents can do the first. The other two each need their own approval from the operator.
Draft and preview
An agent uploads the complete build (inline files over MCP, or a directory with the CLI). Flats validates it and stores an immutable Private Draft revision. That never changes what visitors see. expected_revision catches two writers overwriting each other's Drafts. A Draft can be opened as a preview on its own private origin. For server apps, the preview runs on an isolated copy of the live data.
Version
create_version freezes one specific Draft revision and content hash, then waits for approval. If the agent saves again in the meantime, the later save isn't what gets approved. After approval the files become an immutable numbered version (v1, v2, …). Version numbers are never reused, and versions aren't deleted automatically. A per-flat quota (20 by default) limits how many there can be.
Deploy and rollback
Deploying a version is a second approval. Flats health-checks the candidate against a copy of the data (GET the manifest's health path, 15-second timeout, any 2xx or 3xx passes). It then snapshots the data, starts the new version, and moves the live pointer in a single SQLite compare-and-swap. The previous handler gets five seconds to finish in-flight requests. If the check fails, the old version keeps serving, and the error includes the status code and the start of the response body.
Rollback is an approved deploy of an earlier version. Data belongs to the flat, not to a version, so rollback restores code only unless you also ask to restore the data snapshot taken before the current deploy. That is a separate, explicit choice.
Documents and designs: the revision model
Documents and designs work differently. They have no Draft, versions, previews or publish step. A save takes effect immediately, Private, with no approval, and every change is recorded as a whole-flat revision that can be listed, pinned or rolled back.
- Documents open in a built-in collaborative Markdown editor. Private links can edit; Public links are read-only. Mermaid code fences render as diagrams. Agents use
update_documentfor small guarded edits to the live text, so they don't overwrite what people just typed. - Designs lay out HTML artboards side by side. People can move, retype and tweak elements in place, and play click-through prototypes.
Because changes take effect immediately, any save to a document or design that is already Public is visible to everyone right away. Making it Public in the first place still needs approval, as with every other type.
Server apps
A website with "kind": "server" in its flats.json runs a handler in a style similar to Cloudflare Workers:
export default {
async fetch(request, env) {
env.DB.exec("CREATE TABLE IF NOT EXISTS hits (n INTEGER)");
env.DB.exec("INSERT INTO hits VALUES (1)");
const [{ n }] = env.DB.query("SELECT count(*) AS n FROM hits");
return Response.json({ hits: n });
}
};
JavaScript runs in QuickJS compiled to WebAssembly, inside a separate worker process. Each request VM is limited to 64 MiB of memory and a 10-second wall-clock deadline, and a VM that hits a limit is replaced. A .wasm entry runs as a WASI preview1 command, with a fresh instance per request that reads request JSON on stdin and writes response JSON on stdout.
| Capability | JavaScript | WASI .wasm |
|---|---|---|
| SQLite | env.DB, per flat | Not available |
| Persistent files | env.FILES, per flat, local disk | Not available |
| WebSocket | open / message / close callbacks | Not available |
| Outbound HTTP | fetch to exact origins the operator grants (up to 32) | Not available |
| Settings | Environment variables and encrypted secrets as env.NAME | Process environment |
| Node.js APIs | None; bundle dependencies | None |
New flats can't make any outbound requests. Only the operator, in the console or with the CLI on the host, can grant outbound origins. Agents can read the grant list but can't change it. Live data survives deploys, ordinary rollbacks and host restarts.
Who can reach it
Flats keeps four things separate: whether a version is live, whether the flat is Private or Public, which network providers it may use, and whether a route is ready right now. Configuring a network doesn't publish anything, and a URL doesn't tell you a flat's visibility.
| Access | Providers | Who can open it |
|---|---|---|
| Private | Local (always on), Tailscale, NetBird | This machine, or devices your tailnet ACLs or NetBird policies allow |
| Public | Portal, Tailscale Funnel, zrok, Cloudflare quick tunnel, NetBird Expose | Anyone on the internet who has the link |
A provider has to be enabled for the host in Settings, and then allowed for each flat that should use it. Allowing Tailscale doesn't allow Funnel. Going from Private to Public, or back, is an approved request. Websites and pages also need a live version before they can become Public.
The trust model
Flats has no user accounts. Operator authority depends on where a request comes from and which headers it carries, and the project documents exactly what that protects.
What holds
- MCP, the CLI and the HTTP API have no approve, reject or settings operations. Publish, deploy, visibility and delete calls only create approval requests.
- Management requests must name the server in
Host, and anyOriginmust match. This blocks DNS rebinding and cross-site requests. - Console decisions require the console header and a same-origin browser request, which a third-party web page can't send.
- Secrets are encrypted with AES-256-GCM. APIs and MCP only ever return secret names, never values.
What doesn't
These checks are CSRF protection, not authentication. A process with a shell on the host can imitate the console and approve its own request, and anything that can read the data directory can recover the secrets. Approvals stop agents that only use MCP or the API, and they stop web pages. Run only trusted agents on the host, and use tailnet ACLs to limit which devices can reach the console.
Working with agents
Flats provides its own instructions to agents, so a client needs no copy of the docs:
- Claude Code, Codex and Cursor install the Flats plugin, which runs
flats mcpto relay to the selected host. Any other MCP client registers the endpoint directly. For a remote host, that is the console's tailnet URL plus/mcp. - An agent first calls read-only
check_clientto compare its skill and CLI against the server. It then readsguide, which sends it to short topics for each task: static sites, server apps, pages, documents, designs and approvals. - The host also serves
/llms.txt, a full agent guide, and the runtime API v1 reference, which is also available as MCP resourceflats://docs/runtime-api/v1.
The guide sets one rule above the rest: a site isn't live because a save succeeded or a request was made. It's live when get_flat shows the version as current with a ready endpoint, and the agent has fetched the page itself.
Try it
# install the latest release as a background service
curl -fsSL https://raw.githubusercontent.com/gosuda/flats/main/install.sh | sh
# save a Draft
mkdir -p hello && printf '<h1>Hello from Flats</h1>\n' > hello/index.html
flats save-draft hello ./hello
# run the create-version command it prints, approve v1 in the console,
# then request the deploy and approve that too
flats deploy --flat hello --version 1 # exit code 3 = waiting for approval
open http://hello.localhost:7879
The console is at http://127.0.0.1:7878. Never expose the console port to your network: anything that can reach it can approve deploys.
How this page got here
This article is itself a flat of type page. An agent running in Hermes connected to a Flats host over the host's tailnet MCP endpoint. It read the page and design guides, saved this HTML with save_page, checked the Draft preview, and then requested a version and a deploy. The operator approved each request in the console. The agent wrote the page, a person approved it, and the files are served from that person's machine.