Repository navigation
feat(agent): refresh the inventory when its source files change - #99
Conversation
b8996ff to
adbea4f
Compare
|
@copilot resolve the merge conflicts in this pull request |
adbea4f to
50f399e
Compare
|
Taking this one — rebased onto current The conflicts were only import collisions in Kept as a single commit rather than resolving in the web editor, which would have added a merge commit. 341 tests pass locally against the rebase. Sorry for letting it drift to the point of needing a nudge. |
|
Test and lint failed on the rebase, but I think the source is fine and the job compiled against stale artifacts. Evidence, in case it is useful beyond this PR. Every unresolved symbol is something #146 added, and all of them live in crates this PR does not touch: So I checked out the exact merge commit the job tested — As for why: That is inference rather than something I can prove from outside, and the falsifiable form is: any PR that touches I cannot re-run the job myself (needs admin). Could someone re-run it, ideally after clearing the |
|
The re-run reproduced it identically, and its log confirms the cause rather than just repeating the symptom:
The Container job is the control. It builds in Docker with no cache, compiles So a plain re-run will not clear this — it restores the same entry. The The part worth more than this PR: the failure direction here is the lucky one. The same mechanism can also make a PR pass against stale workspace artifacts — green on a Happy to open a separate issue for that if you would like it tracked away from this PR. |
The interval added in agentdesktop-dev#75 bounds how stale the inventory can get. This bounds how long a change takes to appear: a developer adding an MCP server no longer waits out `inventoryInterval` before the fleet view reflects it. After every published snapshot the daemon watches the directories the files in that snapshot came from, and rescans when one reports activity. It reuses the debounced-watch approach the controller already uses for its own configuration file, including the same 250ms window. The watch set is derived from the inventory rather than a fixed path list, so no provider code is involved and a new provider is followed by virtue of reporting its sources. A discovered MCP configuration contributes its own directory, watched shallowly, so an atomic replace and a sibling configuration both register. A discovered skill contributes the root its walk started from, watched recursively, because that walk descends and a skill added elsewhere in the tree would otherwise go unnoticed until the interval. Skill paths that do not sit under a `skills` root fall back to the file's own directory. Executable directories are deliberately not watched. An upgrade changes a version rather than a configuration, and install directories are noisy enough to make most of those scans wasted. The watcher owns the channel it signals on, which has capacity one: a scan answers every change that arrived before it began, so activity while a signal is already pending is dropped rather than queued. Without that, a broad watched directory could enqueue batches faster than they are consumed and run one full scan per stale token. A watcher that cannot start is not fatal. Its sender drops, the receiver never yields, that select branch disables itself, and the interval carries on alone. Refs agentdesktop-dev#62. Signed-off-by: Jaganmohan Reddy Sanikommu <jagansanikommu7@gmail.com>
50f399e to
3b9d50d
Compare


The interval from #75 bounds how stale the inventory can get. This bounds how
long a change takes to appear: adding an MCP server no longer means waiting
out
inventoryIntervalbefore the fleet view reflects it.Refs #62. I left that issue open after #75 and asked whether you wanted this as
a follow-up; this is that follow-up, so close #62 with it if the pair is what
you had in mind.
Approach
After every published snapshot the daemon watches the directories holding the
files that snapshot came from — the parent of each discovered MCP configuration
and each discovered skill file — and rescans when one reports activity. It
reuses the debounced-watch approach already in
controller/src/daemon_config.rs,including the same 250ms window.
Deriving the watch set from the inventory rather than from a hardcoded path list
means no provider code is touched. A new provider is followed automatically
by virtue of reporting
sourceandpathin its discovery. That also keepsthis clear of #94, which rewrites every provider's
discovery.rs.Parent directories rather than the files themselves, so an editor replacing a
file atomically and a sibling file appearing beside a discovered one both
register. Events are not inspected past debouncing: a scan is the only way to
know whether the inventory really changed, and the existing publish-on-change
comparison already discards a scan that matches the current snapshot, so a
spurious wake costs one discovery walk and nothing downstream.
Executable directories are deliberately not watched — an upgrade changes a
version rather than a configuration, and install directories are noisy enough to
make most of those scans wasted. The interval still catches upgrades.
What this does not catch
A tool that had no discovered files at the last scan contributes nothing to
the watch set, so a brand-new install is still picked up by the interval rather
than immediately. Watching for that would mean watching speculative paths for
every supported tool on every device, which seemed a bad trade against a 15m
default. Happy to revisit if you disagree.
Update after review
Two real findings, both mine, both fixed.
Unbounded change signals. Every debounced batch was queued, so a burst ran
one full discovery per token. The description above originally argued a spurious
wake "costs one discovery walk and nothing downstream" — true for correctness,
false for amplification. The signal channel now has capacity one and is sent
with
try_send, so activity arriving while a signal is pending is droppedrather than queued; a scan answers every change that preceded it.
InventoryWatchnow owns that channel instead of accepting a sender, so theguarantee cannot be widened by a caller.
Sibling skills were missed. Watching each
SKILL.md's own directory missedskills added elsewhere in the tree — and on a real machine Codex nests them two
levels below the root (
.codex/skills/.system/review-agent/SKILL.md), soneither the root nor the intermediate directory was watched. A discovered skill
now contributes the root its walk started from, watched recursively. MCP
configurations still contribute their own directory shallowly, since
$HOMErecursively would be indefensible.
Recovering that root walks up to the nearest ancestor named
skills, which isthe shape of every root the providers scan. Retaining the providers' own roots
would be the better fix, but it means editing every
discovery.rsand #94 isstill an open draft rewriting exactly those files. Happy to switch once that
lands.
Consequences worth flagging
Claude Code keeps
.claude.jsondirectly in the home directory, so the homedirectory is in the watch set, non-recursively. Direct children of
$HOMEtherefore wake a rescan — now coalesced, so a burst costs one scan rather than
one per event. If you would rather not watch
$HOMEat all, the cost is thatedits to
.claude.json, the main Claude Code MCP surface, stop being noticed.A tool with no discovered files contributes nothing to the watch set, so a
brand-new install is still picked up by the interval rather than immediately.
Watching speculative paths for every supported tool on every device seemed a bad
trade against a 15m default.
Validation
make check,make generate-schema(no diff), and the workspace suite —145 tests, nine new. Coverage includes the skill root being watched rather
than the leaf, the fallback when a skill sits outside a
skillsroot, the watchset re-syncing as tools gain and lose configuration, a real write reported
through the debouncer, a burst of writes producing at most one pending signal,
and a change signal refreshing without waiting for the interval. The filesystem
tests use generous timeouts because macOS delivers through FSEvents, which
batches with its own latency on top of the debounce window.
Rebased onto current
main.Not run: the provider integration suite. It requires a Linux-built daemon
(
ensure!(cfg!(target_os = "linux"))intests/common/container.rs) and I amon macOS. Nothing here touches provider discovery, but flagging it rather than
implying otherwise.
notifyandnotify-debouncer-fullwere already workspace dependencies; addingthem to
agentdesktop-agentis the onlyCargo.lockchange.