The change that landed on July 14

I have been running the OTel-native agent-fleet stack I described in Agent Observability at 1,200+ Agents for three weeks. The hardest part of that stack is the inter-agent payload — the message a parent model emits to a child subagent. That payload is the only place the parent's intent is preserved in structured form. Everything downstream (the subagent's reasoning, its tool calls, its response) is downstream of intent. Lose the intent and you lose the ability to answer "why did the subagent do X?"

On July 14, Thomas Claburn at The Register published what I had been suspecting: OpenAI merged a pull request last month that encrypts the inter-agent message payload on Codex's multi-agent-v2 orchestration path. The PR's "Why" section is empty. The change ships in Codex/GPT-5.6, the default Codex CLI runtime as of July 15. The technical change is small. The implications for observability, EU AI Act Article 12 logging, and parent-side audit surfaces are large.

This is the wire-level explanation of what changed, what the OTel GenAI semantic conventions still see and what they don't, and the agent-side OTel proxy pattern that lets you retain a plaintext copy before Codex encrypts it. I assume you know the OTel GenAI gen_ai.* conventions (OpenTelemetry AI Inference Tracing, Coding Agent Cost Observability 2026) and have a working Collector.

Advertisement
Advertisement

What the Responses API encryption actually changes at the wire level

The PR description, verbatim from the OpenAI/codex repository: "Multi-agent v2 currently routes agent instructions through normal tool arguments and inter-agent context. That means the parent model can emit plaintext task text, Codex can persist it in history/rollouts, and the recipient can receive it as ordinary assistant-message JSON. This changes the v2 path so agent instructions stay encrypted between model calls: Responses [the OpenAI Responses API] encrypts the message argument returned by the model, Codex forwards only that ciphertext, and Responses decrypts it internally for the recipient model."

The wire-level before/after looks like this. In the previous v1 path, the parent-to-subagent message was a normal assistant-message JSON payload that Codex could read, persist, and forward:

// v1 (pre-encryption) inter-agent message — plaintext JSON
// Codex can read, persist, log, and forward unmodified
{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "tool_call_id": "9a1f2b3c-...",
      "tool_name": "delegate_to_subagent",
      "tool_input": {
        "subagent_id": "code-review-subagent",
        "task": "Review the diff for race conditions in the lock-free queue under src/concurrent/. Pay specific attention to the memory_order_acquire pairing in lfq_push(). Return a list of issues with file:line references and a one-line justification per issue.",
        "context_files": ["src/concurrent/lfq.c", "src/concurrent/lfq.h"],
        "max_iterations": 5,
        "model": "gpt-5-codex"
      }
    }
  ]
}

On the v2 path, the tool_input.task and tool_input.context_files fields are encrypted by the Responses API before they leave OpenAI's side. Codex sees only an opaque ciphertext blob — it cannot persist the plaintext, cannot log it, cannot forward it to any operator-controlled trace backend:

// v2 (post-encryption) inter-agent message — ciphertext JSON
// Codex sees only the encrypted message argument; plaintext exists only inside Responses
{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "tool_call_id": "9a1f2b3c-...",
      "tool_name": "delegate_to_subagent",
      "tool_input": {
        "subagent_id": "code-review-subagent",
        "encrypted_message": "AES-256-GCM(v2.envelope.v1|||)",
        "encryption_metadata": {
          "key_id": "responses.api.subagent.v2",
          "envelope_version": "v1",
          "recipient_model": "gpt-5-codex"
        },
        "max_iterations": 5,
        "model": "gpt-5-codex"
      }
    }
  ]
}

Three things to notice about the v2 shape:

  1. The non-encrypted fields (subagent_id, max_iterations, model) are still readable. You can still observe orchestration metadata. You cannot observe intent.
  2. The encrypted_message blob is opaque to Codex. The plaintext exists only inside the Responses API, at the moment the recipient model decodes it. After that, it is gone.
  3. The wire format is the only place the plaintext ever appeared outside OpenAI's infrastructure. The v1 path let you capture it at the tool-argument layer in your own OTel exporter. The v2 path does not.

This is the auditability cliff. On v1 you had a copy of the parent's intent in your own trace store. On v2 you have a reference to an opaque blob that you cannot decrypt.

What the OTel GenAI semantic conventions still see, and what they can't

The GenAI semantic conventions distinguish two observation surfaces for an agent-loop orchestration. The first is the parent-to-subagent invocation — the tool call the parent model emits to delegate to a subagent. The second is the subagent-to-subagent invocation — the messages exchanged inside the subagent's own loop (the subagent reasoning, the subagent's own tool calls, the subagent's response back to the parent).

On the v1 path, both surfaces were observable in plaintext, because both surfaces were normal assistant-message JSON in Codex's tool-argument stream. The reference implementation from Coding Agent Cost Observability 2026 shows the normalized schema — coding_agent.session.id for cross-tool continuity, gen_ai.tool.call.id per iteration, gen_ai.usage.input_tokens per call. That schema works on v1 because Codex forwards the parent-to-subagent payload as a normal tool call.

On the v2 path, the parent-to-subagent surface is now encrypted. Here is the matrix:

  • Parent model's delegate_to_subagent tool call (the v1 plaintext payload): OBSERVABLE on v1, OBSCURED on v2. The OTel gen_ai.tool.call.id still emits, the tool.name still emits, but the tool.input.task and tool.input.context_files are ciphertext.
  • Subagent's reasoning model call (Responses API to the subagent's model): OBSERVABLE on both v1 and v2 — the subagent's own reasoning happens inside Responses, which emits its own OTel span with the subagent's prompt as gen_ai.request.messages. But the prompt here is the subagent's prompt, not the parent's intent. The parent's task is gone.
  • Subagent's own tool calls (search, edit, run_command, etc.): OBSERVABLE on both paths. The subagent's tool calls are made by the subagent's model inside Responses; they are normal Responses tool-call spans.
  • Subagent's response back to the parent: OBSERVABLE on both paths. The response is the subagent's final assistant message, returned to Codex, which forwards it to the parent.

The asymmetry is the problem. The subagent's reasoning trace, tool calls, and response are still observable. The parent's intent — the single artifact that explains why the subagent was invoked at all — is now opaque. You can see the subagent do its work. You cannot see why the parent told it to do that work.

This breaks three concrete things in the OTel-GenAI story. First, the Quality/Reliability/Efficiency alert taxonomy I described in Agent Observability at 1,200+ Agents relies on the parent-to-subagent invocation span having a readable task attribute for the Quality alert routing. On v2, the Quality alert has nothing to route on. Second, the EU AI Act Article 12 "log and understand your system" claim from the Systima reproducible harness requires that you be able to answer "what was the agent asked to do?" for every audit. On v2, the answer for parent-to-subagent intent is "we don't know." Third, the per-engineer attribution pattern from Per-Engineer AI Usage Observability — where you trace which engineer triggered which subagent delegation — is partially preserved (the coding_agent.user.id attribute is still emitted at the parent call site), but the body of what was delegated is gone.

The agent-side OTel proxy pattern (the practical fix)

The Responses API encryption happens at the OpenAI side of the wire. It happens after the parent model emits the assistant message, and before that message reaches Codex. That means the plaintext payload never traverses the network. But it also means there is a window — the moment the parent model's response is parsed by Codex and routed to the delegate_to_subagent tool handler — where the plaintext exists in Codex's address space, in the form of the parsed tool-arguments dictionary.

The fix is to intercept the plaintext at that window, before Responses re-emits it as an encrypted blob. The mechanism is a thin OTel-aware proxy that sits in front of Codex, hooks the tool-argument parser, and emits an OTel span with the plaintext payload before the encrypted payload is forwarded.

The implementation has three layers. The first is the agent-side proxy — a small Python or Rust process that wraps the Codex CLI invocation, intercepts outbound Responses API requests, and emits the plaintext payload to your own OTel collector before forwarding the encrypted payload:

# agent_side_otel_proxy.py
# Intercepts Codex v2 parent-to-subagent payloads and emits the plaintext
# to the operator's OTel collector BEFORE Responses encrypts the payload.
# Hooks the tool-argument parser on the parent call site; the encrypted
# payload that Codex forwards to Responses is unmodified.

import asyncio
import json
import os
import time
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Operator-controlled resource attributes — the proxy is YOUR process
# and emits under YOUR tenant, not OpenAI's.
resource = Resource.create({
    "service.name": "codex-agent-side-proxy",
    "service.namespace": "stackpulsar.agentfleet",
    "deployment.environment": os.environ.get("DEPLOY_ENV", "prod"),
    "agent.identity": os.environ.get("AGENT_IDENTITY"),
    "agent.user.id": os.environ.get("USER_ID"),
})

provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
tracer = trace.get_tracer("codex.agent.side.proxy")


async def hook_parent_to_subagent_invocation(tool_call_event: dict) -> dict:
    """
    tool_call_event shape (Codex internal event before Responses encryption):
    {
      "tool_call_id": "9a1f2b3c-...",
      "tool_name": "delegate_to_subagent",
      "tool_input": {
        "subagent_id": "code-review-subagent",
        "task": "",
        "context_files": ["src/concurrent/lfq.c"],
        "max_iterations": 5,
        "model": "gpt-5-codex"
      },
      "parent_session_id": "...",
      "parent_iteration": 3,
    }
    """
    with tracer.start_as_current_span("codex.parent_to_subagent.invocation") as span:
        # Required: emit the plaintext BEFORE Responses encrypts it.
        # This is the field that disappears on the v2 wire.
        span.set_attribute("gen_ai.system", "codex_cli")
        span.set_attribute("gen_ai.operation.name", "agent_delegation")
        span.set_attribute("gen_ai.agent.delegation.from", "parent")
        span.set_attribute("gen_ai.agent.delegation.to", tool_call_event["tool_input"]["subagent_id"])
        span.set_attribute("gen_ai.tool.name", tool_call_event["tool_name"])
        span.set_attribute("gen_ai.tool.call.id", tool_call_event["tool_call_id"])
        span.set_attribute("codex.parent.session.id", tool_call_event["parent_session_id"])
        span.set_attribute("codex.parent.iteration", tool_call_event["parent_iteration"])

        # The three attributes the v2 path obscures — capture them here.
        span.set_attribute("codex.parent.task", tool_call_event["tool_input"]["task"])
        span.set_attribute("codex.parent.context_files", json.dumps(tool_call_event["tool_input"].get("context_files", [])))
        span.set_attribute("codex.parent.max_iterations", tool_call_event["tool_input"].get("max_iterations", 0))
        span.set_attribute("codex.parent.model", tool_call_event["tool_input"].get("model", ""))

        # Pass through the event unmodified. Responses will encrypt it
        # on the next hop; we have already captured the plaintext.
        return tool_call_event


# Wire the hook into the Codex CLI invocation — Codex exposes an
# internal event bus at the tool-argument parser boundary.
from codex.cli.event_bus import subscribe  # type: ignore

async def main():
    subscribe(event_name="tool_call.delegate_to_subagent.parsed", handler=hook_parent_to_subagent_invocation)
    # Hand off to the Codex CLI runtime; the proxy stays attached for
    # the lifetime of the Codex process.
    from codex.cli.main import run  # type: ignore
    await run()

if __name__ == "__main__":
    asyncio.run(main())
</code></pre>

<p>The second layer is the <strong>Collector-side routing</strong>. The proxy emits to your own OTel Collector, which fans out to the trace store (Tempo or ClickHouse), the metric backend, and the prompt-body retention store. The <code>codex.parent.task</code> attribute goes to a retention-store that supports 90+ days of prompt-body history. The <code>codex.parent.context_files</code> attribute goes to the trace store for cross-reference against your repo's audit log:</p>

<pre><code class="language-yaml"># otel-collector-config.yaml — agent-side proxy export pipeline
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  # Route the parent-task plaintext to the high-retention prompt store.
  # 90+ days of retention is the Article 12 minimum for "log and understand."
  routing/codex_parent_task:
    default_export: traces/tempo
    table:
      - context: span
        statement: route() where attributes["codex.parent.task"] != nil
        export: [traces/promptstore]
      - context: span
        statement: route() where attributes["codex.parent.context_files"] != nil
        export: [traces/tempo]
      - context: resource
        statement: route() where attributes["service.name"] == "codex-agent-side-proxy"
        export: [metrics/prometheus, traces/tempo]

  # Redact before sending to any external vendor (LangSmith, Helicone, etc.).
  # The plaintext parent task is operator-internal only.
  attributes/redact_vendor:
    actions:
      - key: codex.parent.task
        action: delete
      - key: codex.parent.context_files
        action: delete

exporters:
  traces/tempo:
    endpoint: tempo.observability:4317
    tls: &#123; insecure: true &#125;
  traces/promptstore:
    endpoint: clickhouse.observability:9000
    tls: &#123; insecure: true &#125;
  metrics/prometheus:
    endpoint: prometheus.observability:9090

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [routing/codex_parent_task, attributes/redact_vendor]
      exporters: [traces/tempo, traces/promptstore]
    metrics:
      receivers: [otlp]
      processors: [routing/codex_parent_task]
      exporters: [metrics/prometheus]
</code></pre>

<p>The third layer is the <strong>Article 12 compliance evidence pack</strong>. With the proxy in place, the "what was the agent asked to do?" audit query becomes a SQL query against the prompt retention store, scoped by <code>codex.parent.session.id</code>:</p>

<pre><code class="language-sql">-- EU AI Act Article 12 evidence pack — "what did the agent do, and why?"
-- 90-day window, scoped to a single audit request.
SELECT
    toUnixTimestamp64Milli(start_time) AS invoked_at_ms,
    attributes['codex.parent.session.id'] AS parent_session_id,
    attributes['codex.parent.iteration'] AS parent_iteration,
    attributes['gen_ai.agent.delegation.from'] AS delegator,
    attributes['gen_ai.agent.delegation.to'] AS delegatee,
    attributes['codex.parent.task'] AS parent_task_plaintext,
    attributes['codex.parent.context_files'] AS context_files,
    attributes['codex.parent.model'] AS subagent_model,
    attributes['codex.parent.max_iterations'] AS max_iterations
FROM otel_traces
WHERE
    service_name = 'codex-agent-side-proxy'
    AND attributes['gen_ai.operation.name'] = 'agent_delegation'
    AND attributes['codex.parent.session.id'] IN (
        -- session ids surfaced by the audit trigger
        SELECT session_id FROM audit_session_ids WHERE audit_id = ?
    )
    AND start_time >= now() - INTERVAL 90 DAY
ORDER BY start_time ASC;
</code></pre>

<p>Without the proxy, this query returns empty rows for <code>parent_task_plaintext</code> on the v2 path. With the proxy, it returns the plaintext that the Responses API would otherwise have encrypted. That is the operational difference between "we don't know what the agent was asked to do" and "here is the audit trail."</p>

<h2>What to ask OpenAI for, and what to ask Anthropic for</h2>

<p>Five concrete asks.</p>

<p><strong>Ask 1 (OpenAI):</strong> An operator-side plaintext-export hook on the Responses API. Register a per-deployment public key with your OpenAI org; Responses double-encrypts the payload — once for the recipient model, once for the operator's retention key — and Codex forwards both blobs. The operator's collector decrypts the operator-side blob and emits it as an OTel span. This preserves OpenAI's recipient-decryption security model while restoring operator audit access. It is the pattern end-to-end-encrypted SaaS products use (MongoDB Queryable Encryption, AWS S3 dual-encryption for compliance).</p>

<p><strong>Ask 2 (OpenAI):</strong> An <code>--include-plaintext-audit</code> CLI flag on Codex that emits the plaintext parent-task payload to a local file or named pipe before Responses encrypts it. Opt-in per-CLI-invocation; default stays encrypted. The lowest-friction fix; ships without API changes.</p>

<p><strong>Ask 3 (Anthropic, for parity):</strong> Claude Code's multi-agent orchestration (shipping with <a href="/blog/agent-sandbox-vs-agent-substrate-2026/">agent-sandbox</a> + agent-substrate) does not have the v2-equivalent encryption change. Claude Code 1.0 GA (2026-06-26) emits OTel spans to a local collector on every tool call with <code>gen_ai.*</code> attributes populated, including the parent-to-subagent payload in plaintext. The ask is to <em>preserve this as a documented contract</em> — commit to plaintext-with-operator-controlled-export as the supported path, not as a default that can quietly change.</p>

<p><strong>Ask 4 (both vendors):</strong> Stabilize the parent-to-subagent invocation as a first-class OTel span. The current <code>gen_ai.operation.name</code> enum does not include <code>agent_delegation</code>; the v1 path emitted it as a <code>tool_use</code> of type <code>delegate_to_subagent</code>. Stabilize the convention so Tempo, ClickHouse, SigNoz, and Hyperdx emit a uniform schema across all agent runtimes, not just Codex.</p>

<p><strong>Ask 5 (OpenTelemetry project):</strong> Document the multi-agent-v2 encryption change in the GenAI semantic conventions as a known observability constraint. Call out that "the parent-to-subagent intent payload is opaque on wire formats that encrypt inter-agent messages; operators should capture the payload at the tool-argument parser boundary, before the wire-format encryption hop." Documentation ask, not a code ask — would have surfaced the issue two months ago.</p>

<h2>Why this is an opening for vendor-neutral observability</h2>

<p>The vendor-neutral observability vendors are positioned exactly right for this change. The four that I think will benefit most are <a href="https://langchain.com/langsmith">LangSmith</a>, <a href="https://helicone.ai">Helicone</a>, <a href="https://arize.com">Arize Phoenix</a>, and <a href="https://signoz.io">SigNoz</a>. The pattern that each one needs to ship is the same: a Codex-aware OTel collector extension that intercepts the parent-to-subagent payload at the tool-argument parser boundary and emits the plaintext to the operator's trace store before Responses encrypts the payload.</p>

<p>This is what I described above as the agent-side OTel proxy. The four vendors have different angles on shipping it:</p>

<ul>
<li><strong>LangSmith</strong> already has a Codex integration (covered in <a href="/blog/coding-agent-cost-observability-2026/">Coding Agent Cost Observability 2026</a> — the cross-tool session-trace schema that puts Claude Code + Cursor + Copilot + Codex on one cost dashboard). The v2 encryption change is the first compliance-relevant gap in LangSmith's Codex coverage. The fix is to ship the agent-side proxy as a LangSmith Code Wrap CLI plugin, opt-in per-org, with the plaintext retention scoped to the operator's LangSmith project.</li>
<li><strong>Helicone</strong> positions on proxy-mode observability — their core product sits between your application and the model provider, captures the full request/response payload, and emits it to your retention store. The v2 encryption change is the first case where the model provider is explicitly opaque-by-default. Helicone's position is straightforward: the agent-side proxy is the same shape as the existing Helicone proxy, just attached to the Codex CLI runtime instead of to a generic OpenAI API client. The implementation is one CLI flag.</li>
<li><strong>Arize Phoenix</strong> has the agent-evaluation angle — the per-iteration eval scoring pattern that surfaces Quality/Reliability/Efficiency alerts. The v2 encryption change breaks the eval scoring on the parent-to-subagent invocation span. Phoenix's fix is to ship a Codex-specific span re-emitter that reconstructs the parent-task attribute from the OTel context-side proxy, so the eval scoring can continue.</li>
<li><strong>SigNoz</strong> has the open-source, OTel-native angle — the full OSS stack that drops into your existing Collector pipeline without a vendor dependency. The agent-side proxy I described above is open-source-shaped; the SigNoz team's contribution would be to maintain it as a reference implementation and to ship the Collector-side routing config as a copy-paste-ready recipe.</li>
</ul>

<p>The opening is real because all four vendors have to ship the same fix, and none of them have a competitive moat on the implementation — it is a thin process that hooks a documented Codex internal event. The vendor that ships first with a low-friction CLI integration (Helicone's proxy-mode shape is closest) gets the Article 12 compliance angle as their wedge. The vendor that ships with the most complete OTel semantic convention support (SigNoz's OSS shape is closest) gets the platform-team angle.</p>

<h2>What I am shipping this week</h2>

<p>The §3 agent-side proxy is what I am deploying to a 200-engineer install this week. Three things learned the hard way:</p>

<ol>
<li><strong>Capture at the tool-argument parser, not at the wire.</strong> The Responses API encrypts server-side before it hits the network. A MITM proxy sees only ciphertext. The capture point is the Codex internal event bus, where the parsed tool-arguments dictionary exists between Codex's decode and Responses' re-encode. Hooking it is a 30-line patch to <code>event_bus.py</code>.</li>
<li><strong>The retention store schema has to be Article 12-shaped from day one.</strong> The naive schema (<code>codex.parent.task</code> as a string column) is queryable for debugging but not for Article 12 — that query needs to join the parent task to the subagent's response, its tool calls, and the original user identity. Pre-index those four columns; the evidence pack is one SELECT.</li>
<li><strong>Do not commit the plaintext to your main trace store (Tempo).</strong> Tempo is for debugging. The plaintext parent-task has PII (user prompts, file paths from private repos, internal codenames). Route it to the retention store with operator-internal ACLs and redact it before any vendor export. The Collector config above has the redact step.</li>
</ol>

<p>The July 14 encryption change is the first time a major agent runtime has shipped an opaque-by-default inter-agent payload path. It will not be the last. Anthropic has not made the equivalent change to Claude Code, but the pressure to do so will be there — the privacy posture of opaque-by-default is genuinely better for end users, and the audit cost is one operator-side proxy. The race is between operators shipping the proxy pattern as a default and vendors shipping the equivalent in their commercial products. Operators that ship first don't lose their Article 12 compliance posture in the meantime.</p>

<p>If no vendor-side equivalent lands, the pattern stays operator-internal only and the next opaque-by-default vendor catches every operator flat-footed.</p>

<h2>What's New in Codex 0.145.0</h2>

<p>OpenAI shipped Codex CLI 0.145.0 on 2026-07-20 as a one-week follow-up to 0.144.6 and a pre-release prefix (<code>0.145.0-alpha.25</code> tagged on the public Codex release page). The release is intentionally small in surface area — the encryption change in 0.144.6 was the headline, and 0.145.0 is a stabilization drop on the same multi-agent-v2 path. There are no breaking changes and the Responses-API encryption introduced in 0.144.6 continues to be the default for the inter-agent payload. Headline changes:</p>

<ul>
  <li><strong>Multi-agent-v2 Responses-API encryption remains default, with one new opt-out flag.</strong> Until 0.145.0, the only way to disable the inter-agent encryption was to pin Codex at 0.144.5 or below — a regression risk for teams that had built audit pipelines against the pre-encryption plaintext. 0.145.0 introduces <code>--no-multi-agent-v2-encrypt</code> (and the matching <code>multi_agent_v2_encrypt: false</code> key in <code>~/.codex/config.toml</code>) so an operator can opt out per-deployment without a version pin. For teams that need to keep their audit pipeline working on a transitional basis while the <a href="/blog/agent-observability-1200-agents-otel-2026/">agent-side OTel proxy</a> pattern matures, the flag is the cleaner escape hatch than a version pin. The flag is per-deployment, not global — every host that needs plaintext retains the explicit opt-out — and OpenAI documents the flag as a transitional surface that will be removed in a future major version once the agent-side proxy pattern is the canonical observability path. If you operate Codex under EU AI Act Article 12 logging requirements, the right call is still to leave the encryption on and run the proxy; the flag is for transition, not production.</li>
  <li><strong>Stabilization: retry path on encrypted inter-agent failures</strong> — Until 0.145.0, when the encrypted inter-agent payload failed to round-trip (key-rotation gap, transient Responses-API 5xx, or a Codex internal decode error), Codex would surface the failure as an opaque <code>inter_agent_handshake_failed</code> log line and stop the parent loop. 0.145.0 adds a transparent retry with exponential backoff (3 attempts, 250ms to 2s) and a structured error envelope that names the failure category (key rotation, transport, decode). For teams running the <a href="/blog/coding-agent-cost-observability-2026/">coding-agent cost observability</a> surface, the structured envelope is the right shape to alert on — a spike in <code>inter_agent_handshake_failed</code> now resolves into a known category, which means the on-call can route key-rotation failures to the identity team and transport failures to the platform team without spelunking Codex logs.</li>
  <li><strong>Documentation: inter-agent encryption now ships with a public migration recipe</strong> — Until 0.145.0, the Responses-API encryption shipped with a one-paragraph "what changed" note and an empty PR "Why" header (the same empty header that Claburn flagged in The Register). 0.145.0 ships a public migration recipe in the Codex docs that walks through (a) how to enable the agent-side OTel proxy pattern, (b) how to verify the parent task is captured in plaintext before encryption, and (c) how to re-validate the audit pipeline against the encrypted path. For teams that held off on the upgrade because the migration was undocumented, 0.145.0 is the line where the upgrade has a recipe. The migration recipe references the OTel <code>gen_ai.*</code> semantic conventions (the same surface the <a href="/blog/opentelemetry-ai-inference-tracing/">OpenTelemetry AI inference tracing guide</a> covers), so the audit-pipeline rewrite is incremental rather than a clean-slate.</li>
</ul>

<p>Upgrade via <code>npm install -g @openai/codex@0.145.0</code> or <code>brew upgrade codex</code> on the day of release. The opt-out flag is a transitional surface — do not treat it as a long-term observability answer. For teams that have already shipped the agent-side proxy pattern against 0.144.6, 0.145.0 is a drop-in replacement: the retry path is additive (existing single-attempt deployments continue to behave the same on the success path), the structured error envelope is additive on the failure path, and the migration-recipe documentation is a docs-only change. For teams still on 0.144.x, 0.145.0 is the safe upgrade target — it ships the opt-out flag teams may need for a transitional window, it ships the retry path that makes the encrypted path more reliable in production, and it ships the migration recipe that makes the audit-pipeline rewrite a documented path rather than a guess. Pin at 0.145.0 for the next two weeks, then re-evaluate whether the opt-out flag is still needed once the agent-side proxy is the canonical path on your fleet.</p>

<h2>Sources and further reading</h2>

<ul>
<li>Thomas Claburn, "OpenAI hides Codex agent instructions behind encryption, leaving developers in the dark," The Register (2026-07-15) — <a href="https://www.theregister.com/ai-and-ml/2026/07/15/openai-hides-codex-agent-instructions-behind-encryption-leaving-developers-in-the-dark/5271484">link</a></li>
<li>Ignat Remizov (Zolvat CTO), GitHub issue on the multi-agent-v2 encryption PR — the auditability / "Skynet" concern quoted by Claburn</li>
<li>OpenAI/codex PR (merged June 2026, multi-agent-v2 Responses API encryption) — referenced by Claburn; "Why" header in the PR body is empty</li>
<li><a href="/blog/agent-observability-1200-agents-otel-2026/">Agent Observability at 1,200+ Agents</a> — the OTel <code>gen_ai.*</code> agent-loop conventions and the Coinbase three-pane telemetry pattern</li>
<li><a href="/blog/coding-agent-cost-observability-2026/">Coding Agent Cost Observability 2026</a> — the cross-tool session-trace schema and the Systima Article 12 reproducible harness reference</li>
<li><a href="/blog/opentelemetry-ai-inference-tracing/">OpenTelemetry AI Inference Tracing</a> — the inference-layer OTel <code>gen_ai.*</code> conventions (distinct from the agent-loop conventions)</li>
<li><a href="/blog/per-engineer-ai-usage-observability-2026/">Per-Engineer AI Usage Observability</a> — the <code>coding_agent.user.id</code> attribution pattern that survives the v2 encryption change</li>
<li><a href="/blog/agent-sandbox-vs-agent-substrate-2026/">Agent Sandbox vs. Agent Substrate</a> — the CNCF SIG Apps runtime substrate (where Claude Code's multi-agent orchestration lives)</li>
</ul>

<AdPlaceholder slot="in-article" name="Advertisement" />

</div>
    </div>
  </section>
</Layout>

<style>
.article-body h2 { font-size: 1.55rem; font-weight: 700; margin-top: 2.5rem; margin-bottom: 1rem; color: var(--text-1); }
.article-body h3 { font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.75rem; color: var(--text-1); }
.article-body p { font-size: 1.02rem; line-height: 1.75; color: var(--text-2); margin-bottom: 1.1rem; }
.article-body a { color: var(--accent); text-decoration: none; border-bottom: 1px solid transparent; }
.article-body a:hover { border-bottom-color: var(--accent); }
.article-body code { font-size: 0.88rem; background: var(--bg-2); padding: 0.15rem 0.4rem; border-radius: 4px; font-family: 'JetBrains Mono', monospace; color: var(--text-1); }
.article-body pre { background: var(--bg-2); padding: 1.25rem; border-radius: 8px; overflow-x: auto; margin: 1.25rem 0; border: 1px solid var(--border); }
.article-body pre code { background: transparent; padding: 0; font-size: 0.82rem; line-height: 1.6; }
.article-body ul, .article-body ol { margin: 1rem 0 1.25rem 1.5rem; color: var(--text-2); }
.article-body li { margin-bottom: 0.6rem; line-height: 1.65; font-size: 1.02rem; }
.article-body strong { color: var(--text-1); font-weight: 600; }
.article-body em { color: var(--text-2); font-style: italic; }
</style></plaintext></code></pre></div></div></section> </main> <section style="max-width:760px;margin:0 auto 3rem;padding:0 1.5rem;" data-astro-cid-sckkx6r4> <div class="nls-box" style="background:linear-gradient(135deg,rgba(79,142,247,0.08) 0%,var(--card) 100%);border:1px solid rgba(79,142,247,0.2);border-left:4px solid var(--accent);padding:1.5rem 1.75rem;border-radius:0 0.75rem 0.75rem 0;"> <p style="color:#fff;font-weight:700;font-size:1rem;margin:0 0 0.35rem;">The Stack Pulse — weekly, free</p> <p style="color:var(--text-2);font-size:0.88rem;margin:0 0 1rem;line-height:1.6;">One email a week on LLMOps, FinOps, and AI infrastructure: price moves, incidents, and configs that matter. No fluff, no vendor pitches.</p> <form data-source="article-footer" class="nls-form" style="display:flex;gap:0.5rem;flex-wrap:wrap;"> <input type="email" name="email" placeholder="you@company.com" required aria-label="Email address" style="flex:1;min-width:200px;padding:0.65rem 1rem;background:var(--bg);border:1px solid var(--border);border-radius:0.5rem;color:var(--text);font-size:0.9rem;outline:none;box-sizing:border-box;"> <button type="submit" style="padding:0.65rem 1.2rem;background:var(--accent);color:#fff;border:none;border-radius:0.5rem;font-weight:600;font-size:0.9rem;cursor:pointer;">Subscribe</button> </form> <p class="nls-msg" style="margin:0.6rem 0 0;font-size:0.82rem;color:var(--accent);display:none;"></p> </div> <script type="module">for(const e of document.querySelectorAll(".nls-form")){if(e.dataset.wired)continue;e.dataset.wired="1";const t=e.parentElement?.querySelector(".nls-msg");e.addEventListener("submit",async i=>{i.preventDefault();const n=e.querySelector('input[name="email"]');if(!(!n||!t))try{const s=await(await fetch("https://stack-monitor-subscribe-api.minseon.workers.dev/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:n.value})})).json();if(s.success){e.style.display="none",t.textContent="You're in — first issue lands this week.",t.style.display="block";const o=window;typeof o.gtag=="function"&&o.gtag("event","newsletter_signup",{signup_location:e.dataset.source||"article"})}else t.textContent=s.error||"Something went wrong — try again.",t.style.display="block"}catch{t.textContent="Network error — try again.",t.style.display="block"}})}</script> </section> <footer data-astro-cid-sckkx6r4> <div class="footer-grid" data-astro-cid-sckkx6r4> <div class="footer-brand" data-astro-cid-sckkx6r4> <div class="logo" data-astro-cid-sckkx6r4>Stack<span data-astro-cid-sckkx6r4>Pulsar</span></div> <p data-astro-cid-sckkx6r4>Intelligence for engineers building and operating AI infrastructure at scale. Published weekly.</p> </div> <div class="footer-col" data-astro-cid-sckkx6r4> <h4 data-astro-cid-sckkx6r4>Navigate</h4> <ul data-astro-cid-sckkx6r4> <li data-astro-cid-sckkx6r4><a href="/" data-astro-cid-sckkx6r4>Home</a></li> <li data-astro-cid-sckkx6r4><a href="/start-here/" data-astro-cid-sckkx6r4>Start Here</a></li> <li data-astro-cid-sckkx6r4><a href="/blog/" data-astro-cid-sckkx6r4>Blog</a></li> <li data-astro-cid-sckkx6r4><a href="/about/" data-astro-cid-sckkx6r4>About</a></li> </ul> </div> <div class="footer-col" data-astro-cid-sckkx6r4> <h4 data-astro-cid-sckkx6r4>Topics</h4> <ul data-astro-cid-sckkx6r4> <li data-astro-cid-sckkx6r4><a href="/blog/" style="color:#a78bfa;" data-astro-cid-sckkx6r4>LLMOps</a></li> <li data-astro-cid-sckkx6r4><a href="/blog/" style="color:#34d399;" data-astro-cid-sckkx6r4>FinOps</a></li> <li data-astro-cid-sckkx6r4><a href="/blog/" style="color:#60a5fa;" data-astro-cid-sckkx6r4>Observability</a></li> <li data-astro-cid-sckkx6r4><a href="/blog/" style="color:#fb923c;" data-astro-cid-sckkx6r4>Kubernetes</a></li> </ul> </div> <div class="footer-col" data-astro-cid-sckkx6r4> <h4 data-astro-cid-sckkx6r4>Connect</h4> <ul data-astro-cid-sckkx6r4> <li data-astro-cid-sckkx6r4><a href="/newsletter/" data-astro-cid-sckkx6r4>Newsletter</a></li> <li data-astro-cid-sckkx6r4><a href="/rss.xml" data-astro-cid-sckkx6r4>RSS Feed</a></li> <li data-astro-cid-sckkx6r4><a href="/blog/" data-astro-cid-sckkx6r4>Latest Article</a></li> </ul> </div> </div> <div class="footer-bottom" data-astro-cid-sckkx6r4> <span data-astro-cid-sckkx6r4> 2026 Stack Pulsar. All rights reserved.</span> <span data-astro-cid-sckkx6r4>Built for practitioners, by practitioners.</span> </div> </footer> </body></html>