Parse/build RECUSE governance signals and enforce agent halt mid-flight.
Project description
recuse-signal
Parse and build RECUSE governance signals, and force-stop an LLM agent mid-flight when a stop signal arrives — even when the agent won't stop on its own.
recuse-signal is the reusable core of the Recuse project:
a small, protocol-agnostic, in-band response format that a server emits to tell a
connecting automated agent that its access is governed, plus a harness-level
interceptor that enforces the stop instead of relying on the agent's cooperation.
- Zero runtime dependencies (Python standard library only).
- Python 3.9+.
- Apache-2.0 licensed.
Why enforcement, not cooperation
The RECUSE stop-signal study delivered a RECUSE/0.2 halt directive in-band to a
running LLM agent and measured whether the agent voluntarily stopped. It did not:
halt was honored 0/40 times by cooperation alone. Agents do not reliably interrupt
themselves mid-task — finishing the assigned work tends to win.
The fix in this package is to stop relying on the agent's cooperation. The
HaltInterceptor wraps the agent's tool-execution loop and scans every tool result
(and, optionally, the model's own output) for a RECUSE stop sentinel using fail-closed
parsing. The instant a halt is detected, the loop is terminated — a HaltEnforced
exception propagates so no further tool call and no further model turn can run. The agent
never gets the chance to "decide" to keep going; the harness stops it, guaranteeing the
stop.
Install
pip install recuse-signal
The distribution is named recuse-signal; the import package is recuse.
The signal
One sentinel line, protocol-agnostic and greppable:
RECUSE/<major>.<minor> <directive>; <key>=<value>; <key>=<value> ...
Four directives:
| Directive | Meaning | Running agent |
|---|---|---|
deny |
Automated access is prohibited on this resource. | stop |
throttle |
Automated access permitted but rate-limited/discouraged. | advisory |
warn |
Advisory only; access is governed and observed. | advisory |
halt |
In-session "stop now" for an already-running agent. | stop |
Parsing is fail-closed: a RECUSE/ anchor with a malformed sentinel, or an unknown
directive, is treated as a stop. Detection is case-sensitive on RECUSE/, so a URL
like https://github.com/mthamil107/Recuse never false-trips a parser.
Quickstart — parse a signal
import recuse
sig = recuse.parse_signal("RECUSE/0.2 halt; reason=maintenance; id=abc-123")
print(sig.directive) # "halt"
print(sig.reason) # "maintenance"
print(sig.id) # "abc-123"
print(sig.is_stop) # True
# Advisory signals do not stop a running agent:
recuse.parse_signal("RECUSE/0.1 warn; reason=production").is_stop # False
# Find every sentinel anywhere in a blob of tool output:
for s in recuse.scan_text(tool_output):
print(s.directive, s.params)
Quickstart — build a signal
import recuse
line = recuse.build_signal(
"halt",
reason="operator-request",
ref="https://example.com/ai-policy",
id="abc-123",
)
# "RECUSE/0.2 halt; reason=operator-request; ref=https://example.com/ai-policy; id=abc-123"
Quickstart — force-stop an agent loop
Wrap any provider's tool-execution loop as three callables. run_guarded scans every
tool result and stops the instant a halt is enforced:
from recuse import run_guarded
def step_fn():
# One model turn -> (assistant_text, [tool_call, ...]).
# Empty tool-call list means the agent is done.
...
def tool_fn(call):
# Execute one tool call and return its result (str / bytes / dict / object).
...
def feed_fn(call, result):
# Feed the tool result back to the model.
...
result = run_guarded(step_fn, tool_fn, feed_fn, max_steps=8)
if result.halted:
print("stopped at step", result.halt_step,
"reason:", result.signal.reason,
"actions prevented:", result.actions_prevented)
Or drive it yourself with the interceptor, e.g. as a decorator on your tool executor:
from recuse import HaltInterceptor, halt_guarded, HaltEnforced
interceptor = HaltInterceptor()
@halt_guarded(interceptor)
def run_tool(call):
return call_the_real_tool(call) # its return value is scanned automatically
try:
while not done:
for call in model_tool_calls():
run_tool(call) # raises HaltEnforced the moment a halt appears
except HaltEnforced as e:
handle_stop(e.signal)
HaltInterceptor records halted, signal, halt_step, source,
actions_prevented, and an events audit log.
CLI
recuse parse "RECUSE/0.2 halt; reason=maintenance" # detect + print directive/JSON
recuse parse "RECUSE/0.2 halt" --json
recuse check path/to/tool-output.log # scan a file; non-zero exit on a stop
recuse build halt --reason operator-request --id abc-123
recuse version
If the recuse console script is not on your PATH, run it as a module:
python -m recuse.cli parse "RECUSE/0.2 halt"
Security note
RECUSE is a cooperative governance control, not a security boundary. A malicious or
non-conforming agent can ignore the signal and proceed with valid credentials. The
halt interceptor in this package raises the assurance from "cooperation" to
"enforcement" within a loop you control, but real security still rests on not issuing
production credentials to agents, least-privilege roles, and network controls, with
process termination and credential revocation as the backstop.
References
- Repository: https://github.com/mthamil107/Recuse
- Paper (arXiv): https://arxiv.org/abs/2606.06460
- RECUSE signal spec:
spec/recuse-signal-v0.1.md(thedeny/throttle/warndirectives) andspec/recuse-signal-v0.2.md(the in-sessionhaltdirective), plus the Internet-Draft in the repository.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file recuse_signal-0.3.0.tar.gz.
File metadata
- Download URL: recuse_signal-0.3.0.tar.gz
- Upload date:
- Size: 22.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a551ece0d73797b44f18387f1c285ad9145c923b6e8e55e1d17c2ee7afae4b81
|
|
| MD5 |
2275bb16384f9acba13975c1a96ce963
|
|
| BLAKE2b-256 |
b0ec55bc048fd9795238f0c077f53526a481fb268c74f87db4e18a42494e6b71
|
File details
Details for the file recuse_signal-0.3.0-py3-none-any.whl.
File metadata
- Download URL: recuse_signal-0.3.0-py3-none-any.whl
- Upload date:
- Size: 19.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a7fc03355e349decaf34f4467afc6cbc0827f29fb99d2ea5ed4cf27f7ac1442
|
|
| MD5 |
868bee55a1e1f87a3508af8d0d0c7b3c
|
|
| BLAKE2b-256 |
082123b6e26bb6d0d6f38390ee2c2497aead07cbd6926468bac131055f17492f
|