OpAMP Fleet is a Rust implementation of OpenTelemetry
OpAMP-based fleet management: an
API-first Server that manages a fleet over the protocol and exposes an
OpenAPI-described REST API for any UI or portal, and a Client that
supervises many managed processes at once — OpenTelemetry Collectors and,
through plugins, foreign agents that do not speak OpAMP — and that can equally
run as a gateway multiplexing other clients upstream. The work is driven by
a written specification (docs/SPECIFICATION.md)
and Architecture Decision Records (docs/adr/), so intent and
the reasoning behind every structural choice stay explicit and reviewable. How
much of the protocol each end implements is tracked in
docs/CONFORMANCE.md; candidate measures for hardening
the Client–Server link further are collected — as a backlog, not as decisions —
in docs/HARDENING.md.
📖 Running it? Read the User Manual — what each end can do, how to start it, and every configuration key, split into Server and Client. Setting up a fleet for the first time? Start with Setting up a fleet: the certificates, both configurations, and enrolment, in order.
For agent instructions, see
AGENTS.md— the single source of truth for all coding agents.
A telemetry fleet is a heap of agents on a heap of machines, each configured by a local file. That works for one agent and breaks down for a fleet: changing what a hundred agents do means reaching a hundred machines, and nobody can say with certainty what each one is actually running. Configuration drifts, rollouts are ad-hoc, and a bad configuration shows up as missing telemetry rather than as a report.
OpAMP — the Open Agent Management Protocol — closes that loop: an agent accepts configuration over the protocol and reports back what it applied and how it is doing. OpAMP Fleet is a Rust implementation of both ends, built for a heterogeneous fleet — OpenTelemetry Collectors and agents that were never built to speak OpAMP:
- Server — an API-first control plane (Linux). It holds the configuration the fleet should run, tracks what each agent reports back, and only reconfigures an agent whose configuration actually differs. Its contract is an OpenAPI-described REST API, so any UI or portal can read the fleet's state and change what it runs; the Server ships only a rudimentary UI of its own and is built to be integrated into an existing portal.
- Client — one process, installed as a native operating-system service on
Linux, macOS, and Windows and able to update its own binary in place. It has
two modes, independent of each other and combinable on the same host:
Supervisor Mode runs many supervisors at once, each managing one
process, applying the configuration it is sent and reporting health and
effective configuration back; Gateway Mode accepts other clients' OpAMP
connections and folds them onto a small pool of upstream ones, so a fleet can
grow past one connection per agent. Every supervisor also exposes a
Supervisor Endpoint on loopback — not a mode of its own, but part of what
a supervisor is — because the Collector's
opampextensionis a client only and needs something to connect to; a Collector carrying it reports through that endpoint instead of being watched from outside. A Collector supervisor manages a Collector natively; a custom supervisor manages a foreign agent — an agent of a kind the project does not already know, needing a plugin written for it — by translating its lifecycle into the protocol. - Plugins over a hexagonal core — supervisors are plugins behind stable ports. Bringing a new kind of process under management means writing a plugin, not changing the core, so the same control loop reaches agents OpAMP was never designed for.
- The protocol, in full and on the record — both ends implement OpAMP as
completely as the protocol allows, against a pinned upstream version, with
every capability's status and maturity written down in
docs/CONFORMANCE.mdrather than left to be discovered.
The goal is one place — reachable by any UI — to decide what every agent in the
fleet runs and to see what each one is really running, whether or not it speaks
OpAMP. The full problem statement, goals, vocabulary, and non-goals live in the
specification (docs/SPECIFICATION.md); the
reasoning behind each structural choice lives in the ADRs
(docs/adr/).
The picture keeps the shape of the OpAMP reference architecture — a supervisor owning a Collector, exchanging OpAMP with a backend — and extends it with what makes OpAMP Fleet different: an API-first Server on two listeners, whose contract is an OpenAPI REST API, a single Client whose two modes compose freely, Supervisors whose compiled-in kinds each know their agent behind a hexagonal core — each exposing a Supervisor Endpoint for a Collector that speaks the protocol itself — and a Connection Pool that carries many Agents over few connections, every one of them admitted by a client certificate.
The image carries its Excalidraw scene: open the PNG there to edit it, and export it again with "Embed scene" switched on.
On the wire the Server sees only Agents, told apart by instance_uid and
never by the connection that carried them — so whether an Agent is a Supervisor
running a Collector or a Foreign Agent, a Collector reporting through its own
opampextension, or a Client several hops away behind a Gateway is invisible to
it. Every connection to the Agent plane is TLS 1.3 with a client
certificate, which the Server's client CA issues once an operator approves a
host's enrolment and renews by itself afterwards; the Operator plane, where
the REST API and the UI live, is a listener of its own and stays on the loopback
unless it is published behind a password. The Supervisor Endpoint is bound to
loopback and admits only the process its Supervisor started, by a token. A
Supervisor's kind is the authority on its agent: collector serves every
Collector, with or without the extension; command runs any other program and
observes it from the outside. Adding a new kind of managed process means writing
another kind against the same ports — the core does not change. The terms used
here (Server, Client, Agent, Client Modes, Supervisor Endpoint, Connection Pool,
Foreign Agent, Plugin, Port, Selector, Package, Deployment, …) are defined in
docs/SPECIFICATION.md.
- VS Code with the Dev Containers extension, or any DevContainer-compatible IDE
- Docker / Podman (rootless) available on the host
- Open the repository in VS Code and choose Reopen in Container. The Dev
Container and the preconfigured agent extensions build automatically, and the
container enables the repository's git hooks (
.githooks/): no commit onmain, and the checks run before a push. Working outside the container? Enable them yourself, once per clone:git config core.hooksPath .githooks. - Authenticate your coding agent inside the container (for Claude Code:
claude login). - Start working with the agent. Drive the work from the specification and the ADRs.
The toolchain is Rust stable, provided by the Dev Container; the code is one
Cargo workspace with five crates — opamp (the OpAMP communication layer,
publishable on its own, with an Agent's client and a server endpoint with their
TLS and listener behind the client and server features, ADR-0010),
fleet-core (what both ends share beyond the protocol), fleet-server (the
Server), fleet-agent (the Client, in all its modes), and fleet-tools (the
operator command-line tools, ADR-0010). This section is the single source for
build/test/run commands — both humans and agents rely on it (AGENTS.md links
here).
- Build:
cargo build --workspace - Test:
cargo test --workspace - Lint:
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings - Lint
opampper feature:for f in "" client server; do cargo clippy -p opamp --all-targets --no-default-features --features "$f" -- -D warnings; done— inside the workspace Cargo buildsopamponce with every feature any crate asks for, so only a build of each feature on its own shows that it stands alone (ADR-0010). - Lint and test
fleet-agentwith fewer Supervisor kinds:cargo clippy -p fleet-agent --all-targets --no-default-features -- -D warnings && cargo test -p fleet-agent --no-default-features— a wrapped kind, when the code base carries one, is a feature, so only these show that a build leaving one out compiles and refuses itstype(ADR-0017). - Check the Windows build:
cargo xwin clippy -p fleet-agent --all-targets --target x86_64-pc-windows-msvc -- -D warnings(needscargo install cargo-xwinandrustup target add x86_64-pc-windows-msvc; the Dev Container carries thellvm-libit requires). Worth running whenever a change touches platform-gated code or the tests around it: CI builds the Client on Windows and macOS, and a#[cfg(unix)]mistake compiles perfectly well on Linux. - Check the supply chain:
cargo deny check(needscargo install cargo-deny): advisories, licences, sources and banned crates, asdeny.tomlsets them; reviewed advisories are recorded there with a reason - Fuzz a parser:
cargo +nightly fuzz run <target> fuzz/corpus/<target> fuzz/seeds/<target>(needscargo install cargo-fuzzand a nightly toolchain); the targets are listed infuzz/Cargo.toml, and every parser that reads bytes from the network or a downloaded artifact has one (specification Q-2) - Check interop against
opamp-go:cargo test -p fleet-agent --test interop_opamp_go -- --ignored --nocapture --test-threads=1(needs a Go toolchain onPATH, which nothing else here needs and the Dev Container does not ship); the scheduledInteropworkflow runs it weekly, andinterop/README.mdsays what it covers and how a red run is triaged (ADR-0011) - Make development certificates:
scripts/dev-pki.sh— both binaries refuse to run without TLS (ADR-0013); it runs the operator tool'sopamp-fleetctl pki init(ADR-0028), no other tool, and writes the set and aserver.tomlandsupervisor.tomlthat use it to.dev-pki/. The development Client enrols with the bootstrap certificate;scripts/seed_test_configs.sh --enrolapproves it. The VS Code launch configurations run it when.dev-pki/has no configuration yet - Run the Server:
cargo run -p fleet-server -- --config .dev-pki/server.toml - Run the Client:
cargo run -p fleet-agent -- --config .dev-pki/supervisor.toml - Run the operator tool:
cargo run --bin opamp-fleetctl -- package fetch(fetch a known agent's release and hand it to the Server),cargo run --bin opamp-fleetctl -- package --help(build, hash, and sign an artifact out of any program) orcargo run --bin opamp-fleetctl -- pki --help(the fleet's certificates) — documented in the manual. Every release also publishes it for Linux and macOS (ADR-0028).
Both binaries read a TOML configuration file
(ADR-0010);
every setting has a default except the TLS material, without which neither
starts. The annotated examples live in config/.
scripts/check-rust.sh runs exactly these
build/test/lint commands, locally through scripts/check-all.sh and in CI as
the rust job of
.github/workflows/checks.yml;
.github/workflows/ci.yml additionally checks the
Client on Windows and macOS and release-builds the Client for Linux, Windows,
and macOS and the Server for Linux.
Releases
(ADR-0019,
ADR-0012): the
version is [workspace.package] version in Cargo.toml, and the
Release workflow makes the version/* tag from it before it builds — so
bumping the version is an ordinary reviewed commit and nobody types a tag.
Running it publishes one archive per platform,
supervisor_<version>_<os>_<arch>.tar.gz for Linux, macOS and Windows on the
architectures each ships on, a CycloneDX SBOM <stem>.cdx.json beside each
archive of the Client and of the operator tool, and a SHA256SUMS file. Every
asset is a subject of one build-provenance attestation, which the workflow
verifies before it creates the release
(ADR-0045);
an operator checks a download with
gh attestation verify SHA256SUMS --repo neteye-platform/classis --signer-workflow neteye-platform/classis/.github/workflows/release.yml
and then sha256sum -c --ignore-missing SHA256SUMS. The files are named after
the Set an operator uploads them to, not after the product inside them
(ADR-0019)
— and the program inside them and its configuration file are called supervisor
too. The dpkg/rpm/MSI package and the service carry the product's name,
opamp-fleet
(ADR-0019):
that is the name that identifies an installation, and a second one is a second
build rather than a flag. The fields are separated by _ because a name and a
version both contain -
(ADR-0019),
and the last two are exactly what an Agent reports as os.type and host.arch
(linux_amd64, darwin_arm64, …), so uploading a whole release under one
package name needs no translation
(ADR-0026).
Started with dry_run (the default) it builds and packs everything and
publishes nothing. Before it builds anything at all it checks that the version
is still free — a version/* tag or a release already carrying that number
fails the run on the spot, dry or not, so a forgotten bump costs seconds rather
than five build jobs — and the built binary must report the version the
artifacts are named after. Each archive is also a ready package artifact: the
same file an operator downloads is the one a fleet is handed for a Client
self-update.
The Server ships as a container image and nowhere else
(ADR-0052):
ghcr.io/mbrigl/opamp-fleet-server:<version> for linux/amd64 and
linux/arm64, built by
packaging/container/Containerfile from
the Server binaries the release built and checked, on a distroless base pinned
by digest, with no shell, running as UID 65532. The release pushes it by digest,
attests the digest, verifies it, and only then tags it with the version; there
is no latest tag, and the release notes name the digest to deploy. Its SBOMs
are server_<version>_linux_<arch>.cdx.json beside the other assets. Check an
image with
gh attestation verify oci://ghcr.io/mbrigl/opamp-fleet-server@<digest> --repo mbrigl/opamp-fleet --signer-workflow mbrigl/opamp-fleet/.github/workflows/release.yml;
running it is in the Server's manual.
This section is a tour. The complete operator reference — every option and every configuration key of both ends — is the User Manual: Setting up a fleet · Server · Client · Command-line tools.
A minimal closed control loop on one machine:
- Start the Server:
cargo run -p fleet-server -- --config config/server.toml— it serves two planes on two ports (ADR-0013). The Agent plane on4320: the OpAMP endpoint at/v1/opamp(plain HTTP and WebSocket, ADR-0013) and the package downloads the offers point at. The Operator plane on127.0.0.1:4321: the REST API under/api/v1/(ADR-0023), the API docs, and the bundled UI at/— on loopback, because it is open until[rest.auth]guards it with Basic credentials (ADR-0021). - Start a Client:
cargo run -p fleet-agent -- --config config/supervisor.toml— it connects over WebSocket by default (ws://127.0.0.1:4320/v1/opamp), reports its description and health, and appears in the fleet. Pointendpointat anhttp(s)://URL to use the polling transport instead. - Open the UI at http://127.0.0.1:4321/ — the Agent is listed as
Connected. Press Configurations, create a Configuration with its
configuration text, and press OK. Then press Deployments, create a
deployment whose Selector (
key=valuepairs an Agent's reported attributes must equal, never empty) matches the Agent, add the Configuration, press OK, and press the row's Roll out — a Configuration reaches Agents only through a Deployment (ADR-0034). - Watch the loop close: a WebSocket Client the Deployment claims receives
the configuration within a second, an HTTP Client on its next poll. The Agent
stores it (under its
state_dir), reports it Applied with the matching hash, and its effective configuration shows up in the table. Distributing the same configuration again sends nothing — the config-hash comparison gates every push. An Agent receives every Configuration its Deployment holds for its type as named entries and merges them itself; an Agent in no Deployment is left running what it already runs.
The same operations are available to any portal through the REST API — the
OpenAPI document at /api/v1/openapi.json is the contract to generate a client
from:
$ curl http://127.0.0.1:4321/api/v1/agents # the fleet, with reported attributes
$ curl http://127.0.0.1:4321/api/v1/configurations # every Configuration
$ curl -X PUT -H 'Content-Type: application/json' \
-d '{"service_name": "otelcol", "body": "receivers: {}"}' \
http://127.0.0.1:4321/api/v1/configurations/linux-base # content for one Agent type
$ curl -X PUT -H 'Content-Type: application/json' -d '{"selector": {"os.type": "linux"}}' \
http://127.0.0.1:4321/api/v1/deployments/linux # aimed at a subset
$ curl -X PUT http://127.0.0.1:4321/api/v1/deployments/linux/configurations/linux-base
$ curl -X POST http://127.0.0.1:4321/api/v1/deployments/linux/rollout # distribute
# Content the agent reads by path rather than is configured with (ADR-0023): written next to the
# configuration under its own name, never passed to the process as configuration.
$ curl -X PUT -H 'Content-Type: application/json' \
-d '{"service_name": "otelcol", "body": "rules: []", "role": "supplementary"}' \
http://127.0.0.1:4321/api/v1/configurations/ruleset
# A package defines a Set (ADR-0026), identified by name, Agent type, and version, with one entry
# per platform (ADR-0026); each Agent is offered the entry that fits it. Saving stages a draft —
# nothing reaches the fleet until the Set is published (ADR-0025).
$ curl -X PUT -H 'Content-Type: application/json' -d '{}' \
http://127.0.0.1:4321/api/v1/packages/otelcol/otelcol-contrib/0.109.0
$ curl -X PUT --data-binary @otelcol-linux-amd64.tar.gz \
http://127.0.0.1:4321/api/v1/packages/otelcol/otelcol-contrib/0.109.0/entries/linux/amd64
$ curl -X PUT -H 'Content-Type: application/json' -d '{"published": true}' \
http://127.0.0.1:4321/api/v1/packages/otelcol/otelcol-contrib/0.109.0/publication
# Rolling back is a publication move (ADR-0026): retract the newest version, and the fleet falls
# back to the newest one still published under the same name.
$ curl -X PUT -H 'Content-Type: application/json' -d '{"published": false}' \
http://127.0.0.1:4321/api/v1/packages/otelcol/otelcol-contrib/0.109.0/publicationFor TLS, give the Server a certificate ([tls] in server.toml) and the Client
a wss:// or https:// endpoint — plus ca_file under [tls] when the
certificate comes from a private CA. The UI and the REST API can present a
certificate of their own, for example from your organization's PKI, under
[rest.tls]; without it they present [tls]'s.
The Client registers itself as a native service on Linux (systemd), macOS (launchd), and Windows (SCM) — ADR-0019:
supervisor service install --config /etc/opamp/supervisor.toml # system service (root/Administrator)
supervisor service start
supervisor service status
supervisor service stop
supervisor service uninstall # never deletes layout or state-
One service per build: the service is named after the product,
opamp-fleet, with no suffix and nothing to look up — so the verbs above take no name at all. A second installation on one host is a second build with its ownPRODUCT_NAME, not a runtime flag (ADR-0019). -
Two roots:
--root <dir>given alone puts everything under the one directory it names; nothing is ever installed to a fixed path. Without it, a Linux system install splits the defaults: the executable layout —versions/supervisor-<version>-<commit>/and thecurrentpointer the service runs from — lives at/opt/opamp-fleet, whilesupervisor.tomland the defaultstate/directory stay at/var/lib/opamp-fleet(SELinux never lets systemd execute a binary under/var/lib).--data-root <dir>names that second half explicitly. macOS, Windows and user scope keep one directory for everything — including a Windows host installed by the MSI, whereProgram Filesholds the delivered payload and the layout goes under%ProgramData%\opamp-fleet, because the self-update rewrites the layout at runtime andProgram Filesis not a tree a service account should be able to write. -
Scope:
--usertargets the user-level manager (development); the default is a system service that starts at boot. -
Stopping the service sends the OpAMP
agent_disconnectgoodbye (SIGTERMon Unix, an SCM stop control on Windows); after a crash the manager restarts the service, after an explicit stop it stays down. -
Self-update (ADR-0018): the Client is always its own Agent, so the Server can see which version each host runs. Letting the Server replace that version is a consent that stands by default, takes effect once a verification key is set, and takes only the package named after the Client's own Agent type,
supervisor— anything else is refused, because a package aimed at the whole fleet would otherwise be written over the Client itself:[packages] self_update = true # the default; false keeps this Client's update manual verification_key = "<hex-encoded Ed25519 public key>"
A new version is staged beside the running one under
versions/, run once to prove it is this program at the version offered, and switched to by movingcurrent. The Client then exits and the service manager starts the new version; one that does not reach the Server within a few restarts is rolled back to its predecessor, and either outcome is reported to the Server by whichever version came up.
The Service smoke workflow exercises the real thing on an ephemeral runner
— install, start, the Agent appearing in the fleet, its process killed and
brought back by the manager, an explicit stop that stays stopped, uninstall —
and an install that enrols with a bootstrap.p12 from --fleet-files, whose
service then reaches the Server on the certificate it was issued. It runs weekly
and on demand rather than per push (it installs a system service and waits on
timers), on Linux, macOS and Windows. The test is
crates/fleet-agent/tests/service_smoke.rs; it is #[ignore]d, so an ordinary
cargo test never installs anything.
What still needs a human, per platform: starting at boot (a runner never
reboots), the logs (journalctl -u opamp-fleet on Linux, Console/log show on
macOS), the Agent in the fleet UI, and an SELinux-enforcing host (Fedora,
RHEL, or SUSE 16 with getenforce answering Enforcing): the .rpm install
must start — a service dying with status=203/EXEC and an AVC denial in
ausearch -m avc is the failure ADR-0019 clause 8 exists to prevent. Each
wrapped kind — Telegraf, Icinga 2 with its plugins, GLPI Agent with its tasks —
under the packaged account opamp-fleet and under --run-as root: it starts,
reports, and its sudo or setuid helpers still work
(ADR-0042).
Known platform gaps (tracked in the ADR): launchd status is advisory and
install does not auto-start there. The SCM still discards a Windows service's
stderr, but the service now writes its own rotating log under <state_dir>/logs
on every platform (ADR-0019), which is where to look when the manager shows a
service that will not start.
README.md # overview & setup for humans
AGENTS.md # single source of truth for coding agents
docs/manual/ # the user manual: setting up a fleet, then Server, Client, and the operator tools, option by option
docs/SPECIFICATION.md # the specification: problem, goals, vocabulary
docs/GLOSSARY.md # the vocabulary everyone uses, kept current inline
docs/CONVENTIONS.md # how this project writes what no check decides, kept current inline
docs/ARCHITECTURE.md # the system as it currently stands
docs/CONFORMANCE.md # OpAMP Protocol Baseline + capability conformance matrix
docs/SECURITY-MODEL.md # the Dev Container, secrets, pinned actions, the fleet trust model, supported versions
docs/HARDENING.md # candidate hardening measures for the Client-Server link (a backlog, not decisions)
docs/adr/ # Architecture Decision Records (+ template)
crates/ # Cargo workspace: opamp (shared) · fleet-core · fleet-server · fleet-agent · fleet-tools (operator CLIs)
config/ # annotated example configuration files (server.toml, supervisor.toml)
packaging/ # what release.yml packs with: Linux maintainer scripts, the WiX sources, the Server's Containerfile
scripts/ # consistency checks and sensors (check-all.sh runs them all), run in CI
scripts/check-docs.sh # documentation & protocol-baseline consistency checks
interop/ # the Go side of the interop check against opamp-go (ADR-0011)
observability/ # the development observability stack — Collector, ClickHouse, Grafana — run on the host
rust-toolchain.toml # pinned Rust toolchain (an exact stable release + rustfmt + clippy)
.githooks/ # git hooks: refuse a commit on main and a push while the checks are red
.github/ # CI workflows, issue & pull request templates
renovate.json # Renovate: the organisation preset, with Cargo requirements kept as ranges (ADR-0060)
.devcontainer/ # Dev Container definition (base image + Features + the OTLP forwarder)
.vscode/ # shared editor settings
.editorconfig # editor-neutral formatting baseline
.gitattributes # line-ending normalization (LF everywhere)
.agents/skills/ # the procedures of AGENTS.md as Agent Skills, one directory each (ADR-0005)
.claude/CLAUDE.md # pointer for Claude Code to read AGENTS.md
.claude/skills/ # pointers (one symlink per skill) for Claude Code to read .agents/skills/
.claude/settings.json # Claude Code permissions: deny reading .env files (see docs/SECURITY-MODEL.md)
The environment is defined by
.devcontainer/devcontainer.json: a prebuilt
base image with Dev Container Features and VS Code extensions layered on top —
no Dockerfile. Customise it by adding Features, switching the base image, or
adding extensions. Features are pinned by major tag and resolved in the
committed devcontainer-lock.json. Extensions are listed by identifier only,
because a published extension version cannot be repointed and a version suffix
is VS Code-specific.
The development observability stack — Collector, ClickHouse and Grafana — is a
Compose project of its own in observability/. It
runs on the host, apart from the container, and is started when you want it:
docker compose -f observability/compose.yaml up -d # on the host, from the repository rootThe container reaches the Collector through the host. Since the Client sends own
telemetry in plaintext to a loopback IP literal alone
(ADR-0016),
.devcontainer/forward-otlp.sh puts the host's
Collector on the container's loopback on every start: http://127.0.0.1:4318
inside the container is the Collector on the host, so a server.toml naming
http://127.0.0.1:4318/v1/logs means the same thing in both places. Grafana is
on http://localhost:3001 in the host's browser.
The Dev Container deliberately has no access to the host Docker daemon: the
socket is not mounted (ADR-0002). To
manage the host's containers from VS Code, run the Container Tools extension
(ms-azuretools.vscode-containers) on the host side: install it in your
host VS Code. .vscode/settings.json already pins it
to run locally via remote.extensionKind, so it keeps talking to the host
engine even when this folder is reopened in the container.
This Dev Container preinstalls the Claude Code and Mistral Vibe VS Code
extensions (see
.devcontainer/devcontainer.json); other
agents (OpenAI Codex, Cursor, OpenCode, GitHub Copilot) work too once you add
them.
The rules every agent follows live in AGENTS.md; that there is
exactly one such file is decided in
ADR-0001. An agent that cannot read
it natively gets a pointer file instead.
.claude/CLAUDE.md is the one shipped here; it carries the
pointer and no rules of its own (AGENTS.md §3). It
exists for one gap only: Claude Code loads CLAUDE.md, not AGENTS.md, tracked
upstream as
anthropics/claude-code#34235.
Once that lands, delete the pointer file rather than keeping a second file to
maintain.
The procedures those rules name (proposing an ADR, planning a feature, reviewing
a change, revising the design) are Agent Skills under
.agents/skills/, the open format and the directory Codex,
Gemini CLI, and Cursor read (ADR-0005).
An agent invokes one by name (/review) or picks it up when the task matches
its description. Claude Code reads .claude/skills/ only, so that directory
holds one symlink per skill into .agents/skills/; a directory-level symlink is
not followed. The symlinks are pointers of the same kind as CLAUDE.md, tracked
upstream as
anthropics/claude-code#31005.
Delete them when that lands, and add one for every skill added meanwhile; the
documentation check fails on a skill without its pointer. A rule is enforced
only by what every agent hits: the git hooks under .githooks/,
the checks, and the repository settings below. A tool's own configuration is not
used to enforce one, because a gate that one agent honours and the next does not
looks like a rule and is not one.
Some of this project's rules cannot be enforced by files in the repository
(AGENTS.md §6). They are settings on the GitHub
repository itself, configured once by a maintainer and worth re-checking after a
repository move or transfer:
- a ruleset on
mainthat requires pull requests with one approving review, requires thedocs,traceability,devcontainer,actions,sensors,shell,rust,client-windows,client-macos,s3-store, andpostgres-storejobs as required status checks — every job of the Checks workflow, the Client's Windows and macOS jobs of the CI workflow, and its jobs that run the S3 and PostgreSQL stores' tests against a real store (rulesets list checks by their job name) — and blocks force pushes and branch deletion; - enable secret scanning with push protection, the only mechanical backstop
behind the secrets rule (
AGENTS.md§7), which is otherwise carried by review alone; - enable private vulnerability reporting (see
SECURITY.md). - after the first release that pushes the Server's image, set the package
opamp-fleet-serveronghcr.ioto public and linked to this repository: a package a workflow publishes for the first time is private, and operators pull the image without an account (ADR-0052).
- Template release: unreleased — the release of NUC this repository carries; a project moves the line when it takes up a later one (ADR-0008).
See CONTRIBUTING.md for the workflow (specification- and
ADR-driven, small reviewable changes) and
CODE_OF_CONDUCT.md for the community standards we expect
of everyone taking part. Security issues: please follow
SECURITY.md instead of opening a public issue.
Licensed under either of the MIT License or the
Apache License 2.0, at your option — see also
NOTICE.
