component RiaAgent { goal { Run a toolkit agent on an edge device that connects outbound to RIA Hub to capture, stream, or run inference on live radio. Own the agent CLI, its two execution modes, and its persisted registration. } interface { CLI ria-agent with subcommands: register (--hub, --api-key, --name, and TX interlock flags), stream, run (legacy), and detect (list SDR drivers). register POSTs to {hub}/screens/agents/register with the X-API-Key registration key and saves agent_id and token to ~/.ria/agent.json (mode 0600). Two execution modes: a WebSocket streamer that opens an SDR and streams raw IQ to the hub (the hub does inference), and a legacy long-poll NodeAgent that runs ONNX inference locally on the device. } } expand RiaAgent { logic { The streamer connects to wss://{hub}/screens/agent/ws with a bearer token, heartbeats, auto-reconnects, and runs concurrent RX (ship interleaved float32 IQ) and TX (play inbound IQ) sessions, sharing one driver for full-duplex when RX and TX name the same device. The legacy NodeAgent connects outbound only: it registers at /composer/nodes/register, heartbeats, long-polls /commands, and dispatches run_campaign, load_model, start/stop/configure_inference, and TX commands, reporting to /events and /campaign-status. } constraints { TX is opt-in (--allow-tx) and bounded by tx_max_gain_db, tx_max_duration_s, and allowed frequency ranges, with a TX watchdog; a TX-start request exceeding the interlocks is rejected (per-frame handling rejects malformed frames and enforces max duration). Registration keys are personal (ria_reg_ prefix); the legacy shared key is deprecated. Failures are reason-coded (invalid_key, expired, revoked, already_consumed, rate-limited). Config is AgentConfig at ~/.ria/agent.json (override RIA_AGENT_CONFIG). } }