The Agent Audit Trail Paradox: Your Forensics Got Worse
Your agent spawned a child. The child did something you did not expect. You open the agent audit trail to find out what the child was told to do, and the field is ciphertext.
That is not a logging failure. It is a design decision, made in a merged pull request you never read. Codex PR #26210 marked the multi-agent v2 message parameter as encrypted for spawn_agent, send_message, and followup_task, then stored the payload in InterAgentCommunication.encrypted_content with the plaintext content field left empty. It merged on 2026-06-05 across 34 files (openai/codex#26210).
The uncomfortable part is that the reasons were good. A readable audit trail is also an injectable one, and that is measured, not theoretical. I run a five-subagent review pipeline and have written the oncall doctrine for it, which assumes I can reconstruct a run afterwards. This post is about what happens when you cannot.
Key Takeaways
- Auditability and injection resistance are genuinely in tension, and your vendor is resolving it for you without a changelog entry. The regression report has been open since 2026-06-13 with 27 comments and 106 thumbs-up, and not one comment from an account carrying owner, member, or collaborator status on the repository (openai/codex#28058, retrieved 2026-07-29).
- The counter-case is real. Adversarial log content reached 96% injection success on a summarization task with no defenses, and 38% even with constrained output (Pandey and Bhujang, arXiv 2605.24421, 2026).
- Across 991 of my own subagent dispatches, 97.8% of delegated task text fits under 8 KiB. The bounded audit copy the fork proposes is not expensive. It is a rounding error.
- Three shipping implementations expose content capture as a policy value. One ships it as a constant with no switch.
- Ask for a size-bounded audit companion that the child model never reads. Do not ask for a revert.
What exactly stopped being auditable?
One field. The delegated task text. Codex marked the v2 message parameter encrypted, and no plaintext copy was kept anywhere on your machine. The recipient model still receives the instruction, because Responses decrypts it internally. You do not. The issue reporter states the mechanism precisely: the change leaves “InterAgentCommunication.content empty” while storing only encrypted_content (openai/codex#28058).
What makes this worse than a single regression is the compounding. Three later merges each closed a different fallback route to the same question.
- #33030 removed task messages from
list_agentsoutput, merged 2026-07-14. Live inspection stopped working too. - #33841 made parent-owned sub-agent threads read-only in the TUI, merged 2026-07-17 across 37 files. You can no longer ask the child directly.
- #34383 marked multi-agent v2 stable, merged 2026-07-20, classifying the feature as stable “while keeping it disabled by default”.
Each of those is individually defensible. Together they close every route to one question: what did the parent ask this child to do?
There is one detail here that separates a contract problem from a neglect problem. PR #30872, “Log multi-agent communication lifecycle”, merged on 2026-07-02 and was written by an OpenAI collaborator. Real observability work. The structured log still substitutes encrypted_content into its content field when the plaintext is absent. Somebody built the logging, and the logging faithfully recorded ciphertext. Nobody was careless. The contract was wrong.
Worth knowing before you spawn anything: what spawning already costs before you add a forensics gap to the bill.
Why does everyone say to log everything?
Because regulation and research both say so, and they are mostly right. EU AI Act Article 12(1) requires that high-risk systems “shall technically allow for the automatic recording of events (logs) over the lifetime of the system”, and Article 26(6) obliges deployers to keep those logs “for a period appropriate to the intended purpose of the high-risk AI system, of at least six months”. Those obligations become applicable on 2026-08-02 (EU AI Act, Article 12).
The research is blunter. “No agent system can be accountable without auditability,” argue Nian and co-authors, who name five dimensions of it: action recoverability, lifecycle coverage, policy checkability, responsibility attribution, and evidence integrity (Nian et al., “Auditable Agents”, arXiv 2604.05485, 2026). They found 617 security findings across six prominent open-source agent projects.
So the vendor checklists are not wrong. They are incomplete, and they are incomplete in a specific way. The genre inherited its shape from application logging, where the log content is your own data and the only real question is how long you keep it. Agent logs break that assumption twice over. Part of the content is attacker-supplied, and part of it is the vendor’s intellectual property.
That is the flaw to hold onto. Every ranking page on this topic treats the audit trail as costless. It is not.
I have written inside that conventional frame myself, treating the postmortem as a primitive that assumes you can reconstruct the run. This is not a disavowal of that. It is the part I left out.
Why is the tension real rather than vendor carelessness?
Because a readable audit trail is also an attack surface, and an agent that reads its own logs is injectable through them. Pandey and Bhujang measured it: adversarial log content reached 96% injection success on a summarization task with no defenses, and 38% even with constrained output. Across 48 strategy, defense, and task combinations, effectiveness ran from 26.6% under naive prompting down to 11.8% under their strongest defense (Pandey and Bhujang, “Poisoning the Watchtower”, arXiv 2605.24421, 2026-05-23).
Read that as a design constraint, not a curiosity. Many log fields are attacker-controlled: user agents, URLs, request payloads, DNS queries. Once your parent agent reads a child’s readable task history, that history is untrusted input. It is exactly the composition that makes the trifecta lethal.
There are three defensible motives for the encryption, and they deserve stating fairly.
- Context. The PR is explicit that parent context no longer carries plaintext v2 task instructions from those tool arguments. That is a real token saving and a real reduction in injection surface.
- PII posture. Less sensitive text at rest on developer machines is less to leak.
- Intellectual property. Nobody at OpenAI has said this. Thread participants raised distillation resistance and vendor lock-in as suspected motives, and one comment argues the encryption “effectively locks users into OpenAI’s proprietary model”. Treat that as unverified speculation, made load-bearing only by the absence of any maintainer answer.
Three good reasons to redact. One bad way to act on them. The failure here is not the encryption. It is shipping a policy decision as a constant, with no flag, no changelog framing, and no maintainer reply for seven weeks. Replay and fork recovery in long-running agent runs both depend on the field that just went dark.
Which incidents actually need this field?
In July 2026’s two loudest agent disclosures, the malicious instruction lived in text no human ever read. In those incidents the audit trail is not a compliance artifact. It is the only artifact.
GitLost (Noma Labs, 2026-07-06). An unauthenticated attacker filed an issue in a public repo. The assigned GitHub Agentic Workflow read the hidden instructions, fetched content from private repos in the same organization, and posted it back as a public comment. Noma’s write-up is unambiguous about the bar: “To exploit this vulnerability, the attacker needed no coding skills, access, or credentials. All that was needed was to open an issue in a public repository belonging to an organization that uses GitHub’s Agentic Workflow setup and wait” (Noma Security). The Register reported no GitHub fix at time of writing (Jessica Lyons, The Register, 2026-07-07).
Azure DevOps MCP (Manifold Security, 2026-07-21). The pull request description tool lacked the spotlighting guardrail that the pipeline and wiki tools had. An HTML comment invisible in the web UI reached the agent verbatim through the REST API. The agent then triggered a pipeline in an unrelated project, pulled restricted wiki content, and posted it back as a PR comment. Manifold validated it with two different agents. Their framing is the one that matters here: “every tool call was one it was allowed to make”, and the problem was “the sequence and intent, driven by text that a human never saw” (Manifold Security). MSRC acknowledged and triaged it, with no fix reported at publication.
Here is the point. In both incidents every individual action was authorized, so permission logs prove nothing. The only record that distinguishes an agent following your instructions from an agent following someone else’s is the instruction text itself. That is precisely the field the encryption removed. The install-time half of this same trust problem is the agent tool supply chain.
What does a usable audit trail actually contain?
Fewer fields than the checklists claim, and one of them has no substitute. To find out which, I pulled every subagent dispatch out of my own Claude Code session transcripts: 991 delegations between 2026-07-06 and 2026-07-29, across 65 sessions and 18 repositories. Not a purpose-built study, just the record the tool already keeps.
Seven fields carry the forensic weight, and the ledger says how well each one holds up.
| Field | Present | What it answers |
|---|---|---|
| Dispatch timestamp | 100% | when the delegation happened |
| Parent record ID | 100% | which turn caused the spawn |
| Child dispatch ID | 100% | which child this is |
| Working directory | 100% | which codebase was in scope |
| Delegated task text | 100% | what the child was told to do |
| Agent type | 94.5% | which policy the child ran under |
| Terminal disposition | 99.8% | what came back |
Now null one field and re-ask the questions. This is the part that surprised me. Claude Code keeps a short human label alongside the full task text, and the label survives redaction perfectly well. It is also useless: a median of 4 words against a median 346-word task, which is 1.13% of the instruction by length. You keep the filename and lose the file.
So the degradation is not uniform. “Which tool call moved the data” stays answerable, because the child’s own actions are still recorded. “What did the child read” gets slower. But “was the child told to do this, or did it decide to?” has no degraded mode at all. It either has a persisted source or it does not, and no amount of investigation time recovers it. Mishra and Sharad give this the formal statement: without execution-time delegation binding, audit logs and execution traces “can be identical under multiple incompatible delegation assignments” (Mishra and Sharad, arXiv 2606.09692, 2026-06-08). A spawn tree without task text is not a reduced trail. It is an ambiguous one.
The framework that replaces the checklist is a split. A trace is for an engineer debugging at 03:14. It can be lossy, sampled, and short-lived. An audit record is evidence for a third party. It must be complete, bounded, and retained. Different readers, different redaction policy, different retention. Most tooling ships one artifact and satisfies neither reader.
And retire the cost objection now, because it is already dead. Pre-execution mediation with tamper-evident records adds 8.3 ms median overhead (Nian et al., arXiv 2604.05485, 2026). Nobody is deleting the audit trail to save time.
Caveats, stated plainly: this is one developer, one harness, 24 days. It is a field-availability census, not a controlled study, and Claude Code is the tool that keeps the field rather than the one that lost it. The wave-dispatch shape it came from is subagent-driven development; the mechanism for writing your own is the hooks substrate.
The audit trail binary is manufactured
You are not being asked to choose between auditability and injection resistance. You are being asked to accept somebody else’s default. Three shipping implementations already expose the choice as a policy value, and they converged on the same shape without coordinating.
Claude Code redacts user prompt text, tool input details, and tool content from spans by default, and gates each behind an explicit flag: OTEL_LOG_USER_PROMPTS, OTEL_LOG_ASSISTANT_RESPONSES, OTEL_LOG_TOOL_DETAILS, OTEL_LOG_TOOL_CONTENT, and OTEL_LOG_RAW_API_BODIES, all disabled (Claude Code monitoring docs). OpenTelemetry’s GenAI conventions mark gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, and gen_ai.tool.definitions as opt_in, and the Python capture policy defaults to NO_CONTENT with SPAN_ONLY, EVENT_ONLY, and SPAN_AND_EVENT as the alternatives. The fork on #28058 adds a message_delivery policy with three values, bounds the audit copy at 8 KiB, and keeps that copy out of the child model’s input.
That fork is not a thought experiment. It is dated code: 2ffe360 on 2026-07-15 across 9 files adding the delivery-policy config, f6ddcea on 2026-07-20 across 35 files surfacing v2 task messages in transcripts, and b60e626 on 2026-07-21 across 33 files putting inter-agent messages into thread transcripts. Encrypted delivery is preserved throughout. Any claim that you must choose is falsified by a working branch.
Remember the 8 KiB bound, and check it against reality. In my 991 dispatches, 97.8% of delegated task text fits under 8 KiB, with a median of 2,650 bytes and a maximum of 9,655. The bounded audit copy is not a compromise. For almost every real delegation it is the whole thing.
Three independent teams, on three different products, converged on the same contract: private by default, plus an explicit, bounded, operator-controlled opt-in. That convergence is the argument, not the outrage. And it was already available when #26210 shipped.
What to ask for, and what to do this week
Ask for one thing: a delivery-policy value with a private default. Then instrument your own ledger today instead of waiting for it.
The acceptance contract is already written, and the issue’s own spec is unusually good, so credit it rather than restating it badly (openai/codex#28058). Six clauses:
- Keep the encrypted payload as the delivery mechanism. Do not ask for a revert.
- Add a size-bounded plaintext audit companion, rejected if empty.
- Persist it in rollout, history, and structured trace metadata, never in the child model’s input.
- Correlate tool calls to delivered items by ciphertext or ID, never by plaintext equality.
- Surface it in the parent activity row, not just the child path.
- Render an explicit placeholder in encrypted-only mode, never raw ciphertext in a field labelled readable.
There is a standards ask too, and the window for it is open right now. OTel’s GenAI agent spans define create_agent, invoke_agent, invoke_workflow, plan, and execute_tool, plus content attributes for messages and system instructions. There is no attribute for the task a parent agent hands a child. Searching the conventions repository for “delegation” returns a CrewAI reference scenario and a labels file, nothing normative. Propose gen_ai.delegation.task and gen_ai.delegation.parent.id, opt-in and size-bounded under the existing capture policy. Every attribute in that repo is still at development stability and no release has been cut, so the shape is still changeable.
Four things you can do this week.
- Write the ledger hook. About an hour. Append every dispatch to JSONL before you need it, not during an incident.
- Pin a delivery mode you can audit. On Codex today that means forcing v1 multi-agent tools through a custom model catalog, or running a fork. Both are workarounds with real costs, not recommendations.
- Set your redaction policy deliberately. In Claude Code, decide on those five flags rather than inheriting them, and write down why.
- Add one forensic drill to your postmortem template. Pick a past run and try to answer “what was the child told” from the record you already have. That drill belongs in the escalation path.
Measure it this way: track the share of your postmortems where the delegated task text was recoverable. If it is not 100%, you have a forensics gap, not a logging preference.
FAQ
Why would a vendor encrypt agent-to-agent messages at all?
Three defensible reasons. It removes plaintext from the parent’s context, cutting tokens and shrinking the injection surface. It reduces PII at rest. It plausibly protects orchestration IP. Codex PR #26210 states the first explicitly (openai/codex#26210); the third is thread speculation, unanswered by maintainers.
Is a readable audit trail its own security risk?
Yes, and that is the honest half of the argument. Adversarial log content reached 96% injection success on a summarization task without defenses, and 38% with constrained output (arXiv 2605.24421, 2026). The fix is a bounded audit copy the model never reads, not deleting the record entirely.
Does this only affect Codex, or my stack too?
The regression is Codex-specific; the contract question is universal. Claude Code redacts prompts, tool details, and tool content from telemetry by default, so most teams already export less trail than they assume (Claude Code monitoring docs). Check your own defaults before blaming a vendor.
Will full audit logging slow my agents down?
No. Pre-execution mediation with tamper-evident records adds 8.3 ms median overhead (arXiv 2604.05485, 2026). Across 991 real dispatches, 97.8% of task text fits in 8 KiB. Bound the field, exclude it from model input, and the remaining cost is storage.
Conclusion
Auditability and injection resistance really are in tension. Both sides of that tension are legitimate, and the measured injection numbers mean nobody gets to call the encryption paranoid. The only unacceptable resolution is the silent one.
What needs to change is small and specific. Content capture in agent tooling should be a documented policy value with a private default, not a constant. Removing a persisted field is a breaking change to the operator contract, and it belongs in a changelog with a stated reason. Seven weeks of silence on a regression report with 119 reactions is the actual failure here, not the encryption.
Run one drill this week. Pick a subagent failure from last month and try to answer “what was the child told” from the record you already have. If you cannot, you have your answer, and the time to add the field is before your next incident rather than during it.
Sources
- openai/codex, PR #26210: Encrypt multi-agent v2 message payloads, merged 2026-06-05, retrieved 2026-07-29.
- openai/codex, Issue #28058: Regression: encrypted MultiAgentV2 messages remove readable task audit trail, opened 2026-06-13, retrieved 2026-07-29.
- openai/codex, PR #30872: Log multi-agent communication lifecycle, merged 2026-07-02, retrieved 2026-07-29.
- openai/codex, PR #33030: Remove task messages from list_agents output, merged 2026-07-14, retrieved 2026-07-29.
- openai/codex, PR #33841: Make parent-owned sub-agent threads read-only in the TUI, merged 2026-07-17, retrieved 2026-07-29.
- openai/codex, PR #34383: Mark multi-agent v2 as stable, merged 2026-07-20, retrieved 2026-07-29.
- ignatremizov/codex, commit 2ffe360: feat(config): select multi-agent message delivery, 2026-07-15, retrieved 2026-07-29.
- Sasi Levi, Noma Labs, GitLost: How We Tricked GitHub’s AI Agent into Leaking Private Repos, 2026-07-06, retrieved 2026-07-29.
- Jessica Lyons, The Register, GitHub AI agent leaks private repos when asked nicely, 2026-07-07, retrieved 2026-07-29.
- Manifold Security, Azure DevOps MCP Server Vulnerability, 2026-07-21, retrieved 2026-07-29.
- Rohan Pandey and Archit Bhujang, Poisoning the Watchtower: Prompt Injection Attacks Against LLM-Augmented Security Operations Through Adversarial Log Content, arXiv 2605.24421, 2026-05-23, retrieved 2026-07-29.
- Yi Nian, Aojie Yuan, Haiyue Zhang, Jiate Li and Yue Zhao, Auditable Agents, arXiv 2604.05485, 2026-04-07, retrieved 2026-07-29.
- Abhinav Mishra and Kumar Sharad, Observability for Delegated Execution in Agentic AI Systems, arXiv 2606.09692, 2026-06-08, retrieved 2026-07-29.
- Anthropic, Claude Code monitoring and usage, retrieved 2026-07-29.
- OpenTelemetry, GenAI semantic conventions, retrieved 2026-07-29.
- OpenTelemetry, Inside the LLM Call: GenAI Observability with OpenTelemetry, retrieved 2026-07-29.
- EU AI Act, Article 12: Record-Keeping, retrieved 2026-07-29.
- EU AI Act, Article 26: Obligations of Deployers of High-Risk AI Systems, retrieved 2026-07-29.
Original data note: the 991-dispatch ledger is a census of subagent delegations recorded in my own Claude Code session transcripts between 2026-07-06 and 2026-07-29, across 65 sessions and 18 repositories. Field presence, task-text size distribution, and the label-to-task ratio were computed over that set. It is one developer on one harness over 24 days, and it measures the tool that keeps the field rather than the one that lost it. Treat it as a field-availability census, not a controlled study. No task text, repository names, or client identifiers are published.
Verification note: all four fact-check gates were re-run on 2026-07-29. Issue #28058 was still open with 27 comments and 119 total reactions, and none of the 27 comments carried an owner, member, or collaborator association on the repository. No upstream Codex PR implementing a plaintext audit companion had merged. Neither GitHub nor MSRC had published a fix for GitLost or the Azure DevOps MCP flaw. The OpenTelemetry GenAI conventions repository had cut no releases, every attribute remained at development stability, and no delegation attribute existed. One fact moved since the brief: PR #34383 marked multi-agent v2 stable on 2026-07-20, while the regression report stayed open.
If it was useful, pass it along.