Documentation — Architecture

# Connectivity & Agent Protocol

The agent installed on your Oracle server never opens a port and never accepts a connection. It calls out, asks if there's anything to do, and waits. Everything that follows describes exactly how.

## Long-polling, not fixed-interval polling

A naive polling agent asks "anything to do?" every N seconds, whether or not anything is waiting — wasted round trips, and up to N seconds of latency before it notices new work. TuneVault's agent does something better: it holds a single outbound request open for up to 25 seconds, and the server wakes it the instant work arrives.

This is built on Postgres LISTEN/NOTIFY, not a message broker or a second piece of infrastructure — Postgres was already required, so the channel adds no new dependency. When a command needs to reach your server, TuneVault inserts a row into a durable queue table and issues a `NOTIFY`. If your agent's request happens to already be held open and listening, it wakes in under a millisecond. If it isn't, the row sits in the queue until the agent's next poll picks it up.

Either path works. The durability guarantee doesn't depend on perfect timing — a command survives a TuneVault restart, an agent restart, or a network blip, sitting in the queue until someone claims it.

## The claim: why two agents never run the same command twice

If more than one worker could pick up the same queued command, you'd risk running a destructive operation twice. The queue is protected with a standard Postgres pattern:

Claiming a command — conceptually
SELECT * FROM agent_command_queue WHERE connection_id = $1 AND status = 'pending' ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1;

`FOR UPDATE SKIP LOCKED` means: lock the row I'm about to claim, and if another process already has it locked, don't wait — just skip to the next one. Combined with `SKIP LOCKED`, this guarantees exactly one claimant per command, even if two poll requests race each other at the exact same moment.

Claims aren't permanent until confirmed. A claimed row that never gets a result reported against it — because the agent process died mid-command, for example — automatically reverts to unclaimed after 60 seconds, so it can be picked up again rather than lost.

## What happens on each side

Step | Runs where | What actually happens
--- | --- | ---
Long-poll | Agent → TuneVault | Agent opens an outbound HTTPS request. TuneVault holds it open, listening for a NOTIFY on that agent's channel.
Enqueued | TuneVault | A health check schedule fires, or you click an action in the dashboard. TuneVault inserts the command and issues NOTIFY.
Runs locally | Agent, on your server | The agent receives the command payload and executes it — a SQL query via `python-oracledb`, or a whitelisted shell command for EBS ops. Nothing about this step involves an inbound connection.
Reports back | Agent → TuneVault | The agent POSTs the result outbound. TuneVault updates the queue row and NOTIFYs the original request, which resolves and returns the result to whoever asked for it.

## Heartbeat and staleness

Separately from the work-poll cycle, the agent's connectivity itself is tracked so the fleet dashboard can show accurate status. `agent_channel_state.last_poll_at` records every poll; if it hasn't updated in 90 seconds, the connection is flagged; past 300 seconds, it's shown as stale. These are the same two numbers behind the "online / stale / offline" indicator you see on the Servers page.

## Self-hosted vs cloud — same protocol, different endpoint

Nothing about this mechanism changes between deployment models. In cloud mode, the agent's long-poll target is `tunevault.app`. In self-hosted mode, it's your own TuneVault instance, running on your own network. The agent doesn't know or care which — it's configured with one URL and one API key at install time, and the protocol is identical either way.

See Architecture Overview for how this fits into the rest of the system, or Oracle Privilege Model for exactly what the agent is permitted to query once it does receive a command.