Abstract & Central Claim
Input guardrails are designed to intercept policy-violating inputs before an AI agent executes state-modifying actions. This paper evaluates whether the OpenAI Agents SDK (v0.22.3) provides a structural happens-before guarantee (T1 ≺ T3) ensuring that input guardrail evaluation (T1) strictly precedes tool side-effect commitment (T3).
In OpenAI Agents SDK v0.22.3, under
run_in_parallel=True and within the tested experimental conditions, no structural barrier is observed that guarantees the input guardrail decision precedes tool side-effect commitment. In parallel DENY trials lacking an explicit authorization barrier, 100% of cases produced local state persistence before the runner communicated the tripwire exception (T4); across both execution engines (Runner.run() and Runner.run_streamed()), 99 out of 100 trials per suite additionally committed state before the guardrail verdict was logged (T1).
Conversely, configuring sequential mode (run_in_parallel=False) or introducing an explicit pre-commit authorization barrier yields 0 out of 100 side-effect commits on DENY. We present architectural call-chain analysis, empirical trace breakdowns, and recommended mitigations for agentic control plane developers.
1. Introduction & Problem Statement
Agentic AI frameworks delegate tasks to LLMs via function calling. To maintain safety, control planes implement input guardrails intended to sanitize inputs, detect prompt injections, or enforce policy boundaries before state-modifying actions are committed.
In concurrent systems, safety enforcement requires a formal causal happens-before relation (≺) between guardrail evaluation (T1) and side-effect commitment (T3):
Guardrail Start (T0) ≺ Guardrail Verdict (T1) ≺ Tool Start (T2) ≺ Side Effect Committed (T3)
If chronological telemetry reveals T3 < T1 (side effect committed before guardrail verdict logged), the structural precedence requirement (T1 ≺ T3) is falsified for that execution path. Even if the framework subsequently raises a tripwire exception (T4), the state modification (T3) has already occurred in storage.
Expected Safe Execution (Sequential / Barrier):
[T0: Guardrail Start] ---> [T1: Guardrail Verdict] ---> [T2: Tool Start] ---> [T3: State Commit]
Observed Parallel Execution Race Window:
[T0: Guardrail Start] ---+---> (Async Guardrail Task) ---------------------> [T1: Verdict] ---> [T4: Tripwire]
|
+---> [T2: Tool Start] ---> [T3: State Commit] <-- CHRONOLOGICAL RACE (T3 < T1)
2. Architectural Call-Chain Analysis (SDK v0.22.3)
Inspection of the OpenAI Agents SDK v0.22.3 source code reveals the structural root cause:
- Runner Entry Point: Both
Runner.run()andRunner.run_streamed()instantiateagents.run.AgentRunnerand delegate turn execution to_run_single_turn(). - Asynchronous Task Dispatch: When
run_in_parallel=True,_run_single_turn()dispatches input guardrails as backgroundasyncio.Taskinstances or viaasyncio.gather(). - Un-Awaited Tool Execution: Concurrently, assistant turn item processing forwards model function call outputs directly to
tool_execution.py:execute_tool_call().execute_tool_call()invokes the tool function without awaiting the completion of the input guardrail tasks. - Post-Commit Exception Raising: The runner gathers input guardrail results at turn completion. If
tripwire_triggered == True, the runner raisesInputGuardrailTripwireTriggered(T4), but state modification (T3) has already been written to the persistent sink.
3. Experimental Methodology & Harness Architecture
To isolate SDK task loop scheduling mechanics from network I/O jitter, the experiment uses:
- Offline Scripted Model: An offline
ScriptedModelemitting deterministicResponseFunctionToolCallandResponseOutputMessageturn items. - Persistent Local Sink: A local SQLite database (
effects.dbfor non-streaming,stream_effects.dbfor streaming) recordingcommitted_effectsandtelemetry_events. - High-Resolution Monotonic Telemetry: Monotonic nanosecond timers (
time.perf_counter_ns()) logging lifecycle events (T0 to T4). - Latency Control: Synthetic latency parameterization: 50 ms for slow DENY guardrails, 5 ms for fast tool execution.
4. Empirical Findings Across 1,200 Trials
We executed 1,200 trials (N=100 per condition across 6 non-streaming and 6 streaming conditions):
| Condition | Runner Engine | Latency Spec | Barrier Mode | Chronological Race (T3 < T1) | SQLite Commits | Tripwire (T4) | Empirical Outcome |
|---|---|---|---|---|---|---|---|
| R2-A | Runner.run() |
G=50ms, T=5ms | None | 99.0% (99/100) | 100/100 | 100/100 | Chronological Race (T3 < T1) |
| R2-B | Runner.run() |
G=50ms, T=5ms | None (Sequential) | 0.0% (0/100) | 0/100 | 100/100 | Precedence Maintained (0 commits) |
| R2-C | Runner.run() |
G=5ms, T=50ms | None | 0.0% (0/100) | 100/100 | 0/100 | Coincidence (T1 < T3) |
| R2-D1 | Runner.run() |
G=50ms, T=5ms | Barrier (ALLOW) | 0.0% (0/100) | 100/100 | 0/100 | Authorized Commit Permitted |
| R2-D2 | Runner.run() |
G=50ms, T=5ms | Barrier (DENY) | 0.0% (0/100) | 0/100 | 100/100 | Barrier Enforced (0 commits) |
| R2-E | Runner.run() |
G=50ms, Error | None | 0.0% (0/100) | 0/100 | 100/100 | Error Propagated Cleanly |
| S-A | Runner.run_streamed() |
G=50ms, T=5ms | None | 99.0% (99/100) | 100/100 | 100/100 | Chronological Race (Identical to R2-A) |
| S-B | Runner.run_streamed() |
G=50ms, T=5ms | None (Sequential) | 0.0% (0/100) | 0/100 | 100/100 | Precedence Maintained (Identical to R2-B) |
| S-C | Runner.run_streamed() |
G=5ms, T=50ms | None | 0.0% (0/100) | 100/100 | 0/100 | Coincidence (Identical to R2-C) |
| S-D1 | Runner.run_streamed() |
G=50ms, T=5ms | Barrier (ALLOW) | 0.0% (0/100) | 100/100 | 0/100 | Authorized Commit (Identical to R2-D1) |
| S-D2 | Runner.run_streamed() |
G=50ms, T=5ms | Barrier (DENY) | 0.0% (0/100) | 0/100 | 100/100 | Barrier Enforced (Identical to R2-D2) |
| S-E | Runner.run_streamed() |
G=50ms, Error | None | 0.0% (0/100) | 0/100 | 100/100 | Error Propagated (Identical to R2-E) |
5. Four-Way Categorization of Execution Outcomes
To maintain strict epistemological clarity, execution outcomes are partitioned into four distinct categories:
- Category 1 — Chronological Precedence Race (T3 < T1): Side effect committed (T3) before guardrail decision logged (T1). Observed in 99.0% of parallel DENY trials (99/100 in R2-A and 99/100 in S-A).
- Category 2 — Post-Verdict Commit Before Tripwire Exception (T1 < T3 < T4): Observed in 1 trial per parallel DENY suite (R2-A Trial 31, Δ = +13.35 ms; S-A Trial 1, &Delta = +38.02 ms). Python
asyncioevent-loop task scheduling jitter is a plausible contributing factor for delaying T1 logging; however, tool dispatch T2 was still executed and committed state before T4. The SDK runner did not block tool dispatch upon T1 completion. - Category 3 — Side-Effect Persistence Prior to Tripwire Exception: In 100% of parallel DENY trials lacking an authorization barrier (100/100 in R2-A and 100/100 in S-A), local database state was committed (T3) prior to the runner raising the tripwire exception (T4).
- Category 4 — Complete Side-Effect Suppression Under Authorization Barrier: Introducing an explicit application-level authorization gate (R2-D2 and S-D2) yielded 0/100 commits on DENY across 200 trials.
6. Transparency, Reproducibility & Editorial Precautions
6.1 Verification & Reproducibility Notice
All empirical statistics reported in this paper were verified locally by LatinFlash Research using frozen execution traces (r2_replication_results.json, stream_replication_results.json) and local SQLite database instances (effects.db, stream_effects.db). Researchers can independently re-run the replication harness using:
python experiments/a02-07-guardrail-happens-before/r2/r2_repro_harness.py 100 --out-dir experiments/a02-07-guardrail-happens-before/r2-repro python experiments/a02-07-guardrail-happens-before/r2-stream/stream_repro_harness.py 100 --out-dir experiments/a02-07-guardrail-happens-before/r2-stream-repro
6.2 Status of JEV Automated Review
Evaluations generated by JEV (jev-1.13.0) represent automated LLM-based rubric assessments of report text and source excerpts. JEV scoring does not constitute external academic peer-review or independent physical validation of SQLite disk transactions. JEV results are preserved strictly as supplementary audit metadata.
6.3 Telemetry & Timezone Disclosure
Telemetry intervals (Δ = T3 − T1) are calculated using Python's monotonic high-resolution clock (time.perf_counter_ns()), unaffected by system clock adjustments. Wall-clock run metadata timestamps recorded in JSON headers and SQLite metadata tables are stored in standard UTC ISO-8601 format (e.g. 2026-09-24T10:56:00Z). Local workspace log timestamps correspond to CEST (UTC+2, e.g. 2026-09-24T12:56:00+02:00).
7. Methodological Scope & Boundaries
- Latency Parameterization: The 99.0% race rate is parameter-dependent (50 ms guardrail vs 5 ms tool execution). It is not asserted as a universal empirical probability for all production workloads.
- SQLite Local Sink: Local SQLite database commits demonstrate persistent state writes in an offline harness; they are not asserted as universally irreversible operations or equivalent to live external financial API transactions.
- SDK Version Scope: Conclusions apply specifically to OpenAI Agents SDK
v0.22.3.
8. Recommended Mitigations
- Proposed Framework-Level Design: We propose adding a configuration flag to
RunConfig(e.g.await_guardrails_before_tool_dispatch: bool = False). When enabled, the runner would insert an explicitawaitbarrier on all active input guardrail tasks before invokingexecute_tool_call(). (Note: This is a proposed design enhancement; it is not an existing setting in SDK v0.22.3). - Application-Level Design Pattern: Implement explicit pre-commit authorization gates (Pattern D2) within application code, ensuring tools check input guardrail status before committing persistent side effects.
9. Provenance & Hashes
Verified Dependencies & Environment: -------------------------------------------------------------------------------- Python Version: 3.11.15 SDK Package: openai-agents v0.22.3 OpenAI Client: openai v1.65.5 Pydantic: pydantic v2.10.6 Database Driver: sqlite3 v3.45.3 Operating System: Windows-10-10.0.19045-SP0 Corpus SHA-256 Hashes: -------------------------------------------------------------------------------- r1-snapshot/harness.py: ff5de1f3e98f06003717eaab49c0f3c6652c2d39e490960ff5d4803703885216 r2/r2_harness.py: 25212b883bc9d79e893544c7f6fea0f4851780969de91dba330dfc701bb1d527 r2/effects.db: 7a70f1279b827bb1749255846982a4b7fc22077a50142be0efe1055281cab95d r2/raw_traces/r2_replication_results.json: a2074faabb0d4296702af301225cc16bed1b5768aab8401641c642e4937dbb04 r2-stream/stream_harness.py: e94e87ca2b21db97e007b7bc3f3a05e64d551d3c6579c81eee271b4d27097d45 r2-stream/stream_effects.db: 1834285fdd2153e66275a90dfe3394204dd50913f4889b4aa2c2546e6ad4c197 r2-stream/raw_traces/stream_replication_results.json: 16f0280c95e04ba30c9bed942a37de80da15652893ada9d0e30d4b83ad446d65 -------------------------------------------------------------------------------- Publication Manifest Verification: 57 / 57 Binary SHA-256 PASS (0 Mismatches)