SpacetimeDB as the core real-time backend
Live tasks and approvals on every screen at once
Each GurtOS box runs its own SpacetimeDB, with a TypeScript module the box publishes at boot. It holds the live state of every task and every approval. The box's own screen subscribes to it, so an agent's request for an OK lands the moment it is asked, and the screen answers through a reducer that takes calls only from connections the box has vouched for. The first answer wins, whether it comes from the screen, a paired phone or a chat.
Proof In every merge-gate run, a task stops over its budget and SpacetimeDB shows the request live with its amount, payee and purpose. Another task keeps running and finishes meanwhile. The OK goes through SpacetimeDB's answer reducer into the box's log under the name the box vouched for, a stranger's identity is refused, and the task spends its $8.00 of test money and finishes, and SpacetimeDB shows it done with its cost.
What SpacetimeDB asked for
From SpacetimeDB's track description, as copied into the project's track plan.
SpacetimeDB as the core real-time backend.
MetSpacetimeDB is the box screen's live view of the box. The screen reads the
taskandapprovaltables and answers through theanswerreducer. The box writes throughputTaskandputApproval, which refuse anyone but the box, and vouches for the screen and paired phones withregisterDevice.Used meaningfully, not on the side: live shared state.
MetOne shared state for every person and every task on the box: what each task is doing now (
step), its cost against its budget, and every pending approval with its question, amount, payee and purpose.Instant sync.
MetAn approval appears on the box screen the moment the box writes it. Approve there and the box picks up the answer from its subscription and writes it to its log within milliseconds; a phone that approved first through the API clears the screen's banner the same way.
Agents coordinating.
PartlyAgents and people meet in the
approvaltable: a paused agent's request is a row, and whoever answers first, from any screen or a chat, lets it carry on. The credit desk's two agent seats run side by side and both stop there for the controller. Agents do not message each other through SpacetimeDB yet.
Screenshots




Open it yourself
- The module, core/spacetime/src/index.tsThe public tables and five reducers, in 88 lines.
- The box's mirror, core/live.ts
- The portal's client, portal/src/live.ts
- Docs: live updates
- Docs: approve from your phone
Evidence
From the Oct 24 merge-gate run on the x86 test VM (22 sections, 175 checks, 0 failed):
moment 3: a pause for your OK
ok P stopped over budget, and SpacetimeDB shows it live: "Spend $8.00 to Test Vendor on example.spend (test spend on a GPU hour)? Budget $5.00, spent $0.00."
ok another task kept running and finished while P waited
ok the portal's inbox lists it too (GET /api/approvals?state=pending)
ok the request carries the amount ($8.00), payee and purpose, so the phone can fill its rows
ok the OK came back through SpacetimeDB into the log, under the name the box vouched for
ok P carried on, spent $8.00 (test money) and finished
ok SpacetimeDB shows it done live, with its cost and last step
ok only one of the two parallel $0.60 payments went through: $0.60 spent, not $1.20 (atomic budget)
moment 4: pull the plug
ok the box is down hard: no processes, no mounts, a torn last line
ok the box came back and picked up the long job (running)
ok the long job carried on from where it was: no step done twice, none skipped
ok the deploy is still waiting for its OK, live in SpacetimeDB
ok approved after the restart, it finished
The test plays the box screen through SpacetimeDB itself: it reads the rows with SQL over SpacetimeDB's HTTP API and
calls the answer reducer from an identity the box vouched for, then checks that an identity nobody vouched for is
refused. A task's own user cannot answer at all: SpacetimeDB listens on loopback, the firewall keeps task users away
from it, and moment 2 checks it ("it could not reach SpacetimeDB or the box's API to approve itself").
How it works
- TaskA task asks for an OKOver its budget, or with
box_ask. The broker pauses it. - GurtOS coreThe log, then SpacetimeDBThe box writes
approval.askedto its log, then callsputApproval. - YouEvery screen sees itThe box screen subscribes to the
approvaltable; phones follow the log stream. - YouSomeone answersThe screen calls
answerfrom a vouched connection; a phone posts to the API. A pending row changes once; the first answer wins. - GurtOS coreThe box picks it upIts subscription sees the update and writes the answer, with who gave it, to the log.
- TaskThe task carries onThe other screens' banners go away. Other tasks never stopped.
- Task
- its own Linux user, its own copy of the files, no keys
- GurtOS core
- the GurtOS service on the box, root, the only part that reads the vault
- You
- a person, on the portal, a phone, Jira or a chat
The module (core/spacetime/src/index.ts, SpacetimeDB 2.13):
| Table or reducer | Who | What |
|---|---|---|
task (public) |
read by everyone | id, title, state, source, workspace, worker, costCents, budgetCents, updatedAt, step, note |
approval (public) |
read by everyone | id, task, question, costCents, payee, purpose, state, by |
owner (private) |
set at init |
The identity that published the module. That is the box. |
putTask, putApproval |
the box only | Insert or update a row. Anyone else gets "only the box may do this". An answered approval never goes back to pending. |
registerDevice, removeDevice |
the box only | Vouch for a SpacetimeDB identity (the box screen, or a paired phone that told the box its identity), or take it back |
answer({id, approved, by}) |
vouched connections only | Sets a pending approval to approved or denied, recorded under the vouched name, not the by it sends. A second answer does nothing. |
The log stays the truth. The action log on disk is fsynced line by line, and undo and resume are rebuilt from it. SpacetimeDB is the live mirror: the box writes every task and approval change to it as it happens. If SpacetimeDB is down, the box keeps working and catches up when it returns, and an answer given while the box was offline is applied when its subscription reconnects. After a power cut, the box republishes the module and refills the tables from the log, which is why the deploy in moment 4 is "still waiting for its OK, live in SpacetimeDB".
Clients. The box's own screen calls the HTTP API once, then subscribes to both tables over ws://127.0.0.1:3000
and tells the box its identity so its answers count. A phone or laptop elsewhere never reaches SpacetimeDB: it sends
its paired token to the API and follows GET /api/log/stream, and the log names the phone that answered. Either way
the answer lands in the same row, so every screen agrees.
Try it
On a box (Ubuntu 24.04 with sudo core/install.sh):
import { DbConnection } from './core/spacetime/bindings/index.ts';
DbConnection.builder()
.withUri('ws://127.0.0.1:3000')
.withDatabaseName('box')
.onConnect((conn) => {
conn.db.approval.onInsert((_ctx, a) => console.log('needs an OK:', a.question));
conn.subscriptionBuilder().subscribeToAllTables();
// conn.reducers.answer({ id: a.id, approved: true, by: 'my script' });
})
.build();
Then start a task that spends over its budget and watch the row arrive:
curl -s 127.0.0.1:7700/api/tasks -H 'content-type: application/json' -d '{"title": "Buy a GPU hour",
"tools": ["example.spend"], "budgetCents": 500, "prompt": "Spend 8 dollars on a GPU hour with example.spend."}'
Or open the box screen and a paired phone, and approve on one.
Live, test and simulated
| Part | State | Details |
|---|---|---|
| SpacetimeDB on the box | Live | Self-hosted, installed by core/setup.sh as spacetimedb.service on x86 and ARM64. Each ./demo run publishes its own database. |
| The amounts in the demo | Test money | The $8.00 is test money (example.spend). |
| Phones through a tunnel | Live | When port 3000 is out of reach, the portal falls back to the action log stream and says so in Settings. |
Limits
- SpacetimeDB has no login of its own. It listens on loopback only, and
answeraccepts only vouched connections; anyone who could read the port could still read the tables. Exposing it on a network (STDB_LISTEN) is opt-in, for a network you trust. - Phones do not subscribe to SpacetimeDB. They are live through the log stream, which is a refetch per event rather than a row update.
- One database per box. Two boxes show in one portal through each box's own SpacetimeDB, not a shared one.
- Agents do not coordinate with each other here yet. The shared state is between the box, its paused agents and the people who answer them.