feat(agent): governed persistence funnel — stage-or-deny via pre_persist_write - #2
Merged
Merged
Conversation
…sist_write New agent/persist_boundary.py::governed_persist(kind, path, content, meta) is the single chokepoint for runtime writes to Hermes' canonical memory/skill/ SOUL.md paths: it invokes the pre_persist_write REQUIRED hook (mirroring how cron/scheduler.py and hermes_cli/kanban_db.py invoke pre_run_start), and is the ONLY place that performs the actual disk write -- refactored call sites never touch a canonical path themselves. - allow+staged -> content quarantined governed-side; canonical path untouched. - block -> PersistResult(denied=True); call sites raise PersistDenied, the same clean-error contract a failed write already had. - bare allow (no persistence enforcer registered yet -- the pre-cutover default, mirroring the pre_run_start precedent) -> governed_persist performs the original atomic write itself, unchanged. - hook unreachable or an unrecognized directive -> decision-less local stage under $HERMES_HOME/persist-quarantine-local/<sha256hex>/, warned, grants nothing. hermes_cli/plugins.py: registers pre_persist_write as a valid required-hook boundary (VALID_HOOKS + register_required_hook's allowed set) and generalizes invoke_required_hook's context pass-through (previously run_id/session_id only) to also carry digest/staged, so a pre_persist_write directive's payload survives the hook aggregation. tools/memory_tool.py::MemoryStore._write_file and tools/skill_manager_tool.py::_atomic_write_text now route through governed_persist (kind="memory" / kind="skill" respectively); agent/learning_mutations.py shares the memory site's _write_file and is covered automatically. SOUL.md: recon found only bootstrap/default-creation writers and the dashboard's human-editor endpoint -- no runtime SOUL.md writer exists in this fork today; kind="soul" is implemented and tested for a future writer. Tests: tests/agent/test_persist_boundary.py covers the funnel's directive handling, the hook-unreachable local stage, both refactored write sites (deny/stage/passthrough/kind-mapping), and an AST-scoped source-scan pin asserting no canonical write primitive survives outside the funnel module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
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.
Summary
Companion to agent-lineage PR NousResearch#20 (verified persistence). All runtime memory/skill writes now route through one funnel —
agent/persist_boundary.py::governed_persist— which invokes thepre_persist_writeREQUIRED hook: allow+staged → content quarantined, canonical path untouched; block → clean caller error; hook machinery unavailable → decision-less local staging (grants nothing). The real write sites (tools/memory_tool.py::MemoryStore._write_file,tools/skill_manager_tool.py::_atomic_write_text— learning mutations ride the former) are refactored through it; no runtime SOUL.md writer exists (documented;kind="soul"future-proofed).hermes_cli/plugins.py:pre_persist_writevalid required hook + typed digest/staged directive passthrough. Fail-closed hardening from review: malformed enforcer allow (missing/non-bool staged) stages locally when an enforcer is registered (fail-SAFE presence check); AST-based write-site regression pin (RED-verified by plant,open()mode read fromargs[1]).Pre-cutover behavior is byte-identical (no enforcer registered → passthrough). Deployment to the governed install is a separate explicit cutover.
Verification
🤖 Generated with Claude Code