delete-guard ยท diff

git:20261013.c1f33e7 to git:20261019.1cb1d3a

30 added, 13 removed. Audit A to A.

---
name: delete-guard
description: >-
Recoverable deletion discipline for AI agents. Use whenever you need to
delete, clean, or discard files/directories or run destructive git
operations (clean, reset --hard, restore, force push) inside a workspace.
Routes supported operations through quarantine or Git snapshots, and
explains how to stop safely when compensation or policy blocks you.
---
# delete-guard
You operate inside a workspace where irreversible destruction is not a
default capability. This skill defines the deletion discipline the guard
enforces and how to work with it productively. The four pillars:
- **Scope** - you act inside the workspace; the workspace root, `.git`, and
everything outside are never yours to delete.
- **Recoverability** - supported valuable deletions relocate to the selected
quarantine with a manifest; supported Git overwrites snapshot tracked state.
Provably regenerable, ignored artifacts may be deleted directly.
- **Authorization** - a human decides permission boundaries. You can be
*downgraded* to RESTRICTED mode; you can never promote yourself back.
- **Auditability** - supported writers record decision/recovery metadata in
JSONL according to their CLI contract. Mutation intents must be durable;
assessment/blocked audit may be best effort, and early no-op paths may not
write an audit. Calls outside the covered path are not automatically visible.
## The one rule
**Prefer `safe_delete` over `rm`.** It applies the guard's recovery policy.
```bash
# delete explicit targets; valuable content is quarantined:
- python3 <repo>/skills/delete-guard/scripts/safe_delete.py src/old_module.py
- python3 <repo>/skills/delete-guard/scripts/safe_delete.py 'build/**/*.tmp' --reason "stale build output"
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/safe_delete.py src/old_module.py
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/safe_delete.py 'build/**/*.tmp' --reason "stale build output"
# undo a transaction (list first if unsure):
- python3 <repo>/skills/delete-guard/scripts/restore.py list
- python3 <repo>/skills/delete-guard/scripts/restore.py <txid>
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/restore.py list
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/restore.py '<txid>'
# current guard state:
- python3 <repo>/skills/delete-guard/scripts/status.py
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/status.py
```
Globs passed to `safe_delete` are expanded explicitly before anything moves.
That is why `safe_delete '*.log'` works while `rm *.log` is blocked: the
guard refuses opaque target sets, not cleanup work.
## If you run `rm` / destructive git anyway
A harness adapter may intercept the command before execution. Verdicts you
can receive:
| Verdict | Meaning | Your move |
|---|---|---|
- | `ALLOW` (harness may show `PROCEED`) | safe as-is or compensation already applied | continue; record and verify any `txid` |
+ | `ALLOW` | no recovery action for recognized operations; unrecognized effects are unchecked | continue only within the authorized scope; do not infer that scripts or overwrites are safe |
+ | `RELOCATE` / `SNAPSHOT` (harness may show `PROCEED` after compensation) | quarantine or tracked Git snapshot required | record and verify the resulting `txid` before continuing |
+ | `ASK` | this understood operation needs single-execution approval | use the host's human approval path; if unavailable, stop or split the command |
| `BLOCK_UNDETERMINABLE_EFFECT` | targets unresolvable (`$VAR`, `bash -c`, `find -delete`, `xargs`) | restate with explicit paths, or use `safe_delete` |
| `BLOCK_WILDCARD` | glob target set is opaque | use `safe_delete` with the glob |
| `BLOCK_OUT_OF_WORKSPACE` / `BLOCK_PROTECTED_PATH` / `BLOCK_PROTECTED_ANCESTOR` | outside boundary, workspace root, `.git`, or a filesystem root (`/`, `/home`, `/usr`, `$HOME`) | do not retry; this is a hard boundary. Ask the human if it is truly needed |
| `BLOCK_RESTRICTED_MODE` | session is downgraded | only explicit single-file deletes are permitted; ask the human for anything more |
| `BLOCK_FORCE_PUSH` | remote history destruction | do not retry; escalate to the human |
Shape rules on compound commands: a single line that `cd`s anywhere before a
destructive op (F1), or that creates files (`touch/mkdir/cp/mv/tee`,
- redirections) before destroying them (F2), is refused as undeterminable.
- Run deletions as standalone commands with an explicit workdir.
+ redirections) before destroying them (F2), can return `ASK` when its targets
+ are resolvable and within policy boundaries. Unresolvable effects and hard
+ boundaries still return `BLOCK`; hosts without a usable approval path deny
+ the `ASK` call. Run deletions as standalone commands with an explicit workdir.
A block is not an error to route around. Retrying the same operation in a
disguised form (`/bin/rm`, `python -c`, a script) violates this discipline and
can bypass both protection and audit. Do not treat a missing audit as proof
that no operation occurred.
`git reset --hard [commit]` first checks whether its target tree would
overwrite untracked or ignored content. A tracked-only stash cannot recover
those paths, so a collision returns `BLOCK_GIT_RESET_COLLISION` without
moving them or creating a snapshot. Preserve the conflicting content
separately before retrying. Use a standalone reset without shell wrappers;
compound commands, submodule effects, unknown options, and stash-relative
targets are refused as undeterminable. Unrelated untracked files are allowed.
## Compensation preflight and verification
Before sandboxed use, ensure `.agent-trash/` is already ignored by a tracked
`.gitignore` or by host-managed `.git/info/exclude`. If Git metadata is
read-only and no ignore rule exists, the guard blocks before moving a source.
After a relocation or snapshot, verify that its `txid` appears as
`RESTORABLE` in `restore.py list` before running a related destructive command.
After restoration, verify that a consumed relocation shows `RESTORED`.
If the guard reports an internal, preflight, manifest, or compensation error,
stop immediately. Inspect both the origin and `.agent-trash/`; never infer
that an error means no filesystem change occurred. A durable
`relocate-intent` can keep an interrupted relocation discoverable.
## Assessment and preflight
Advisory `check.py` and `safe_delete.py --dry-run` preserve command targets,
but may create quarantine/audit metadata and a Git local exclude rule.
They are not zero-write filesystem checks. Advisory exit 0 means the
assessment completed; inspect the decision and never execute a BLOCK/ASK
command merely because the exit was zero. Blocked safe_delete still exits 2
under dry-run. Audit warnings do not imply that all metadata writes were
undone. Use only a disposable fixture when verifying this behavior; the
[policy reference](references/policy.md#assessment-and-dry-run-side-effects)
defines the no-match, existing-ignore and external-bucket branches.
Since 0.2.5-rc1, new `safe_delete` audit events record decisions, target counts and established
recovery IDs/counts rather than target paths or free-form reasons. `--reason`
is still accepted, but its text is not persisted in audit or transaction
metadata. Exact recovery paths remain in the CLI result and manifest.
At released RC1, historical records and automatic session handling are unchanged.
Since RC2, the shared audit layer projects supported metadata on append/read,
uses opaque session correlation and limits status/GC metadata; see the
[shared audit contract](../../docs/guides/audit-metadata.md) and the
[policy reference](references/policy.md#safe_delete-audit-metadata).
## Quarantine location queries
+ In 0.2.5, deletion/status results explain
+ retained storage and include GC commands. Moving files to quarantine keeps
+ their data on disk. Review the plan before choosing `--execute --all-eligible`
+ or a specific `--execute --txid`; use the same working directory and workspace/
+ trash settings. Age/size thresholds do not perform automatic cleanup.
+
+ `status.py` reports local recovery data and does not verify host activation.
+ DSH's status tool separately reports the plugin process startup check and
+ listener registration, with live interception still `UNVERIFIED`. A missing
+ plugin cannot supply its own status tool. `ALLOW_NOOP` does not certify
+ unrecognized script/interpreter effects. See the
+ [feedback guide](../../docs/guides/recovery-feedback.md).
+
When the user asks where quarantines are or how many exist, use the shared
read-only query with a current-directory or user-selected scope:
```bash
- python3 <repo>/skills/delete-guard/scripts/status.py --trash-index --json
- python3 <repo>/skills/delete-guard/scripts/status.py --trash-index --root <projects> --max-depth 3 --json
- python3 <repo>/skills/delete-guard/scripts/status.py --trash-index --trash <custom-store> --json
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/status.py --trash-index --json
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/status.py --trash-index --root /absolute/path/to/projects --max-depth 3 --json
+ python3 /absolute/path/to/agent-guard/skills/delete-guard/scripts/status.py --trash-index --trash /absolute/path/to/custom-store --json
```
Report the scope, candidate count, metadata-identified count and read/budget
errors. Do not claim that unconfirmed/unreadable locations are absent or that
the result covers the whole host. Explain that these are bucket locations,
not file/transaction totals. If you supplement the result yourself, identify
that source separately. Existing native status tools may show the current
project only; use the CLI for this indexed query. Location metadata and cached
JSON are not authorization to purge, and recovery file contents must not be
read just to answer a location/count question.
## RESTRICTED mode
- RC2 and RC3 retain RC1 protection for the quarantine root, root manifest,
+ Version 0.2.5 retains RC1 protection for the quarantine root, root manifest,
audit/state files, state temp file, sessions tree and storage access parents.
Housekeeping does not override those checks. Use the existing explicit
- `gc.py --execute --txid <id>` entry for a validated, audited transaction purge.
+ `gc.py --execute --txid '<txid>'` entry for a validated, audited transaction purge.
Ordinary unprotected payload housekeeping retains its existing policy.
GC plans omit `PURGED` history; they do not delete stored Git stashes.
After a human veto, the session runs with narrowed powers: explicit
single-file deletes inside the workspace still work (quarantined as usual);
recursive deletes, globs, and destructive git operations are refused. Only a
human can restore NORMAL. If a task requires more, say so plainly
and ask.
## Regenerable artifacts
Provably regenerable targets (git-ignored AND matching known artifact names
like `node_modules/`, `dist/`, `__pycache__/`) are allowed to be deleted
directly - no quarantine overhead. If git cannot prove a target is ignored,
it is treated as valuable and quarantined. Uncertainty increases restriction.
## Recovery
Every relocation writes `origin -> trash` pairs into
`.agent-trash/manifest.jsonl`. Restore is non-destructive: it refuses to
overwrite anything that now exists at the origin unless a human passes
`--force`. Git snapshots are stored as stashes named `agent-guard:<txid>`;
restore applies them and never drops them.
With human `--force`, an existing occupant is first preserved by the normal
relocation journal in a separate transaction. The result's optional
`backup_txids` lists preservation attempts; an ID alone does not prove the
copy completed. Inspect those IDs with `restore.py list --json` and the
filesystem after any error. A failed old-version move must not be followed
by deleting the preserved occupant. Restoring a completed backup transaction
uses the same `restore.py <txid>` command; if the origin is occupied, the
usual conflict/force rules apply again. Preserved versions consume quarantine
space and remain subject to its existing explicit GC policy.
See `references/policy.md` for the complete rule table, verdict codes, and
manifest format.
The candidate's restore audit records counts and transaction associations rather
than duplicate paths/error text. CLI results and recovery manifests still
contain the exact data needed to inspect and restore; a failed CLI result
must still be checked against the filesystem and preservation attempts.