Guard & governance
Pause, stop, kill#
Four controls, one actuator, and an honest answer per session about whether each one can work.
What each does#
| Control | POSIX | Windows |
|---|---|---|
| Pause | SIGSTOP | NtSuspendProcess |
| Resume | SIGCONT | NtResumeProcess |
| Stop | SIGINT — the equivalent of Ctrl-C | Console Ctrl+C from a detached helper |
| Kill | SIGTERM, escalating to SIGKILL across the tree | taskkill /T, escalating to TerminateProcess |
Kill walks the process tree, not just the parent. An agent that has spawned a shell that has spawned a build is not stopped by signalling the top of it.
Pid-reuse guarding#
Every action re-verifies the target before signalling. Process ids are reused, and a stale pid from thirty seconds ago can belong to something entirely different by the time a policy fires.
This is the kind of bug that is invisible until the day it kills your database. The actuator checks identity, not just liveness.
One actuator for both paths#
A hand-pressed pause and a policy-driven pause go through the same code, so they
do exactly the same thing to the process — including resume, which used to
bypass it.
There is therefore one place where "what happens to the process" is defined and one place to audit.
Capability is answered per session#
runtime_control_support(runtime, session_id, cwd) is the single verdict the
Guard tab, the daemon and the actuator all read. Nothing re-derives it.
Three axes vary independently:
The OS#
Windows gets native equivalents, not a signal emulation. Two consequences:
- A Windows Ctrl+C cannot be addressed to one process. It reaches the whole
console. The UI says so rather than implying a precision it does not have.
- Win32 calls declare their
argtypesandrestypeexplicitly. The default
c_int return type truncates a 64-bit handle, and every call against a
truncated handle fails silently — which looks exactly like a control that
does nothing.
The runtime, per session#
A Cursor CLI session is a real process tree and is controllable. A Cursor editor conversation shares the one IDE process and is not — killing it would kill the editor.
The resolver answers per session. It does not refuse a whole runtime because one of its surfaces is uncontrollable.
OpenClaw pause#
OpenClaw has no pause primitive. ClawMetry's pause for an OpenClaw session
writes a flag file at ~/.clawmetry/hitl/pause_<session_id>, and that file is
enforced only by the enforcement proxy.
With no proxy running, a "pause" on an OpenClaw session changes nothing.
ClawMetry probes the proxy's status and reports advisory_only rather than
claiming the agent was held.
If pausing OpenClaw agents matters to you, run the proxy:
clawmetry proxy startThe rule#
A control that cannot work says why, next to a disabled button.
Never ship one that quietly does nothing. A greyed-out Pause with "OpenClaw has no pause primitive; start the enforcement proxy to make this advisory hold" is useful. A Pause button that appears to work and does not is worse than no button at all, because someone will rely on it during an incident.
Using them#
From the Guard tab: select a session, press the control.
From the API:
curl -s localhost:8900/api/guard/control -X POST \
-H 'content-type: application/json' \
-d '{"session_id": "…", "action": "pause"}'Accepted actions: pause, resume, stop, kill. Anything else is refused
rather than passed through to a signal helper.
resume is control-only. There is no policy that resumes a process — that is a
human decision.
These endpoints are origin-checked, because they signal real processes. They are not entitlement-gated: you pressed the button.
There is also a per-agent form:
curl -s localhost:8900/api/agents/<agent_key>/pause -X POST -d '{}' -H 'content-type: application/json'
curl -s localhost:8900/api/agents/<agent_key>/resume -X POST -d '{}' -H 'content-type: application/json'
curl -s localhost:8900/api/agents/<agent_key>/stop -X POST -d '{}' -H 'content-type: application/json'Choosing between them#
| Situation | Control |
|---|---|
| It is looping but the work is salvageable | Pause, look at the transcript, then resume or stop |
| It is going nowhere and you want a clean exit | Stop — the runtime gets to write its transcript |
| It is doing something actively harmful | Kill |
| It is spending and you cannot tell yet | Pause. It is the only reversible one. |
Prefer stop to kill where you can: a runtime that receives an interrupt
usually flushes its session file, and a killed one may not — which means the
evidence of what it was doing is exactly what you lose.
Emergency stop#
For "everything, now":
curl -s localhost:8900/api/emergency-stop -X POST -d '{}' -H 'content-type: application/json'
curl -s localhost:8900/api/emergency-stop/clear -X POST -d '{}' -H 'content-type: application/json'→ Alerts