The Agent
The agent runs CCM continuously. It resolves facts and external data, fetches manifests from one or more sources, applies them on an interval, and runs health checks that can trigger a remediating apply between intervals. It is a single scheduler driving many per-source workers.
Where it lives
agent: the loop and its workers. Key files: agent/agent.go (the Agent type and the
control loop), agent/worker.go (the per-manifest worker), agent/config.go,
agent/http.go and agent/object.go (remote source watchers), agent/nc.go (a caching NATS
connection).
One scheduler, many workers
The Agent (agent/agent.go:30) creates one worker per manifest. Each worker only watches
its own source and requests an apply; it never schedules one. A single applyTicker in Run
drives every scheduled apply, which serializes fact refreshes and prevents two manifests from
applying at once. Workers signal the loop through a buffered-size-one applyTrigger channel,
and the send is coalesced so a burst of source changes collapses into one priority apply.
- DefaultInterval
- 5 minutes. The apply cadence, floored at
MinIntervalof 30 seconds. - MinFactUpdateInterval
- 2 minutes. Facts are not re-gathered more often than this, independent of the apply interval.
- applyTrigger
- Buffered channel of size one. A worker whose source changed pushes a priority apply that runs even inside the interval window.
- Sources
- Dispatched by scheme in
worker.cacheManifest:obj://to a JetStream object store watcher,http(s)to a conditional-GET fetcher, empty scheme to a local file.
The loop
Facts, data, and resilience
Facts and data resolution sit behind a mutex and a retry policy. getFacts skips entirely
when the cached facts are younger than the 2-minute floor. Otherwise it retries under jittered
backoff, and after a configured number of failures it falls back to the last good facts rather
than blocking the loop. getData resolves external data through Hiera on each cycle and falls
back the same way. This is why a transient NATS or HTTP outage does not stall applies: the
agent keeps running on the last known-good inputs.
Health checks are deliberately independent of applies. runHealthChecks runs each worker in
health-check-only mode and does not refresh facts or data. A worker reporting a critical result
increments a remediation counter and queues a priority apply, but the queued applies fire only
after every check completes, so applies and checks never interleave.
Metrics and shutdown
When a monitor port is configured, the agent registers Prometheus collectors and serves
/metrics. It exposes apply and health-check durations, remediation counts, manifest fetch
counts and errors, and facts and data resolve timing. Shutdown is graceful: Run returns on
context cancellation after waiting for the workers, and Stop closes the manager. Workers use
a cancel-with-cause context, so a manifest deleted from an object store propagates a readable
reason.
Load-bearing decision
All fact and data mutation happens under the agent mutex, and every scheduled or triggered apply acquires it. The single ticker plus the size-one trigger channel is what keeps concurrent manifests from applying over each other. Removing the shared lock or the single scheduler would reintroduce apply races.
Two items are reserved rather than active. AgentHealthCheckTime is registered but never
observed, so the health-check duration series stays empty. A TODO in agent.go notes the
intent to watch the KV for external data and only re-fetch on change, rather than re-resolving
every cycle.
Next
Continue to Data, Facts, and Templates to see how the inputs the agent refreshes are produced.