Skip to content
This repository was archived by the owner on Sep 3, 2026. It is now read-only.

curator: enforce skill lock/pin, drop the review fork's shell, stop self-inflating review telemetry - #3

Merged
fabiosiqueira merged 3 commits into
local/all-fixesfrom
fix/curator-frontmatter-lock-77
Aug 4, 2026
Merged

fabiosiqueira merged 3 commits into
local/all-fixesfrom
fix/curator-frontmatter-lock-77

Conversation

@fabiosiqueira

@fabiosiqueira fabiosiqueira commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Why

Some skills wrap deterministic tooling that was exhaustively tested against its exact prompt text — automatic curation rewriting or archiving them degrades behavior that was tuned deliberately. Three independent gaps let the curator's autonomous review pass reach such a skill anyway.

What

  1. tools/skill_usage.pyis_curation_eligible() never read the skill's own frontmatter. It gated on provenance only (external/hub/bundled/protected), so a skill opting out via metadata.hermes.pinned/metadata.hermes.locked was still a valid auto-archive candidate. Now checks the frontmatter, fail-open on a missing/unparseable SKILL.md (matching _locked_guard's existing fallback).

  2. agent/curator.py — the review fork could shell out. Its only defense against archiving via terminal mv was a prompt instruction ("DO NOT call terminal to mv skill directories into .archive/"); advisory text is not an enforcement boundary. The fork is now pinned to enabled_toolsets=["skills"].

    Carry divergence: upstream ships ["skills", "terminal"] (perf(curator): pin the LLM review fork to the skills+terminal toolsets (salvage #62082) NousResearch/hermes-agent#77667) so the fork can mv support files into an umbrella's references/ while consolidating. We drop terminal and lose that consolidation move — an autonomous fork with a shell reaches past every guard the skill_manage path enforces, and we don't use umbrella consolidation. The two upstream tests that pin the intended surface are adapted in-place, with the divergence stated in their docstrings.

  3. tools/skills_tool.pyskill_view inflated the signal the review reads. The bump fired for every caller including the review fork, so inspecting a skill to judge whether it was stale counted as use. Now guarded by tools.skill_provenance.is_background_review().

Upstream routing

Fix 3 is the only one that stands alone against upstream main, and it went there as NousResearch#78819.

Fixes 1 and 2 stay fork-only, deliberately:

Tests

TDD throughout — tests written first and confirmed failing against the old behavior. tests/agent/test_curator.py, tests/tools/test_skill_usage.py, tests/tools/test_skills_tool.py: 86 passed (test_curator_slot_is_canonical_aux_task deselected — it fails identically on pristine upstream/main for a missing python-multipart dependency, not a regression from this branch).

fabiosiqueira pushed a commit that referenced this pull request Aug 4, 2026
… (re-review #3)

The last_activity_at/description/provenance columns already live in
SCHEMA_SQL and the column reconciler; existing DBs heal via the
reconciler, but the version stamp must advance so downgrade/upgrade
tooling sees the new layout. No version-literal test assertions exist
(tests compare against the imported constant).
…ible

archive_skill() and the other 4 call sites of is_curation_eligible only
checked provenance (external/hub/bundled/protected); a skill's own
SKILL.md frontmatter opt-out (pinned/locked, already enforced by
skill_manager_tool's _pinned_guard/_locked_guard for edit/delete) was
never read on the curation-eligibility path, so the curator's auto-
archive could still take a pinned/locked skill.

ref NousResearch#77
_skill_view_with_bump bumped telemetry for every caller unconditionally.
The curator's background-review fork calls skill_view to inspect
candidates while judging them for curation — that inspection was
counting as use, self-reinforcing the very signal (last_used_at) the
curator's stale timer reads. Guard with the existing
is_background_review() provenance check, already used by
skill_manager_tool's write guards for the same fork.

ref NousResearch#77
Upstream pins the review fork to enabled_toolsets=["skills", "terminal"]
so it can `mv` support files into an umbrella's references/ while
consolidating. The prompt's "DO NOT call terminal to mv skill
directories into .archive/" is advisory only, and this fork is
autonomous with no user in the loop: a shell it can reach archives an
operator-authored skill past every guard the skill_manage path enforces.

Drop `terminal` from the list rather than stacking a contrary
disabled_toolsets kwarg on the same call. Consolidation moves are the
cost of this carry; we don't use them.

ref NousResearch#77
@fabiosiqueira
fabiosiqueira force-pushed the fix/curator-frontmatter-lock-77 branch from f90fe57 to 3163c20 Compare August 4, 2026 18:10
@fabiosiqueira fabiosiqueira changed the title curator: enforce skill lock/pin and stop self-inflating review telemetry curator: enforce skill lock/pin, drop the review fork's shell, stop self-inflating review telemetry Aug 4, 2026
@fabiosiqueira
fabiosiqueira merged commit 466f9c7 into local/all-fixes Aug 4, 2026
33 of 34 checks passed
@fabiosiqueira
fabiosiqueira deleted the fix/curator-frontmatter-lock-77 branch August 4, 2026 18:13
fabiosiqueira pushed a commit that referenced this pull request Aug 20, 2026
…age_id)

Live-canary finding #3 (Alice, staging): the relay inbound leg is
at-least-once. On WS re-handshake the connector replays its durable
per-instance buffer; a long multi-tool turn (60-100s) straddling a quiet
socket drop got its ORIGINAL inbound replayed after the turn finished,
re-running the entire turn — the user saw the final answer posted 2-5x
(each a separate execution, hence slightly different texts). Receipts:
same msg text at history=0 in back-to-back sessions 121647/121840, no
Slack-side retry on the connector (envelope dedupe never fired).

Consumer-side idempotency: bounded FIFO seen-set (512) keyed by platform
message identity; events without a message_id never dedupe (fail-open —
dropping a real message is worse than rerunning one). No wire change;
contract v1 untouched.

Transplanted-from: victor-fork/feat/relay-slack-live-cards@73ce04ae75 (extracted for the rc.4 relay-fixes train; tests moved to a standalone file with no live-cards dependencies)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant