The primary source is `hermes-agent/agent/auxiliary_client.py`. Let me read the key sections: `_CodexCompletionsAdapter`, `CodexAuxiliaryClient`, request construction, and the overall structure


Let me read the capability/completion budget helpers and the provider resolution path for `openai-codex


Let me read `_build_codex_client`, `_read_codex_access_token`, base URL constants, and cloudflare headers, plus `_to_async_client`.


Let me find `_CODEX_AUX_BASE_URL`, the local OpenAI SDK's `responses.create` signature, and the dualcoach_admin provider-auth gate


The OpenAI SDK is probably installed in a virtualenv. Let me find the installed SDK and its responses.create signature. Let me also read dualcoach_admin.py fully (lines 1-480). And find the Tasks21-25 controller.

Let me search for the installed SDK and the Tasks21-25 controller.Let me read the dualcoach_admin.py provider-auth gate in full and find the installed OpenAI SDK plus the Tasks21-25 controller


The provider-auth gate uses `models.list` (dualcoach_admin.py:297-320), which is unsupported for `CodexAuxiliaryClient`. Now I need: (1) the installed OpenAI SDK's `responses.create` signature, and (2) the Tasks21-25 controller. Let me explore both


The real controller is `hermes-agent/gateway/platforms/dualcoach_tasks21_25_controller.py`. Let me read it, the test file for the adapter, and the codex conversations converter to confirm persistence and request shape


Let me read the remaining test assertions and the codex conversations to Responses converter to confirm the exact request kwargs and persistence flags


I have the core evidence. Let me confirm two contrasting details: (1) the adapter never sets `metadata`/`previous_response_id`/`conversation_id`/`store` anywhere else, and (2) whether the production Codex transport forwards those (to contrast persistence). Also confirm the `store` default behavior for a `responses.create` call


Confirmed. The main Codex transport also sets `store: False` (codex.py:207) and never passes metadata/previous_response_id/conversation_id. The only `store` occurrence in auxiliary_client.py is line 1083. The adapter never sets metadata/previous_response_id/conversation_id.

Let me read the main codex transport build_kwargs around lines 60-320 to confirm the store semantics contrast and whether previous_response_id is ever used for conversation. This confirms persistence claims across both the auxiliary adapter and production transport.

And let me verify `gpt-5.1-codex-mini` / the configured model. Let me quickly look at the dualcoach config provider defaults already seen: `draft_provider` default openai-codex, `draft_model` default `gpt-5.1-codex-mini`.

Let me check the codex.py build_kwargs to confirm no conversation_id / previous_response_id for the production path.Let me read the production Codex transport `build_kwargs` to confirm it never sets `previous_response_id`/`conversation_id`/`store:True`, confirming the non-persistence claim holds for both paths


I have enough evidence. Let me compile the final structured findings.

<results>
<files>
- /home/cube/projects/richard/hermes-agent/agent/auxiliary_client.py - Core of the investigation: `_CodexCompletionsAdapter` (l.1038), `CodexAuxiliaryClient` (l.1419), `_build_codex_client` (l.2471), `resolve_provider_client` openai-codex branch (l.4126-4163), `_CODEX_AUX_BASE_URL` (l.502), request-param builder `resp_kwargs` (l.1077-1086).
- /home/cube/projects/richard/hermes-agent/agent/transports/codex.py - Production Codex `build_kwargs` (l.60); confirms `store:False` (l.207) and that `max_output_tokens` is suppressed for `is_codex_backend` (l.305-306).
- /home/cube/projects/richard/hermes-agent/gateway/platforms/dualcoach_admin.py - The provider-auth gate: `_models_list`/`_probe` (l.297-323), `provider_auth_check` (l.396), `_MODEL_LIST_UNSUPPORTED_PROVIDERS` (l.39), exit table including PROBE_UNKNOWN=29 (l.65-93).
- /home/cube/projects/richard/hermes-agent/gateway/platforms/dualcoach_tasks21_25_controller.py - Fail-closed arm/launch controller; `_verify_provider_receipt` (l.222-237) requires receipt `result=="ready"` else NO_GO; `_profile_snapshot` (l.157) captures rollback/no-op hashes.
- /home/cube/projects/richard/hermes-agent/agent/codex_responses_adapter.py - `_chat_messages_to_responses_input` (l.306); the shared chat→Responses input converter used by the adapter (proves persistence is per-request, no conversation object).
- /home/cube/projects/richard/hermes-agent/tests/agent/test_auxiliary_client.py - `TestCodexAdapterReasoningTranslation` (l.2759+) shows the exact captured `responses.create` kwargs and confirms `store:False` fixed, no metadata/conversation params.

</files>

<answer>
I traced the live source (not the snapshots). Here is the determination for the production `openai-codex` provider that `resolve_provider_client` resolves to `CodexAuxiliaryClient` (auxiliary_client.py:4126-4163 → `_build_codex_client` returns `CodexAuxiliaryClient(real_client, model)`).

**Request construction (`_CodexCompletionsAdapter.create`, auxiliary_client.py:1046-1186)**
The adapter builds `resp_kwargs` for `self._client.responses.create(**stream_kwargs)`:
- `model`: the resolved model (e.g. `gpt-5.1-codex-mini`, default in dualcoach config).
- `instructions`: `"You are a helpful assistant."` (or the `system` message content).
- `input`: `_chat_messages_to_responses_input(replay_messages)` or `[{"role":"user","content":""}]`.
- `store`: **False, hardcoded** (l.1083) — the only occurrence in the file; never overridable.
- `stream`: **True, always** (l.1257).
- `timeout`: forwarded only if the caller passed `timeout`.

**Supported parameters (exact, from code trace):**
- `store`: fixed to `False`. Not settable.
- `max_output_tokens`: **NOT sent on the production chatgpt Codex backend.** The endpoint capability contract for `chatgpt.com/backend-api/codex` is `_CHATGPT_CODEX_RESPONSES_CAPABILITIES` with `supports_max_output_tokens=False` (l.683-686). The budget path (`_codex_completion_budget`, l.992) is only entered for structured output, and for the role Any codex endpoint the budget is folded into `instructions` as text, not `max_output_tokens`. The production transport corroborates this: `if max_tokens is not None and not is_codex_backend:` (codex.py:305). Generic `api.openai.com` Responses would send it, but the configured production openai-codex host is `https://chatgpt.com/backend-api/codex` (l.502).
- `stream`: always `True`; the adapter consumes raw SSE and reconstructs the Chat-Completions-shaped response.
- `tools`: supported; chat-completions function schemas are deep-copied, sanitized (`strip_pattern_and_format`, `strip_slash_enum`), and converted to `{type, name, description, parameters}` (l.1148-1186).
- `reasoning` / `include`: supported only via `extra_body={"reasoning": {...}}` (l.1129-1146).
- `text` (structured output): via `response_format` only when the model is in `_CODEX_STRUCTURED_OUTPUT_MODELS` (l.942, 948) — not for the default probe model.
- `metadata`: **never emitted.** Test `test_extra_body_without_reasoning_key_is_noop` proves a caller-provided `metadata` in `extra_body` is dropped.
- conversation/thread IDs (`previous_response_id`, `conversation_id`, Responses `conversation` object, `prompt_cache_key`): **never emitted** by the adapter. A repo-wide scan of `auxiliary_client.py` finds no `previous_response_id`/`conversation_id`; the production transport only sets `prompt_cache_key`/`session_id` HTTP headers for non-codex/non-xAI. No Responses conversation is created or threaded.

**Persistence and billing**
- Server-side conversation/content: **not persisted**. `store:False` + no `previous_response_id` means no Responses conversation entry and no server-side retrievable response is created. A minimal one-shot probe does not attach to any conversation/thread and leaves no client-visible stored content.
- Billing/usage: **yes, it occurs.** Any `responses.create` call is a model generation; it consumes input+output tokens (`resp.usage` is parsed and returned at l.1335-1341) and counts against the ChatGPT Codex subscription. The adapter response exposes `usage` and `provider_status`. So an authenticated probe obtained this way is non-persistent but **not zero-cost** — there is no free/read-only probe for this provider.

**No strictly non-persistent ACTIVE probe exists that is also side-effect-free.** Precisely:
- A strictly **non-persistent** authenticated probe DOES exist: `CodexAuxiliaryClient(...).chat.completions.create(messages=[{"role":"user","content":"ok"}])`, which sends `store:False`, no tools, no metadata, no conversation id.
- But the **currently-wired** provider-auth probe is `models.list` (dualcoach_admin.py:297-323), and `CodexAuxiliaryClient` has **no `.models`** (only `chat`, `api_key`, `base_url`, `close`). `_models_list`'s fallback reaches `_real_client.models.list`, which would hit `GET https://chatgpt.com/backend-api/codex/models` — unsupported. That is exactly why the frozen Task26 result is `probe_unknown` / exit `29`: no `models.list` probe is supported, and the gate refuses to treat construction as proof of auth.
- Therefore the only working authenticated probe is a **generation** request, which bills usage. There is **no non-billing read-only probe**. If the requirement is "authenticate with absolutely zero usage/effects," then no such active probe exists — I state this explicitly.

**Smallest safe request that proves auth without any customer/service/delivery/registry/Telegram effect:**
```python
from agent.auxiliary_client import resolve_provider_client
client, model = resolve_provider_client("openai-codex", "gpt-5.1-codex-mini")
resp = client.chat.completions.create(messages=[{"role": "user", "content": "ok"}])
# -> requests: POST <base_url>/responses  body {model, instructions, input:[user:"ok"], store:false, stream:true}
# auth proven iff resp.model is set and resp.usage is not None (status "completed").
```
Exact wire shape it produces: `model`, `instructions="You are a helpful assistant."`, `input=[{"role":"user","content":"ok"}]`, `store=False`, `stream=True`. No tools, no metadata, no conversation/thread id, no `max_output_tokens` (dropped on the codex backend). This is the minimal generation.

**Where it can run fail-closed in the Tasks21-25 controller**
- The fail-closed seam is `dualcoach_admin.provider_auth_check` (dualcoach_admin.py:428): `client, _ = _resolve_provider_client(...)` then `_probe(client)`. This is where `_probe` would have to be replaced with the adapter-based generation above (it is currently `models.list`, which freezes for `CodexAuxiliaryClient`). This runs entirely offline except for the one provider request — no Telegram, no registry, no service, no delivery.
- Receipt fields the arm gate checks (`_verify_provider_receipt`, dualcoach_tasks21_25_controller.py:222-237): `schema == "dualcoach-provider-auth-receipt-v1"`, `candidate_digest` match, `result == "ready"`, `success is True`, `exit_code == 0`, `provider_adapter == "agent.auxiliary_client.resolve_provider_client"`, `provider_adapter_version == "v1"`. Any non-`ready` (including `probe_unknown`/29) yields `NO_GO` and arm exits 1 — fail-closed, before any registry activation.
- The controller's own rollback/no-op evidence is `_profile_snapshot` (l.157): `config_sha256`, `auth_sha256`, `registry_sha256`, `gateway_state_sha256`, per-path `delivery_path_sha256`, `delivery_count:0`, `customer_count:0`, plus checks that the service is `inactive`/`dead` and the profile has no matching `/proc` process. On `launch`, it re-snapshots and fails on `profile_baseline_drift`. So if an auth probe is added, it must stay inside `provider_auth_check` before `_write_receipt`; the existing hash-pinned snapshot already guarantees no customer/delivery effect and no mutation of the arming baseline.

**Bottom line:** There is no free, non-billing authenticated probe for the production `CodexAuxiliaryClient`/openai-codex path. The only working probe is a minimal generation (`store:false`, no tools/metadata/conversation), which is non-persistent but consumes Codex usage. The current `models.list` probe (exit 29) does not work for this client. As built, no active probe is both non-persistent and zero-usage, and no probe exists today that returns `ready` for openai-codex — arm is correctly fail-closed until a generation-based probe is added.
</answer>

<next_steps>
If you need a real authenticated-pass signal, the smallest correct change is in `dualcoach_admin.py`: replace the `models.list` probe (`_probe`/`_models_list`, l.297-323) with an adapter generation probe `client.chat.completions.create(messages=[{"role":"user","content":"ok"}])`, mapping a valid `resp.model` + `resp.usage` (or `resp.provider_status=="completed"`) to `READY`/exit 0 and every SDK error/timeout to an explicit non-zero exit so `_verify_provider_receipt` still fails closed. This is a code edit — outside my read-only scope — and it will consume a small number of Codex tokens per run. Confirm with the account owner that per-run generation cost is acceptable before wiring it into the Tasks21-25 arm gate, since no usage-free active probe is available for this provider.
</next_steps>
</results>