Skip to content

§6.4's mandatory fail-open audit event has no conformant shape, so the decision-failure rate is unmeasurable #37

Description

@Premmethuku75

Found while checking §6.4 against infrastructure measurements from an agent evaluation I ran; the numbers are below.

The gap

§6.4 requires: "Every step that proceeds without a decision MUST be recorded as an audit event, so the bypass is visible rather than silent." I went looking for where that event can conformantly live, and there is no answer:

  • The SessionContext chain can't hold it. context-entry.json defines step_type as "the method name from the originating request envelope," and a fail-open proceed is not a method. Structurally worse: the chain is written by the Guardian (§8), and the defining feature of a decision failure is that the Guardian produced nothing for this step.
  • The Trace vocabulary can't hold it. The acs.decision span event enumerates exactly the five dispositions, and the OCSF severity mapping covers only those five. "Proceeded without a decision" is not representable.
  • sessionEnd has no field for it. Its summary object carries outcome, step_count, errors[]. No failure classes, no counts.

So the spec contains a MUST that no deployment can satisfy in a way another party could consume. It has also already solved this exact problem once, at the handshake: §4.1 says a session started unguarded "MUST be recorded in the deployment's own audit log — there is no Guardian to receive the event — and SHOULD be surfaced on Trace events when the deployment claims ACS-Trace." That pattern stops at the handshake and never reaches per-step decision failures, which is where the volume is.

This bears on #32. The proposal there to flip the default rests on "the audit event already records every fail-open proceed, so the choice remains observable." As of v0.1.1 that event has no defined shape, so the premise is asserted rather than specified. The default question and this issue are separable: either answer to #32 needs the event to exist first.

Why the rate matters: measured data

§6.4 prices the trade qualitatively ("an adversary who can disrupt the channel converts control into audit"), and the handshake schema prices timeout latency per millisecond. Nothing anywhere lets a deployment or an auditor learn what fraction of steps resolved by posture instead of decision. From what I measured, that fraction is not an edge case on realistic infrastructure.

Over ten weeks I evaluated an LLM agent on a self-hosted serving stack (~120B open-weight model, 4-bit quantized, behind a reverse proxy; data could not leave the network). A production Guardian under §12.2 has the same shape: an LLM evaluator receiving full hook payloads, and agentResponse, toolCallResult, and knowledgeRetrieval all carry content. What I measured:

  • Same 86 KB payload, 10 sends, 2 s apart: 4 of 10 returned 504 at the proxy's ~60 s upstream timeout; 6 of 10 returned 200 in 25–47 s. The stack was healthy at ~46 KB bodies; the failing calls were 83–86 KB. Payload size, not load, was the controlling variable.
  • Effective per-step failure rate: 16% with a single retry, 2.56% after retry-with-backoff (3 attempts, 10/20/40 s).
  • In one evaluation run, 3 "final answers" were verbatim proxy error pages (504 Gateway Time-out ... nginx), recorded as if the model had answered. Until traced, the substrate's failure was indistinguishable from a decision.

Small sample, single environment, stated as such. Even the mitigated rate is ~1 in 40 governed actions resolving by posture — under the default, proceeding ungoverned — with no conformant way to see the aggregate. Unmitigated it is 1 in 6. And because the rate is payload-size-dependent, it is attacker-improvable without compromising anything: inflate the payload → cross the proxy's tolerance → timeout → posture proceeds. Same bypass shape as the -32600 oversize path #32 documents, reached through the timeout half of §6.4 instead of the refusal half.

Proposed direction (wire-minimal, per SPEC_REVIEW_PRINCIPLES.md, Principle 3)

  1. Define the decision-failure audit event. A named Trace event class alongside acs.decision — e.g. acs.decision.failure — carrying: failure class (timeout / transport / error_without_decision), the §17.1 error code when one exists (adopting @astrogilda's point in §6.4 conflates Guardian refusal with decision failure; refusals are attacker-reachable and should fail closed #32 that the class must travel as a code, not prose), negotiated timeout, elapsed ms, payload-size bucket, and the posture applied. Emitted by the Observed Agent, since the Guardian by definition cannot.
  2. Extend §4.1's language to per-step failures: recorded in the deployment's own audit log; SHOULD be surfaced on Trace when ACS-Trace is claimed.
  3. Add a session-cumulative count to sessionEnd.summary (decision_failures by class), so the per-session rate is computable from artifacts the spec already defines.

Payload-size bucketing is the one field I'd argue for from data rather than symmetry: it is the dimension along which the failure rate actually moved, and the one an anomaly detector needs to tell organic failure from induced failure.

Out of scope here: whether proceed is the right default. That is #32's live question; this issue is the observability floor under either answer.

References

Prem Methuku

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions