Skip to content

Self-hosted personal cloud

devplane

One signed install turns a fresh Ubuntu VM into a personal cloud computer whose agent asks before it touches the outside world.

DevPlane is a self-hosted personal cloud computer. One signed install gives a fresh Ubuntu 24.04 VM a single owner, a chat agent, files, services and public hosting, and nothing with outside consequences runs until that owner approves it.

Jul to Oct 2026Running, hardening for v1
devplane.local/chat

personal cloud / local instance

DevPlane

Chat

loading

Chat state

Ready

The selected instance is connected. Chat composes through its action registry.

Ask for work, or type @ to attach a tool.

Public exposure

  1. requestedagent asked in chat
  2. approvedowner decided
  3. activepublic over HTTPS
  4. revokedowner pulls the URL

Chat uses the selected instance action registry. No fallback provider is assumed.

Ask your instance to begin. Use @ to attach a tool.

Ask your instanceEnter to send · @ to attach a tool
Describe the work to do

01 / 05 · Ready

Motivation

I wanted my own version of Zo Computer, a personal machine in the cloud with an agent that can do real work on it. The baseline spec says it plainly. DevPlane's public feature set has to stay a superset of Zo's.

I also wanted a stricter default. The owner gets separate admin auth, the agent runs unprivileged, secrets list as metadata only, and public exposure moves through requested, approved, active and revoked. Zo's looser behaviors survive only in an isolated compatibility profile.

It has to stand alone. Files, terminal, services and the agent keep working if the optional fleet control plane goes away.

Capabilities

  • Owner bootstrap from a one-time setup link the installer prints, then a normal sign-in
  • Chat with an agent on the node over SSE, with tool-call cards, stop, resume and @capability mentions from a typed registry
  • Approval-gated effects that the owner decides in the chat card or in one shared inbox
  • Service hosting path: register, pass an exact /health gate, private preview, owner approval, publish over HTTPS, revoke
  • Model provider settings with write-only keys, model discovery and per-purpose routing that shows which config layer won
  • Signed releases checked with Ed25519 signatures and SHA-256 digests before anything is trusted
  • One W3C trace from the browser's action span through the server to the node

Key decisions

Rust owns the computer, TypeScript owns the web API

Why

The Rust node is the durable owner of identity, capability grants, secret leases, audit, ingress and lifecycle. The Fastify and tRPC server and the React apps sit above it.

What it cost

Two languages, so every shared contract goes through codegen, and a check diffs the generated Rust and TypeScript twins.

The agent is not trusted

Why

OMP owns the agent loop, models and sessions. The node runs a pinned OMP child over JSONL inside a bubblewrap sandbox, and the node's model proxy socket is its only way out to a model.

What it cost

A hard dependency on one upstream agent, and CI has to fetch the pinned build and grant user namespaces for bubblewrap.

One conductor executes every action

Why

Two approval paths disagreed, and the old one could run a single approval twice under different caller keys. I made the Effect-based conductor canonical. Approvals bind actor, caller key, intent fingerprint and a digest of the resolved targets, and are consumed once.

What it cost

A migration in eleven slices across server, node and wire contracts, plus new OpenTelemetry dependencies.

Fail closed when the UI can't prove something

Why

The client decodes every server answer with Effect Schema and treats anything unknown as a failure. Richer states render only when a producer sends typed evidence, and ingress defaults to an unavailable provider.

What it cost

A lot of defensive normalization code. The hosting screen alone is 1,649 lines.

Ingress behind a port, Tailscale Funnel first

Why

Publishing had to work now and be swappable later by configuration, for a real domain or a managed VM. One shared contract suite runs against every adapter.

What it cost

Public URLs live on the tailnet's ts.net domain, and someone has to enable Funnel in the tailnet ACL by hand.

Architecture

The browser only talks to the TypeScript server. The Rust node owns the machine, and the sandboxed agent reaches models only through the node's proxy socket.

Limits and what's next

  • The v1 milestone is still open. It needs three clean passes on real VMs from one build, plus an install-lifecycle pass.
  • Files, Terminal, Automations and Integrations are on the roadmap, and the shell already reserves their places.
  • The next UI pass gives the real controls the full content area instead of sharing it with a status panel.
  • ADR-002 picked DBOS and Postgres for durable workflows. Today the conductor persists to files with locks, and the DBOS move is planned.
  • Single owner, single computer by design. Multi-user is out of scope for v1.

Notable

An approval binds exactly what will happen

When the agent wants to publish, the stream's approval event carries the targets the server already pinned, and the chat card lists them under "Approving binds exactly these targets". The conductor binds the approval to actor, caller key, intent fingerprint and a digest of those targets, then consumes it once. That closed a real bug where one approval ran twice under two caller keys. The node also journals each decision before dispatch, so an effect runs at most once per idempotency key, even across restarts.

Stack and numbers

React 19 + ViteFastify + tRPCEffectRustOMP (Oh My Pi)bubblewrapTailscale FunnelPlaywrightOpenSpec
Commits in three months
1,337
Rust lines
168
k
TypeScript lines
126
k
Rust test functions
1,699