xbin: a workspace you own, and rewrite from inside it

xbin

9 min read Original article ↗

self-hosted workspace runtime

Code and state, in separate sandboxes.

A Harvard architecture for self-hosted software. Agents and terminals rewrite a tile's code from inside the workspace; its live state answers only to the tile's own API.

$ curl -fsSL https://xbin.dev/install.sh | sh

prints the plan and asks before changing anything · Linux & macOS (Lima VM) · MIT / Apache-2.0 · no SaaS or telemetry

Free for an hour, no signup: ssh xbin@vcpu.sh

the xbin shell: a canvas of floating tiles — a paste bin, the Traefik ingress terminator, the manager's updates pane, and the welcome tile's notes — beside a sidebar of orgs and apps

01the model

Everything is a directory.

No registry, no build step: a component is a directory and its path is its identity. Open a terminal in it and grant it what it needs; everything stays inside the workspace by default.

1

A tile is a directory

An index.html and/or a backend (Go, Node, Python, or none). mv renames it, cp -r forks it, rm -r deletes it.

2

Terminals in the browser

Attach a sandboxed shell to any tile to edit, build, and run agents. No SSH, no local checkout.

3

Grants are default-deny

A tile cannot reach the network or another tile until you approve a grant. Secrets come from its own vault.

4

Internal by default

Tiles serve their users inside the workspace. Publishing one to the internet is a separate opt-in (bx expose, behind your TLS) that most tiles never need.

apps/dashboard — terminal

# scaffold a tile, edit it live, ship it
$ bx new apps/dashboard --runtime go
$ bx term apps/dashboard        # a shell, in the browser
  ~/apps/dashboard $ vim backend/main.go
  ~/apps/dashboard $ git commit -am ship

$ bx grant apps/dashboard res:apps/db reader
✓ live in the workspace · sandboxed · versioned · backed up

Yours

One unprivileged daemon on a box you control: your files and your git history on your disk. No accounts, no SaaS; nothing phones home. Dual MIT / Apache-2.0.

Sandboxed

Every tile runs rootless: user, mount, pid and net namespaces, seccomp and landlock. Grants are default-deny; secrets live encrypted in a vault.

Self-modifying

Open a browser terminal in any tile: edit and rebuild it live, or point an agent at it. Each tile is its own git repo, versioned and backed up.

02composition

Small tiles with typed wires.

Tiles don't hardcode each other. A tile declares typed interface slots: "I need an LLM", "I need MCP servers", "I need somewhere to archive". The owner binds each slot to any tile that speaks that contract. Same mechanism for the network, in both directions.

Call another tile's API

Declare uses, get a role-gated grant. The callee sees your verified identity instead of a shared secret, and holds no ambient authority.

Typed slots with swappable providers

An agent's llm slot binds to a gateway tile; its mcp slot (multi) binds to several MCP servers at once. Rebind later. The code never changes.

Egress is composition too

A tile has zero network until its net slot is bound to the internet, a LAN, or through another tile: a WireGuard/firewall tile that provides net. Chains compose.

agent · llmhttp:llmtiles/llm-gw

one key, every model; swap the gateway and the agent code stays put

agent · mcp ×2http:mcpgithub-mcp + search-mcp

agent · archivehttp:archivetiles/s3-archivers3://backups

agent · netnettiles/wg-vpninternet

all egress rides the VPN tile—the agent can't bypass it

interfaces to bind

apps/agentmcphttp:mcp github-mcpsearch-mcp bind

apps/agentarchivehttp:archive tiles/s3-archiver bind

apps/agentnetnet tiles/wg-vpn (net provider) bind

Binding is the owner's click: a tile — or an agent inside one — can request a capability, but it can't wire itself to anything. Until you bind, it has nothing. The same mechanism gates the outside world: no egress without a net binding, and no public endpoint until an exposes endpoint is bound to an ingress terminator — an opt-in most tiles never take.

03terminals

Terminals in the browser.

Every tile opens a real shell in the browser. xbin has zero opinion about your harness: run whatever you already use. Every workspace ships an AGENTS.md / CLAUDE.md contract, so any agent lands with the full mental model.

Claude CodeOpenCodeCodexHermesaider…or just vim

The terminal is where tiles get built, and it is a sandbox with a job to do. It can ship this tile; it can't wander the box. A rogue agent's blast radius is one tile's directory, and its apt installs land in a throwaway overlay, never in the base image.

Your $HOME follows you: one per-user homedir across all your terminals. Dotfiles, harness config, and keys are set up once.

apps/dashboard agent+ >_{ } net: internet api

# any harness—the workspace doesn't care ~/apps/dashboard $ head -1 $XBIN_WORKSPACE/AGENTS.md # AGENTS.md—building in this xbin workspace ~/apps/dashboard $ claude "wire the archive slot to S3" ● read AGENTS.md · edit backend/main.go +24 −3 · build ~/apps/dashboard $ sudo apt install ripgrep → lands in this terminal's overlay—the base image stays read-only ~/apps/dashboard $ cat ../billing/backend/main.go → read-only (and only because you can read that tile) ~/apps/dashboard $

Read-only base imageEach terminal stacks a throwaway overlay on the shared base. apt install anything; the image never changes.

One home, all your terminals$HOME is your per-user directory: persistent, shared across your sessions, and separate from the host's home.

This tile read-write, the rest read-onlyOnly the tile the terminal was opened on is writable. Sibling tiles are readable for patterns and APIs, and only the ones your account can read.

Network is a switchPer-terminal scope: none, internet, or host. Flip it in the titlebar; offline agents are a first-class mode.

Tile API access is a toggleThe terminal's token acts as this tile and never as the owner. You can switch API access off entirely, so an agent can edit code without reaching the live app.

Runtime state stays with the backendThe tile's live resources (its database, its resource filesystem) are not mounted in the terminal. State is reached through the tile's API, on its terms.

04workflows

See real workflows.

Change a tile

an in-memory paste bin gains file uploads

Create a tile

a home calendar from empty directory to shipped in one prompt

05who it's for

From solo homelab to small team.

Homelab & self-hosters

Everything in one place on your box

Dashboards, notes, a calendar, a media tool, an agent: one binary with backups built in, reachable over Tailscale. No docker-compose sprawl.

Linux enthusiasts

Real isolation you can inspect

Rootless user namespaces, cgroups v2, seccomp, and landlock with no container wrapper in between. Buildless frontend (no npm); the shell itself is a tile you can hack.

IT admins

Multi-user with real RBAC

Users, orgs, per-tile ownership and access levels, approvals delegated to org admins. An audit log records who approved what. Least privilege by default.

Small teams

Own your internal tools

No per-seat SaaS, no vendor lock-in. Agents build the dashboards and integrations; they run on your infrastructure, versioned and auditable.

06users & orgs

Users and orgs.

Every tile has an owner (you or an org). Admins set policy once; org admins run their own day-to-day, down to the 2am approval.

Accounts

Accounts by invitation only

Admins hand out single-use invite links; the invitee sets their own password. Pause an account without losing a grant. Hit a tile you can't read and the page itself offers request access; the owner approves at a level and that's it.

Orgs

Roles that mean what they say

Flat orgs with per-member viewer / developer / admin presets. Org admins manage members, sharing, lifecycle, and wiring, with real authority inside the org and none outside it.

Ownership

Tiles move like property

Transfer a tile to an org and its governance moves with it. Every transfer shows a preview first: your remaining access, which bindings die under the new owner's policy, who gains control. No silent surprises.

Delegation

The 2am loop without the admin

Workspace admins grant orgs allowances as precise as "internet, but only *.stripe.com". Org admins approve inside those bounds; requests outside them wait, visibly, for you.

07security

Isolation is the default.

xbin is a rootless sandbox runtime first and a UI second. The boundary is the kernel, and it's on for every tile from the start.

Per-tile rootless sandboxesUser, mount, pid, IPC, UTS and net namespaces per backend, mapped to a delegated sub-uid range with no host root.

seccomp + landlockA syscall block-list and a filesystem allow-list on every tile; the mount family and friends are denied unless a tile is explicitly a container host.

Default-deny grantsAn element principal reaches no other tile, resource, or network until the owner approves an explicit grant. Inbound identity headers are stripped.

Encrypted vaultPer-component secrets and stateful resources are encrypted at rest; the workspace can start sealed and be unsealed after login. A tile's secrets are read only by its own running backend.

Git per component + backupsEvery tile is its own repo; scheduled backups and one-click restore are built in.

Bound network egressTiles have zero IP egress until you bind a net interface: internet, host, a LAN CIDR, or through another tile (a firewall/VPN).

08in the box

All of it ships in one binary.

Tiles & screensA snappable grid of apps; drag, pin, float, or full-page any of them.

Browser terminalsA real sandboxed shell on any tile, with a code + diff review pane.

Git per componentVersion, diff, and restore every tile independently.

Optional ingressPublish HTTP or raw TCP/UDP behind your TLS with the Traefik tile; off unless you bind it.

ContainersAn opt-in cap:containers tile runs rootless podman for dev boxes.

Backups & restoreScheduled, encrypted, one-click restore of any tile or the whole workspace.

bx CLI + Go SDKZero-dependency SDK; a bx CLI for everything the UI does.

Agent-readyAn AGENTS.md contract, per-tile terminals, and a status channel agents drive.

09install

Self-host it in one line.

On any Linux box with unprivileged user namespaces: the installer preflights the kernel, builds from source, and starts the daemon on loopback.

$ curl -fsSL https://xbin.dev/install.sh | sh

The installer explains both modes (a dedicated xbin system user, or everything under your account), prints the numbered plan for each, and asks; it escalates via sudo only if you pick system-wide. On macOS the same command sets up a sized Lima VM and installs inside it. Already decided? … | sudo bash or … | bash -s -- --user skip the chooser; both still print the plan first.

tailscale serve 127.0.0.1:8642 or point Caddy / Traefik at it or build from source: make