OneStep Control Plane
The Control Plane is the centralized ops control surface for the OneStep runtime, providing remote monitoring, telemetry collection, and command dispatch.
Architecture Overview
┌──────────────────┐ WebSocket ┌──────────────────┐ ┌────────────┐
│ OneStep Agent │ ──────────────────▶ │ Control Plane │ ──▶ │ PostgreSQL │
│ (ControlPlane │ onestep-agent.v1 │ API (FastAPI) │ │ │
│ Reporter) │ ◀────────────────── │ │ │ services │
└──────────────────┘ commands │ /api/v1/agents │ │ instances │
│ /ws │ │ sessions │
│ │ │ tasks │
│ /api/v1/query │ │ commands │
│ │ │ events │
│ Console Auth │ │ metrics │
└────────┬─────────┘ └────────────┘
│
│ SSE / REST
▼
┌──────────────────┐
│ Web Console │
│ (React + Vite) │
│ │
│ Service List │
│ Task Dashboard │
│ Instance View │
│ Command Panel │
│ Notifications │
└──────────────────┘Core concept: OneStep runtime Agents actively connect outbound via WebSocket to the Control Plane (no inbound ports required on the Agent), reporting topology, heartbeats, metrics, and task events, while receiving operational commands.
Core Concepts
Service / Instance / Task
- Service: Logical service identifier, uniquely determined by
(name, environment) - Instance: Runtime instance (UUID identifier), carrying node name, hostname, PID, and version info
- Task: Task graph definition (Source, Sink, concurrency, timeout, retry policy), tracked for consistency via
topology_hash
Topology Description
The Reporter's sync telemetry carries task Source/Sink types, names, and config summaries for the control plane to display the task graph and compute topology_hash.
Connectors report by stable type names, e.g., cron, interval, rabbitmq_queue, mysql_table_sink, redis_stream, feishu_bitable_incremental, feishu_bitable_table_sink, and http_sink. Redis Streams reports stream, group, consumer, batch, block, trim, and other topology config; HttpSink reports URL, method, timeout, query parameter names, and success status codes.
Sensitive information is not sent into topology as-is: HttpSink hides URL credentials, removes URL query/fragment, and marks header and params values as <redacted>.
WebSocket Session Lifecycle
- Agent establishes WebSocket connection, sends
hello(protocol version, capability declaration) - Server responds with
hello_ack(containing acceptedaccepted_capabilities) - Agent begins sending
telemetrymessages (sync / heartbeat / metrics / events) - Server can dispatch
command(ping / shutdown / drain / pause_task, etc.) - Agent replies with
command_ack(accepted/rejected) andcommand_result(execution result)
Capability Negotiation
The Agent declares its capabilities in hello (e.g., command.ping, command.shutdown, telemetry.sync), and the server only dispatches commands the Agent has accepted.
Command Lifecycle
pending → dispatched → accepted/rejected → succeeded/failed/timeout/cancelledAll commands are persisted to the database; unacknowledged commands can be re-dispatched after reconnection.
Since Worker Agent 0.1.5 there is also command result recovery: once an agent advertises the agent.command_recovery capability, every command is persisted to work_dir/commands/ before execution and terminal results are durably recorded before being reported; a repeated command ID returns the existing result directly, and commands for the same deployment execute serially. The control plane re-dispatches accepted commands only to agents with that capability; after an agent restart, an interrupted preparation ends as cancelled, and exit events are retained until the control plane acknowledges receipt.
Online / Offline Detection
- Instance
last_seen_atwithinINSTANCE_OFFLINE_AFTER_S(default 90s) → online - Within health participation window
INSTANCE_HEALTH_PARTICIPATION_WINDOW_S(default 1h) → counted in service health denominator
Integrating with OneStep Agent
Enable Control Plane reporting in your OneStep application:
from onestep import (
ControlPlaneReporter,
ControlPlaneReporterConfig,
OneStepApp,
)
app = OneStepApp("my-service")
reporter = ControlPlaneReporter(
ControlPlaneReporterConfig.from_env(app_name=app.name)
)
reporter.attach(app)It can also be enabled directly in YAML:
reporter: truereporter: true is still the recommended minimal configuration, reading connection info from environment variables. To pin the service description in the config file, use a mapping instead:
reporter:
service_description: Synchronizes billing data into the warehouseEnvironment variable configuration:
| Variable | Description | Example |
|---|---|---|
ONESTEP_CONTROL_PLANE_URL | Control Plane service URL | http://192.168.1.100:8080 |
ONESTEP_CONTROL_PLANE_TOKEN | Worker reporting auth token | my-token |
ONESTEP_CONTROL_PLANE_ENVIRONMENT | Deployment environment label | prod / staging |
ONESTEP_SERVICE_NAME | Service name; defaults to app.name if not set | billing-sync |
ONESTEP_SERVICE_DESCRIPTION | Service-level description; shown in service catalog and detail views | Syncs billing data to warehouse |
reporter.service_description / ONESTEP_SERVICE_DESCRIPTION is service-level metadata, independent of task-level descriptions like tasks[].description.
See ControlPlaneReporterConfig.from_env for more configuration options; the complete variable reference is in Environment Variables.
Deploying the Control Plane
The Control Plane is a separate repository and deployment unit, providing one-click Docker Compose setup:
git clone https://github.com/mic1on/onestep-control-plane
cd onestep-control-plane
cp .env.example .env
# Edit .env to configure database, Token, etc.
docker compose up --build -dAfter startup:
- Web Console:
http://127.0.0.1:4173 - API:
http://127.0.0.1:8000 - Interactive API Docs:
http://127.0.0.1:8000/docs
SQLite local dev mode is also available (no Docker required):
./scripts/start-local.shQuery API
The control plane provides REST query endpoints for the Web Console or third-party integrations:
| Endpoint | Description |
|---|---|
GET /api/v1/services | Service list |
GET /api/v1/services/{name}/dashboard?environment= | Service dashboard |
GET /api/v1/services/{name}/instances?environment= | Instance list |
GET /api/v1/services/{name}/tasks?environment= | Task list |
GET /api/v1/services/{name}/tasks/{task}?environment= | Task details |
GET /api/v1/services/{name}/events?environment= | Event stream |
GET /api/v1/services/{name}/commands?environment= | Command history |
GET /api/v1/services/{name}/sessions?environment= | Session records |
GET /api/v1/events?environment= | Cross-service recent event stream |
GET /api/v1/services/{name}/events supports the following filter parameters:
| Parameter | Description |
|---|---|
task_name | Filter by task name |
kind | Filter by event type (e.g., succeeded / failed / retried / dead_lettered) |
instance_id | Filter by worker instance |
occurred_after / occurred_before | Filter by time range (UTC ISO time) |
limit / offset | Pagination |
All query endpoints require Console Auth (username/password) authentication.
Notifications & Webhook
The Control Plane supports pushing task events to instant messaging tools:
- Feishu group bot
- WeChat Work group bot
- Custom webhooks
Supported event types: task started, succeeded, failed, retried, dead-lettered, missed scheduled start.
Channels are classified along three independent axes: event types, service scope (an empty list = all services, including future ones), and the missed-start grace period. Damping is deliberately asymmetric per event kind: connectivity flips use a stable confirmation window plus flap suppression (rapid up/down oscillations no longer trigger notification storms); task_failed uses burst damping per (channel, service); task_started / task_succeeded / task_missed_start are not damped. Damping state is persisted, so a restart mid-incident cannot reset a counter; a suppressed burst still reports suppressed_failure_count in the summary — silence must never read as health.
POST /channels/{id}/test performs a real webhook request and returns delivered / status code / error message; delivery outcomes are queryable at GET /deliveries — the only feedback loop for "did my alert actually go out?".
Monitoring & Alerting
The control plane ships two complementary alert planes:
- Prometheus rules (Plane A): alert rules live in
apps/control-plane/monitoring/prometheus/rules/control-plane.ymlwith runbooks beside them inapps/control-plane/docs/runbooks/alerts.md(each rule links itsrunbook:anchor).docker-compose.monitoring.ymlruns Prometheus + Alertmanager + blackbox + postgres_exporter in one command; the/metricsendpoint requires a bearer token. Acriticalalert means the control plane is down, or it has lost the ability to report problems (scan failing, notification delivery failures, metrics missing, scan never ran) — the second class is the more dangerous one: a silent alerting system hides incidents better than a loud outage. - Notification subsystem (Plane B): the control plane itself scans and derives events → notification outbox → webhook. Plane B is driven by the control plane, so it is necessarily silent when the control plane is down — which is exactly why Plane A exists.
After touching monitoring configuration, run bash scripts/check-monitoring.sh (also a CI check); it verifies that every metric referenced by a rule is emittable, runbook anchors resolve, and scrape job names line up.
More Resources
- Agent WS Protocol — WebSocket communication protocol specification
- Cross-Repo Collaboration — Boundaries and collaboration flow between the two repositories
- GitHub: onestep-control-plane — Source repository