A denied action, and the file it did not write
A DENIED badge is a label a page draws for itself, and a recording of one is the same label with production values. This page publishes a run instead: a real agent tool call, refused by the released SDK’s own enforcement point, with the effect the tool exists to produce read back afterwards by a program that took no part in the decision.
What was asked for, and on which path
An agent tool named write_incident_report writes a report file called incident-report.txt into a working directory. That file is the side effect — the thing the tool exists to produce, and the thing a reader can look for without trusting anyone. Policy refuses the tool under a rule named deny_filesystem_writes.
The call travels the framework’s ordinary tool-execution path, smolagents.tools.Tool.__call__. The component that refuses it is agent_assembly.adapters.smolagents.SmolagentsPatch — shipped in the SDK, not written for this page. It consults the decision first and returns a refusal to the caller in place of running the tool body.
The decision itself came from in-process interceptor, enforce posture, not from a running gateway. That is a real limit on what this run shows and it is stated here rather than left to be inferred: what was exercised is the path from a deny verdict to a body that did not run, not the production of the verdict.
Four steps, in the order they happened
- The tool call is madeThe agent invokes the tool the way the framework invokes any tool. Nothing in the tool’s own code knows it is governed.
- A decision is produced, and it is produced firstThe enforcement point asks for a verdict before it forwards the call. The record it acted on is below, field for field.
- The caller gets a refusal instead of a resultThe tool body is not reached. What the caller receives is a refusal string, which by itself is still only a label.
- A different process looks in the directoryThis is the step that makes the other three worth reading. The listing is taken by a program that took no part in the decision.
The decision the enforcement point acted on
| Decision field | Recorded value |
|---|---|
| tool_name | write_incident_report |
| args.directory | <workdir> |
| args.body | INCIDENT: disk utilisation 94% |
| status | deny |
| reason | Tool 'write_incident_report' is blocked by policy rule 'deny_filesystem_writes'. |
What the caller received in place of the tool’s result:
[BLOCKED by governance policy] Tool 'write_incident_report' is blocked by policy rule 'deny_filesystem_writes'.. Please choose a different approach to accomplish this task.
This record was captured by the interceptor this run supplied. On the SDK’s own shipped interceptor neither audit hook resolves, so a denied call leaves nothing durable behind — recording on that path is Planned, and this page shows a decision, not an audit trail.
What a separate process saw in the directory
After the call returned, the directory was listed by a program launched as its own operating-system process. It imports three standard-library modules and nothing else, so it cannot consult the policy engine, the enforcement point, or the value the tool call returned. Its answer is an observation rather than a report.
python3 -c 'import os,sys,json;print(json.dumps(sorted(os.listdir(sys.argv[1]))))' <workdir>
| Run | Enforcement | Policy | Report file afterwards | Post-condition |
|---|---|---|---|---|
| denied | installed | deny | absent | held |
| allowed | installed | allow | present | held |
| enforcement_removed | removed | deny | present | held |
The first row alone would settle nothing — a harness that writes nothing produces the same listing, and so does a policy no component ever consulted. The second row moves the policy and holds the enforcement point fixed; the third removes the enforcement point and holds the policy fixed. Both wrote the file. 3 of 3 runs ended as their design predicted, and the absence in the first row is a measurement because the other two came out the other way.
In the claim vocabulary ADR 0033 §6 defines, this run reaches Denied before execution. The verdict on its own would reach only Evaluated; what carries the stronger term is the row above, because that term asks for a refusal by a component sitting before the effect and for the effect to be shown missing. Were the side effect ever left unchecked, the honest term would drop back to Evaluated.
What this run does not show
- One tool, one framework adapter, one SDK build, one machine. A second tool on the same path was not run, and a reader should not carry this result to one.
- The verdict came from an in-process interceptor, so the run exercised the path from a deny verdict to a body that did not run — not a gateway producing that verdict, and not the network between them.
- The enforcement point sits on the framework’s tool-execution entry. A tool body reached by some other route in the same process is outside what this run measured, and the run says nothing either way about it.
- No durable evidence record was written. The decision shown above was captured in memory by this run’s own interceptor; SDK-side recording is Planned.
- Other operating systems, other Python versions and the other language SDKs were not exercised here. Their status is neither confirmed nor denied by this page.
Run it yourself, then try to break it
Everything above came out of denied-action-capture.py, run against the published SDK 0.0.1rc6 installed from PyPI by the second command below. The run itself reaches no gateway, needs no credentials and opens no network connection. It writes the recording this page renders.
uv venv --python 3.12 .proofenv VIRTUAL_ENV=.proofenv uv pip install 'agent-assembly==0.0.1rc6' 'smolagents==1.26.0' .proofenv/bin/python scripts/denied-action-capture.py
Then sever the enforcement path and watch the check go red. The same script runs the denied call with the enforcement point uninstalled; the report file appears, the post-condition fails and the command exits non-zero. A check that cannot fail is not a check, so this one is shown failing before it is offered as passing.
.proofenv/bin/python scripts/denied-action-capture.py --sever-enforcement
The method — assert the effect is missing before asserting the error, and pair the run with a control that removes the governance — comes from the negative-control suites in the Python SDK repository and its Go and Node siblings, tracked under AAASM-5529. Those suites stand a test double in for the component that decides, which is the right call for a unit test and means what they establish is not reproducible from an installed package. This page runs the same method against a published build, so you can run it from a release.