Check that work actually happened. Independent verification for AI agent claims.
Project description
attested
Check that work actually happened.
AI agents report success. Sometimes they are wrong — and the failure mode isn't
an exception, it's a confident summary of work that never occurred. attested
checks the claim against the machine, with nothing in the checking path that
could itself be confused.
from attested import verify, command_exit_code
agent.run("fix the failing auth tests")
check = verify(command_exit_code("pytest tests/auth"))
if not check:
escalate(check.detail)
"I could write that myself in three lines"
You could. Here are four ways those three lines quietly say yes to nothing. Every one of them was live in production code that had been running for months:
"" in output # true for every output ever written
expected = spec.get("expected", "") # missing key → matches everything
"EXIT:1" in "EXIT:10" # substring match on a number
if "No such file" not in output: # empty output → "the file exists!"
None of these throw. None appear in a log. They return True, the dashboard
goes green, and the work was never done.
That last one is the worst: an empty probe result means the probe didn't run — the worker died, the command wasn't found, the result never came back. The naive check reads "no error message present" and reports success.
A verifier that passes when nothing was verified is worse than no verifier at all, because it manufactures confidence. So:
attested fails closed. Empty output, malformed spec, unknown key, timeout,
unparseable result — every ambiguous case resolves to not verified, with a
reason you can act on.
Install
pip install attested
No dependencies. Standard library only, and it stays that way — a component whose job is to be trusted shouldn't ask you to audit a dependency tree.
Use
from attested import verify, command_exit_code, file_checksum, file_exists
verify(command_exit_code("pytest tests/auth"))
verify(file_exists("/app/config.yml", min_bytes=100))
verify(file_checksum("/app/config.yml", sha256=expected_hash))
Every call returns a Result:
check = verify(command_exit_code("pytest"))
bool(check) # True if it passed — so `if not check:` reads naturally
check.detail # why, in words
check.command # what actually ran
check.output # what actually came back
check.raise_for_status() # raise VerificationFailed instead, if you prefer
verify() never raises on a failed check. Most callers are deciding whether to
retry or escalate, and control flow reads better than exception handling for
that. A timeout or an executor that can't run is a failed check, not an
escaped exception — a probe that didn't finish hasn't confirmed anything.
Catching a plausible lie
Checking that reality is fine is one thing. Checking that the agent's claim matches reality is the interesting one:
from attested import verify, agent_result_matches
claim = agent.run("how much disk is free on /?")
check = verify(agent_result_matches("df -h / | tail -1"), agent_result=claim)
FAIL: probe: '42G' | agent claimed: '500G'
This exists because substring checks false-pass on fabrications. A completely
invented df table still contains a /. Comparing the claim against ground
truth doesn't have that hole.
Where the probe runs is the whole game
verify() takes an executor — any callable that runs a shell command and
returns its output. The default runs locally:
from attested import verify, ssh, command_exit_code
verify(command_exit_code("systemctl is-active nginx"), executor=ssh("web-01"))
verify(command_exit_code("pytest"), executor=my_container_exec)
The one rule: the executor must not run inside the thing being verified. If you ask an agent to confirm its own work, you've rebuilt the problem this package exists to solve. A separate process is the minimum; separate hardware is better when the claim is about hardware.
An executor is just Callable[[str], str], so wiring up Docker, a git worktree,
or a remote runner is a few lines.
Checks
| Check | Confirms | Strength |
|---|---|---|
command_exit_code(cmd, expected=0) |
the command exited with that status | strong |
file_checksum(path, sha256) |
contents hash exactly | strongest |
file_exists(path, min_bytes=None) |
file present, optionally non-trivial | moderate |
agent_result_matches(cmd) |
the agent's claim equals ground truth | strong |
command_output_contains(cmd, expected) |
output contains a string | weak |
command_output_contains is documented as weak on purpose. A fabricated output
can contain the expected token by coincidence. Reach for command_exit_code or
file_checksum when the answer matters.
Specs are plain dicts, so they can live in a database column, travel over a
queue, or sit in a config file. The constructors are ergonomics on top of that
format — verify() accepts either.
verify({"type": "command_exit_code", "command": "pytest", "expected_exit_code": 0})
Malformed specs are rejected by name, not silently reinterpreted:
FAIL: invalid spec: unknown key(s) 'expected' for type 'command_exit_code';
allowed: command, expected_exit_code. A misspelled key is not ignored here --
it would silently change what gets checked.
That specific typo shipped in a real test harness and made the gate quietly check for exit code 0. It was correct by luck, which is exactly why it survived.
Status
0.0.1. The API above is what exists and works, with a test suite covering the
evaluation logic including every failure mode described here.
It's early. The spec format and executor signature may still move before 0.1.
If you use it and something is awkward, that's worth an issue — the shape is
still cheap to change.
Extracted from Helmward, a self-hosted agent orchestrator, where this logic gates whether agent tasks are allowed to be marked done.
MIT.
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 attested-0.0.1.tar.gz.
File metadata
- Download URL: attested-0.0.1.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c2275930a31c5e81f6a759ce88955cf259de9730ae020522600c08fe49b1ad1
|
|
| MD5 |
9887d1dbf7925dbce916b82583142ee1
|
|
| BLAKE2b-256 |
e9f9e0e3c626aac9db40200681be2d06dad899e8c582ab610643036f077c08ff
|
File details
Details for the file attested-0.0.1-py3-none-any.whl.
File metadata
- Download URL: attested-0.0.1-py3-none-any.whl
- Upload date:
- Size: 13.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7205cd8cd1cf7fce939f95cd515ed5eaeb49b3f2689645114ff0f739109a0d96
|
|
| MD5 |
288333476491641cb8e9e1fec7f8ad80
|
|
| BLAKE2b-256 |
6e3931f127a6673fe572f765a1e4a7d8a4a357504d44b552445f5f05715c7692
|