asop
The ASOP contract — a versioned, verified, self-revising procedure standard for agentic work.
This repository is the normative specification (ASOP.md) and its
reference implementation. It is deliberately standalone: it depends on nothing,
and nothing in it imports a coordination plane or a runtime.
Two implementations speak it today — the AgentCo Hub (coordination) and the AgentCo Harness (execution). They are independent of each other, and both are mine: the standard has not yet been implemented by anyone who did not also write it, which is the honest status and the thing I would most like to change.
What exists to make that possible: a normative schema as JSON Schema in YAML, and 39 conformance vectors as data, so an implementation in any language can prove it conforms without depending on this package or reading a line of Python.
The ASOP contract, as code. A procedure that is versioned, verified, and self-revising is only useful if two independently-owned pieces of software — a coordination plane and the harness actually running the work — agree on what a gate, an attestation, and a procedure record are, without either importing the other. This package is that agreement.
Who imports this
- The AgentCo Hub (
agentco/gates.py,agentco/errors.py,agentco/sop.pyandagentco/policy.pyare thin shims over this package, keeping every existing caller and test unchanged). - The AgentCo Harness, the
standalone execution runtime, from its P2 (adopting this schema in place
of its own
VERIFY_KEYS/validate_verify). - Anyone else building a plane or a harness that wants to speak the same gate shape as either, without depending on the code that runs either one.
What's here
| Module | Contract |
|---|---|
asop.errors |
Refusal — the one exception type: a stable machine code, a human message, and a remediation sentence. Every refusal in this package (and, by re-export, in the plane) is one of these. |
asop.gates |
The unified gate schema. validate_gate(payload, *, require=(), max_park_seconds_ceiling=None) normalises a deterministic / judged / human gate — merging the plane's park-clock fields with the Harness's staged-check and runtime-hint fields into one shape — or refuses. validate_attestation, attestation_passes, retry_decision cover the evidence side. |
asop.sop |
The SOP record contract: SopStatus, the SOP and ASOP dataclasses, validate_fields / validate_asop / validate_step. Not the store — keeping versions, locking a file, and filing runs from a template is plane- or harness-side machinery, layered on top of this record shape. |
asop.revision |
The revision policy of ASOP.md §6.4: the four rules an agent reviser is bound by — protected tags, the human ratchet (absolute on a first activation), no-undoing-a-human, and the human_only verbs — plus the four operator declarations they read (ASOP_HUMANS, ASOP_PROTECTED_TAGS, ASOP_VERIFIERS, ASOP_ADJUDICATORS), each still falling back to its pre-split AGENTCO_* name for the two implementations that predate the split; whether that fallback is a permanent courtesy or gets a deprecation date is still open. Shared rather than plane-side because a rule that held on the plane and not in the runtime executing its work would be a rule with a door beside it. Which verb calls which rule stays each side's own wiring. |
asop.refusals |
The refusal-code vocabulary — every machine-readable code a Refusal carries across the reference plane, named once with a one-line meaning, for a reader scanning codes rather than call sites. |
The gate schema
One canonical field decides the shape: kind (deterministic \| judged \| human). class is accepted as a read alias — the Harness's historical
name for the same field — and normalised output always uses kind.
Sending both, disagreeing, is refused.
Three field groups:
- core (always required) —
kind, and exactly one ofcheck(one command) orchecks(an ordered, staged list; a one-elementchecksnormalises intocheck). - clock (the plane's park clock) —
max_park_seconds,on_timeout,escalate_to(only withon_timeout: escalate),verifier(required forhuman, refused fordeterministic, optional forjudged). Declared as a whole or not at all — a gate carrying some of these fields but not bothmax_park_secondsandon_timeoutis always refused, regardless of whether a caller requires the clock at all. - execution (the Harness's runtime hints) —
cwd,timeout_s,rubric(judgedonly),judge_route(judgedonly).
Whether the clock group is mandatory is the one thing that differs by
caller, and the one knob validate_gate exposes: require=("clock",) is
the plane's convention today; require=() is a caller with no park-clock
concept, such as the Harness before it adopts one. The 30-day park-clock
ceiling is not a package constant — it is the plane's own decision
about its own registry, passed in as max_park_seconds_ceiling. A caller
with no ceiling passes None.
Output is always fully normalised: every known field present (None where
absent), plus "schema_version": 1.
Versioning
asop.SCHEMA_VERSION covers the gate schema's normalised shape. It bumps
only on a breaking change — a new required field, a renamed one, a changed
type. An additive optional field or a new refusal code does not bump it,
because existing readers already tolerate both. See asop/__init__.py.
Install
pip install asop-spec # or: uv add asop-spec
The distribution is asop-spec; the import is asop:
from asop import validate_gate, Refusal
(The bare asop name on PyPI was taken by an unrelated placeholder before this
package existed.)
Standard library only, and that is a rule rather than a coincidence. This package is imported by every side of the contract, so a dependency taken here is forced on everyone who speaks it. A standard that drags a dependency tree behind it is a framework wearing a standard's clothes.
Testing
uv run --extra dev pytest -q # 150 tests
uv run --with pyyaml python conformance/runner/python.py --verbose # 61 vectors
pytest is a dev dependency, not a default one — a bare uv run pytest fails with
"Failed to spawn: pytest" because nothing is installed to spawn; --extra dev is
what pulls it in. The conformance runner takes PyYAML the same way, inline rather
than as a dependency, for the reason given above: asop itself has none. See
conformance/README.md for what a vector is, how counts
are recomputed rather than typed by hand, and what the suite does not yet prove.
Licence
Apache License 2.0. The patent grant is the point: this package is the contract other people's harnesses are asked to depend on, and a contract adopted across organisations needs the grant stated in the file rather than implied.
Copyright 2026 Marcelo Bidoli Fernandes and the ASOP Authors.
Metadata
Release files for asop-spec 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| asop_spec-0.4.1.tar.gz | 105.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| asop_spec-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 189.5 kB
Release files / asop_spec-0.4.1.tar.gz
| Download URL | asop_spec-0.4.1.tar.gz |
|---|---|
| Size | 105.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0c59a188c033c7a6c8c93e0a15aa63a91efb3867e30f39ba24f2c00eb664eb07
|
|
BLAKE2b-256 checksum How to use checksums |
e511f8217fd314209ea02f5fd5cdde1cd81ee477c4764a5de00276f950157807
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / asop_spec-0.4.1-py3-none-any.whl
| Download URL | asop_spec-0.4.1-py3-none-any.whl |
|---|---|
| Size | 84.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f49435966a95be3073bafcb0a9c2c18c9b6b1d2c6ad8d11761aa622a175f898a
|
|
BLAKE2b-256 checksum How to use checksums |
26fd1370785c49258f64a92396704795b878a9b110260c72d1bdfcc005c99aeb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|