In this article
August 13, 2026
August 13, 2026

One audit trail for every coding agent, and what it proves

We built audit logging for Claude Code, Codex, OpenClaw and pi with no API key on any laptop. Use it to get total visibility into your entire org's agentic activity.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

pi, the open-source coding agent I'll use below, can read, write and delete files on the machine out of the box, and none of that is recorded anywhere you could query. Multiply that across every agent your engineers run and "who deleted that file on Friday night" has no better answer than a JSON transcript on somebody's laptop. We built workos-audit-harness to fix the plumbing half of that problem, and to be precise about the half it can't fix.

Nothing is watching

One command changes that:

  
pi install https://github.com/workos/workos-audit-harness
pi list
  

The install is one command; and allows wiring it to a fleet without handing out API keys. The extension hooks pi's session, prompt and tool lifecycle and emits pi.session.started, pi.prompt.submitted, pi.tool.called and pi.turn.completed. It runs on the same shared core as the Claude Code, Codex and OpenClaw plugins.

Then we made it do something ordinary and something regrettable. Create goldeneye.md with a summary of the 007 movie; delete the GoldenEye file. Two requests, roughly six audit events, and the second request is the kind somebody asks you about six weeks later.

Ask who deleted the file

The console is AuthKit-gated, and the model answers by querying the Audit Logs Export API itself and citing the events it found. Ask what has Jônatas been doing and you get the file written, then the file deleted, seconds earlier, with timestamps. Ask who else has been active today and the same answer widens to colleagues, with Claude Code sessions sitting alongside pi.

The answer names a person rather than a device id because the proxy resolved that mapping before the event was ever stored. And the console treats every event body as untrusted data, for reasons we'll explain later in the article.

Where the events land

Everything lands in one WorkOS organization. Events are org-scoped, schema-validated so malformed events are rejected rather than quietly kept, exportable through the Export API and SIEM streaming, and queryable, including by the agent itself over MCP. The Claude Code plugin exposes a workos_audit_query MCP tool for exactly that.

The schemas are deliberately generic: action names take the form <prefix>.session.started, <prefix>.session.ended, <prefix>.session.shutdown, with a shared metadata surface, so a new integration doesn't need a schema fork. The metadata records hashes and previews rather than full content: prompt_sha256, prompt_preview, tool_input_sha256, command_preview, result_sha256, plus per-turn and per-session token counts like turn_total_tokens and session_model_calls. That's enough to correlate and enough to prove a match, without turning your audit log into a copy of every prompt your engineers typed.

No API key on any laptop

The hard part is getting events into that log from a machine you don't fully control. WorkOS API keys are full access (there is no audit-append-only scope), so an sk_ key on a laptop makes a stolen laptop a full environment compromise.

So the laptop doesn't get one. It authenticates with a device certificate it already has, over mTLS, verified by Cloudflare at the edge against your CA, which means a certless request never reaches the Worker.

Architecture diagram: the coding-agent plugin on a laptop posts an audit event over mTLS to the Cloudflare edge, which verifies the device certificate against your CA so a certless request never gets through; the audit proxy Worker behind it resolves the device serial to a person, stamps actor, organization and IP, and forwards the event to WorkOS Audit Logs using the sk_ key it alone holds.

The Worker resolves the certificate's device serial to a real person through the MDM, caches that in D1, stamps the actor, organization and connecting IP, and drops anything the client claimed about its own identity. The sk_ key exists only as a Worker secret. Ingest is POST /api/events, which returns 202 on success. When Cloudflare Access does the verification, it forwards the cert's CN in a signed Cf-Access-Jwt-Assertion header, and the Worker verifies that JWT's RS256 signature against your team's public keys and checks iss/aud/exp before reading it. That's honored only when ACCESS_TEAM_DOMAIN and ACCESS_AUD are configured, so a forged header on some other hostname buys nothing.

On Okta-managed Macs the default CN pattern matches the Okta device-attestation cert (OktaManagementAttestation for <SERIAL>), which is already there with zero provisioning. The net effect: a compromised laptop can do exactly one thing, append rate-limited audit events as itself.

What a fleet rollout actually costs you

While building this, we needed to make four important decisions:

  • Unknown devices. They get a 403 by default, so their events are never attributed to anyone. If loaner machines still need to land in the log, an opt-in placeholder policy attributes them to a synthetic unassigned-device actor with the serial preserved in metadata.
  • MDM outages. Device-to-user lookups cache in D1 for 24 hours by default, and if the MDM is unreachable the proxy serves the stale entry rather than dropping the event.
  • The kill switch fails open. If D1 is unreachable the proxy can't see paused: true and keeps ingesting, because a control-plane outage must never silently drop the fleet's audit events.
  • Your MDM is probably not the one we support. The proxy ships with exactly one MDM integration, for Iru (the company formerly called Kandji), and it is written against that vendor's specific API. Its environment variables still start with KANDJI_, so existing deployments don't have to rotate a secret to upgrade. If you run Jamf or Intune, pointing that integration at them will not work. You have two options. Keep the proxy's device_user table filled in yourself, from whatever system already tracks your devices, which needs no code. Or write a small adapter in src/device.ts.

Rollout itself is one MDM-pushed JSON file per device: /Library/Application Support/workos-audit/config.json on macOS, /etc/workos-audit/config.json on Linux, %ProgramData%\workos-audit\config.json on Windows:

  
{ "proxyUrl": "https://audit-proxy.yourcompany.com/api/events" }
  

Those four decisions are about correctness, not throughput. Profile Worker invocations, D1 reads and the added per-event latency against your own fleet size before you push this company-wide.

One client-side limitation to know before you plan a rollout: the mTLS emission path is macOS-only today, because it depends on the macOS keychain and Apple's Secure Transport curl backend. The proxy is platform-agnostic; elsewhere the plugins fall back to API-key or CLI transport. And everywhere, missing config degrades to log-and-continue: a misconfigured machine never blocks the coding agent.

What the log proves, and what it doesn't

The log says I deleted goldeneye.md. What the proxy actually proves is narrower than that: the event came from my laptop, as me, from that IP address, at that time. It does not prove the file ever existed.

The reason is that the event body is assembled on a machine its user administers. That user can send a false event, or stop events being sent at all. Neither move requires root access. And neither is a bug we can patch, because it follows from where the events are produced.

A false event is the smaller problem. It still arrives under the sender's own name, and the proxy limits how many they can send, so the worst they can do is add noise about themselves. They cannot pin anything on a colleague.

Turning events off is the harder problem, because the obvious fixes don't work. Signing the program that sends events doesn't help: a signed program still sends whatever it is handed, and the user decides which programs the agent runs, so a made-up event looks exactly like a real one. You can force the plugin onto every laptop through managed settings, but you can't force it to keep reporting.

So you watch for it instead, and today you watch the bill. Anthropic's Admin usage API reports how many tokens each person was billed for. Someone who ran up a token bill and sent no audit events has events missing.

That check has one clear limit. It finds sessions that logged nothing. It cannot tell you whether a session that did log was logged completely, because adding fake events to a real session doesn't change the token count.

The better version needs the vendor's help: compare two records of the same work, what the vendor's servers saw against what the laptop reported.

If you make the agent, send the events from your own servers

Everything above runs on the laptop. That's the only option when you don't control the coding agent itself, and it's also the one place where every agent your engineers use ends up in a single log that belongs to you.

If you're the company that makes the coding agent, you can do better. Your servers already handle every model call, run the tools, and know which session and which turn each action belongs to. Send the audit events from there, into each customer's own WorkOS organization. A developer at the customer can't reach into your servers and change what they recorded, which is why this is the only place the contents of an event can be trusted at all. Use the same event names the laptop plugins use, and you can check the two sets of records against each other.

Help us improve the agentic audit logs plugin

The repo has four working integrations, for Claude Code, Codex, OpenClaw and pi. Each is a thin wrapper around the same shared code, and the repo also holds the proxy and the chat console, all under an MIT license. Install it and you can start asking questions about what your agents have been doing.

The thing that would actually close the gap isn't ours to build. We want model vendors to sign a receipt for every turn they serve, so the proxy can check it. Then the contents of an event would carry proof from a machine the user doesn't control. No vendor offers that yet. Until one does, comparing audit events against billed tokens is the best signal you have.

We'd love it for you to try out our agentic audit logs harness and give us feedback.