Repository navigation
Re-discover a profile's models when its harness home is edited - #1472
Merged
Merged
Conversation
Issue 1337: editing config.toml, models.json or settings.json in a profile home never reached the catalog, so mj models, the Settings and Move pickers and auto reviewer selection kept the pre-edit answer for the daemon's whole life. 55b8ea7 fixed this by hashing home files into the discovery key; b39c0e6 removed that hash to stop read-path home I/O and one global generation, and nothing since carried a home edit to the catalog. The catalog owner now watches each distinct profile home from its background task: a 30-second stat poll of the three catalog inputs, hashing contents only when a stat changes. A home whose inputs hash differently re-probes exactly the profiles that use that home, through the keep-serving-the-old- answer refresh path. Re-probes bypass the process-global cache, which would otherwise answer a deliberate re-probe with the stale row it exists to replace. The first pass only baselines (also the restart case: entries and the process cache live only as long as the daemon, so a stopped-then-started daemon discovers the current home), and a detected change settles only once re-probes launched for it and nothing selected for it is still re-probing, so an in-flight probe that staged the pre-edit home cannot re-introduce the old state. One owner decides; reads still do no home-file I/O; profiles with other homes are untouched; no TTL. harness_home_files_do_not_invalidate_configured_capabilities is replaced by tests for these rules: an edited home re-publishes within one pass and re-probes only that home, a shared home re-primes each of its profiles, a new catalog generation discovers the current home, and metadata-only rewrites do not register as edits. Validation: cargo test -p brokk-mj-controller profile_catalog (34 passed) and profile_config (12 passed); fmt clean.
Review of the home watch found the launch-gated baseline could settle an edit on a failed re-probe, leaving it silent until the one-hour expiry, and could not see probes still pending that staged the pre-edit home: the first-sight window after daemon start, and a sibling profile of a shared home whose own probe had not finished. Both re-introduced the stale answer the watch existed to catch. Every attempt now reads the home's catalog inputs before staging and, on success only, records that digest per identity. The watch compares each identity's settled digest against the current files, so a probe that read pre-edit files records the pre-edit digest, a failed probe records nothing, and a pending probe records nothing yet: all three stay pending for the next 30-second pass instead of settling wrongly. Failed re-probes retry after five minutes, matching the expiry refresh cadence, rather than every pass. An input modified within the last minute is re-hashed every pass even when its stat matches, catching a same-length rewrite hidden inside one mtime tick on a coarse-granularity filesystem. Adds the failure-retry and fixed-mtime-rewrite regressions; the in-flight regression now pins the recording rule directly. Validation: cargo test -p brokk-mj-controller profile_catalog (36 passed) and profile_config (12 passed), repeated runs stable; cargo clippy --all-targets -- -D warnings clean.
Review round 2: the watch armed home_edit_retry_wait when a re-probe launched and discharged it only when a later pass observed convergence. A successful re-probe followed by another home edit within the window served the second edit up to five minutes late — the very staleness this watch exists to catch. A success now clears the window alongside recording the settled digest, so a genuinely new edit re-probes on the next pass; a failure still records nothing and keeps the bounded retry. Pinned by a_follow_up_edit_reprimes_without_waiting_for_the_retry_window. Validation: cargo test -p brokk-mj-controller profile_catalog (37 passed) and profile_config (12 passed).
Review round 3: home_settled was one digest per identity, last-success-wins, so when a detection pass re-probed an identity's entries and they diverged — the default entry's probe succeeding while a model entry's failed — the success settled the whole identity and the failed entry's pre-edit copy stayed served until the one-hour expiry refresh. The settled digest is now keyed by the entry, and the watch requires every served entry of an identity to record the current digest; claim_models stamps the digest of the probe it copies the current-model entry from, so a copied entry settles with its source. Pinned by a_failed_entry_reprimes_even_when_its_sibling_reprobe_succeeded. Validation: cargo test -p brokk-mj-controller profile_catalog (38 passed) and profile_config (12 passed), repeated runs stable; clippy clean.
Review round 4: a failed entry's backoff retry and reread_failed read the process-global cache (refresh = false), so a row stored before a home edit could be published with the home's post-edit digest recorded as settled. The watch then saw every entry settled at the current digest and stayed quiet for up to the choice lifetime — the edit-not-picked-up defect again. Discovery now reports whether the harness actually ran. The catalog records a settled digest only for a probe that read the home; a cache-served answer settles nothing, so the entry keeps its older digest, stays stale, and the watch's next pass launches the cache-bypassing re-probe that reads the edited home. Muse probes always read the installation and always settle. Pinned by resolve_cached_discovery_reports_whether_the_harness_ran. Validation: cargo test -p brokk-mj-controller profile_catalog (38 passed) and profile_config (13 passed); clippy clean.
Review round 5: the every-entry settle rule counted Entry::Failed, which never records a settled digest, so one permanently failed entry kept its identity stale forever and re-probed the healthy entries every pass, about two harness launches per 30 seconds. A failed entry serves no home-derived answer; its own supervisor retries it with backoff, and its success records a digest that rejoins the check. Failed entries therefore neither settle nor block convergence. Re-probe failures that keep an entry Ready still hold the identity pending, as before. Pinned by a_failed_entry_does_not_keep_repriming_its_healthy_siblings. Validation: cargo test -p brokk-mj-controller profile_catalog (39 passed) and profile_config (13 passed), repeated runs stable; clippy clean.
Review round 6: any success of the identity discharged the throttle, so a persistently failing entry mixed with healthy siblings re-probed the whole identity on every 30-second pass — the throttle never bound it. A failed drive_refresh now re-arms the window, and only a success discharges it, so a mixed identity retries at the five-minute cadence like any other persistent failure while a genuinely converged one re-probes immediately on a new edit. The sibling regression now pins both halves: the window holds right after a failure, and the failed entry still re-probes after it. Validation: cargo test -p brokk-mj-controller profile_catalog (39 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 7: arming the retry window on failure and discharging it on any success raced between a failing entry and a succeeding sibling — when the sibling completed last, the window was cleared before the next pass and the identity re-probed every entry every 30 seconds. A success now discharges the window only when every non-Failed entry of the identity settles at an identical digest; a still-failing sibling keeps its older digest, so the five-minute bound holds whatever order the probes complete in. The home watch additionally clears the window for identities it observes converged, and failures keep re-arming it. Validation: cargo test -p brokk-mj-controller profile_catalog (39 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 8: a timestamp-preserving rewrite (cp -p) hides from the stat poll, which keeps reusing its old content hash, while any probe reads the home in full and records the fresh digest. The watch then saw settled and polled digests disagree on every pass and re-probed the identity every 30-60 seconds without a bound — the re-probe storm class #1028 removed — because the file's stat never changed and the poll could never catch up. The watch now re-hashes a home in full when the poll saw no change yet a settled digest disagrees: the poll is provably behind, since a probe's full read and the poll's hash can only differ if the content changed after the hash was taken. One extra read ends the churn, and the documented cp -p residual — undetected until something reads the home — now converges quietly instead of looping. Pinned by a_stat_hidden_rewrite_converges_once_a_probe_reads_it. Validation: cargo test -p brokk-mj-controller profile_catalog (40 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 9: two P3 defects in the settle rule and the retry window. A still-Pending entry — like a Failed one — serves no complete answer, yet the settle check required it to carry the current digest, which no pending probe can record. One slow harness launch left the identity stale permanently, re-probing every healthy entry every retry window. Pending entries are now excluded from the settle check; their supervisor's success records a digest that rejoins it. The retry window was digest-blind: after a failed re-probe of one edit, a fresh edit waited out the window, up to five minutes, before re-probing. Launches and failures now arm the window against the digest they retry, and a pass whose current digest differs from the armed one is a new edit and re-probes on the next pass; the window still bounds retries of the same failing digest. Pinned by a_pending_entry_does_not_keep_repriming_its_healthy_siblings and a_fresh_edit_bypasses_the_retry_window_of_an_earlier_failure. Validation: cargo test -p brokk-mj-controller profile_catalog (42 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 10: a failed refresh with no prior window — the routine expiry refresh is one — armed the window with an "any digest" marker, so a fresh home edit inside the window was suppressed for up to five minutes instead of re-probing on the next pass. Such a failure now arms the window with the failing entry's settled digest, the state the retry was correcting: an unchanged home retries at the bound, and a pass whose current digest differs is a fresh edit and bypasses, the same rule a watch-launched window already followed. Pinned by a_fresh_edit_bypasses_the_window_of_an_unrelated_failure. Validation: cargo test -p brokk-mj-controller profile_catalog (43 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 11: a Ready entry published from a cache-served discovery has no settled digest, so a failure could still arm the window with an "any digest" marker, holding a fresh edit — and each re-armed failure extended the hold. The failed attempt now reports the digest it read before running, the home state the failure is about, and the window arms against that — falling back to the entry's settled digest, then to the marker only when no state is known at all. A pass whose current digest differs remains a fresh edit and bypasses the window in every case the fixtures can reach. Validation: cargo test -p brokk-mj-controller profile_catalog (43 passed) and profile_config (13 passed), three repeated runs stable; clippy clean.
Review round 12: a refresh failure whose attempt could not hash the home — a hard read error, or a profile no longer enabled — and an entry a cache-served publish never settled could still arm the window with the "any digest" marker, holding a fresh edit until the window lapsed. With no known state, any current digest could be a fresh edit's, so the window now stays unarmed rather than suppress what it cannot characterize. An existing concrete window still extends on later failures, so retries of a known state stay bounded. Validation: cargo test -p brokk-mj-controller profile_catalog (43 passed) and profile_config (13 passed); clippy clean.
Review round 13: the watch re-armed the window for every identity whose entries launched, against the pass's digest, before the probes' outcomes were known. When a home edit landed while one entry's probe was already in flight, the pass launched only the sibling and armed the window anyway; the sibling succeeded and the lagging entry — which never failed — then waited out the window before re-probing, up to five minutes stale. Arming at launch was redundant: the refreshing map already keeps launched probes from doubling, and a failure re-arms with the digest it was correcting. Removing it leaves failures bounded, fresh edits bypassing, and a lagging sibling re-probing on the next pass. Pinned by a_lagging_entry_reprimes_on_the_next_pass_after_its_sibling_launched. Validation: cargo test -p brokk-mj-controller profile_catalog (44 passed) and profile_config (13 passed); clippy clean.
Review round 14: with launch-arming gone, extending the window kept the old armed digest, so after a fresh edit bypassed the window and its re-probe failed, the edited state matched no window and re-probed every 30-second pass for as long as the failure persisted. A failure now also adopts the digest its probe read, so the window retries the state that just failed: an unchanged home waits the bound, and only a newer edit bypasses. The unrelated-failure regression now pins the whole cycle: bypass, bounded retry of the failed fresh edit, and convergence once the failure lifts. Validation: cargo test -p brokk-mj-controller profile_catalog (44 passed) and profile_config (13 passed); clippy clean.
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.
Closes #1337.
What
Editing
config.toml,models.json, orsettings.jsonin a profile's harness home never reached the catalog, somj models, the Settings and Move pickers, and automatic reviewer selection kept the pre-edit answer for the daemon's whole life.55b8ea778fixed this by hashing home files into the discovery key;b39c0e69cremoved that hash to keep read paths free of home-file I/O and one global generation — and nothing since carried a home edit to the catalog.How
The catalog owner now watches each distinct profile home from its background task:
One owner decides; catalog reads still do no home-file I/O; profiles with other homes are untouched; a home shared by several profiles re-probes each of them; there is no TTL. Edits made while the daemon is stopped are picked up at start because entries and the process cache live only as long as the daemon.
Tests
harness_home_files_do_not_invalidate_configured_capabilitiesis replaced by tests for the issue's acceptance rules, plus one per review finding (15 rounds): edited home re-publishes within one pass and only that home re-probes; shared homes re-prime each profile; a new catalog generation discovers the current home; in-flight and lagging probes cannot settle an edit; failed re-probes retry bounded and adopt the failing digest; fresh edits bypass failure windows; pending and failed entries do not force re-probes of healthy siblings; stat-hidden (cp -p) rewrites converge once a probe reads the home; same-size rewrites inside one mtime tick are caught by the recency re-hash.Validation
Workspace
cargo testgreen (controller suite ~2,360 tests exit 0),cargo clippy --all-targets -- -D warningsclean,cargo fmt --all -- --checkclean; watch tests stable across repeated parallel runs. Reviewed clean by the project review flow.Plan:
.agents/plans/profile-home-refresh-watch.md.