feat(memory,skills): approve/deny gate for memory + skill writes (#38199)

Adds memory.write_mode and skills.write_mode (on|off|approve), applied to
both foreground turns and the background self-improvement review fork — the
source of the unprompted 'wrong assumption' saves users reported.

- on (default): write freely, unchanged behaviour
- off: never write; the tool returns a clean disabled result
- approve: don't commit. Memory foreground writes prompt inline (small,
  reviewable in a chat bubble); background memory writes and ALL skill writes
  stage to a pending store instead (a SKILL.md is too large to review inline,
  and a daemon thread can't block on a prompt)

Review staged writes from CLI or any messaging platform:
  /memory pending|approve|reject|mode
  /skills pending|approve|reject|diff|mode

Skill review respects the size asymmetry: inline you see a one-line gist;
the full unified diff stays out-of-band (/skills diff, dashboard, or the
staged JSON file).

New: tools/write_approval.py (gate + pending store), hermes_cli/
write_approval_commands.py (shared CLI+gateway handlers). Gates wired at the
single entry points memory_tool() and skill_manage(), using the existing
write-origin ContextVar to distinguish foreground from background_review.
This commit is contained in:
Teknium
2026-06-09 21:51:43 -07:00
committed by GitHub
parent fdc90346ea
commit 96af61b6ef
13 changed files with 1270 additions and 11 deletions
+2 -1
View File
@@ -6932,7 +6932,6 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
def _show_gateway_status(self):
"""Show status of the gateway and connected messaging platforms."""
from gateway.config import load_gateway_config, Platform
@@ -7245,6 +7244,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
elif canonical == "skills":
with self._busy_command(self._slow_command_status(cmd_original)):
self._handle_skills_command(cmd_original)
elif canonical == "memory":
self._handle_memory_command(cmd_original)
elif canonical == "platforms":
self._show_gateway_status()
elif canonical == "status":