Skip to content

Operations

apply converges the host to agents.yaml and restarts exactly what changed. Run it after editing agents.yaml, after apt upgrade agentctl, or to heal drift.

agentctl apply                # the whole host
agentctl apply noor           # one agent
agentctl apply --dry-run      # print the plan, change nothing

apply semantics

Reconcilers render the desired output and write only on a diff, and the restart tail bounces only the agents whose inputs moved. A second apply on an unchanged host is a true no-op: no writes, no reloads, no restarts.

An agent restarts when its fingerprint moved this run: payload/version, ledger entry, overlay hash, secrets. It also restarts when it is a local agent paired with a gateway that bounced this run — the version-skew cascade, teams/slack by channel and webview by user_email.

flag effect
(none) reconcile deltas, re-render env/hooks/crons, skip unchanged agents by fingerprint, restart what changed
--force reconcile AND restart every agent and gateway regardless of fingerprint (full base-unit refresh)
--fast deprecated no-op alias; the fingerprint skip is the default. Prints a deprecation note
--dry-run print the plan, including the exact ↻ restart set, and apply nothing
--prune list agents live on the host but no longer in the ledger (orphans)
--prune --yes tear those orphans down: userdel -r plus unit/timer/socket cleanup. Destructive
--json emit the plan or the result as JSON
[<agent>] scope the run to one agent

A deb-version bump moves every fingerprint and force-bounces the gateways, so a plain apply after an upgrade reconciles and bounces the whole host on its own.

Host templates propagate to their instances

apply runs a host pass and then a per-agent pass, and each pass gates restarts on its own changed files. A process-scoped record of the host-role dests changed earlier in the same run carries into the per-agent pass. browser@<agent> declares the template, the private-display drop-in and the display env as restart inputs.

The record is an event, not a state: it says "the template changed during this apply", not "this unit is older than its fragment".

case result
a shared @ template changes during the run every instance restarts on the new command line
apply dies between the host write and the agent phase a stale process the next apply does not correct
agentctl converge <agent> runs no host pass, so it inherits no record

Troubleshooting

agentctl doctor [<name>] is the first stop. It does not print a fixed set of named states — diagnoseAgent emits a free-text problem string per finding, each with its own generated fix: line:

⚠ scribe — 2 issue(s)
  ✗ unit failed/dead
      fix: agentctl logs scribe --since '-10 min' ; agentctl agents restart scribe
  ✗ claude login dead (refresh token expired)
      fix: agentctl login claude scribe  (or wait for the plugin's self-serve re-login)

Common findings: unit not installed, unit <state>/<substate>, crash-looping (NRestarts=…), <provider> login dead (…), and activated MCP "x" is missing its required_env secret "K". Exit code is 1 on any FAIL, 2 on an unresolvable agent name, 0 on WARNs or a clean sweep.

agentctl logs <name> [-f] tails the agent's own output. For the fuller escalation — the raw systemd unit, a host-wide failed-unit sweep, journal reads for non-agent units, and cross-host reach — see Troubleshooting.