← All writing
Essay 20 / 22Protocol / design

The Request That Failed Before the Network

The chat client preserved A105's contract. The installed Responses adapter did not. That is a pre-network NO-GO, not a provider result.

The previous systems audit ended at an intentionally artificial boundary. A105-R2 proved that our repository adapter could carry 128 canonical treatment cells into two in-memory serialization profiles without arm-dependent drift. It did not run the installed agent framework, the installed OpenAI client, or an HTTP request serializer.

That distinction mattered. A local adapter can preserve a contract that the real client later changes.

A105-R3 therefore moved the interception point as far downstream as we could go without making a network call: httpx.BaseTransport.handle_request, after AutoGen normalization and OpenAI SDK HTTP request serialization, but before any network transport. The capture harness supplied a synthetic response locally. No DNS lookup, socket, proxy, TLS session, router, or provider was touched.

The result was mixed, and the failure is the result we need to preserve.

Across 256 intercepted HTTP requests and 64 matched factorial blocks, both profiles remained perfectly arm-invariant. The current OpenRouter-compatible chat-completions path also preserved the required submit_move tool contract. But the installed Responses adapter rewrote a required submit_move tool choice to "auto" before raw HTTP serialization. Its semantic gate failed in every captured cell.

That path is a NO-GO. We did not repair it, reinterpret it, or make a paid call to see whether a provider might rescue the request.

The boundary we actually observed

“Client boundary” can sound more comprehensive than it is. The precise capture point was an injected httpx transport. At that point the installed stack had already done three consequential things:

  1. AutoGen had normalized the model request.
  2. OpenAI Python had selected and serialized the endpoint-specific HTTP body.
  3. httpx had constructed the request object that a real transport would send.

The harness then intercepted that request instead of sending it. This let us inspect method, URL, headers, JSON body, body bytes, tool contract, and relevant parameters without credentials or external side effects.

It did not observe transfer encoding below the httpx.Request, DNS, TLS, system or explicit proxies, OpenRouter normalization or route selection, provider parsing and defaults, provider-received bytes, or model behavior. Calling this a wire-level or provider-received-byte test would be false.

A full factorial systems test

R3 reused the 128 canonical R0 cells: 16 edge fixtures, four board/legal-list arms, and two panel orders. Each cell was run through two installed client profiles:

ProfileCapturesArm invariantSemantic gate
openrouter-chat-client-v1128passpass
openai-responses-client-v1128passNO-GO

Together they produced 256 intercepted requests and 64 profile-specific matched blocks. Within every block, route shape, header names, tool schema, parameter contract, normalization behavior, and all bytes outside registered board and legal spans had to remain equal across arms.

They did. all_profiles_arm_invariant is true.

That fact is necessary but not sufficient. A transformation can be perfectly uniform across treatment arms and still invalidate the experiment. If every arm loses a required action constraint, there is no treatment leakage—but there is also no longer the registered response contract.

This is why R3 has two gates rather than one:

  • the arm-invariance gate asks whether treatment assignment leaks into unregistered request fields; and
  • the semantic gate asks whether the installed stack still expresses the experiment we registered.

The Responses profile passed the first and failed the second.

What the chat pass means

For openrouter-chat-client-v1, the raw serialized request retained the required submit_move selection and the frozen tool definition across all 128 cells. The four arms differed only where the renderer had registered board and legal content differences. High-reasoning normalization was uniform. No request acquired a condition-specific route, header, schema, or parameter.

This authorizes one narrow sentence:

Under AutoGen 0.11.2, OpenAI Python 2.30.0, and httpx 0.28.1, the audited OpenRouter-compatible chat profile preserved A105's local serialized request contract at the injected pre-network HTTP boundary.

It does not say OpenRouter received those bytes. It does not say the route accepts the tool contract. It does not say a model obeys it. It does not authorize the paid factorial run.

What the Responses no-go means

The Responses profile began with the same registered requirement: the model must return a submit_move tool call. But the installed AutoGen Responses adapter rewrote that required submit_move tool choice to "auto" before the OpenAI SDK serialized the HTTP request.

The rewrite was uniform. That makes the failure cleaner, not smaller.

If we ignored it, the experiment would silently change from “produce the registered action through this required tool” to “the client permits automatic tool selection.” Any difference in execution failures, parsing, latency, or move quality could then reflect the altered response affordance. The factorial arms might still be balanced, yet the measured player would no longer match the registered protocol.

The correct response is not to average the profiles, call the discrepancy a minor implementation detail, or speculate that the provider would interpret "auto" equivalently. The correct response is to fail closed.

The installed Responses path remains blocked. A future repair would be a new, versioned intervention requiring its own capture bundle and review. It cannot retroactively turn this report into a pass.

Version and hash provenance

This result is intentionally version-bound:

  • AutoGen: 0.11.2
  • OpenAI Python: 2.30.0
  • httpx: 0.28.1
  • A105-R3 report SHA-256: b5ce3ab56bdfdcda455b87664064410b227326856dd751263a2db4e440af760c
  • report file SHA-256: e8c5dd2c781f3fbb7fdf635fdd0153e9591207be0cd2c73b1045364da060887f

The relevant dependency source snapshots were also hashed:

  • AutoGen client source: 751af3120298c082c3b9c9294ec9c0f1fbd0f5911f6c9abd15adc1a86a932f41
  • AutoGen Responses source: 169583351dd3b5fa7c6e86d160b2d2322bbb6802a048d24a20e89f04fc40a990

Those hashes do not make third-party code immutable. They make the exact code behind this claim identifiable. Upgrading any dependency, changing the client profile, or altering the capture harness invalidates the shortcut “R3 already passed.” The gate must run again.

Safety was part of the instrument

The runner refuses endpoint authority and credentials. Its synthetic transport terminates the request locally. The generated report records zero provider, network, engine, and observed-model-outcome calls. Environment credentials are not captured.

These constraints matter because a “preflight” that can accidentally spend money or contact a provider is not merely an audit. It is an uncontrolled execution path. R3 was built so that the evidence could not be confused with an empirical model result.

The authorization field is explicit:

a105_paid_execution_authorized: false

That remains false despite the chat profile's local pass.

Why a no-go improves the research

A systems failure discovered before outcomes is cheap intellectually, even when it is inconvenient operationally. Discovered afterward, the same failure creates an identification problem: did the treatment matter, or did a client adapter quietly redefine the action interface?

The R3 split result changes the execution plan:

  1. Keep the installed Responses profile blocked.
  2. Preserve the passing chat profile as a version-bound candidate, not as proof of provider compatibility.
  3. Freeze final positions and real model/route metadata separately.
  4. Add a separately audited provider-compatibility or received-request gate before any paid factorial execution.
  5. Re-run the full capture matrix after any stack or profile change.

This is the larger lesson for agent evaluation. Treatment integrity has at least two axes. Relative integrity asks whether experimental arms differ only as registered. Absolute integrity asks whether the resulting request still means what the protocol says it means. A clean difference-in-differences design can fail absolutely. Uniform middleware can destroy an estimand without creating a single arm imbalance.

What we can now claim

We can claim that, at one precisely injected pre-network boundary, 256 installed- stack requests were arm-invariant. We can claim that the chat profile preserved the required local tool semantics in 128 captures. We can claim that the Responses profile did not: the installed adapter uniformly rewrote required submit_move selection to "auto".

We cannot claim provider-received-byte equivalence, provider compatibility, tokenization equivalence, model compliance, move quality, power, final-position validity, or a causal A105 effect. No Luna or DeepSeek outcome was observed.

The most important artifact is therefore not a green check. It is a boundary with one green branch and one red branch, preserved before money or outcomes could create pressure to rationalize the failure.

Reproduce the provider-free boundary gate

From the repository root:

uv run python research/experiments/run_a105_client_boundary_preflight.py
uv run pytest -q tests/test_a105_client_boundary_preflight.py \
  tests/test_blog20_client_boundary_nogo.py

The frozen report is research/analysis/a105_client_boundary_preflight_v1/analysis.json.

It is local serialization evidence only. The Responses result is still a NO-GO. Paid A105 remains unauthorized.

Public-surface note

Operational paths, raw logs, credentials, mutable run metadata, and unpublished artifacts are intentionally excluded. Research references resolve to the versioned publication bundle rather than the host filesystem.