fix(run): bound catalog watches to declaration space - #333
Merged
schickling-assistant merged 3 commits intoAug 25, 2026
Conversation
The supervisor registered one recursive inotify watch over the whole catalog root before its first reconcile. notify eagerly walks the entire tree and allocates one watch per directory BEFORE any callback filtering, and follows symlinks by default, so Resource payload trees (37GB / 151k directories on dev3) dominated startup and could exhaust kernel limits. When installation then failed, the dropped channel sender made RecvTimeoutError::Disconnected look like an immediate wake, turning the nominal 30s timer into a tight full-reconcile loop (~48 passes/min, ~70% of a core, measured live). - Replace the recursive registration with CatalogDeclarationWatcher: one non-recursive watch per declaration-space directory, discovered without descending into Resource payloads (gated by is_catalog_path), refreshed after each pass so new directories become watched. - Treat directory topology mutations as wakes so create/rename/remove still reconcile immediately; file mutations filter through is_declaration_path as before. - Diagnose watcher installation failure once instead of discarding the error behind Option. - Wait on an absolute deadline where Disconnection sleeps the slice instead of waking, so the timer fallback actually honors the interval while stop stays responsive in bounded slices. - Drive the real supervisor loop in tests with an injectable watcher factory: installation failure must stay on timer cadence (the spin was only covered at helper level before), and a live watcher must wake on declaration mutation long before the timer. Closes #314, closes #328. Verified against a synthetic oversized catalog (2051 payload dirs plus a symlinked external tree): 7 inotify watches installed, first reconcile immediate, zero CPU ticks across a 3s idle window, SIGINT responsive. Co-authored-by: schickling-assistant <schickling-assistant@users.noreply.github.com> agent-identity: unknown agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.0.3 agent-runtime: OMP 18.0.3 tooling-profile: dotfiles@f33cd9c-dirty
schickling-assistant
marked this pull request as ready for review
August 24, 2026 21:18
Contributor
Author
|
@codex Please review this PR. Posted on behalf of @schickling
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd0fcc7bee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Review follow-ups on the declaration-space watcher: - Topology wakes matched every create/remove/rename in declaration space, so a scratch, log, or editor-swap FILE could wake a full-catalog reconcile — the churn this watcher exists to prevent. Classify by entry type where the backend provides it (Linux does), check the live tree for untyped rename arrivals, and leave rename-away silent (an in-catalog rename emits the arrived side; a full removal is bounded by the timer plus refresh). - An inotify watch dies with its inode, but `watched` tracked names only: a directory deleted and recreated at the same pathname was never resubscribed until the timer. Track (dev, ino) identity per watched directory and force re-registration when a replacement differs. Co-authored-by: schickling-assistant <schickling-assistant@users.noreply.github.com> agent-identity: unknown agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.0.3 agent-runtime: OMP 18.0.3 tooling-profile: dotfiles@f33cd9c-dirty
Identity comparison alone cannot catch every replacement: deleting and recreating a directory at the same pathname can reuse the old (dev, ino), so refresh would see no change while the backend watch had died with the original inode. Share the tracked-directory map with the notify callback and drop entries the moment the backend reports their directory removed or renamed away (including descendants), making resubscription at the next refresh deterministic instead of trusting a stat race. agent-identity: unknown agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.0.3 agent-runtime: OMP 18.0.3 tooling-profile: dotfiles@f33cd9c-dirty
schickling-assistant
deleted the
schickling-assistant/issue314-1-supervisor-watch
branch
August 25, 2026 07:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The supervisor registers one recursive
notifywatch over the entire catalog root before its first reconcile. On Linux,notifyeagerly walks the full tree and allocates one inotify watch per directory before any callback filtering runs, and follows symlinks by default. Catalog Resource payload trees (a production deployment measured 37 GB / 1.74 M entries / 151 k directories) therefore dominated startup: ~13 minutes of traversal before the first reconcile, a multi-GiB memory peak, and installation failure once kernel limits were hit.When installation failed, the dropped channel sender made
RecvTimeoutError::Disconnectedlook like an immediate wake in the wait loop (run.rs), turning the nominal 30 s timer into a tight full-catalog reconcile loop: ~48 passes/min at ~70 % of one core, with per-pass git subprocess churn. The failure itself was discarded behind anOption, so timer-only fallback was silent.Goal
Watcher installation scales with declaration space, not Resource payloads; declaration mutations still wake reconciliation immediately; failures are visible; the timer fallback honors its interval; stop stays responsive.
Decisions
agent_spec::is_catalog_path, soresources//archive/inbox/.git/.st2prune discovery, andRecursiveMode::NonRecursivenever traverses payloads. Arefresh()after each pass picks up newly created directories without backend traversal.agent.kdlexists yet — the created dir may receive one next, and refresh needs to run.Verification
up_loop_until: injected watcher failure stays on timer cadence (~4 passes in 350 ms at a 100 ms interval; the old code produced hundreds), a live watcher wakes on declaration mutation long before the timer, and a disconnected channel waits out the interval.Complexity
CatalogDeclarationWatcheris a small BTreeSet-diff over declaration dirs; it replaces rather than adds machinery. No new dependencies.Concerns
refresh()walks declaration space each pass; pruned at payload boundaries, so cost tracks agents, not data.Follow-ups
main:agent_publish::incomplete_apply_marker_blocks_declarations_but_not_the_state_planefails identically at base commit (resident supervisor exits becausepublish_owner_bindingtreats the incomplete-apply marker as fatal); filed as Resident supervisor exits on incomplete-apply catalog instead of degrading: publish_owner_binding treats the marker as fatal #337.References
Closes #314. Supersedes the unmerged #316 approach with the missing supervisor-level proof. Related: #328.
Posted on behalf of @schickling
agent_identityagent_personaagent_supervisoragent_toolagent_tool_versionagent_runtimetooling_profile