SoyaOS

Architecture

Planet, Moon, and Comet — the three node roles that compose every SoyaOS deployment.

SoyaOS is one binary that can play three roles. A deployment is just a particular shape of those roles. Whether you run solo on a laptop or enterprise-private in a sealed datacenter, the role boundary and the protocol between roles never changes — only the topology does.

The three node roles

Comet

A Comet is an ephemeral worker. It runs a single Agent invocation, streams its output, and exits. Comets are stateless — they pull the SoyaPack bundle from a Moon, mount a sandboxed /workdir, and emit Scope events as they progress.

Mental model: a Comet is a fast, opinionated kubectl exec for one Agent run.

Concretely a Comet:

  • Subscribes to a Moon for work over an outbound HTTPS WebSocket.
  • Materializes the SoyaPack into a temporary directory, verifies its signature, and refuses to run if the capability allowlist isn’t satisfiable on this host.
  • Streams stdout and Scope events back through the same WebSocket.
  • Self-terminates after idle_timeout (default 300s) or max_runs (default unlimited).

Moon

A Moon is a per-tenant control surface. It hosts the API surface that developers and users talk to, holds the SoyaPack registry, mints API keys, and brokers Scope events. A Moon does not run Agents itself; it dispatches to Comets.

Mental model: a Moon is the “team workspace”. One per company / studio / household.

Inside a Moon:

  • The OpenAI-compatible endpoint (/v1/chat/completions) — takes a model: "soya:*" request, resolves it to a SoyaPack version, picks a Comet, returns a streaming response.
  • The SoyaPack registry — versioned, content-addressed bundles backed by S3-compatible object storage.
  • The auth surface — issues API keys and capability tokens; verifies signed packs on push.
  • The Scope broker — multiplexes per-run events to subscribers (Studio, Developer Portal, webhooks).

Planet

A Planet is the federation root. It owns identity (who you are), billing (when there is billing), and cross-Moon routing. In solo deployments there is no separate Planet — Planet, Moon and Comet collapse into one process.

Mental model: a Planet is the “constellation operator”. Optional for single-tenant deployments.

A Planet’s responsibilities:

  • Identity — OIDC issuer for users and Moons; signs SSO assertions.
  • Routing — maps a tenant URL (tenant.moon.example.com) to a specific Moon.
  • Cross-Moon contracts — the only entity that can authorize a SoyaPack to run on a Moon outside its origin tenant.

How a request flows

SoyaOS request flow A client SDK calls a Moon over OpenAI-compatible HTTPS. The Moon resolves the tenant URL via the Planet (federation root, optional in solo), picks a warm Comet, which calls the upstream LLM. Scope events flow back through the Moon WebSocket to subscribers like Studio, the Developer Portal, and webhooks. Planet identity · routing · billing federation root optional in solo resolves tenant URL Moon /v1/chat/completions SoyaPack registry Scope broker Client SDK OpenAI-compatible · HTTPS pick a warm Comet (or cold-start) Comet sandboxed /workdir capability allowlist upstream LLM Claude · GPT Qwen · Ollama Scope events · back through Moon WebSocket Studio · Developer Portal · webhooks
How a SoyaOS request flows.
  1. Client calls the OpenAI-compatible endpoint on a Moon: POST /v1/chat/completions with model: "soya:compo".
  2. The Moon resolves soya:compo → a specific SoyaPack version (e.g. soya:[email protected]) and picks a warm Comet (or cold-starts one).
  3. The Comet executes the Agent inside its sandbox, calling out to upstream LLMs via the capability allowlist, and streams Scope events back through the Moon.
  4. The Moon multiplexes those events to subscribers (Studio, Developer Portal, webhooks).
  5. When the Agent completes, the Comet emits a final artifact JSON and self-terminates (or returns to the warm pool, depending on idle_timeout).

A concrete event trace

For a single soya:compo invocation, the Scope event stream looks roughly like:

00.000 run_started        run_id=run_018f… pack=soya:[email protected] comet=cmt-a3
00.012 stage_started      stage=outline
00.087 tool_called        tool=fetch_reference  args={url:…}        # capability check: egress.host
00.412 tool_completed     tool=fetch_reference  ok=true
00.514 llm_request        upstream=claude-sonnet-4-6  prompt_tokens=1842
01.823 llm_response       completion_tokens=612
01.834 stage_completed    stage=outline  artifacts=[outline.v1]
01.835 stage_started      stage=writer

04.219 run_completed      ok=true  artifacts=[outline.v1, guide.v1]

Every event is JSON, every event has the same envelope (run_id, ts, kind), and every event is signed by the Comet’s run key — see Scope events & observability.

Why this split?

  • Scaling shape: Comets are cattle, Moons are pets, Planets are almost-pets. You can run a million Comets behind one Moon. A Moon scales vertically until ~10k concurrent runs; past that, shard tenants across Moons.
  • Security: every Agent runs inside a Comet’s capability allowlist; the Moon never executes user code. A compromised Comet cannot reach the Moon’s secrets — the Comet only ever sees its own short-lived run key.
  • Federation: many Moons can hang off one Planet, or roll up to many Planets in enterprise-cloud deployments. A Planet can fail without taking Moons offline — Moons cache their identity certs and continue serving until the cert expires.

Mapping roles to editions

EditionWhere Comet runsWhere Moon runsWhere Planet runs
soloin-processin-processn/a (collapsed)
clusteryour LAN / VPCyour VPSn/a or your VPS
cloudsoyaos.aisoyaos.aisoyaos.ai
hybridyour VPCsoyaos.aisoyaos.ai
ent-cloudsoyaos.ai (dedicated)soyaos.ai (dedicated)soyaos.ai (region-pinned)
ent-privatecustomer-operatedcustomer-operatedcustomer-operated

See Editions for the full matrix; see Capabilities & sandbox for what a Comet can and cannot do.

Edit this page on GitHub