fix(curator): make the autonomous write policy consistent (#67140)
The background write guard decided ownership from `isinstance(usage_rec, dict)`, so a local skill with NO usage record passed. That successful write called bump_patch(), which created a `created_by: null` record — and the identical write was refused from then on. "Allowed exactly once, then never" is a race with our own bookkeeping, not a policy. Reproduced on main: patch #1 succeeds, patch #2 with the same arguments is refused. Option B from the issue. Option A (split `session_review` from `scheduled_curator` and let the session fork patch user-owned skills it consulted) would widen autonomous write permission onto skills the user owns with no user present to consent — wrong direction for a no-user-present actor. - skill_manager_tool: missing and explicit-null records now resolve IDENTICALLY, both fail closed. The refusal names the reason and points at `hermes curator adopt <name>`. - background_review: both review prompts told the reviewer to patch any skill consulted in the session and claimed pinned skills could be improved, while enforcement refused both. Prompts now list pinned, external, and user-owned skills as protected, and tell the reviewer to RECOMMEND adoption instead of attempting a write that will be refused. - skill_usage: document that `created_by` is a curator-management policy flag, not a provenance claim, and add `is_curator_managed()` so call sites read as the question they ask. Field name retained — it is on disk in every `.usage.json` and renaming would strand those records. - curator CLI: `hermes curator list-unmanaged` itemizes unmanaged skills with the reason each is unmanaged (completes the #67139 spec). Foreground writes are untouched: a user-directed edit to a user-owned skill still works, including on pinned skills. Sibling tests: 9 failures in test_skill_manager_tool.py were fixtures that created record-less skills to exercise OTHER guards (consolidation-delete, read-before-write) and relied on ownership falling through. Fixed at the fixture, since the real curator only ever operates on managed sediment. One test asserted the old "manually authored" wording; rewritten to assert the behavior contract instead of the string. Validation: 274 targeted tests + all 7 background-review files (60 tests) pass. E2E on a temp HERMES_HOME (30 checks) covers the flip, foreground writes, adoption unblocking, pin semantics, prompt/enforcement parity, and the new verb. Each new test sabotage-verified: revert the fix, confirm it goes red. Fixes #67140
This commit is contained in:
@@ -105,6 +105,7 @@ hermes curator pin <skill> # never auto-transition this skill
|
||||
hermes curator unpin <skill>
|
||||
hermes curator adopt <skill> # hand an unmanaged skill to the curator
|
||||
hermes curator adopt --all-unmanaged # hand over every unmanaged skill
|
||||
hermes curator list-unmanaged # itemize skills with no provenance marker
|
||||
hermes curator restore <skill> # move an archived skill back to active
|
||||
hermes curator list-archived # list skills currently in ~/.hermes/skills/.archive/
|
||||
hermes curator archive <skill> # manually archive a single skill now
|
||||
@@ -202,7 +203,8 @@ A large library can therefore look fully curated while most of it is
|
||||
untouchable. `adopt` closes that gap by **declaration**:
|
||||
|
||||
```bash
|
||||
hermes curator adopt <name> [<name> ...] # hand specific skills over
|
||||
hermes curator list-unmanaged # itemize them, with reasons
|
||||
hermes curator adopt <name> [<name> ...] # hand specific skills over
|
||||
hermes curator adopt --all-unmanaged --dry-run # preview the full list
|
||||
hermes curator adopt --all-unmanaged # hand over everything (prompts)
|
||||
hermes curator adopt --all-unmanaged --yes # skip the prompt
|
||||
@@ -214,6 +216,21 @@ existing `last_activity_at`, so handing over a library you already stopped
|
||||
using does not buy it a fresh 90-day window. Expect adopted long-idle skills to
|
||||
go `stale` (or `archived`) on the next pass; that is the point.
|
||||
|
||||
Adoption is also what unblocks autonomous *improvement*. The background review
|
||||
fork refuses to patch a skill that isn't curator-managed, so if it notices one
|
||||
of your skills is outdated it will say so and recommend adoption rather than
|
||||
edit it. Foreground (user-directed) edits are never affected — you and the
|
||||
agent can always edit your own skills on request.
|
||||
|
||||
:::note `created_by` is a policy flag, not a provenance claim
|
||||
The stored field is named `created_by`, but it is consumed as "may autonomous
|
||||
curation touch this?" — not "who wrote this file". Those are different
|
||||
questions, and for records predating the marker the authorship answer is simply
|
||||
unrecoverable. The name is kept because it is already on disk in every
|
||||
`.usage.json`; read it as policy. `hermes curator adopt` changes the policy, and
|
||||
says nothing about who authored the file.
|
||||
:::
|
||||
|
||||
:::note Provenance is declared, never inferred
|
||||
Adoption is deliberately manual. Telemetry cannot establish authorship: a skill
|
||||
with thousands of patches proves the agent **maintains** it, not that the agent
|
||||
|
||||
Reference in New Issue
Block a user