Skip to main content

Accountable Surface

Accountable Surface

Accountable Surface lets an AI agent take only the file, command, web, or browser action a person has approved. It checks the request and authorization, blocks or pauses when needed, verifies the outcome, rolls back reversible failures, and records decisions and outcomes in a journal. Persisted journals are hash-chained so later edits, deletions, or reordering are detected.

version license

The Python core uses only the standard library. Browser automation is an optional extra.

Try it

After cloning the three repositories and installing Accountable Surface, run the complete local file-action example:

python examples/actuate_demo.py

The example refuses an unapproved write, performs an approved write, detects a wrong result, restores the previous file, and prints the journal entries.

Verified Today

The published GitHub v0.2.0 release is available from commit b2ae9be77038753d9bdbda861e9908542883ff2c. Its release assets are:

  • accountable_surface-0.2.0-py3-none-any.whl (sha256:f2fb09ac66d00bb188da7fdd6fd1ba37485f5fe245c0596902adc355f6bccdfa).
  • accountable_surface-0.2.0.tar.gz (sha256:47a126a821861ce49581b49eb67892220a61633de8b71323e41749e8fa6538c6).
  • SHA256SUMS.txt (sha256:b7acee277353171f79865a32640bf64b48efcfbb919a4e7c2143a4a71c845855).

The release was verified from a clean isolated worktree with these checks:

  • python -m pytest: Python suite passed.
  • node --test web/*.test.mjs: browser-interface tests passed.
  • python tools/check_repo_art.py --json, python tools/check_repo_card.py, and python tools/check_repo_flow.py: repository documentation/art gates passed.
  • python -m build --sdist --wheel: built the local sdist and wheel.
  • python -m twine check dist/*: passed with Twine 7.x.
  • A fresh virtual environment installed the wheel, checked both console scripts, and ran a private synthetic durable-authority control against a disposable authority-state database.

These checks cover the repository's deterministic local paths. They do not establish safety in every host environment or validate every third-party browser, website, or model provider.

Features

  • Third-party API actuation. ApiEffector writes through a service's official API under the same contract as the rest. The caller hands in an intent and a body; the host, method, route shape, and credential belong to the effector, so an agent can ask to post a comment and cannot reach an admin route or read the token back. Verification re-reads the resource, so a service that answers 201 and stores nothing is refuted rather than believed. FakeApiDriver makes the whole path testable with no network and no credential.
  • Native web actuation, no browser. WebEffector navigates, fills fields by their visible label, and submits forms on live server-rendered pages, driven by a stdlib HTTP and HTML parser backend (HttpDriver). Origin-bounded by construction.
  • JS-capable browser actuation, optional. BrowserEffector clicks by accessible label, follows navigation, and runs JavaScript on single-page apps. Tests and offline demos use the deterministic FakeBrowserDriver; production can inject PlaywrightDriver (the [browser] extra, lazily imported, never a hard dependency).
  • OS command actuation. CommandEffector runs allowlisted commands only, as argv with shell=False, in a bounded working directory. Irreversible commands escalate to needs-human.
  • Filesystem actuation with rollback. FilesystemEffector is bounded to a root, verifies its own writes by re-perceiving, and rolls back a reversible action that fails verification.
  • Windows application actuation, by label not by pixel. UiaEffector reads a window's control tree and acts on one control by its accessible name: press a button, set a field. It is built for a single window and refuses a target naming another. A click cannot be undone, so the caller declares what should follow it (a control appears, disappears, or carries some text) and a plan without that is refused before anything is touched. Verification re-reads the window, so an application that accepts a click and does nothing with it is refuted rather than believed. FakeUiaDriver runs the whole path on any operating system, with no window open.
  • A ladder that says what it cost. Reaching a desktop application starts with the control tree and falls to pixels only when the cheap instrument cannot answer. The escalator asks each rung in that rung's own terms and records why it fell: the tree was clipped, the label named two controls, the window did not read. Falling does not buy an answer. When the ladder runs out you get NEEDS_HUMAN, the whole trace, and the witnessed sight the deepest rung produced, because a perceptual hash is a real receipt for an image and no answer at all about a control named Save.
  • Structured perception. Organs read a target as a content-addressed structural observation with a falsifiable self-test, not a screenshot.
  • Grounding. A reference cortex (ReferenceCortex) scores reference relevance for a subject and reports "ungrounded" instead of guessing, with native arXiv lookup via the stdlib. An action can carry a justification; an ungrounded premise escalates to needs-human.
  • Bounded autonomy. pursue runs a multi-step plan under one grant envelope with no per-step prompt, halting the instant a step is denied or fails verification.
  • Shared world server. A zero-dependency live server (stdlib http.server plus SSE) where proposed actions run the real loop and stream to every open browser tab, with a small web UI in web/. Optional pilots connect a model (Claude or Ollama) to drive it.
  • Durable, tamper-evident memory. An append-only JSONL journal that replays across sessions, hash-chained so an edited, deleted, or reordered entry is caught on reload even though it still parses (corruption and tamper are counted separately, never conflated). verify_journal() re-derives the chain and returns the verdict; interocept() is a content-addressed view of the surface's own conduct.
  • Live MCP server. perceive, propose, actuate, session_journal, and interocept exposed over MCP stdio (the [server] extra). actuate reaches only the effectors the operator exposed, and nothing is exposed by default.
  • Interoperable MCP server (no FastMCP). A zero-third-party-dependency stdio server, accountable_surface.interop_mcp, carries the six accountable primitives (perceive, propose/gate, actuate, journal, receipt) plus the shipped read-only verb device_ls, so other harnesses (Claude Code, Codex, Cursor, the Flywheel bundled lane) adopt one seam. See docs/interop-mcp.md and interop/.
  • Action certificates. certify composes the gate, effect, and grounding verdicts into one certificate token; a denial or failed effect makes the whole action REFUTED, and an escalation yields UNVERIFIABLE, never a rounded-up pass.

The gate is default-deny: with no operator grant loaded, nothing acts. The model cannot supply its own authorization.

Install

pip install accountable-surface

Add the MCP server with pip install "accountable-surface[server]". Use a source checkout when you need current main branch features, or a GitHub release asset when you want a wheel with a published checksum.

Current PyPI release: 0.3.1, published by an OIDC trusted publisher with PEP 740 attestations, so the index records which workflow built the bytes. v0.2.0 and v0.1.0 remain older tagged GitHub packages. The package is still alpha.

Install the released wheel with the published checksum file:

& {
  $ErrorActionPreference = 'Stop'

  $Version = "0.2.0"
  $Wheel = "accountable_surface-$Version-py3-none-any.whl"
  $PinnedWheelSha256 = "f2fb09ac66d00bb188da7fdd6fd1ba37485f5fe245c0596902adc355f6bccdfa"
  $Base = "https://github.com/HarperZ9/accountable-surface/releases/download/v$Version"

  Invoke-WebRequest "$Base/SHA256SUMS.txt" -OutFile SHA256SUMS.txt
  Invoke-WebRequest "$Base/$Wheel" -OutFile $Wheel

  $WheelPattern = [regex]::Escape($Wheel)
  $ChecksumRows = @(Select-String -Path SHA256SUMS.txt -Pattern "^\s*([0-9A-Fa-f]{64})\s+\*?$WheelPattern\s*$")
  if ($ChecksumRows.Count -ne 1) { throw "Expected exactly one SHA256SUMS row for $Wheel; found $($ChecksumRows.Count)" }

  $Expected = [regex]::Match($ChecksumRows[0].Line, "^\s*([0-9A-Fa-f]{64})\s+").Groups[1].Value.ToLowerInvariant()
  if ($Expected -ne $PinnedWheelSha256) { throw "SHA256SUMS row for $Wheel did not match the pinned GitHub release hash" }

  $Actual = (Get-FileHash $Wheel -Algorithm SHA256).Hash.ToLowerInvariant()
  if ($Actual -ne $Expected) { throw "SHA256 mismatch for $Wheel" }

  python -m pip install $Wheel
}

Current source checkout, including the durable-authority work in 0.2.0:

git clone https://github.com/HarperZ9/accountable-surface.git
git clone https://github.com/HarperZ9/coherence-membrane.git
git clone https://github.com/HarperZ9/proof-surface.git
cd accountable-surface
$env:PYTHONPATH = "src;..\coherence-membrane\src;..\proof-surface\src"
python -m pip install -e ".[test]"

Requires Python 3.10+. The package declares coherence-membrane and proof-surface as runtime dependencies, because accountable_surface imports both. mcp stays optional under the [server] extra, since only the MCP server path needs it.

Quickstart

python examples/demo.py        # perceive, gate allow, gate deny, journal
python examples/actuate_demo.py  # the full act-verify-rollback loop
python -m pytest               # the Python test suite

demo.py prints a witnessed structural reading of a local page (title, links, sha256 digest), then a gate ALLOW for an action inside the grant, a gate DENY for one outside it, and the journal of every perception and decision.

More transcripts: web_actuate_demo.py (native web actuation against a real localhost server), spa_actuate_demo.py (the JS-capable browser path, offline), goal_demo.py (bounded autonomy), grounding_demo.py and grounded_actuate_demo.py (the reference cortex), smoke_mcp.py (a real MCP stdio round-trip).

Worked example

An operator grant is a plain JSON object. The surface acts only when the gate allows the exact plan, then verifies the effect on disk.

from accountable_surface import AccountableSurface, FilesystemEffector

grant = {
    "authorization_version": "0.1",
    "receipt_id": "rcpt-example",
    "kind": "authorization-grant",
    "principal": {"id": "operator-1", "role": "operator"},
    "agent": {"id": "example-agent"},
    "intent": "write the report file",
    "scope": {"allowed_actions": ["fs.write"], "allowed_targets": []},
    "granted_at": "2026-06-19T00:00:00+00:00",
    "expires_at": "2030-01-01T00:00:00+00:00",
    "revoked": False,
}

surface = AccountableSurface()
out = surface.actuate(
    FilesystemEffector("/path/to/sandbox"),
    target="/path/to/sandbox/report.txt",
    content=b"written natively, verified by re-perceiving",
    authorization=grant,
)
print(out.acted, out.decision, out.verified)  # True allow True

With authorization={} the same call returns acted=False, decision="deny" and the file is never created. A faulty effector that writes the wrong bytes is caught at verification and rolled back; examples/actuate_demo.py shows both paths.

Eight stages of one actuation: perceive, preview, gate, act, re-perceive, verify, roll back, journal. The target is read first, so there is a witnessed record of what it looked like before anything ran. The preview describes the intended write and content-addresses it, with no side effect. The gate answers allow, deny, or needs a human, against the operator's grant. The effector acts only on an allow that names this exact plan, and only inside the root it was constructed with. The target is then read back off disk. Verification compares what landed against the digest that was authorized. A reversible action that fails verification is undone, and the target is read once more. The journal records both digests, the decision and the verdict. Three outcomes: acted and verified, not acted, and rolled back.

What each verdict contributes

One actuation produces up to three verdicts: what the gate decided, what the re-read found, and how well the premise was grounded. They are composed into a single certificate by a lattice meet, so a worse step can never be laundered into a better result.

Ten rows mapping each verdict the surface produces onto what it contributes to the composed certificate. A gate allow, a passing effect and a grounded premise each contribute verified. A gate denial, a failed effect, a refusal by the effector and an ungrounded premise each contribute refuted, and one refuted input makes the whole certificate refuted. An escalation to a human and a weak premise contribute unverifiable, which pulls a verified result down without making it false. The last row is accented: a verdict string none of the maps recognizes becomes unverifiable rather than raising.

Run as an MCP server

python -m pip install -e ".[server]"   # adds mcp
python -m accountable_surface.server   # or: accountable-surface-server

Client configuration:

{
  "mcpServers": {
    "accountable-surface": {
      "command": "python",
      "args": ["-m", "accountable_surface.server"],
      "env": {
        "PYTHONPATH": "C:/path/to/accountable-surface/src;C:/path/to/coherence-membrane/src;C:/path/to/proof-surface/src",
        "ACCOUNTABLE_SURFACE_GRANTS": "C:/path/to/operator-grants.json",
        "ACCOUNTABLE_SURFACE_JOURNAL": "C:/path/to/session-journal.jsonl",
        "ACCOUNTABLE_SURFACE_AUTHORITY_STATE": "C:/path/to/authority-state.sqlite3"
      }
    }
  }
}

Interoperable stdio server (no FastMCP)

For adoption by other harnesses, accountable_surface.interop_mcp is a stdlib-only JSON-RPC-over-stdio server: no FastMCP, nothing to pip install for a harness to spawn it and list tools. It carries the six accountable primitives plus the shipped read-only verb and emits an offline-re-derivable action-receipt/v1.

python -m accountable_surface.interop_mcp   # or: accountable-surface-mcp

Manifests for Claude Code, Codex, Cursor, and the Flywheel lane live in interop/; the overview and the evidence-bound comparison over ungated computer use are in docs/interop-mcp.md. The hard exclusions (CAPTCHA solving, anti-bot stealth / fingerprint patching, reCAPTCHA token harvest, mass or obfuscated authenticated outreach) are unreachable through any tool and asserted by test.

ACCOUNTABLE_SURFACE_GRANTS points to a JSON file with one authorization grant or a list; with none loaded the gate is default-deny. Remote reads are scoped separately from writes: allowed_actions may name fs.write, but the remote server still refuses until allowed_reads covers the target state phases it must read (before, backup, after, and rollback for filesystem writes). ACCOUNTABLE_SURFACE_JOURNAL points to an append-only JSONL file; when set, the journal replays on launch so the witnessed self-view spans sessions.

ACCOUNTABLE_SURFACE_AUTHORITY_STATE points to an optional stdlib-SQLite state file for durable revocation, atomic usage reservations, and idempotency. With it set, remote actuate requires an idempotency_key, refuses protected grant/state/journal paths even under broad filesystem grants, and keeps unresolved precommit reservations unavailable until operator recovery. See docs/durable-authority.md.

ACCOUNTABLE_SURFACE_EFFECTORS points to a JSON file naming which effectors a remote caller may reach:

{"effectors": [
  {"action_kind": "fs.write", "type": "filesystem", "root": "/srv/agent-sandbox"},
  {"action_kind": "api.post", "type": "api", "service": "github"}
]}

Three operator decisions guard remote actuate and all have to agree: the registry exposes an effector, allowed_actions names the write, and allowed_reads names the state reads needed to precondition, verify, and roll back that write. With the variable unset, the file empty, or the action kind missing from it, actuate refuses before it reads a grant. The file refuses command, browser, web, and uia by name, each with the reason. Ask doctor for the exposed set, the reach of each entry, and the entries it turned down. MCP perceive likewise needs a web.document read grant, and full session_journal replay needs a journal.session read grant.

Shared world server

A live surface you can watch in a browser: proposed actions run the real perceive-gate-act-verify loop and stream over SSE to every open subscriber.

python -m accountable_surface.world.server 8808

It serves the web UI from web/ and binds to localhost by default. Grants are operator-supplied at startup; the built-in fallback is an explicit sandbox-scoped demo grant, and default-deny still holds.

Layout

  • src/accountable_surface/surface.py: AccountableSurface with perceive, propose, actuate, pursue, interocept, and the durable journal.
  • effector.py, web_effector.py, browser_effector.py, os_effector.py, api_effector.py, uia_effector.py: the six effectors and their drivers.
  • http_driver.py: the stdlib HTTP and HTML backend behind native web actuation.
  • playwright_driver.py: the optional JS-capable browser driver.
  • reference.py: the grounding cortex, certify.py: action certificates, grant.py: grant helpers.
  • server.py: the MCP server. world/: the shared world session, server, sight, and pilots.
  • tests/: the Python test suite. examples/: eight runnable transcripts. web/: the shared world UI plus Node tests.
  • docs/: design specs (SPEC-actuation.md, SPEC-interoception.md, SPEC-persistence.md), design notes, and docs/INTRODUCTION.md, the first-ten-minutes guide.
  • docs/art/: the diagrams above, rendered from accountable-surface.art.json by tools/render_repo_art.py and checked by tools/check_repo_art.py. Brand assets: .github/assets/zentropy-banner.png.

Status

Alpha, version 0.2.0. The API is settling and may change between 0.x releases. CI runs the Python suite and the Node web tests on every push and pull request, with sibling checkouts of coherence-membrane and proof-surface. Local verification:

$env:PYTHONPATH = "src;..\coherence-membrane\src;..\proof-surface\src"
python -m pytest
node --test web/*.test.mjs

Release packaging steps and hold points are in docs/RELEASE.md.

  • coherence-membrane: the perception organs and certificate types this surface composes.
  • proof-surface: the pre-execution gate (allow, deny, needs-human).
  • USAGE.md: the operational guide, including the browser backend.

Development disclosure

Zain Dana Harper maintains this repository. AI-assisted tools are used for parts of development and documentation. Public source, tests, and release artifacts are the evidence for what ships; AI output is not treated as proof.

Why the gate and the journal

Agent autonomy without silent authority: every action here is checked against an operator grant before it runs, verified against its intended effect after it runs, and recorded in a journal you can replay and re-check. The receipt is the floor; the features above are the point.

A journal that can be quietly rewritten records nothing worth reading. Each entry is chained to the one before it, so an edit breaks the entry's own hash and a delete or a reorder breaks the link to its neighbour. verify_journal.py re-derives the whole chain with no import from this package, which means a stranger holding only the JSONL file can check it.

Eight stages of the journal chain: entry, canonical, chain hash, append, reload, edit, move, verdict. An entry is a kind, a summary and a detail, and nothing outside those three is hashed. The entry is serialized one way only, with sorted keys and no spaces, so the same content always produces the same bytes. Its chain hash is taken over the previous entry's hash together with those bytes. The line is appended as one compact JSON object. On reload every line is parsed and rechained from the genesis anchor. An edited field no longer hashes to the record that carries it. A deleted or reordered entry breaks the link to the running head. The verdict counts unparseable lines separately from chain breaks, and never merges the two. Three outcomes: match, drift, and unverifiable.

License

FSL-1.1-MIT. Copyright (c) 2026 Zain Dana Harper. See LICENSE.

What this believes

This tool is one lane of a family that holds a single belief steady across every surface: knowledge open to anyone who can attain the means; acceptance decided by external checks, never reputation; every result re-runnable; honest nulls first-class; ownership earned by comprehension; learning woven into the work. The full text lives in CREDO.md. The long form of this belief: The Unbundling.


Zentropy Labs · order out of entropy. An independent lab building evidence-first tools that leave a re-checkable artifact behind. Built by Zain Dana Harper in Seattle. The full workbench is at Project Telos.

Release files for accountable-surface 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for accountable-surface 0.3.1
File Size Uploaded
accountable_surface-0.3.1.tar.gz 441.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for accountable-surface 0.3.1
File Interpreter ABI Platform
accountable_surface-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 580.5 kB

Release files / accountable_surface-0.3.1.tar.gz

Download URL accountable_surface-0.3.1.tar.gz
Size 441.9 kB
Tags Source
SHA-256 checksum
How to use checksums
9e9931c3c17e2692bd6325a88d7c9b3c46276342f68b05d70bce00763701ca7c
BLAKE2b-256 checksum
How to use checksums
690a32d4da210283913e3c6caf7983cc9880299a5ec28e67a369044a852fea8b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / accountable_surface-0.3.1-py3-none-any.whl

Download URL accountable_surface-0.3.1-py3-none-any.whl
Size 138.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
631dac3ed27fa786d1ed5601baea313ce83659d287eacb7297b130c4e10f46f7
BLAKE2b-256 checksum
How to use checksums
de8d44e3dad1cd0a3e6ab33d21f7a1be59251c18a34fb647d63dede644b9bc94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page