Skip to main content
A capture policy is four fields the server resolves per (team, service) and hands to every relay daemon that heartbeats for that service. The daemon writes it into the spool directory, the SDK reads it, and the SDK lowers its own settings to match. A capture policy can only ever lower what a process configured at init(); it never raises a ceiling. The heartbeat route’s request and response shapes are on Ingest. Flags are spelled with a single dash, as -h prints them; the binary accepts both spellings.

The four fields

The SDK clamps three fields downward: enabled and capture_logs become ceiling AND received, and value_sample_rate becomes min(ceiling, received). What each SDK setting means is on What gets captured.

How a heartbeat resolves it

Each POST /v1/heartbeat names a service, and the team comes from the ingest token. The server answers with the first row it finds, whole, and never merges two rows:
  1. The service’s own row for that team.
  2. Otherwise the team-wide row, set with no service.
  3. Otherwise the system default: enabled: true, value_sample_rate: 1, max_value_bytes: 16384, capture_logs: true.
Each default is the identity value for the SDK’s clamp, so a team that has never set one sees no change to any process. vexlo policy show sends one heartbeat and prints the answer; on a fresh server it prints the system default:
updated_at is stamped fresh on every heartbeat response; it is a liveness stamp, not the time the capture policy last changed.

Set a capture policy

vexlo policy set issues PUT /v1/policy with an ingest token. The body is the whole capture policy, so all four fields are required on every call: a field left out is an error, never “keep the current value”. -enabled and -capture-logs are Go boolean flags, so a false is written -enabled=false with the equals sign; -enabled false leaves the flag true and passes false on as a stray argument.
The next heartbeat resolves to the new row:
A missing field:

The team-wide row

Omit -service to write the team-wide row. The token has to be one without a -service pin: a pinned token’s PUT /v1/policy with no ?service= is 422 service_scope_mismatch.
vexlo policy show requires -service, so the team-wide row is read back through a service that has no row of its own:

The two refusals

PUT /v1/policy has two 422 codes. invalid_policy names the field that is out of range in details.field:
service_scope_mismatch is the answer when the token’s -service pin excludes the service the call names:
vexlo policy set prints the server’s status and body on any non-200 answer:

From the server to the process

The relay daemon heartbeats on -heartbeat-interval (default 30 seconds) for every live writer and writes each answer to <spool_dir>/policy/<service>.json, mode 0600, replacing the file atomically:
The SDK reads that file and clamps against it; how often it re-reads and when it treats the file as stale are on Configuration. A change made with vexlo policy set reaches the file at the daemon’s next heartbeat for that service, one -heartbeat-interval later, with no redeploy and no restart. A heartbeat that fails is logged and left for the next tick, which pushes the change out by another interval. A service with no writer in the current or the previous UTC hour sends no heartbeat at all, so its file stays as it is until one of its processes writes records again.

Flag reference

-destination, -token, and -allow-insecure-http read VEXLO_ENDPOINT, VEXLO_INGEST_TOKEN, and VEXLO_ALLOW_INSECURE_HTTP when the flag is absent, the same three the relay daemon reads. The destination follows the daemon’s https-or-loopback rule.