Files
hermes-agent/tools/skill_provenance.py
kshitijk4poor e333113871 fix(review): keep /refine under the background_review origin; attendedness is its own flag
The salvaged commit forked an explicit /refine under a new "refine_review" origin so
the memory delete gate would not treat it as unattended. But is_background_review()
is the key for every other review guard — skill_manager_guards (curator-owned-only,
read-before-write), skill_manager_tool (archive instead of rmtree), skill_ledger
actor, write_approval staging, the [auto] tag — so a /refine fork silently escaped
all of them.

Carry attendedness separately: the fork keeps origin "background_review" and sets
_review_attended; turn_context binds it beside the origin ContextVar; the memory
gate keys on the new is_unattended_review(). Also run the gate AFTER
_validate_single_op / the operations list check, as memory_tool's own docstring
requires, so an invalid replace is rejected now rather than staged and failed at
approve time.
2026-09-09 12:19:13 +05:30

45 lines
1.8 KiB
Python

"""Skill write-origin provenance: a ContextVar separating background-review skill writes from foreground
user-directed writes (the curator only curates skills the self-improvement review fork created; skills a user
asked for belong to the user). run_agent.py binds the origin before each tool loop, mirroring
AIAgent._memory_write_origin: ``token = set_current_write_origin(...)`` / ``reset_current_write_origin(token)``."""
import contextvars
_write_origin: contextvars.ContextVar[str] = contextvars.ContextVar("skill_write_origin", default="foreground")
BACKGROUND_REVIEW = "background_review" # sentinel used by run_agent._spawn_background_review
def set_current_write_origin(origin: str) -> contextvars.Token[str]:
return _write_origin.set(origin or "foreground")
def reset_current_write_origin(token: contextvars.Token[str]) -> None:
_write_origin.reset(token)
def get_current_write_origin() -> str:
""""foreground" for any regular agent (CLI, gateway, cron, subagent); "background_review" for the review fork."""
return _write_origin.get()
def is_background_review() -> bool:
return get_current_write_origin() == BACKGROUND_REVIEW
# Attendedness is orthogonal to origin: an explicit ``/refine`` fork IS a background review (every
# curator / skill-ledger / approval guard keyed on ``is_background_review()`` must still apply), but a
# user asked for it, so the unattended-only memory delete gate (#105921) does not.
_review_attended: contextvars.ContextVar[bool] = contextvars.ContextVar("review_attended", default=False)
def set_review_attended(attended: bool) -> contextvars.Token[bool]:
return _review_attended.set(bool(attended))
def reset_review_attended(token: contextvars.Token[bool]) -> None:
_review_attended.reset(token)
def is_unattended_review() -> bool:
return is_background_review() and not _review_attended.get()