GurtOSat Cal Hacks 13.0

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.

  1. SpacetimeDB as the core real-time backend.
    Met

    SpacetimeDB is the box screen's live view of the box. The screen reads the task and approval tables and answers through the answer reducer. The box writes through putTask and putApproval, which refuse anyone but the box, and vouches for the screen and paired phones with registerDevice.

  2. Used meaningfully, not on the side: live shared state.
    Met

    One 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.

  3. Instant sync.
    Met

    An 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.

  4. Agents coordinating.
    Partly

    Agents and people meet in the approval table: 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

The GurtOS desktop with Notification Center open: two requests waiting for an OK, each with Deny and Approve, and earlier notices below, including a blocked attempt.
The box's screen: requests waiting for an OK, live from the approval table, while other tasks keep running. The amounts are test money.
GurtOS on a phone: a Needs you list with two requests, each with Deny and Approve, three running tasks, and an Ask the box button.
The phone follows the box over its token-checked API and log stream, not SpacetimeDB. Its answer lands in the same approval row, so the box screen updates at once.
The task list on a phone, with each task's state and live step.
Tasks on the phone, live.
The Tasks window listing tasks with their state, what each is doing now and its cost.
The Tasks app: each row's live status and cost.

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

How it works, step by step
  1. TaskA task asks for an OKOver its budget, or with box_ask. The broker pauses it.
  2. GurtOS coreThe log, then SpacetimeDBThe box writes approval.asked to its log, then calls putApproval.
  3. YouEvery screen sees itThe box screen subscribes to the approval table; phones follow the log stream.
  4. YouSomeone answersThe screen calls answer from a vouched connection; a phone posts to the API. A pending row changes once; the first answer wins.
  5. GurtOS coreThe box picks it upIts subscription sees the update and writes the answer, with who gave it, to the log.
  6. 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

PartStateDetails
SpacetimeDB on the boxLiveSelf-hosted, installed by core/setup.sh as spacetimedb.service on x86 and ARM64. Each ./demo run publishes its own database.
The amounts in the demoTest moneyThe $8.00 is test money (example.spend).
Phones through a tunnelLiveWhen 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 answer accepts 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.
Every track