Skip to content

v0.1.3-alpha: The Unmediated Write, Reported From Outside

Pre-release
Pre-release

Choose a tag to compare

@mahirhir mahirhir released this 03 Sep 10:07
· 15 commits to main since this release

Two defects in the OpenClaw plugin example were reported from outside, by a
reader who read the published code. Neither was found by us. Both are fixed
here, and the fixes are in the public tree as of this tag.

What was wrong

The admitted write was re-applied without a precondition. On Admit the
hook returned { params } and let OpenClaw's own write run afterward. A
comment in the source defended this as "a real property of this design, not an
omission". The comment was wrong about the part that matters: the
re-application was unconditional. Anything landing between our commit and that
native write — a later hook in the chain, a background process, the agent's own
bash, a file watcher — was overwritten with no receipt. A single caller was
enough; no concurrency required.

The refusal named the doors that were still open. cfg.tools covered
write alone. With the membrane unreachable, write came back blocked with a
careful paragraph explaining that gx could not be consulted, and that paragraph
landed in the model's context while edit and apply_patch sat there
untouched. The blast radius of an outage was "writes move".

What changed

Admit now returns block: true, naming the transformation and the receipt.
The effect is already in the substrate; the native write is redundant, and
allowing it was the only path by which unmediated bytes could move. Zero
unmediated byte movement now follows from OpenClaw's own control flow rather
than from our intent.

The reporter's suggested repair — rewrite params so the native call becomes a
verified no-op — turned out not to be expressible. hook-before-tool-call-result
carries {params, block, blockReason, requireApproval} and no result
substitution; writeSchema is {path, content} with no field for a
precondition; and params merges by lastDefined, so a later plugin overwrites
ours. Re-hashing afterward detects, it does not prevent. Blocking was the
stronger available shape, not the preferred one.

MEDIATED_TOOLS now covers write, edit and apply_patch, from one exported
constant instead of four separate call sites. edit and apply_patch describe
a delta rather than a target state, so they are covered by refusal rather than
escrow — worse for the agent, better for the invariant. The message was not made
vaguer. bash is declared statically in docs/LIMITS.md instead.

What this does not fix

bash remains unmediated. There is no in-plugin fix for it.

The drift case is proven by a unit test, not by the gx-backed demo.

An admitted write is now recorded by OpenClaw as blocked, so it emits a blocked
diagnostic and a security event for a write that succeeded. Telemetry built on
those counts will be wrong.

Also in this tag

action.yml called gx verify-receipts, a subcommand that has never shipped —
the action could not have passed. It now calls gx policy lint and
gx receipt verify --offline. Two inputs that mapped to no flag,
enforce-inverses and fail-on-tamper, were removed rather than left standing
as dials that did nothing.

Three engine accessors returned null after a restart because they read only the
in-memory table: deadline(), planned_delta(), and the pair behind
get_transformation/get_candidate. Each now falls back to the shadow row or
the blob store. undo_intent does not, and cannot without locator, context and
actor, none of which the shadow carries; that gap is pinned by an ignored probe
rather than papered over.

Five red tests were asserting facts that had gone stale, not defects. Package
names diverged from directory names, a manifest grew a member, a predicate that
once identified one crate had come to match seven. Fixed in the tests, with no
change to product source.

State

Alpha. 2,918 of 2,980 tests pass; 60 fail, of which 44 need database or probe
services this checkout does not start. Four adapters are public, three are on
crates.io. CI has not run a job since 2026-08-20 — the account's Actions billing
is blocked, and every check you see on recent commits is a check that never
started.

The limits page is still longer than the feature list.