Every API call that changes state in our system carries a header naming the run that produced it. The agent writes it. Not a proxy, not a sidecar, not the infrastructure underneath. The agent itself, reading its own environment, copying a string into a header on every mutating request. This looks like the wrong place to put a security control. We think it is the right one.
The reason for writing this down is that the design is easy to misread. Most audit-trail patterns we have seen treat the caller as untrusted and put the correlation at the layer the caller cannot influence. We do the opposite, and the choice is deliberate.
What the header actually does
The header is a UUID. Each heartbeat run is assigned one when the runtime starts. The agent inherits it as an environment variable, and the instructions for every mutating endpoint say: include this string in the run-id header. The server stores it alongside the request. When something later looks wrong, the audit query is one join. Find every call with this run id, in order, with the body it carried and the response it got.
That is the entire control. There is no signature. No nonce. No clock. The string is opaque to the server, which only writes it into the log and indexes it. The value of the field is correlation, not authentication. The server does not check that the run is real. The server records the claim and lets the runtime side of the system corroborate it later.
Why not put it at the boundary
The first instinct when looking at this is that the header belongs at a proxy. Have the runtime intercept every outbound API call, inject the run id, and the agent cannot forget or fabricate it. The audit trail becomes an invariant of the platform instead of a discipline of the caller.
We considered it and rejected it for three reasons.
The first is that we do not have a proxy. The agent talks to the API directly, using a bearer token the runtime hands it. Adding interception would mean introducing a process the agent has to route through instead of the API, and a place where requests could pile up, drop, or stall. The benefit would be a header we can already make the agent send. The cost would be a new dependency in the hot path of every action.
The second is that interception would not actually move the trust boundary. The runtime sets the env var. In the proxy design, the runtime would also set the injection rule. Both versions reduce to the same trust: the runtime is honest, and whatever it writes is canonical. Moving the injection from the agent’s request line to a proxy that the runtime configures does not make the claim stronger. It just hides the claim in a different file.
The third is that the header is not load-bearing for any decision the server makes. It does not authorize. It does not authenticate. It does not change the response. It is a tag the server records. Tags that do not affect anything downstream do not need to be enforced upstream. Enforcing them would be ceremony.
What a fabricated or omitted header actually buys
A hostile agent could write any string into the header. Nothing stops it from sending a random value, a stolen run id from another run, or no header at all. The interesting question is what each of those gets the attacker, because the answer shapes the rest of the design.
A random value isolates the calls from any actual run. They show up in the log under a run id we never issued. The forensic trail becomes “these calls happened but did not correlate to a heartbeat we have a record of,” which is a useful signal in its own right. We would not be left wondering whose calls those were. We would know they were nobody’s.
A stolen run id, if the attacker has one, lets the calls masquerade as part of a real run. That sounds worse, but the real run has its own record of what it actually did, captured at the runtime level on a different machine. The masquerade is detectable as soon as we cross-reference the API log against the runtime log. The cost of detection is a join.
An omitted header means we cannot place the call in time relative to any run. We catch this case the other way: every mutating endpoint requires the header. The request fails before the mutation happens. Forgetting is loud, not silent.
None of these failure modes silently corrupt the audit trail. They produce visible discrepancies, and the discrepancies have a shape we can write a query for. The header is structured so that the dishonest case is the loud case.
What this gets us in practice
The honest summary is that this design does very little, and that is the point. It costs nothing at the wire layer. It produces a column we can query when something has gone wrong. It does not pretend to be a security mechanism. It is a forensic primitive that happens to ride in the same envelope as the real security primitives, and we keep the two functions separate on purpose.
The accidental benefit is cultural. Asking the agent to write the header means the run id is visible to the agent itself. Every mutating call has it in scope. When something goes sideways during a run, the agent can leave a comment naming its own run id, and we have a direct link to every API action that came out of it. That kind of legibility does not happen by accident. It is harder to get when the correlation primitive only exists at a layer the agent does not interact with.
The bigger lesson is one we keep relearning. The audit trail does not have to be strong to be useful. It has to be present, consistent, and queryable. The cheapest version of those three properties usually wins, because the version that gets stitched together later, with infrastructure and ceremony, tends to fall to disuse the first time it gets in the way. We would rather have a string the agent writes by hand and the server stores without thinking, than a beautifully signed envelope that nobody runs the query for.