WMOS

Overview

How the pieces fit

A cell is the unit of isolation. Inside it a router stores every job before it moves, a directory issues and revokes seat tokens, and a database belongs to that cell alone. Seats sit outside the cell and connect in.

A tenant is a cell

Nothing is shared between tenants except the host. A seat is a process on some machine with a token; the router verifies the token, stores the job, and pushes it over a stream the seat holds open.

                ┌──────────────── tenant cell ────────────────┐
  agent seat ──▶│                                              │◀── browser seat (PC)
  (any host)    │   router ── stream ──▶ seat                   │
                │      │                                        │◀── executor seat (host)
  mail seat ───▶│      │ durable queue (SQLite, cell-owned)     │
  (adapter)     │      ▼                                        │◀── test seat (CI)
                │   directory (tokens, revocation snapshot)     │
                └───────────────────────────────────────────────┘

The life of a job

A job is stored before it is delivered, delivered once per acknowledgement, and its result returns to the seat that asked — not to whoever happens to be listening.

caller ──① POST /agents/caller/a2a──▶ router ──② store──▶ SQLite
                                        │
                                        ├──③ SSE job──────▶ seat (adapter)
                                        │                     │ execute
                                        │◀──④ POST result─────┘
                                        └──⑤ SSE egress───▶ caller
                                                              │
                                                    ⑥ ack ────┘
                                       (no ack → retry until TTL)

Four kinds of seat

The router does not know what is behind a seat. What differs is where the seat runs, what identity it holds, and what it is allowed to return.

person ─┬─ agent seats     anywhere        model-driven work
        ├─ mail seat       adapter host    reads mail, returns drafts only
        ├─ browser seat    owner's PC      real session; personal = metadata only
        └─ executor seat   owner's host    approved commands, bound to the task
                                            test seat: CI, deterministic suite

Where it runs

The same cell shape covers a hosted tenant and a cell on a customer's own host. A PC seat needs no inbound port: it connects out, so a laptop behind NAT is a normal seat.

(a) hosted tenant      VPS ─ cell(router, directory, db) ─ web console
(b) customer host     their server ─ cell ─ their seats
(c) PC seats          laptop/PC ── outbound stream ──▶ cell   (no open port)
(d) tests             CI runner ── suite ──▶ failure bundle ──▶ dev agent

Private and public routers

Private and public are two deployment profiles of the same router source, not two products. The contract is shared — API, envelope, correlation, origin return, idempotency — and the differences live in the transport: authentication, registration and address policy.

A public router faces the internet: verified TLS, seat tokens, tenant and address policy, request quotas. A private router sits on the owner's machine or inside the network: loopback only, no auth, seats register themselves and hold an address lease.

        internet
           │  verified TLS · seat tokens · tenant/address/quota
     ┌─────▼──────┐
     │ public cell│  router · directory · db · web console
     └─────▲──────┘
           │  r2r link: signed envelope, path fixed in advance,
           │            receipts returned to the origin
     ┌─────▼──────┐
     │  private   │  loopback · no auth · auto-registered seats
     │  router    │  (owner's machine or inside the LAN)
     └─────┬──────┘
           │
   local seats: executor · browser · codex · opencode

What an adapter does

An adapter is the only place where a message becomes a system action. It holds a seat token, keeps a stream open, receives a job, decides whether the request is allowed, executes it, and returns a result bound to the seat that asked.

The router never interprets the body. It stores, delivers, retries and records; the adapter owns the interpretation, the permission check and the record of what it touched.

seat token ─▶ [ transport ]  SSE in · POST result / egress ack out
                    │
                    ▼
               [ policy ]     origin allowlist · domain list · content policy
                    │
                    ▼
               [ executor ]   system API · browser step · mail draft
                    │
                    ▼
               [ result ]     status · data · activities ──▶ origin seat
                    │
      ↺ reconnect with backoff · renew the token · retry once on a transient code

Adapters share one shape

The loop above is identical for every adapter; only the executor changes. That is why a new integration is a new executor, not a new protocol.

mail      Gmail push / IMAP poll ─▶ triage seat ─▶ proposal ─▶ human ─▶ draft
browser   step list ─▶ ego lite on the owner's PC ─▶ page data / screenshot
executor  task ─▶ approved command on the host ─▶ result bound to the task
engine    job ─▶ codex / opencode session ─▶ result bound to the conversation
router    envelope ─▶ next router ─▶ receipt back along the same path