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 MinInterval of 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

Interval tick · 5mfloor 30sRefresh facts + dataApply each manifestWorkers watch sourcesfile · http · obj://Health tickRun checksno fact refreshcritical?remediateno → ok
Scheduled applies on the left; health checks and remediation on the right feed the same apply step.

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.