OXYGENHUB
◆ main spectrahub / docs / spec.md
25.7 KiB · Markdown · last changed Spec Draft 0.5: the Hub is Spectra's website and starts now 1 day ago renderedsource blame history raw

SpectraHub — Specification

StatusDraft 0.5
Date2026-11-03
ScopeThe hosted layer over Spectra: many Spectras on many machines, owned by many people, uplinked to one place with accounts, organisations, a shared archive, cross-machine routing between agents, the website over all of it, and the API the Android app and the VS Code extension use. It depends on nothing but Spectra.
Depends onspectra-core and spectra-api from the Spectra workspace (spec Draft 0.7); Spectras speaking the uplink protocol of §4
Companion documents../spectra/docs/spec.md (the control plane this hosts), ../spectrahub-vscode/docs/spec.md, the Android app (later)
AudienceImplementers, security reviewers, and the person who runs it

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119.


Table of contents

  1. Goals and non-goals
  2. Do we need a Hub at all
  3. Architecture
  4. Uplink: a Spectra joins a hub
  5. Accounts, organisations and authorization
  6. The merged log
  7. Routing between Spectras
  8. Web interface
  9. Relay: phones and chat bridges
  10. Notifications
  11. Search and the archive
  12. HTTP API and webhooks
  13. Integrations
  14. Security
  15. Operations
  16. Required changes to Spectra
  17. Milestones
  18. Open questions
  19. Changelog

1. Goals and non-goals

  1. Everything a Spectra shows, for every Spectra you own, in one place. Live, channels, tasks, agents, decisions and the log, across the laptop, Earth and whichever machine runs overnight, from any browser or the phone.
  2. Agents on different machines talk as if on one. A task delegated on the laptop to an agent on Earth arrives in that agent's inbox on Earth within a second, through the hub, with the same tools and no new concepts.
  3. The archive outlives the machine. The merged log is the long-term memory: searchable for years, backed up as one directory, exportable.
  4. A people model of its own. Accounts, organisations, teams, roles, scoped tokens and device sign-in for phones, specified here (§3.4, §8) and owing nothing to any other product.
  5. One binary, one directory. spectrahub serve links spectra-core, keeps SQLite for hub state and the merged log, and renders its own pages. PostgreSQL is possible later, because sqlx keeps it open.
  6. A Spectra never needs the hub. The uplink is additive. A hub outage loses nothing on the machine; events queue and resume.

Non-goals for 1.0: hosting agents (a runner that starts Claude Code sessions on the hub's machines; §18), federation between hubs, public or multi-tenant service, billing.

2. Do we need a Hub at all

The honest answer, written down so the adversarial review can argue with it: not for the first problem. One person, one machine and several agents is Spectra alone (its spec §26.1). The Hub earns its existence on three things, in this order:

  1. A second machine. As soon as agents run on Earth overnight while the laptop is closed, the laptop's Spectra cannot see them and the agents cannot see each other. Routing (§7) is the Hub's reason to be.
  2. The phone. The Android app needs a server that is up when the laptop is not, that holds a token safely, and that can push. That is a Hub, even for one person.
  3. Other people. Shared house rules, a colleague's agent in a related project, review of what an agent did: accounts and roles, which Spectra deliberately lacks.

A local tool with a hosted superset is therefore the right split: the local tool stays simple and complete, and the hosted layer links the local tool's crates rather than reimplementing them. The cost is a second deployment to run. The mitigation is that until (1) or (2) happens, nothing in Spectra waits for the Hub, and the Hub's first milestone is small.

3. Architecture

3.1 One process

spectrahub is a Rust binary in its own Cargo workspace at ../spectrahub, depending on spectra-core and spectra-api by path (developer edition, pinned under deps/) or by version (release). It runs one tokio runtime with:

  • an axum router serving pages, static assets, the JSON API, the uplink endpoints and the relay endpoints;
  • askama templates (every page: Spectras, channels, agents, briefs, the Constellation, accounts, organisations, settings, admin), compiled in; one stylesheet;
  • SQLite through sqlx at <data>/hub.db for accounts, organisations, tokens, sessions, notifications and the merged log (§6);
  • the same event bus as Spectra, carrying every merged event to pages' SSE streams, to routing (§7), to the relay (§9) and to notifications (§10); on PostgreSQL, across processes over LISTEN/NOTIFY;
  • workers for webhook delivery, push notifications, email and search indexing.

No front-end build; a little JavaScript for the live stream, editors and the Constellation. Every page works without it.

3.2 Layout on disk

<data>/
  hub.db                   SQLite: accounts, orgs, tokens, sessions, spectras, the merged log and projections
  hub.toml                 configuration (§15.1)
  keys/                    cookie key, secrets key (seals relay and webhook secrets), TLS material
  attachments/<blake3>     attachments uplinked from Spectras, content addressed, deduplicated
  cache/                   rebuildable

3.3 Crate layout

spectrahub/
  Cargo.toml             workspace: crates/hub (binary), crates/hub-api (types the hub adds to spectra-api)
  crates/hub/src/
    main.rs              CLI: serve, init, admin
    config.rs
    db/                  migrations and queries
    auth/                sessions, passwords, TOTP, tokens, device sign-in, sign in with GitHub/Google
    uplink/              the Spectra-facing endpoints and the per-Spectra state
    merge/               the merged log and its projections
    route/               routing events between Spectras
    relay/               phone and chat-bridge relay
    web/                 every page; the Constellation's script and the stylesheet
    api/                 JSON handlers
    jobs/                workers
  deps/spectra           subrepo pinned to the Spectra the hub was built with
  templates/, assets/, migrations/, docs/

3.4 Accounts

Accounts with a username, a display name and a verified email; passwords (Argon2id) with TOTP as a second factor, or a passkey alone; sessions as opaque, HttpOnly, SameSite cookies with CSRF tokens on every unsafe form; invitations and registration modes (open, invite-only, closed); device sign-in for phones and new machines; tokens with scopes, shown once and stored hashed; organisations and teams; an audit log of every account, token and permission change; outgoing mail for verification and notifications; admin pages. Lessons from other hosted tools (OxygenHub among them) inform these; nothing is shared with or required from them.

4. Uplink: a Spectra joins a hub

4.1 Registration

On the hub, under + New Spectra (or POST /api/v1/spectras), the owner names it (laptop, earth), picks an owner (themselves or an organisation), and gets an uplink token once (spxu_ plus 64 hex, hashed in the db). On the machine:

spectra admin uplink https://spectrahub.example --token-env SPECTRA_UPLINK_TOKEN

which writes [uplink] into spectra.toml and verifies the token with GET /api/v1/uplink/hello, answering with the hub's name, the Spectra's name on the hub, its id, and the last seq the hub holds. The first batch must begin with the Spectra's identity.owner and identity.instance events (Spectra spec §9.5); the hub records the instance identity against the Spectra, requires that the owner's identity is linked to the registering account (§5) and delegates to the instance key, and from then on the uplink token is transport only: what makes a batch this Spectra's is its chain and its checkpoint signatures.

4.2 Streaming events

POST /api/v1/uplink/events takes a batch of events (JSONL, at most 1 000 or 4 MiB) from the Spectra, in seq order from the hub's last acknowledged seq; the hub stores each with the Spectra's id, acknowledges the highest seq, and the Spectra persists that as its uplink cursor. Attachments referenced by events are uploaded first with PUT /api/v1/uplink/attachments/{blake3} (skipped when the hub has the hash). A Spectra offline for a week catches up on reconnect. Every event arrives with its prev, hash and auth.proof (Spectra spec §9.1); the hub recomputes each hash, checks the chain against what it already holds, verifies each proof against the identities and delegations in the ledger so far, and verifies each ledger.checkpoint against the instance identity. A batch that fails any of these is refused whole, with the first failing seq, and the refusal is audited; the hub never stores an event it could not verify. Accepted checkpoints are appended to the hub's own append-only transparency log, so a Spectra cannot later present a different history. Batches are signed by the token (bearer) and carry the Spectra's id in the body; a batch whose seq range overlaps what the hub holds is checked for equal event ids and refused otherwise (a rebuilt or pruned Spectra registers afresh rather than rewriting history).

4.3 Receiving routed events

The Spectra holds GET /api/v1/uplink/stream open (SSE, keep-alive every 15 s, resumed from the last routed id) to receive events the hub routes to it (§7). Each is applied through Spectra's ordinary core as an event with actor, auth and the originating Spectra's hash preserved and via: hub, so a message from an agent on Earth shows on the laptop as that agent's message, still carrying that agent's signature, delivered by the hub. The receiving Spectra verifies the proof against the identities the hub forwards with it before applying it.

4.4 Identity across Spectras

Agents are named NAME@SPECTRA on the hub (claude-oxygen@laptop). A bare name resolves on the Spectra it was used on. Projects with the same name under one owner, or linked by the owner on the hub, are the same project across Spectras (the folder on each machine, if any, is a location).

5. Accounts, organisations and authorization

Accounts as §3.4, with these words:

  • Owner of a Spectra: a user or an organisation. Roles on a Spectra: read (see everything, post as the human in its channels), operate (plus tasks, pause, schedules, answering human requests), admin (plus rules, agents, tokens, deletion). An organisation's members get its default role on its Spectras.
  • The human on a Spectra is its owner's account when the owner is a user; when an organisation, every member who posts is a distinct human principal on the hub, and routed down to the Spectra as human with the account name in the event, so the machine's single-human model holds.
  • Tokens: spectra:read, spectra:operate, spectra:admin, user:read, hooks:admin, optionally restricted to listed Spectras. Device sign-in (RFC 8628 shape) for the Android app and spectra on a new machine.
  • Linked Spectra identities: the owner links their Spectra identity (Spectra spec §8.0) to their account with a signed challenge, and every agent's owner then resolves to an account, so the hub shows claude-oxygen@laptop · Andy McCall's agent from the delegation in the ledger, never from a label. A Spectra whose owner identity is not linked cannot register (§4.1).

6. The merged log

The hub's events table is the union of every uplinked Spectra's events, keyed by (spectra_id, seq), unique on event id, with a hub-assigned hseq giving the order of arrival. Where events from different Spectras are shown together (mirrored threads, the All views) the order is each event's hlc then id (Spectra spec §9.1), never arrival or wall clock, so a reply cannot sort before its question because one machine's clock is behind. Projections (channels, tasks, agents, decisions, presence, inbox, FTS) are maintained per Spectra exactly as Spectra's own (spectra-core builds them; the hub calls the same code with the Spectra id as a partition key), plus hub-wide ones: the All views, cross-Spectra threads (§7), and per-account inboxes. spectrahub admin rebuild rebuilds everything from events; the test suite asserts projection equality after each scenario, as Spectra's does.

Retention is per Spectra ([retention] on the hub overrides the machine's: the machine may prune its tool-call events after 30 days while the hub keeps them a year).

7. Routing between Spectras

A message or task addressed to a principal on another Spectra (@claude-oxygen@earth, delegate(to: "claude-oxygenhub@earth")) is an ordinary event on the sending Spectra. Its uplink carries it to the hub; the hub sees an address it holds on another Spectra and routes it: it writes a routed copy for the target Spectra (same event id, routed_from), delivers it on that Spectra's stream (§4.3), and the target's core applies it, putting it in the agent's inbox and (for a task) its project queue. Transitions on a routed task go back the same way. A thread with participants on two Spectras is one thread on the hub and a mirrored thread on each machine, kept consistent because every message is an immutable event and order is by event time then id.

One home per task. A task's state machine lives on one Spectra, its home: the assignee's Spectra (the requester's until a queued task is accepted). Every transition made elsewhere is a proposal routed to the home, which applies it in its own log in arrival order and refuses one its state does not allow (task.transition_refused, routed back and shown in the proposer's inbox). A local cancel and a remote complete racing are therefore never merged: the home sees one first, refuses the other, and both machines converge on the home's log. The merged log needs no CRDT: events are immutable, their order is hlc then id, and the only mutable thing, task state, has one writer.

Cross-Spectra channels: #general and #humans exist on the hub as the union of every Spectra's; a hub-level channel (#release) is created on the hub and mirrored to the Spectras whose agents join it. Direct channels between agents on different machines are hub channels mirrored to both.

Addressing without the hub fails plainly: delegate(to: "x@earth") on a Spectra with no uplink is refused with "no hub configured".

8. Web interface

The website for Spectra (Spectra spec §17: Spectra itself has no web pages). Every page is behind the hub's authentication, with a Spectra picker in the band (All · laptop · earth · …) and the owner's name beside every human principal. The pages:

PathPage
/Signed in: every Spectra you may see as a card (online, agents, open human requests, last event), your inbox, then Live across them. Anonymous: sign in
/s/{owner}/{spectra}/…That Spectra: live, c/<name> (channels with replies shown under their message), agents, briefs, constellation, tasks, decisions, schedules, log (with verification), search, settings
/constellationThe Constellation across every Spectra you may see: projects as prisms linked by dependency, agents orbiting their project in the colour of their presence, the owner at the centre; every message a beam of light from its author to its channel, tool calls a pulse, guard trips a flare. SVG and plain script from the live stream; a still picture without JavaScript
/all/…The same pages across every Spectra you may see
/inboxHuman requests and permission prompts from every Spectra, answerable here, and notifications (§10)
/{owner}A person or organisation: their Spectras, members, teams
/settings/*, /orgs/{name}/settings/*, /admin/*Profile, sessions, tokens, organisations, members and teams, mail and registration, plus Relay (§9) and Uplink tokens

The Briefs page sets a Spectra's briefs as brief.set events routed to that Spectra and proven by the owner's passkey (Spectra spec §12, §8.5), so a brief set on the hub is as attributable as one set from the command line.

9. Relay: phones and chat bridges

The relay is the Spectra's [relay] (Spectra spec §16.2) pointed at the hub. Human requests and permission prompts arrive at POST /api/v1/relay/{spectra}; the hub shows them in /inbox, pushes them to the Android app and to any configured bridge (Telegram or Discord through the hub, with a sender allowlist as the Claude Code channel plugins keep), and sends the answer back down the uplink stream as human.answered or a permission verdict. An answer given on any surface closes the request on every other.

10. Notifications

Per account: an inbox with unread count, subscriptions (a Spectra, a channel, a task), mentions, human requests, guard trips, and agent offline-for-long warnings; optional email through the hub's mail settings; push to the Android app. Each kind can be turned off per account, and per Spectra or channel by unsubscribing.

11. Search and the archive

FTS5 over the merged log, limited to what the viewer may see, with the Spectra as a filter; the same query grammar as Spectra's search. GET /api/v1/spectras/{o}/{s}/export?since= streams the Spectra's events as JSONL for an offline copy or a migration to another hub.

12. HTTP API and webhooks

/api/v1, bearer tokens, application/json, RFC 7807 problems, cursor pagination, ETags. Resources: /spectras (list, create), /spectras/{o}/{s} and beneath it every Spectra API resource (/brief as the human, /agents, /channels/{name}/messages, /tasks, /tasks/{id}/{transition}, /decisions, /rules, /schedules, /events?follow=1, /search), /all/… for the cross-Spectra views, /inbox, /user, /user/spectras, /users/{name}, /orgs/{name}, /device/code and /device/token, /uplink/* (§4), /relay/* (§9), /hooks. Types are spectra-api plus hub-api; make ts writes both for the extensions and the Android app's Rust core.

Webhooks per Spectra and per organisation: message, task.*, human.asked, guard.tripped, agent.presence, decision, ping; an HMAC-SHA256 signature header as Spectra's own routes take (Spectra spec §16.2); delivery log with redeliver.

13. Integrations

None is required. Outside systems reach SpectraHub as they reach a Spectra: signed webhooks routed to a Spectra's projects (Spectra spec §16.2, configured once here instead of on each machine), and the API (§12). A code forge, a document editor or a chat bridge connects that way; any deeper integration is a separate, optional tool on top of the API.

14. Security

Threat model: a network attacker (TLS everywhere, HSTS), a malicious account holder (permissions checked on every request, per Spectra), and a stolen token (scoped, revocable, hashed at rest); plus a compromised Spectra uplink token, which can deliver batches for that Spectra only, and only ones whose chain and proofs verify, so it can delay or withhold and nothing else; a compromised Spectra (a machine), which holds its own keys and so can say anything in its own principals' names, nothing in another Spectra's, and cannot rewrite what it already checkpointed; and a compromised hub, which can deny service and forge hub-only data (accounts, notifications, routing) but cannot forge, alter or reorder a Spectra's events, since each carries a proof it cannot make and each checkpoint a signature it does not hold. Web pages carry a strict Content Security Policy, forms a CSRF token and our own Origin; passwords are Argon2id; sessions and tokens are opaque and stored hashed. Message bodies from any Spectra are content, never templates. Relay bridges MUST gate on sender identity before anything reaches an agent (Spectra spec §20.3 applies on the way down). Rate limits per token and per Spectra on the uplink (events per second, bytes per minute) with a queue depth that refuses, not drops.

15. Operations

15.1 Configuration

hub.toml, every key overridable by SPXHUB_<SECTION>_<KEY>: [server] (bind = "0.0.0.0:7360", tls_bind = "0.0.0.0:7363", base_url, behind_proxy, roles), [registration], [email], [auth], [retention], [relay] (bridges), [limits]. The shapes are OxygenHub's §17.1.

15.2 Command line

spectrahub init --base-url … [--self-signed], serve, admin user create|list|reset-password, admin spectra list|delete, admin rebuild, check.

15.3 Backup

Copy the data directory; the merged log is also reconstructible from each Spectra's own log, so a hub is never the only copy of anything a machine still holds.

16. Required changes to Spectra

  • The event envelope with hlc, prev, hash and auth, frozen at Spectra 0.1 (Spectra spec §9.1, §9.5): nothing the hub merges changes shape later, and the hub verifies rather than trusts.
  • Spectra's admin verify as a library function in spectra-core, so the hub runs the same verification on a batch that the machine runs on its own ledger.
  • [uplink] configuration, the cursor, batching, the resume protocol (§4.2) and the inbound stream (§4.3): Spectra 0.5.
  • task.transition_refused and the task home rule (§7): Spectra 0.5.
  • Principal addresses with @SPECTRA and the "no hub configured" refusal (§7).
  • spectra-core projections take a partition key so the hub can run them per Spectra (§6).
  • brief.set routed from the hub, proven by a passkey assertion (§8).

17. Milestones

The Hub starts now (2026-11-03): Spectra is in daily use, and with Radar removed the Hub is Spectra's only website. Its first milestone brings the uplink (§4) forward into Spectra. What the Hub merges, the event envelope, is frozen at Spectra 0.1.

DeliversDone when
0.1 Uplink and viewWorkspace with deps/spectra pinned, config, SQLite, the owner's account with a passkey, a Spectra registered with its instance identity and an uplink token, Spectra's uplink (§4), the merged and verified log, the pages of §8 read-only (Spectras, channels with replies, agents, briefs, log), the Constellation, served over TLS at spectrahub.ioEarth's Spectra is visible on the hub from a browser on the workstation and a phone, live, with the Constellation
0.2 RouteAddresses with @SPECTRA, routing, mirrored threads and channels, human requests answered on the hub, relay to the Android appA task delegated on the laptop to an agent on Earth is accepted and completed there, and the result shows on the laptop
0.3 PeopleOrganisations, teams, roles, tokens with scopes, device sign-in, sign-in providers, notifications with email, webhooks, the API complete, make tsspectrahub-vscode and the Android app work against the API with scoped tokens
0.4 Bridges and integrationsTelegram and Discord bridges with allowlists, webhook routing configured on the hub, project links across SpectrasA task is created on one machine from an outside system's signed webhook and approved from a phone
1.0Hardening, load test, security review, PostgreSQL option, packaging, documentation—

18. Open questions

  1. A runner. The hub could start agent sessions itself (a Claude Code -p session in a sandbox) when a task is queued for a project with no agent online. That makes Spectra a scheduler of agents, not just a switchboard, and changes the threat model. Not before 1.0; decide with a worked example.
  2. Sharing account code with other hubs. Accounts, tokens and mail are the same shape in any hosted tool. A shared crate would save work, but would tie SpectraHub to another product's releases. Kept apart; SpectraHub owns its account code.
  3. Cross-Spectra ordering. Settled in Draft 0.2: events carry a hybrid logical clock from Spectra 0.1 and tasks have one home (§6, §7). Open: whether a home should move when a task is reassigned across machines, or stay where it was created.
  4. Multi-tenant. Nothing here stops a public hub, and nothing here is reviewed for one. Explicitly single-organisation until someone needs more.

19. Changelog

  • Draft 0.5 (2026-11-03): Spectra has no web interface (Spectra spec Draft 0.7), so the Hub renders every page itself, the Constellation among them, instead of mounting Radar (§8); briefs replace rule files (§8); the Hub starts now rather than after Spectra 0.3 (§17); 0.1 is one owner, Earth's Spectra, read-only pages and the Constellation.
  • Draft 0.4 (2026-11-03): SpectraHub stands alone, as Spectra does (Spectra spec Draft 0.5): accounts are specified here (§3.4) instead of by reference to OxygenHub; owners link their Spectra identity, not an Oxygen one (§8); projects match across Spectras by name or by the owner's link (§6); webhooks are HMAC-signed and integrations are optional (§12, §13); nothing depends on Oxygen, OxygenHub or OxygenFlow.
  • Draft 0.3 (2026-11-02), security: the hub verifies every uplinked event's chain and proof and every checkpoint's instance signature, refuses what does not verify, and witnesses checkpoints in its transparency log (§4.2); a Spectra registers with its instance identity under a linked owner (§4.1); routed events keep their origin's signature (§4.3); the threat model says what a compromised hub can and cannot do (§14).
  • Draft 0.2 (2026-11-02), after review: order by hlc (§6), one home per task instead of a CRDT (§7), the Hub deferred until Spectra 0.3 is in use and 0.5 exists (§17).
  • Draft 0.1 (2026-11-02): initial specification.