The Patch That Did Not Erase the Failure
A105's installed Responses path failed before the network. An isolated repair candidate now passes locally. Both facts remain true.
A failure discovered before outcomes creates a temptation: fix the code, rerun the test, and describe the system as if the failure never happened.
That would be the wrong scientific record.
A105-R3 observed the installed client stack exactly as it existed. AutoGen
0.11.2 accepted a canonical request requiring the submit_move function, then
its Responses adapter unconditionally replaced that requirement with
tool_choice="auto". The OpenAI SDK serialized the changed request, and our
injected httpx transport captured it before any network operation.
That is the primary observed result for the installed Responses path: NO-GO. It remains content-addressed and unchanged.
A105-R3A asks a different question. Can a deliberately narrow adapter restore the required tool choice without changing anything else, leaking treatment assignment, touching the live chess harness, or contacting a provider?
The answer is yes—at the same local pre-network boundary, for the exact frozen dependency versions. But a repair candidate is not retroactive evidence that the original path worked, and it is not permission to run a paid experiment.
Two artifacts, two claims
R3 and R3A must not collapse into one green status.
| Artifact | Object under test | Result | Authorized claim |
|---|---|---|---|
| R3 | Installed AutoGen/OpenAI Responses path | NO-GO | The installed adapter rewrote required submit_move to auto before HTTP serialization |
| R3A | Isolated, version-bound remediation wrapper | local pass | A narrow patch can restore the registered field while preserving local arm invariance |
R3 is observational systems evidence about the installed adapter. R3A is an
engineering experiment on a separate wrapper instantiated only inside the
provider-free audit. R3A does not modify the installed package. It does not
modify custom_agents.py, llm_chess.py, or any active evaluation path. It
does not transform the red R3 branch into a green one.
This distinction is more than editorial hygiene. If a remediation is permitted to overwrite the failure that motivated it, dependency defects disappear from the record and later readers cannot reconstruct which system actually produced which evidence.
The smallest patch we could test
Inspection found a simple but consequential line in the installed AutoGen
Responses adapter: after normalizing tools, it assigns
params["tool_choice"] = "auto".
R3A does not fork the adapter or copy its entire request-conversion pipeline.
Instead, it wraps one fresh synthetic client and places a narrow proxy at the
adapter's call into client.responses.create.
The wrapper first validates the original request. It accepts only:
{"type":"function","function":{"name":"submit_move"}}
It also requires the exact registered, non-enumerated A105 submit_move tool
schema. It then delegates normalization to the unmodified installed adapter.
At the SDK resource boundary, the proxy requires all of the following:
- the installed adapter emitted exactly
tool_choice="auto"; - the normalized tool list contains exactly the registered function;
- this is the first and only SDK create call; and
- AutoGen, OpenAI Python, httpx, and the AutoGen Responses source hash match the dependency identity bound by R3.
Only then does it replace that one field with:
{"type":"function","name":"submit_move"}
Every other SDK argument is passed through unchanged. The original SDK resource
is restored in a finally block. Any unrecognized choice, tool schema, adapter
output, second call, version, or source hash fails closed.
This is intentionally not a general-purpose compatibility layer. Its narrowness is the evidence.
The same boundary, a separate path
R3A reused all 128 canonical A105-R0 cells: 16 edge fixtures, both panel orders, and all four board/legal-list treatment arms. Each cell ran through:
- the installed AutoGen Responses normalizer;
- the isolated required-tool remediation;
- the installed OpenAI SDK serializer; and
- the same injected
httpx.BaseTransport.handle_requestsink.
The sink accepted only https://capture.invalid/v1/responses, opened no socket,
and returned a fixed local error after capture. The matrix produced 128
intercepted requests and 32 complete factorial blocks.
Every request preserved:
- the exact user prompt;
- the exact normalized
submit_moveschema; - required
submit_movetool choice; parallel_tool_calls=false;temperature=0;- high reasoning; and
- the registered non-streaming contract.
Every block was arm-invariant. Relative to the masked control, serialized bodies
differed only inside registered board and legal-content spans. The remediation
trace was also invariant: original requirement validated, installed auto
rewrite observed, normalized schema validated, required choice emitted, and
exactly one SDK create call.
The adversarial test compared the unpatched and remediated JSON bodies for the
same canonical cell. After removing tool_choice, the bodies were exactly
equal. That is stronger than merely finding the desired field in the final
request: it demonstrates the tested mutation scope.
What tried to break it
The audit rejects:
- a canonical request that already says
auto; - any tool other than the exact A105
submit_moveschema; - an installed adapter that stops emitting the R3-observed
autovalue; - normalized tool-schema drift;
- a second SDK create call;
- arm-dependent remediation traces or call counts;
- request-body changes outside registered treatment spans;
- any supported-version or AutoGen source-hash mismatch;
- an external authority, query, method change, retry, or socket attempt; and
- any output that would persist credentials or raw FEN prompts.
These are not decorative tests. A future AutoGen release may fix the original behavior, change the Responses request shape, or normalize tools differently. In any of those cases the R3A wrapper should stop, not continue applying an old patch to a new system.
Exact provenance
The passing remediation report is bound to:
- AutoGen
0.11.2 - OpenAI Python
2.30.0 - httpx
0.28.1 - AutoGen Responses source SHA-256:
169583351dd3b5fa7c6e86d160b2d2322bbb6802a048d24a20e89f04fc40a990 - R3A report identity SHA-256:
ce3ed190709f6d1db38c22dd95ac7ac5b2d65c5a2cccbb77affa3ada2838e836 - R3A report-file SHA-256:
906c65ddd9cfb769c1d2622f51eec754de365ec19c3df4827363f495abbf5535
It also verifies its parent failure before running:
- R3 report identity SHA-256:
b5ce3ab56bdfdcda455b87664064410b227326856dd751263a2db4e440af760c - R3 report-file SHA-256:
e8c5dd2c781f3fbb7fdf635fdd0153e9591207be0cd2c73b1045364da060887f
If the R3 report no longer says
partial_chat_pass_responses_no_go, no longer records the Responses failure,
or no longer keeps paid execution unauthorized, R3A refuses to run. The repair
therefore depends on preserving the failure rather than replacing it.
What remains unauthorized
R3A records a105_paid_execution_authorized: false.
It has not established that OpenRouter or another provider accepts the
remediated Responses shape. It has not observed provider-received bytes,
transfer encoding below the httpx.Request, proxies, route selection, provider
defaults, tokenization, model compliance, latency, or response parsing. It has
not bound final A105 positions, real model aliases, evaluator identity, or paid
retry policy.
Before this path could approach execution it would need independent code review, integration into a non-production candidate harness, another complete local capture, real route/model metadata binding, and a separate provider- compatibility or received-request gate. Paid execution would still require its own authorization decision.
The current live harness was not changed.
Why preserving failed states matters
Agent systems are stacks of mutable dependencies. A model result can depend on how a framework represents tools, how a client selects an endpoint, and how a router interprets a field. When one layer fails, a good laboratory needs both a repair path and an immutable description of the broken state.
R3 tells us what the installed path did. R3A tells us one constrained way to make a synthetic copy of that path express the registered request. Neither tells us what a provider or model will do.
That hierarchy protects the experiment from a subtle form of hindsight. We did not define success as “whatever request the adapter happened to send.” We kept the registered semantic contract fixed, recorded the violation, and made the repair prove that it changed only the violating field.
The patch passed.
The failure still happened.
Both belong in the result.
Reproduce the provider-free remediation audit
From the repository root:
uv run python research/experiments/run_a105_responses_remediation_preflight.py
uv run pytest -q tests/test_a105_responses_remediation_preflight.py
The generated report is
research/analysis/a105_responses_remediation_preflight_v1/analysis.json.
It is an isolated local remediation candidate—not a live-harness fix, provider compatibility result, or paid-execution authorization.
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.