Agent-Surface Sweeps
An agent's reach is not fixed. Skills get installed, and each one widens what the agent can do without anyone deciding that it should. A sweep enumerates the installed skills, records a verdict for everything it examined, and — the part that matters — records everything it could not examine and why.
When this matters
A scan that found nothing and a scan that could not look produce the same report in most tools: an empty findings list. That is the failure this feature exists to remove. A permission error in one skill directory should not be indistinguishable from a clean skill directory.
Sweeps are also how you answer "what changed?" without re-reading everything yourself. Two sweeps you can compare tell you what moved; one sweep only describes today.
What a sweep records
Each sweep writes one record with two lists.
Examined — one entry per item the sweep actually read:
| Field | Meaning |
|---|---|
kind | skill, mcp_config, or plugin — only skill is produced today |
agent | Which agent's surface it belongs to |
name, path | Identity and location |
content_hash | Hash of the content, not the path |
verdict | clean, flagged, or quarantined |
rule_ids | Which rules fired, if any |
Skipped — one entry per item the sweep could not read:
| Field | Meaning |
|---|---|
kind, agent, path | What could not be reached |
reason | A closed enum, never free text |
detail | Human-readable specifics |
The reason values are permission_denied, not_found, unreadable,
parse_error, too_large, symlink_loop, and root_missing. Keeping them a
closed set rather than a free-text string is deliberate: coverage gaps can be
counted and asserted on instead of grepped for.
content_hash keys identity to content rather than to path and mtime, so a
modified skill cannot look unchanged by keeping its filename and timestamp.
Each record also carries a trigger — install_gate, watch, periodic, or
manual — so a sweep you ran by hand is distinguishable from one the daemon ran
on its own schedule.
Running a sweep
The daemon sweeps periodically on its own, by default every six hours. That loop
is off entirely when the skill dir-watcher is disabled via
belay skill-watch off or the
rescan interval is set to 0.
To sweep immediately in the current process instead of waiting:
belay sweep-now
belay sweep-now --format json
Reading history and drift
belay sweep-history # recent sweeps, newest first
belay sweep-history --limit 5
belay sweep-compare # the two most recent
belay sweep-compare --from <sweep_id> --to <sweep_id>
sweep-compare classifies every item as new, persisting, changed,
resolved, or unknown. The unknown class is the honest one: it is where
an item lands when a sweep could not reach it, so an item does not silently
migrate into "resolved" just because this run failed to open it.
All three commands read the same sweeps.ndjson history, and evidence packs
carry sweep history with them.
Honest limits
sweep-nowis not a substitute for the periodic loop. Alert dedup state is per-process, so running it repeatedly from cron re-alerts items that are still flagged rather than behaving like the daemon's own schedule. Use it on demand; leave the loop on for continuous coverage.- Sweeps currently enumerate installed skills only. The record format has
mcp_configandpluginitem kinds and the comparison logic already handles them, but nothing enumerates them yet, so every row a sweep writes today is a skill. Read a clean sweep as "no flagged skills", not as "nothing anywhere widened this agent's reach". - A sweep does not cover your repository. What an agent does inside a repo it is already allowed to touch is handled at the tool-call boundary, not here.
- Recording a gap does not close it. A
permission_deniedentry tells you the sweep could not look. Acting on that is still yours to do.
Related docs
- How It Works — the tool-call boundary
- Rule Catalog & Categories — what the verdicts mean
- CLI Commands — full flag reference