The b10x adapter
Pinned to 0.22.0 of b10x-harness. Adapter class: direct_provider — the loop is this family's
own. Under --decisions observe, the evaluation arm, this adapter observes it and decides
nothing (invariant 9). Under frame or ask the loop hands each write and each run to
metaharness (atlas ADR 0104, design amendment a61; see the caller seam).
A run against another b10x-harness version is unverified rather than refused.
metaharness run b10x --decisions observe -p "…" --model-endpoint https://gateway.example --model-wire anthropic-messages
metaharness run b10x --decisions frame --frame step.frame.json -p "…" --model-endpoint https://gateway.example
metaharness capabilities b10x # the declaration below; no model, no credential
metaharness conformance b10x --contract # 11 deterministic checks, no model or credential
metaharness doctor b10x # resolves the binary the way the spawn will, on the child's PATH
What a run does
- Constructs the child's environment rather than inheriting one (H3):
PATHis$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin, andXDG_CONFIG_HOMEpoints at scratch so an operator profile cannot arrive ambiently. Toolchain and credential variables arrive only when declared. A key named with--api-key-secretis never one of them: the loop reads it from the operator's secret store, and only the two variables its store client finds the session bus by,DBUS_SESSION_BUS_ADDRESSandXDG_RUNTIME_DIR, are forwarded, each when present (design amendment a50). Whether the store opens in that environment is unverified until a run shows it. - Resolves
b10x-harnesson thatPATHat plan time, so the launch record names the file that ran. - Spawns
b10x-harness run --execution-path metaharness --json …with the flags below. The flag gives the inner agent attributable context about the outer observer and grants no authority. Stdout is the record. - Reads the loop's events through the seam into
metaharness.event/1.
What the vendor adapters spend two thousand lines on does not apply here, and not because this adapter is unfinished:
| what a vendor needs | why b10x-harness does not |
|---|---|
| a scratch home, so ambient config cannot leak in | scratch XDG_CONFIG_HOME isolates its optional profile file |
| a hook channel | an observe run's decisions are in-process, and this adapter makes none; a frame or ask run is asked on the loop's own record and answers on its stdin |
| transcript retrieval | --json on stdout is the record |
| credential isolation | it reads no credential it was not pointed at, by construction: a file, a variable, or a name in the operator's secret store (--api-key-secret, since 0.17.1) |
Declared capabilities
| Tier | Status |
|---|---|
registration | unverified |
call | unverified |
turn | unverified |
kill | unverified |
Every tier stays unverified, on purpose. There is no registration seam and no hook. The caller
seam decides only the calls the loop asks about, and its reads run unasked, so declaring call
delivered would let an expectation be satisfied for calls no decision touched.
| Command | Support |
|---|---|
halt, interrupt, tool.decide | honoured |
steer, frame.set, permission.set, message.inject | refused — UNSUPPORTED_CONTROL |
| Decision mode | Status |
|---|---|
observe, ask, frame | delivered |
The CLI's cross-adapter default is frame, and a frame-mode b10x run without a frame is refused
before the spawn: with nothing to decide from, every write would be refused. Pass
--decisions observe for the arm that decides nothing, or name a frame.
The caller seam: frame and ask
Since harness 0.20.0 the loop can hand its approvals to the process that launched it
(run --approve caller, atlas ADR 0104). A frame or ask run launches it that way, with the
ceiling at the loop's floor:
b10x-harness run … --json --approve-up-to low --approve caller …
- The loop announces a call (
tool-requested). The adapter holds the announcement until the record says whether the loop asks about it. - Above the ceiling the loop writes
approval-requiredand blocks on stdin. The call reaches the run as atool.requestedon thecontrol_requestseam, and the frame, the embedder'stool.decideor metaharness's deadline answers it as on any other adapter. - The answer is one line on the loop's stdin:
{"call_id":…,"decision":"allow"}or{"call_id":…,"decision":"deny","reason":…}.abstainandreplacehave no word in that grammar and are written as adenythat says why;tool.decidedstill records which of the four metaharness decided. - The loop's
approval-resolvedis checked against the answer. Agreement is bookkeeping; a disagreement is awarning{code: "approval-disagreed"}.
What it covers, and what it does not:
| Call | Risk in the catalogue | Under the caller seam |
|---|---|---|
file_write, file_edit | medium | asked; metaharness decides |
run | high | asked; metaharness decides |
file_read, dir_list, search, find | low | runs unasked, recorded on seam none |
low is the lowest ceiling 0.20.0 offers, so the reads cannot be decided here. A frame that does
not admit file.read, dir.list and search, in its operation set and in every subject rule, is
refused at start rather than recorded as enforced. A subject rule is judged on the flat entry's own
arguments, so file_write to note.md names file:note.md.
The loop has no timeout of its own while it waits, so the run answers inside the default 30000 ms hook budget: an unanswered call is denied by metaharness's deadline at 25000 ms.
Rendering: file.read → file_read, file.write → file_write, file.edit → file_edit,
dir.list → dir_list, search → search, shell → run; mcp.call, skill.load,
subagent.spawn, task.todo and web.read render to nothing.
conformance b10x runs 11 deterministic vectors: a recorded executable/argv/environment launch,
the same launch handing the loop two --context files (design amendment a35), the caller-seam
launch (a61), a byte-exact replay of the retained 0.22.0 loop record, four enforcement records
(unpublished tool, approval denial, budget stop and cancellation), two caller-seam exchanges
(allow and deny, each asserting that the line metaharness writes is byte for byte the line the
loop was fed), and the captured version banner against the pin. The records came from harness's
loopback-only deterministic endpoint and are provider_emulated, never vendor_live. The two
exchanges fill the fourth checklist row, hook input: the loop's approval-required is the
question.
The four enforcement tiers reach the loop
On this arm the published toolset is the policy. The other three tiers are the operator's, and all four travel as flags to the loop rather than as a seam here — a driven run measured with three of them switched off would not be a comparison:
| Tier | Flag | What reaches the loop |
|---|---|---|
| publication | --allow-program, --substrate, --substrate-embedded, --cgroup-root, --toolchain | which tools exist at all: run only under confinement, and only for a named program |
| write scope | --write-scope <glob>=allowed|partial-only|denied, --scope-announce stated|silent | ordered rules, first match wins; refused on any other kind (ScopeUnsupported), whose scope travels sealed in Frame.subjects |
| ceiling | --approve-up-to high — never --yes, which approves the destructive class and does not combine with a ceiling | calls above the ceiling are asked, not assumed |
| content and program | --hooks <file>, --driver <path> | the operator's content rule, named and never discovered; and a program the confined run must be able to start — staged, mounted read-only and added to the loop's own allow-list, so a path on the allow-list the sandbox does not hold no longer dies at ENOENT |
| instructions | --plugin-dir <dir>, repeatable | the plugin's skills/ and agents/ in the vendor's on-disk format, so both arms of a comparison are handed the same instructions at session start |
| context | --context <file>, repeatable, every kind since design amendment a35 | metaharness reads each file beneath the --cwd tree, checks it (see the CLI reference), and hands the loop's own --context its copy of the bytes under the run's scratch root, so the loop reads what was digested; the loop renders the file into its standing instruction itself — a trust="operator" layer with < and & escaped, no preamble of ours — so the file carries system-prompt authority, and the source it names is that copy's path |
What the seam maps — and what it refuses to claim
| Loop record | Event | Why not tool.decided |
|---|---|---|
hook-ran, blocked | warning{code: "hook-refused", message: <the reason>} | that family says a seam adjudicated the call; this arm has none — consulting programs is what the loop does instead |
hook-ran, failed | warning{code: "hook-failed"} | |
approval-resolved{approved: false}, observe run | warning{code: "approval-denied"}, carrying the call_id a reader joins to the tool.requested | every DecidedBy the protocol has is a metaharness-side decider; the loop's own approver is none of them |
approval-resolved, caller seam | nothing when it agrees with metaharness's answer; warning{code: "approval-disagreed"} when it does not | the decision is already the run's tool.decided |
| a tool the run never published | warning{code: "unpublished-tool"} | |
| a proceed, or an approval that said yes | nothing | bookkeeping; one event per approved call would put the stream back where the opaque mapping had it |
The message names the call and invents no cause: the loop's record carries {call_id, approved},
and the reason lives only in the failed outcome's message. Note for a reader reconciling older
records: up to 0.17.1 b10x-harness had its own events converter, which mapped the same approval
to tool.decided. Both paths emitted metaharness.event/1, and one run described through the two
differed there. 0.18.0 removed the converter, so this seam is the only path at the pin (0.22.0).
session.started on this arm
| Field | Value |
|---|---|
offered_tools, available_operations | read from the loop's opening record |
skills, agents | read from the record, never asserted — the loop can be offered several |
withheld | read when the record carries it; null — the harness did not say — from a build too old to write it |
cwd | read from the record: the canonical root the loop's file tools act in (since 0.15.1); null when the record names none, as under --substrate <socket> — never the directory metaharness planned, which H7 compares it with |
mcp_servers | [], hardcoded: the loop has had an MCP client since 0.12.1, but every recorded run so far reported none, so the shape of an entry is unestablished and nothing is read |
plugins, slash_commands | null, deliberately — not established here; hermetic.installed_plugins answers the plugin question |
credential_source | read from the record, in the loop's own words, which name the credential class and the kind of source and never a path, a variable or a value: on 0.22.0 none, api-key:file, api-key:environment, api-key:secrets (the run named --api-key-secret), oauth:file, oauth:environment or provider:<name>. H4 holds a run that named a secret to api-key:secrets exactly |
context_files | filled by the run from what it handed the loop with --context, each entry "channel": "system-prompt": the loop renders the file into its standing instruction, which its wire codecs send as the system prompt; absent when none was declared |
Measured
The driven evaluation, evals/aep/run-driven.sh, runs one step map on the Claude
Code arm and on this one, each with --plugin-dir on the copied plugin. Paid, and never in the
gate. The b10x arm on 2026-08-29, in order: 17 pass / 12 fail with drive exit 1 before the tiers
reached the loop; 22 / 7 after; 29 / 1 once the census read a program refusal and the adapter stated
mcp_servers: []; 30 / 1 with a row that scored whether the planning guidance arrived rather than
how; 30 pass / 0 fail, EVAL_EXIT=0 once both arms took the plugin. The row that failed along
the way was the-skill-was-offered, which a harness without a skills mechanism cannot satisfy;
b10x-harness has one now.
Sources: crates/metaharness-b10x/src/lib.rs, launch.rs, seam.rs and vectors.rs;
metaharness capabilities b10x; the commit messages of the changes named above.