Station Networks
A Station Network scales Station across processes or machines. One logical Headquarters accepts requests, presents fleet-wide state, and reconciles schedules. Execution stations advertise capacity and definitions, then atomically claim work from the shared adapters.
- Public entry pointHeadquartersAuthenticates a request, validates it and enqueues the run.
- Shared durable stateQueue + leasesAtomic claims assign one attempt to one owner. State is shared across the fleet.
- Private executionEligible workerChecks definitions, placement and capacity, then executes the claimed run.
Roles and request flow
| Role | Responsibility |
|---|---|
headquarters | API, schedules, broadcasts, routing and fleet inventory for the separate dashboard. It does not execute signals or beacons. |
station | Advertises local definitions and executes eligible signal runs and beacon instances. |
standalone | Backwards-compatible single-node mode that performs both roles. |
Headquarters enqueues a run once. Stations race to claim it in the shared queue; the adapter's atomic pending-to-running transition chooses exactly one owner. If that owner disappears, its lease expires and the run is recovered. Fencing tokens prevent the old owner from later completing the recovered attempt.
Specialized stations can also own Sandbox workspaces and Browser Use sessions. Their initial execution API routes requests to an explicitly selected owner through Headquarters; it does not use signal queue placement or migrate live sessions.
- Client or agentWorkspace / session IDRequests another command, browser action or screenshot.
- Authenticated routingHeadquartersResolves the authorized worker target; tenant grants are operator-configured.
- Existing ownerSandbox or browserThe selected worker operates its retained workspace or live browser session.
For example, keep CPU build tools on one Station and browser sessions on another. Both can be private services behind Headquarters. A signal can invoke their APIs, but application code must retain resource IDs and handle interrupted or unknown outcomes. Read the Sandbox and Browser Use lifecycle guides before planning recovery.
A Station Network coordinates work; it does not provision a VPN or Docker-style network. Workers need access to shared adapters and Headquarters needs a reachable, authenticated endpoint for proxied execution. Configure DNS, TLS, firewalls and ingress in the deployment. Keep private worker credentials and tenant assignments in operator configuration; a heartbeat is discovery data, not permission to act as a tenant.
Configure Headquarters
import { defineConfig } from "station-daemon";import { PostgresAdapter } from "station-adapter-postgres";import { StationNetworkPostgresAdapter } from "station-adapter-postgres/network"; const connectionString = process.env.DATABASE_URL!; export default defineConfig({ role: "headquarters", adapter: new PostgresAdapter({ connectionString }), network: { id: "production", stationId: "hq-1", name: "Production HQ", adapter: new StationNetworkPostgresAdapter({ connectionString }), }, signalsDir: "./signals", // catalog + validation; never executed here scheduleAdapter, beaconAdapter,});Configure an execution station
export default defineConfig({ role: "station", adapter: new PostgresAdapter({ connectionString }), // same queue beaconAdapter, // same beacon state network: { id: "production", stationId: process.env.STATION_ID!, name: "Kenya GPU worker", adapter: new StationNetworkPostgresAdapter({ connectionString }), labels: { region: "ke", gpu: "true" }, endpoint: "https://worker-ke.internal.example", }, signalsDir: "./signals", beaconsDir: "./beacons", runner: { maxConcurrent: 12 },});Use the matching /network export for SQLite, PostgreSQL, MySQL, or Redis. Every process must use the same durable queue and network backends. Share beacon state on nodes that coordinate beacons, and share schedule state across Headquarters replicas. The memory implementations are only for standalone mode and tests. SQLite requires a shared filesystem; use PostgreSQL, MySQL, or Redis across machines.
Capacity, placement, and draining
export const render = signal("render") .input(RenderInput) .concurrency({ station: 4, network: 20 }) .placement({ labels: { gpu: "true", region: "ke" } }) .run(async (input) => { /* ... */ }); export const gateway = beacon("gateway") .placement({ labels: { region: "ke" } }) .run(async (ctx) => { const server = await listen(); ctx.expose({ protocol: "http", port: server.port, path: "/gateway" }); ctx.ready(); await ctx.untilStopped(); });Per-station concurrency limits local process pressure. Network concurrency uses shared controller leases and is enforced across the fleet. Placement labels require an exact match. Marking a stationdraining through the Stations dashboard or v1 API stops new claims while current work finishes.
Schedules and exact times
Runtime schedules support five-field cron plus an IANA timezone. The stored nextRunAt is an absolute timestamp and occurrences advance from the prior planned time, so polling latency does not create cumulative drift. Atomic occurrence claims prevent duplicate fires across control-plane processes. As with OS cron, the timestamp is when work becomes eligible; actual handler start can be delayed by polling, queue pressure, or unavailable capacity. See Schedules.
Beacon services
A networked beacon instance is protected by a single-owner lease. Calling ctx.expose() records its station, protocol, port, and base path. Headquarters proxies HTTP traffic at/api/v1/beacons/:name/instances/:id/proxy/*. The owning station must advertise a reachable network.endpoint; private/NAT-only stations need an operator-provided tunnel endpoint. The proxy requires a trigger or admin scope, removes the caller's authorization and cookie headers before forwarding, and does not proxy WebSocket upgrades. Protect direct station endpoints and do not treat the injected x-station-* headers as proof of identity on a publicly reachable service.
What happens when a worker disappears?
| Resource | Recovery model |
|---|---|
| Signal attempt | Lease expiry makes recovery possible. Fencing rejects stale completion; it cannot undo an external effect already performed. |
| Beacon process | Shared intent and ownership leases coordinate a replacement. New process memory starts fresh. |
| Sandbox workspace | Files may persist on its owner. Restart and service recovery depend on the adapter; there is no automatic cross-worker workspace migration. |
| Browser session | The live browser is interrupted. Retained profiles, recordings and explicit checkpoints can help reopen on a compatible owner. |
Draining stops new queued claims. Also inspect worker-owned shells, services and browser sessions before maintenance; an empty signal queue does not prove the worker has no live environments.
Production checklist
- Give every process a stable, unique
stationIdand the samenetwork.id. - Keep the lease duration above normal database, network, and event-loop jitter.
- Drain a station before maintenance; wait for active work before stopping it.
- Test at least two workers against the production backend and assert single ownership, placement, both concurrency levels, schedule deduplication, and expired-lease recovery.
- Measure the production workload. A local SQLite benchmark is useful for regression detection, not fleet sizing.