The control seam
Which tools the harness may call is decided per call, by the embedder, through the protocol — not once at launch.
A launch-time allowlist answers the question "may this agent ever run a shell?". The seam answers "may this agent run this shell command, right now, in this step?" — and it answers it while the harness waits.
Four tiers of control
Control is not one thing. An adapter declares what it delivers at each of four tiers, and an embedder that needs a tier the adapter does not have is refused early rather than quietly served something weaker.
| Tier | What it can do | What it costs / cannot do |
|---|---|---|
registration | Decide the set of tools the model is offered, before the session starts. | Cannot see an argument. Cannot change within a session. |
call | Present every call for a decision before it executes, and the harness waits. | Costs a round trip. Only as universal as the seam's coverage. |
turn | Add text to the conversation between turns. | Cannot stop a call. Only advises. |
kill | Stop a running turn. | Loses the turn. Cannot be selective. |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Tier status is a first-class fact
| Status | Meaning |
|---|---|
delivered | Driven, and asserted by a conformance vector. |
unverified | The mechanism is present on the vendor's surface and metaharness has not driven it. An embedder that requires it is refused, not quietly served. |
absent | The vendor has no such mechanism at this tier. |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Read them with metaharness capabilities <kind>.
Who decides: --decisions
| Mode | Behaviour |
|---|---|
frame (default) | The adapter decides from the frame's admitted set. No round trip. |
ask | The embedder decides. The run blocks — one round trip per call. |
observe | Nobody decides. Every call is allowed and recorded, through the same seam. |
More columns: swipe horizontally, or focus the table and use the arrow keys.
An embedder that answers "yes" to everything the frame already admits has bought nothing. That is
why frame is the default and ask is opt-in.
A run in frame mode is still fully audited. Every mode emits tool.decided, and the decision
census counts them all.
observe — measuring a harness nobody is steering
It is not "run without a seam". The hook is installed exactly as it always is, every call
arrives at it, and metaharness answers allow down the channel a deny would take. The point is
comparability: a run that is not being steered produces the same events, in the same order, with
the same correlation keys as one that is, so one set of expectations can score both.
tool.decided carries decided_by: "observe" — its own decider, because an adapter allow is a
judgement about one call and this is a run-wide posture that judged none.
Three things it says out loud rather than leaving you to find out:
- An
allowgrants. On Claude Code's hook wire it bypasses the rest of the vendor's permission pipeline. An observe run is therefore more permissive than a run with no hook at all, and the launch attestation says so in itsambient_inputs. - A
--framebeside it is refused by name. A frame whose text reaches the model while nothing enforces it tells the model "strictly only these operations" and makes that false. - You have to ask for it. The default is
frame, and an adapter that has not driven theallowhalf of its own decision wire declares the modeunverifiedand refuses it before the run — which is where Codex stands today.
Whose tools: --tool-surface
| Surface | Status |
|---|---|
native (default) | The vendor's own tools, narrowed at the decision seam. |
owned | metaharness owns the tool surface and runs the tools itself. Refused in this build — per-step re-listing depends on unverified vendor behaviour. |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Three properties that are easy to break by accident
These are stated here because each was a real bug or a near miss, and each is invisible until a live run.
-
Every currently-pending
tool.requestedis handed over before an answer to any of them is due, and each one's deadline is armed at delivery. Without both, a single-threaded policy deciding call A would burn call B's budget, and metaharness would emitdeadlinedenials the embedder never chose. -
The decision reaches the child before any control does. Cancelling first clears the active call and leaves the child waiting on a correlation that no longer exists.
-
A record the adapter cannot map becomes
Event::Opaqueand is never dropped. The failure that costs the most is a checker reporting "the tool was never called" when what actually happened is that it stopped being able to see tool calls.
The census is metaharness's, not the vendor's
The decision census counts what metaharness decided. The vendor's record cannot see it, so the census is set on the terminal record rather than computed by the transcript reader.
This matters for anything downstream that counts denials: read them from tool.decided events in
the run's own stream, not from the vendor's transcript.
Proof, not configuration
The seam has been driven against a paid run on both vendors. On Codex, the evidence chain reads:
| Layer | What it said |
|---|---|
| The hook process | "tool_name":"Bash", "tool_use_id":"exec-96257928-…" |
| metaharness | answered deny, with a reason |
| The vendor's session record | Command blocked by PreToolUse hook: this step admits no shell, so the command did not run — with an empty Output: |
| The model's closing message | "The command was blocked and did not run." |
More columns: swipe horizontally, or focus the table and use the arrow keys.
So tool.decide is Honoured and the call tier is Delivered.
The allow half of that wire is not claimed on Codex: only the deny path has been driven.
That distinction is the point of unverified existing as a status.