refactor(memory,skills): replace tri-state write_mode with boolean write_approval (default off) (#43354)
The shipped tri-state write_mode (on|off|approve) conflated two concepts —
whether writes are enabled and whether they're gated — so 'on' (writes flow
freely, gate inactive) read like 'gating is on'. Replace it with a single
clear boolean gate that defaults off.
memory.write_approval / skills.write_approval:
false (default) — write freely; the approval gate is off (pre-gate behaviour)
true — require approval: memory foreground prompts inline, memory
background-review + all skill writes stage for review
The old 'off = block all writes' mode is dropped; memory_enabled: false already
disables memory entirely, so a third 'block' state was redundant.
- tools/write_approval.py: get_write_mode/MODE_* → write_approval_enabled() bool;
evaluate_gate() loses the config-driven 'blocked' path (blocked now only comes
from an interactive user denial).
- tools/memory_tool.py, tools/skill_manager_tool.py: comment + behaviour follow.
- hermes_cli/config.py: memory/skills write_mode → write_approval (False);
_config_version 28→29 with a 28→29 migration that renames any persisted
write_mode (approve→true, on/off/unset→false) and drops the old key.
- slash commands: '/memory|/skills mode <on|off|approve>' → 'approval <on|off>'
('mode' kept as a back-compat alias); set_mode_fn callback now takes a bool.
- write_approval_commands.py, cli_commands_mixin.py, gateway/slash_commands.py,
commands.py: handlers + registry args/subcommands updated.
- docs + tests rewritten for the boolean model; added migration tests.
This commit is contained in:
@@ -209,21 +209,22 @@ memory:
|
||||
user_profile_enabled: true
|
||||
memory_char_limit: 2200 # ~800 tokens
|
||||
user_char_limit: 1375 # ~500 tokens
|
||||
write_mode: on # on | off | approve
|
||||
write_approval: false # false = write freely (default) | true = require approval
|
||||
```
|
||||
|
||||
## Controlling memory writes (`write_mode`)
|
||||
## Controlling memory writes (`write_approval`)
|
||||
|
||||
By default the agent saves memory freely — including from the background
|
||||
self-improvement review that runs after a turn. If you'd rather not have
|
||||
memory written behind your back, `memory.write_mode` gives you three options
|
||||
(applied to **both** foreground turns and the background review):
|
||||
self-improvement review that runs after a turn. If you'd rather approve saves
|
||||
first, set `memory.write_approval: true`. It's a simple on/off gate applied to
|
||||
**both** foreground turns and the background review:
|
||||
|
||||
| Mode | Behaviour |
|
||||
|------|-----------|
|
||||
| `on` | Write freely (default — current behaviour). |
|
||||
| `off` | Never write. The memory tool returns a clean "disabled" result; nothing is saved. |
|
||||
| `approve` | Don't commit writes — review them first. Foreground writes prompt you inline (entries are small enough to read in a chat bubble). Background-review writes are **staged** instead of committed (a background thread can't block on a prompt). |
|
||||
| `write_approval` | Behaviour |
|
||||
|------------------|-----------|
|
||||
| `false` (default) | Write freely — the gate is off (the pre-gate behaviour). |
|
||||
| `true` | Require approval before anything is saved. Foreground writes prompt you inline (entries are small enough to read in a chat bubble). Background-review writes are **staged** instead of committed (a background thread can't block on a prompt). |
|
||||
|
||||
> To turn memory off entirely (not just gate it), set `memory_enabled: false`.
|
||||
|
||||
Review staged writes from the CLI or any messaging platform:
|
||||
|
||||
@@ -231,33 +232,33 @@ Review staged writes from the CLI or any messaging platform:
|
||||
/memory pending # list staged memory writes (auto ones tagged [auto])
|
||||
/memory approve <id> # apply one (or 'all')
|
||||
/memory reject <id> # drop one (or 'all')
|
||||
/memory mode approve # change write_mode and persist it
|
||||
/memory approval on # turn the gate on (or 'off') and persist it
|
||||
```
|
||||
|
||||
This is the answer to "the agent saved a wrong assumption about me": set
|
||||
`write_mode: approve`, and every save — especially the unprompted background
|
||||
`write_approval: true`, and every save — especially the unprompted background
|
||||
ones — waits for your yes/no before it ever enters your profile.
|
||||
|
||||
## Controlling skill writes (`skills.write_mode`)
|
||||
## Controlling skill writes (`skills.write_approval`)
|
||||
|
||||
Skills use the same three-state gate, but the review UX differs because a
|
||||
Skills use the same on/off gate, but the review UX differs because a
|
||||
`SKILL.md` is far too large to read in a chat bubble:
|
||||
|
||||
```yaml
|
||||
skills:
|
||||
write_mode: on # on | off | approve
|
||||
write_approval: false # false = write freely (default) | true = require approval
|
||||
```
|
||||
|
||||
In `approve` mode, skill writes (create / edit / patch / write_file / delete)
|
||||
always **stage** regardless of origin. You review the one-line gist inline, but
|
||||
the full diff stays out-of-band:
|
||||
When `write_approval: true`, skill writes (create / edit / patch / write_file /
|
||||
delete) always **stage** regardless of origin. You review the one-line gist
|
||||
inline, but the full diff stays out-of-band:
|
||||
|
||||
```
|
||||
/skills pending # list staged skill writes + a one-line gist each
|
||||
/skills diff <id> # full unified diff (best viewed in CLI or dashboard)
|
||||
/skills approve <id> # apply it (or 'all')
|
||||
/skills reject <id> # drop it (or 'all')
|
||||
/skills mode approve # change write_mode and persist it
|
||||
/skills approval on # turn the gate on (or 'off') and persist it
|
||||
```
|
||||
|
||||
On a messaging platform, approve a skill from its gist + metadata, or open
|
||||
|
||||
Reference in New Issue
Block a user