ampbase

OPAMP CONTROL PLANE

Documentation

Supervisor reference

The Ampbase supervisor is configured via a YAML file (default: supervisor.yaml). The schema has four top-level sections: version, server, agent, and supervisor.

For install instructions, see Install the supervisor. For the quick path to a connected agent, see the quickstart.

Schema version

Field Type Required Description
version u32 yes Schema version. Must be 1.

server — OpAMP server connection

Field Type Default Description
endpoint URL required OpAMP endpoint, e.g. wss://acme.ampbase.io/channels/clickhouse/v1/opamp.
api_key string required Channel agent key (agent_{channel_short_id}_{random}).
transport string "http" "http" (polling) or "websocket" (persistent).
poll_interval_seconds u32 30 HTTP poll interval. Must be > 0. Values > 5 with WebSocket trigger a warning.
max_message_size_kb u32 64 Maximum WebSocket message size in KiB. Must match the server's maxMessageSize. WebSocket only.

agent — Managed agent settings

Field Type Default Description
type string required "otelcol", "fluent-bit", "vector", "telegraf", "refinery", or "coding-agents". For coding-agents the supervisor manages runtime telemetry + an events pipeline rather than a single agent binary — see AI coding-agent policy.
binary_path path required Path to the agent binary.
config_path path Path to the agent's config file. Required for otelcol, fluent-bit, vector, telegraf. Unused by refinery (managed via state-dir symlinks).
health_check_url URL Health endpoint. Recommended for otelcol and refinery. Ignored for telegraf and vector (detected from the agent's own config). When absent, the supervisor drops to PID-only monitoring.
stop_timeout_seconds u32 10 Wait this long after SIGTERM before SIGKILL.
reload_delay_ms u64 17000 Refinery only. Time to wait for refinery's file poller to pick up config changes. Must be ≥ ConfigReloadInterval + jitter.
config_format string "yaml" "yaml" (otelcol, fluent-bit, vector, refinery) or "toml" (telegraf, vector). "ini" is reserved.
attributes map {} Operator-supplied key/value pairs available to feature-flag targeting. See Feature flags.

supervisor — Runtime settings

Field Type Default Description
state_dir path /var/lib/ampbase Directory for state.json and instance_uid. Created with mode 0700.
log_level string "info" "trace", "debug", "info", "warn", "error".
log_filter string Per-module filter using tracing-subscriber EnvFilter syntax, e.g. "ampbase::transport=debug,info". Overrides log_level.
health_check_delay_ms u64 2000 Wait after config apply before running the post-apply health check.
startup_delay_ms u64 1000 Wait after agent start before the initial health check.
reload_grace_period_ms u64 5000 Wait after SIGHUP before health-checking. Used by fluent-bit and vector.
validation_timeout_seconds u64 10 Timeout for telegraf --test pre-validation.
metrics_port u16 9464 Prometheus metrics endpoint port.
max_memory_mb u64 Soft cap. Logs a warning when the managed agent's VmRSS exceeds this. Use cgroups or systemd MemoryMax= for hard limits.

AI coding-agent policy

When agent.type is coding-agents, the supervisor does not manage a single agent binary. Instead it runs a loopback gateway the coding-agent runtimes export to, plus an events pipeline that redacts each event by policy. The supervisor.gateway block enables it:

agent:
  type: coding-agents
supervisor:
  state_dir: /var/lib/ampbase
  gateway:
    enabled: true
    http_port: 4319          # loopback port the runtimes export to
    reduction:
      enabled: false
    events:
      enabled: true

Intelligence export uses this same gateway — on any agent type, with no config. The loopback gateway is also the receiver for reduced-telemetry export (Fluent Bit / Vector / OTel Collector / Telegraf, not just coding-agents). You do not set gateway.enabled for that: when a channel enables intelligence export in the dashboard, Ampbase sends an ampbase.intelligence_export offer over OpAMP and the supervisor starts the receiver on demand on the first offer, forwarding the reduced stream to Ampbase. Set gateway.enabled: true only to eager-start the receiver at boot — which coding-agent fleets need because their events pipeline must be listening before the runtimes launch.

The channel config for this type is not agent YAML — it is a small policy document, authored in the dashboard and delivered verbatim over OpAMP. This is the one canonical schema: Ampbase renders it and the supervisor parses it, and a contract test pins the two sides so they cannot drift.

version: 1
# Which runtimes to wire. A scalar bool is sugar for
# {enabled: <bool>, metrics: true, logs: true}. `false` removes existing
# wiring; an omitted runtime is left untouched.
runtimes:
  claude-code: true
  codex-cli: true
  cursor: false
  gemini-cli: false
  vscode-copilot: false   # cooperative-mode only — see below
# Events-pipeline policy: redaction tier, local-retention bounds, and
# forwarding gates. Versioned, rolled back, and canaried like any config.
events:
  tier: metadata-only        # metadata-only | no-content | full
  retention:
    max_megabytes: 100
    max_age_days: 7
  forwarding:
    ampbase_ingest: false    # forward redacted events to Ampbase ingest
    customer_otlp: false      # forward to a customer SIEM (the gate)
    # The customer SIEM endpoint is authored with the policy but reaches the
    # supervisor over OpAMP `other_connections` (ampbase.events_customer_otlp),
    # NOT this document — the supervisor ignores otlp_endpoint here.
    otlp_endpoint: ""

Defaults are local-first: tier metadata-only, both forwarding gates off, retention 100 MB / 7 days. Nothing leaves the workstation until an admin opts in. Unknown events.* fields are ignored (logged at debug), not rejected, so a newer dashboard can add fields without breaking older supervisors.

Field Values Meaning
events.tier metadata-only | no-content | full How much of each event survives redaction. no-content guarantees no prompt text leaves the device.
events.retention.max_megabytes int Size cap on the local JSONL event log. Default 100.
events.retention.max_age_days int Age cap on the local JSONL event log. Default 7.
events.forwarding.ampbase_ingest bool Forward redacted events to Ampbase ingest. Default false.
events.forwarding.customer_otlp bool Forward redacted events to a customer SIEM. Default false. The endpoint is delivered over OpAMP, not this document.
events.log_dir path Where the events log and status.json are published. ampbase enroll sets /var/log/ampbase for an enforced install so ampbase status and ampbase events tail work without sudo; unset keeps both in state_dir.

What the tier stamps on each record

Every forwarded record carries ampbase.redaction.location, saying where redaction ran for that record. A SIEM consuming records from more than one path needs it: without it, a record that was never redacted device-side is indistinguishable from one that was.

Value Set by Means
source+gateway supervisor, under metadata-only and no-content Two layers. The runtime's own content flags are pinned off at the source, and the gateway's field-wise redaction ran on top.
gateway supervisor, under full One layer. Content flags are pinned on — prompt text flows from the runtime by design — and only the gateway's hard-secret denylist ran.
none Ampbase, on the cloud-agent OTLP route No device-side redaction at all. There is no supervisor on that path, so no tier applies.

Read it as a scale: each step up is one more redaction layer between the runtime and the stream. The supervisor never stamps none — only the ingest route can know that device-side redaction never ran, so the value is set by the side that actually knows.

Codex CLI: managed configuration

Enabling codex-cli wires the [otel] exporters into /etc/codex/managed_config.toml — Codex CLI's native managed configuration layer (one Unix path for Linux and macOS; the enroll-generated service unit grants the supervisor write access to /etc/codex). Codex reads this file on every run and applies it above the developer's ~/.codex/config.toml and CLI --config overrides, so the managed telemetry wiring cannot be shadowed by per-user settings. No environment variables or extra steps are required.

Notes:

  • The supervisor manages otel.exporter, otel.metrics_exporter, and otel.log_user_prompt in that file; other admin-managed Codex settings are preserved. otel.log_user_prompt is driven by the policy tier: true under full, explicitly false otherwise, so a developer's own enablement cannot ship prompt content into a restrictive channel's gateway (source-side redaction beneath the gateway's per-tier redaction).
  • On macOS MDM fleets, Codex also accepts managed config via a device profile (com.openai.codex, config_toml_base64), which takes precedence over the file — avoid pushing an [otel] table that way, or it will shadow the supervisor's.
  • For user-context or CI installs where /etc is unwritable, redirect the writer with $CODEX_HOME (it then edits $CODEX_HOME/config.toml) or AMPBASE_CODEX_CONFIG_PATH.
  • On Codex releases predating managed configuration, the managed file is ignored — Codex shows as wired but silent in the fleet view; upgrade Codex or use the AMPBASE_CODEX_CONFIG_PATH redirection.
  • Workstations enrolled before supervisor v0.6.0 need a re-enroll (or regenerated unit) to pick up the /etc/codex write grant.

Gemini CLI: system settings

Enabling gemini-cli writes a telemetry block into Gemini CLI's system settings file — /etc/gemini-cli/settings.json on Linux, /Library/Application Support/GeminiCli/settings.json on macOS (the enroll-generated unit grants the supervisor write access to /etc/gemini-cli). Gemini reads this file natively at the highest precedence of any settings file — above the user's ~/.gemini/settings.json and any project .gemini/settings.json — so the managed telemetry wiring cannot be shadowed by per-user settings. Verified against google-gemini/gemini-cli v0.49.

Notes:

  • The supervisor owns telemetry.enabled, target, useCollector, otlpEndpoint, otlpProtocol, and logPrompts; other settings (including telemetry.traces and telemetry.outfile) are preserved. logPrompts is driven by the policy tier — true under full, explicitly false otherwise. Gemini's vendor default is logPrompts: true, so the explicit off-pin is what keeps a developer's own (or the vendor's default) enablement from shipping prompt content into a restrictive channel's gateway.
  • Gemini has a single enabled telemetry switch covering metrics and logs together (there is no per-signal split; traces is separate and left alone). A metrics-only or logs-only policy therefore turns on both.
  • The default OTLP/HTTP wiring reaches the gateway on Gemini >= v0.34.0 — no per-runtime change needed. Older Gemini (< v0.34.0) mis-posted OTLP/HTTP to the endpoint root instead of the /v1/{signal} paths (google-gemini/gemini-cli#15581), fixed upstream in v0.34.0 (2026-03-17). There is no per-runtime transport override today — the policy's otlp.endpoint / otlp.protocol are shared by every runtime, and moving them to a gRPC port would break Cursor's hook relay (it POSTs to that same endpoint) — so the fix for a pre-v0.34.0 Gemini is to upgrade Gemini, not to switch the fleet transport.
  • GEMINI_TELEMETRY_* environment variables and a lower-scope telemetry.outfile sit above what a settings file can control (env overrides every file; a merged outfile diverts telemetry to disk). These launch-environment surfaces are documented, not defeated.
  • For user-context or CI installs where /etc is unwritable, redirect the writer with AMPBASE_GEMINI_CLI_SETTINGS_PATH, or enroll with --user (it targets ~/.gemini/settings.json).

VS Code Copilot Chat: cooperative-mode only

Enabling vscode-copilot wires the GitHub Copilot Chat extension's native OpenTelemetry export by owning its github.copilot.chat.otel.* keys in VS Code's user settings.json (~/.config/Code/User/settings.json on Linux, ~/Library/Application Support/Code/User/settings.json on macOS). Verified against microsoft/vscode-copilot-chat's monitoring docs (2026-07). The supervisor owns otel.enabled, otel.exporterType, otel.otlpEndpoint, and otel.captureContent; every other setting is preserved.

This runtime is cooperative-mode-only. Unlike the others, VS Code exposes no admin-layer surface the supervisor can own to make the wiring non-shadowable: its enterprise-policy layer (/etc/vscode/policy.json) covers only the fixed set of settings VS Code declares as policies, and — as of the current policy reference — none of the github.copilot.chat.otel.* settings are among them (extensions cannot self-register policies). We therefore ship it cooperative-mode-only rather than counterfeit the enforced guarantee with a user-scope write:

  • ampbase enroll --user wires it in the developer's own settings.json.
  • In an enforced enrollment it is not wired: ampbase status, the fleet chip, and pipeline_status report it as cooperative-mode-only (a documented, deferred runtime — not a fault), and no user file is written. It will graduate to enforced automatically if VS Code grows an enforceable policy/managed-settings surface for the OTel keys.

Notes:

  • Copilot Chat has a single enabled telemetry switch covering metrics and logs together (traces is separate and left alone), like Gemini. The default OTLP/HTTP wiring reaches the gateway (the exporter posts to the spec /v1/{signal} paths). captureContent is tier-driven — true under full, explicitly false otherwise.
  • COPILOT_OTEL_* / OTEL_EXPORTER_OTLP_* environment variables override the settings file, and an org GitHub policy can disable Copilot telemetry above the supervisor entirely.
  • VS Code settings.json may be JSONC (comments / trailing commas). This writer edits it as strict JSON and refuses (degrades, never clobbers) a file it cannot parse as strict JSON, rather than stripping a developer's comments. Redirect non-standard installs (Insiders, portable) with the enroll --vscode-settings-path seam.

CLI flags

Flag Description
--config, -c Path to the supervisor config. Default: supervisor.yaml.
--insecure Disable TLS certificate validation. Development only. Intentionally a CLI flag (not a config field) to prevent accidental production use.
--version Print version and exit.

CLI subcommands

With no subcommand the binary runs the supervisor daemon. The subcommands below support the AI coding-agent flow.

ampbase enroll

One-shot enrollment for a coding-agent workstation: writes supervisor.yaml (agent.type: coding-agents), applies the initial runtime wiring, and installs + starts the platform-native service — a systemd unit on Linux, a launchd job on macOS.

sudo ampbase enroll --key agent_… --endpoint https://<org>.ampbase.io/channels/<channel>/v1/opamp
Flag Default Description
--key required Channel-scoped agent key.
--endpoint required OpAMP control-plane endpoint for this org + channel.
--user Cooperative (user) mode: no sudo anywhere. See below.
--config /etc/ampbase/supervisor.yaml (~/.config/ampbase/supervisor.yaml with --user) Where to write the generated config.
--state-dir /var/lib/ampbase (~/.local/share/ampbase with --user) Supervisor state directory.
--gateway-http-port 4319 (allocated + persisted with --user) Loopback gateway HTTP port the runtimes export to.
--runtimes claude-code Comma-separated runtimes to wire.
--transport http OpAMP transport (http or websocket).
--skip-service Write files and wire runtimes but don't install/start the service (for MDM base images).

Cooperative mode: ampbase enroll --user

--user enrolls with no sudo anywhere: the wiring targets the developer's own configs (~/.claude/settings.json, ~/.codex/config.toml, ~/.cursor/hooks.json, ~/.gemini/settings.json), the service installs per-user (systemd --user on Linux, a LaunchAgent on macOS; hosts without a user service manager — devcontainers, sandboxes — still enroll and get the foreground run line), and the loopback gateway ports are allocated once and persisted in the state dir so two users on one machine never collide and re-enrolls don't churn the runtime configs.

The trade is explicit and badged: in cooperative mode the developer can edit or stop everything — removal's only signal is the workstation going stale — so the fleet view, agent drawer, channel rollup, and ampbase status all label the workstation cooperative (enforced installs carry no badge). Policies can target the mode via the mode agent attribute, and a channel can refuse cooperative workstations outright with the Workstation modes: enforced-only channel setting (rejected connections read cooperative-disallowed). A cooperative supervisor that detects enforced (admin-layer) wiring for a runtime on the same machine stands its own writer down for that runtime and reports "enforced elsewhere" — so installing the system supervisor later never produces duplicate Cursor hook events, and graduation from cooperative to enforced is incremental.

ampbase status

Shows what this workstation collects and where it goes. Reads local files only — works offline, never touches the network.

ampbase status [--config <path>]

Prints the enrolled runtimes, the active tier, what stays on-device vs. what forwards, and the local log path. When the host isn't enrolled it says so.

Both commands colour their output when stdout is a terminal, and print plain text when it is redirected or piped, when NO_COLOR is set, or under TERM=dumb. events tail --json is never coloured.

Neither command needs sudo. That is the point of them: the developer being observed can see what is collected, on any enrollment, from their own account.

Without --config, both read this machine's enrollment: the per-user config a cooperative (--user) enrollment writes if there is one, else /etc/ampbase/supervisor.yaml. On an enforced workstation neither of those files is readable by a developer — supervisor.yaml carries the agent key, state.json the per-instance credential, and both stay root-only. So the supervisor publishes the answer instead of the sources:

Path Mode Holds
/var/log/ampbase/status.json 0644 what the status screen prints — tier, forwarding, wired runtimes, policy version, log path
/var/log/ampbase/events.jsonl 0644 the post-redaction events log
/var/lib/ampbase/state.json 0600 the credential and the applied config — never published

Both files are rewritten on every config apply, and once at enrollment so a freshly enrolled workstation answers before its first apply. status.json carries only what the screen already shows: no key, no destination URL, no header.

On a shared enforced host this means any local user can read the coding-agent activity of any other. If that is not the trade you want for a particular fleet, point supervisor.gateway.events.log_dir at a directory with the ownership you need — the supervisor writes both files there instead, and the commands fall back to sudo.

A cooperative (--user) enrollment keeps both files in the per-user state directory, which the developer already owns.

ampbase events tail

Prints the most recent post-redaction events from the local JSONL log — precisely what would leave the device, not a summary of it.

ampbase events tail [-n 20] [--json] [--config <path>]
Flag Default Description
-n, --lines 20 Number of trailing events to show.
--json Print raw JSONL lines instead of the formatted one-line view.

State directory

The supervisor persists state under state_dir as two files:

  • state.json — last-applied config hash, the effective config and the previous config (both inline as base64-encoded payloads keyed by filename), the managed agent's PID for re-adoption on supervisor restart, and the restart counter. On startup the supervisor reapplies effective_config so a brief outage on the Ampbase side never leaves agents config-less; previous_config is the rollback target if a future apply fails.
  • instance_uid — the 26-character ULID that identifies this supervisor instance to OpAMP. Persisted so a restart re-adopts the same agent record rather than registering as a new one.

The directory is created with mode 0700 on first start; do not relax those permissions. state.json is written 0600 — it carries the per-instance credential this workstation authenticates with.

The events log does not live here on an enforced install: it and status.json are published to events.log_dir (/var/log/ampbase) so a developer can read them without sudo. See ampbase status.

Component inventory

The supervisor reports which components are compiled into the agent binary it manages — the receivers, exporters, plugins and sinks that build actually has. Ampbase uses it to decide whether a config version can run on a given agent before routing it there (see Feature flags, under Build eligibility).

Requires supervisor 0.14.0 or newer. Older supervisors don't report an inventory at all, and Ampbase says so rather than guessing.

What runs

Each agent type is enumerated with its own command, on the binary at agent.binary_path:

Agent type Command
otelcol otelcol components
fluent-bit fluent-bit --help-json
vector vector list --format json
telegraf telegraf plugins

refinery, tetragon and coding-agents have no build-time component axis — all builds are equivalent and configuration decides everything — so nothing is enumerated for them, and they are never held back for a missing component.

Components are identified as {kind}/{name} in the agent's own vocabulary: exporter/kafka on a collector, output/kafka on a Fluent Bit, inputs/cpu on a Telegraf. Those are unrelated identifiers, never translated into a shared naming scheme — a config written for one agent's vocabulary is only ever compared against a build of that same agent.

When it runs

  • At supervisor start, so the first message Ampbase receives already carries the inventory.
  • After each successful config apply. A failed apply is rolled back, so the agent is still running the config the stored inventory was read against and nothing is re-read.

The result ships on the next check-in rather than with the apply itself, so it lands one cycle behind.

Re-reading after an apply is what makes a fresh enrollment converge. Fluent Bit's inventory depends on its config (below), so an agent that has just enrolled and been given nothing yet has nothing to enumerate. Its agent page reads Not yet reported until its first config applies, then fills in on the following check-in. That's the expected sequence on enrollment day, not a fault.

A binary swapped underneath a running supervisor without a config apply isn't noticed — nothing watches the file — so the reported inventory stays as last measured until the next restart or apply.

Fluent Bit: external plugins are included

Fluent Bit's --help-json enumerates only what's loaded, and it doesn't read your config. So the supervisor reads the plugin paths your config declares and passes them to the enumeration itself, which means config-declared external plugins appear in the inventory alongside the built-ins. Duplicate paths are collapsed first, so a plugin declared twice is listed once.

A plugin path that fails to load makes the whole enumeration fail — which is the right answer, because it's the same failure Fluent Bit itself would hit when starting with that config.

When enumeration fails

If the command errors, times out, or produces output the supervisor can't parse, the result is "we could not look" — never an empty inventory:

  • If the supervisor has read the build successfully before, it keeps that inventory and reports nothing new. A binary that hasn't changed is still described by the last good reading.
  • If it has never succeeded, nothing is reported, and the agent shows as Not yet reported in Ampbase.

Either way the reason is logged on the host at warn level, which is where to look first. Ampbase counts an agent it can't describe as unknown — neither able nor unable to run a given config — and unknown is stated everywhere it applies rather than folded into either answer.

The consequence to know: an agent whose build Ampbase can't establish is held back from flag variants that require components, and keeps receiving the channel default.

Metrics

The supervisor exposes Prometheus metrics on metrics_port. Key series:

  • opamp_supervisor_connected — 1 when the OpAMP session is open.
  • opamp_supervisor_config_updates — config-update counter.
  • opamp_supervisor_agent_health — last health-check result (gauge).
  • opamp_supervisor_agent_restarts — agent restart counter.

Pull these into the same scrape that grabs your collector's metrics for end-to-end visibility.

Full example

version: 1

server:
  endpoint: wss://acme.ampbase.io/channels/clickhouse/v1/opamp
  api_key: agent_01hxj7k9_8f2c4a7e9b1d3a5f7e2c4a7e9b1d3a5f7e2c4a7e9b1d3a5f
  transport: websocket
  max_message_size_kb: 64

agent:
  type: otelcol
  binary_path: /usr/local/bin/otelcol
  config_path: /etc/otelcol/config.yaml
  health_check_url: http://localhost:13133/
  stop_timeout_seconds: 15
  config_format: yaml
  attributes:
    env: production
    region: us-east-1
    role: edge-collector

supervisor:
  state_dir: /var/lib/ampbase
  log_level: info
  log_filter: "ampbase::transport=debug,info"
  health_check_delay_ms: 2000
  startup_delay_ms: 1000
  reload_grace_period_ms: 5000
  metrics_port: 9464
  max_memory_mb: 512

Spotted a problem with these docs? Email support@ampbase.io.