Explainer · Flats v0.11.1

Flats, explained

Flats turns your own computer into a host for what you build with AI agents: websites, small server apps, HTML pages, Markdown documents and design canvases. Agents can save their work there. You decide what goes live and who can see it.

About 10 minutes to read · Published on a Flats host

The Flats caretaker gopher

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.

TypeMade forHow changes go live
WebsiteStatic sites and sandboxed JavaScript or WASI server appsDraft, then an approved version, then an approved deploy
PageOne HTML document with bundled assets: reports, charts, explainers like this oneSame as websites, with no server code or data
DocumentCollaborative Markdown that people and agents edit togetherImmediately, with every save kept as a revision
DesignA canvas of HTML artboards: screens, posters, click-through prototypesImmediately, 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.

Inside a Flats host Agents use MCP, scripts use the CLI or HTTP API, and the operator uses the console. All of them reach one flats serve process, which stores metadata in SQLite, runs server code in sandboxed worker processes, and serves flats through network providers. Agent MCP /mcp CLI · HTTP API flats … /api Operator console only place to approve flats serve management on 127.0.0.1:7878 core: versions, approvals SQLite metadata · flats.db data dir · versions · secrets one exclusive lock per data directory starts serves flats worker sandboxed server code Network providers Local · Tailscale · Portal … Every flat and every preview gets its own origin, so cookies and storage never mix.
One process provides four interfaces. Server code runs in separate 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.

The version model An agent saves a Private Draft, which can be previewed. Creating a version freezes the Draft as vN after operator approval. Deploying vN, also after approval, health-checks it on a copy of the data and only then moves the live pointer. If the health check fails, the old version stays live. Save Draft agent · Private Create version approval → vN Deploy vN approval Health check on a data copy Live vN serves Draft preview, own origin fails Old version stays live Gold steps can only be completed by the operator in the console.
A version can exist without ever going live. Rollback is an approved deploy of an earlier version.

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.

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.

CapabilityJavaScriptWASI .wasm
SQLiteenv.DB, per flatNot available
Persistent filesenv.FILES, per flat, local diskNot available
WebSocketopen / message / close callbacksNot available
Outbound HTTPfetch to exact origins the operator grants (up to 32)Not available
SettingsEnvironment variables and encrypted secrets as env.NAMEProcess environment
Node.js APIsNone; bundle dependenciesNone

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.

AccessProvidersWho can open it
PrivateLocal (always on), Tailscale, NetBirdThis machine, or devices your tailnet ACLs or NetBird policies allow
PublicPortal, Tailscale Funnel, zrok, Cloudflare quick tunnel, NetBird ExposeAnyone 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

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:

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.