File size: 44,175 Bytes
e9ce6e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
# Autonomous Enterprise Payment Orchestrator (AEPO) β€” Master Technical Document

> **Classification:** Internal Engineering Reference Β· **Version:** 10.0.0
> **Author:** Umesh Maurya Β· **Affiliation:** Meta PyTorch OpenEnv Hackathon Γ— Scaler School of Technology β€” Grand Finale
> **Stack:** Python 3.10 Β· Gymnasium 0.29.1 Β· Pydantic v2 Β· FastAPI Β· PyTorch Β· Docker Β· Hugging Face Spaces
> **Status:** Production-Deployed Β· Validated against `openenv validate` strict-mode Β· 221 tests Β· 97% coverage

---

## Table of Contents

1. [Executive Summary & Value Proposition](#1-executive-summary--value-proposition)
2. [Architecture & Implementation Deep-Dive](#2-architecture--implementation-deep-dive)
3. [Causal State Transitions](#3-causal-state-transitions)
4. [Training β€” Q-Table Agent & LagPredictor](#4-training--q-table-agent--lagpredictor)
5. [Operational Manual](#5-operational-manual)
6. [Verification & Validation Suite](#6-verification--validation-suite)
7. [Hackathon Tasks & Agent Decision Traces](#7-hackathon-tasks--agent-decision-traces)
8. [Incident Post-Mortem & Future Scope](#8-incident-post-mortem--future-scope)

---

## 1. Executive Summary & Value Proposition

### 1.1 The Problem: Siloed Metrics in Fintech Operations

In every Tier-1 payment processor β€” from UPI gateways handling 14 billion monthly transactions to global card networks β€” a dangerous organizational fault line exists between **Security/Fraud Operations** and **Infrastructure/SRE teams**. This divide is encoded into the very monitoring stacks, alerting pipelines, and decision frameworks each team uses.

**The Security Team's Blind Spot:** Fraud analysts operate in a world of risk scores, transaction velocity models, and behavioral biometrics. When a botnet launches a credential-stuffing attack, the fraud team's response is singular: escalate verification, reject suspicious transactions. What they do not see is the infrastructure cost of that response. Every `CHALLENGE` action forces a cryptographic re-verification that adds lag. Every `REJECT` still consumes a Kafka partition slot. A fraud team aggressively rejecting traffic during a botnet storm can inadvertently push Kafka consumer lag past the 4,000-message crash threshold β€” taking down the entire payment rail, including legitimate transactions they were trying to protect.

**The Infrastructure Team's Blind Spot:** SRE teams live in P99 latencies, consumer group lag, and circuit breaker states. When Kafka lag spikes, the SRE playbook is clear: throttle, activate circuit breakers, shed load. But this playbook is fraud-agnostic. Throttling traffic during a botnet attack β€” where 90% of the throttled transactions are malicious β€” is a defensible infrastructure decision, but the SRE team cannot distinguish this scenario from a legitimate flash sale. They make the same infrastructure decision regardless of the security context.

**The Asymmetric Risk Triad:**

| Risk Dimension | Metric Proxy | Team Owner | Failure Mode |
|:---|:---|:---|:---|
| **Financial Fraud** | transaction risk score [0–100] | Security/Fraud Ops | Approved fraudulent transactions β†’ direct monetary loss |
| **Infrastructure Health** | kafka_lag [0–10000], api_latency [0–5000ms] | SRE/Platform | Consumer lag > 4,000 β†’ cascading system crash |
| **SLA Compliance** | rolling_p99 [0–5000ms] | SRE/Product | P99 > 800ms β†’ SLA breach β†’ regulatory penalties |

### 1.2 Theme Alignment Matrix

AEPO is engineered to satisfy the official hackathon themes by direct, code-anchored implementation:

| Hackathon Theme | Feature Implementation in AEPO | Technical Anchor (Code / Logic) |
|---|---|---|
| **Theme #3.1: World Modeling** | LagPredictor MLP (1-step lookahead + Dyna-Q planning) | `dynamics_model.py` (LagPredictor) + `inference.py` veto + `train.py` DynaPlanner |
| **Theme #4: Self-Improvement** | Antagonistic adversary policy (adaptive entropy & threat scaling) | `unified_gateway.py` β€” Attack Phase + 5-episode-lag escalation logic |
| **Causal Reasoning** | 11 physics-based causal state transitions | `step()` deterministic dynamics + accumulators |
| **Realistic Env Design** | Asymmetric Risk Triad (Fraud vs. Infra vs. SLA) | UPI Payment Gateway scope + 10-signal observation schema |
| **Deployment Efficiency** | Optimized edge footprint (2 vCPU / 8 GB RAM) | `Dockerfile` (`python:3.10-slim`) + CPU-only Torch wheel |

**AEPO satisfies the core requirement of Theme #3.1 by** wiring a learned `LagPredictor` world model into both training (Dyna-Q imagined rollouts) and inference (1-step lookahead veto on the crash cliff). **To align with Theme #4, we implemented an adaptive adversarial curriculum that** escalates `adversary_threat_level` whenever the agent's 5-episode rolling reward exceeds 0.6, producing the staircase improvement curve. **This architecture ensures 100% compliance with the hardware constraints specified in the Master Project Requirements** β€” the full training pipeline runs in ~5 seconds on 2 vCPU / 8 GB RAM.

### 1.3 The Solution: AEPO β€” A Causally-Structured RL Decision Surface

The **Autonomous Enterprise Payment Orchestrator (AEPO)** resolves the Siloed Metrics problem by encoding the entire Asymmetric Risk Triad into a single **Gymnasium-compatible Reinforcement Learning environment**. Rather than building another dashboard that correlates metrics post-hoc, AEPO creates a training ground where AI agents learn β€” through thousands of simulated transactions β€” to make decisions that simultaneously optimize across all three risk dimensions.

AEPO evolved from the **Unified Fintech Risk Gateway (UFRG)**, which won Round 1 of this hackathon with a 5-field observation space and 3-field action space. AEPO is a full architectural upgrade:

| Dimension | UFRG (Round 1) | AEPO (Grand Finale) |
|:---|:---|:---|
| Observation fields | 5 | **10** |
| Action fields | 3 (MultiDiscrete [3,3,2]) | **6** (MultiDiscrete [3,2,3,2,2,3]) |
| Causal transitions | None (memoryless) | **11 causal state transitions** |
| Phase structure | None | **4-phase task machine** per episode |
| Dynamics model | None | **LagPredictor MLP** (PyTorch) |
| Training | None | **Q-Table agent**, 500 episodes, hard task PASS |
| Test suite | ~30 tests | **221 tests**, 97% coverage |

**Why Reinforcement Learning?** The Asymmetric Risk Triad is a **sequential decision-making problem under uncertainty** with delayed, compounding consequences. An agent's decision to skip cryptographic verification at step 12 does not merely affect step 12 β€” it reduces lag pressure that prevents a crash at step 47. RL is the natural formalism for problems where:

- Actions have **delayed, non-linear consequences** (EMA accumulators mean today's routing decision affects next step's P99)
- The **state space is continuous** (10-dimensional observation vector with float32 precision)
- The **action space is combinatorial** (216 unique action combinations)
- **Reward signals are sparse and asymmetric** (catastrophic fraud penalty vs. gradual SLA degradation)

---

## 2. Architecture & Implementation Deep-Dive

### 2.1 Technology Stack

| Layer | Technology | Version | Role in AEPO |
|:---|:---|:---|:---|
| **Runtime** | Python | 3.10+ | Core language; modern type hints |
| **RL Framework** | Gymnasium | 0.29.1 | `gym.Env` base class, space definitions, env_checker |
| **Type Safety** | Pydantic | v2.0+ | Runtime validation of `AEPOObservation` and `AEPOAction` |
| **Numerical** | NumPy | 1.26.4 | Array backing for observation space |
| **Dynamics Model** | PyTorch | 2.2.0 | `LagPredictor` 2-layer MLP trained alongside Q-table |
| **API Server** | FastAPI | Latest | Async HTTP endpoints for remote environment interaction |
| **ASGI Server** | Uvicorn | Latest | Production-grade ASGI; serves FastAPI on port 7860 |
| **LLM Client** | OpenAI SDK | 1.0+ | OpenAI-compatible client for Ollama / HF Inference API |
| **Containerization** | Docker | `python:3.10-slim` | Deterministic deployment for Hugging Face Spaces |
| **SDK** | openenv-core | 0.2.0+ | `openenv validate` CLI and manifest schema |
| **Deployment** | Hugging Face Spaces | β€” | Persistent Docker container, always-on at port 7860 |

**Key Architecture Decisions:**

- **Pydantic v2** for `AEPOObservation` and `AEPOAction` provides runtime validation that catches invalid actions before they enter the step function β€” critical when the action source is an LLM that may hallucinate out-of-range integers.
- **Gymnasium 0.29.1** with **4-tuple return** `(obs, reward, done, info)` per OpenEnv specification. This deviates from Gymnasium's native 5-tuple `(obs, reward, terminated, truncated, info)`. Decision is locked β€” switching to 5-tuple would break graders, server, and inference simultaneously.
- **PyTorch LagPredictor** trains alongside the Q-table agent, consuming transitions as they are collected. This justifies the Theme 3.1 World Modeling claim with a technically defensible causally-structured model.
- **Dual-mode architecture:** `unified_gateway.py` works standalone (`train.py`, `graders.py`) and via server (`server/app.py`) with zero code changes.

### 2.2 Core Environment: `UnifiedFintechEnv`

The environment is implemented as a single Python module (`unified_gateway.py`) containing approximately 800 lines of production code:

```
gym.Env
  └── UnifiedFintechEnv
        β”œβ”€β”€ reset(seed, options) β†’ (AEPOObservation, dict)
        β”œβ”€β”€ step(action: AEPOAction) β†’ (AEPOObservation, float, bool, dict)
        β”œβ”€β”€ state() β†’ AEPOObservation
        β”œβ”€β”€ _generate_transaction() β†’ AEPOObservation
        β”œβ”€β”€ _compute_reward(action) β†’ (float, dict)
        β”œβ”€β”€ _close_episode() β†’ None   # adversary escalation, curriculum
        └── _curriculum_level: int    # 0=easy, 1=medium, 2=hard
```

**Internal State Variables:**

| Variable | Type | Initial Value | Purpose |
|:---|:---|:---|:---|
| `current_step` | `int` | `0` | Episode progress counter; `done=True` at step 100 |
| `current_task` | `str` | `"easy"` | Active task; drives phase machine |
| `_phase_idx` | `int` | `0` | Index into current task's phase sequence |
| `_rolling_p99` | `float` | `50.0` | EMA accumulator for P99 latency |
| `_rolling_lag` | `float` | `0.0` | Accumulated Kafka lag |
| `_throttle_relief_queue` | `deque` | `deque()` | Scheduled -150 lag reductions from Throttle actions |
| `_consecutive_deferred_async` | `int` | `0` | Tracks settlement backlog counter |
| `_episode_step_rewards` | `list[float]` | `[]` | Per-step rewards for adversary escalation gate |
| `_curriculum_level` | `int` | `0` | Persists across episode resets; set to 0 only in `__init__` |
| `_adversary_threat_raw` | `float` | `0.0` | Raw adversarial threat level before normalization |

### 2.3 Observation Space

**Gymnasium Definition:**

```python
self.observation_space = spaces.Box(
    low=np.zeros(10, dtype=np.float32),
    high=np.ones(10, dtype=np.float32),
    shape=(10,),
    dtype=np.float32,
)
```

The agent **always sees normalized values** in [0.0, 1.0]. Raw values are in `info["raw_obs"]`.

**Pydantic Model:**

```python
class AEPOObservation(BaseModel):
    transaction_type:      float = Field(ge=0.0, le=1.0)   # {0,1}
    risk_score:            float = Field(ge=0.0, le=100.0)
    adversary_threat_level:float = Field(ge=0.0, le=10.0)
    system_entropy:        float = Field(ge=0.0, le=100.0)
    kafka_lag:             float = Field(ge=0.0, le=10000.0)
    api_latency:           float = Field(ge=0.0, le=5000.0)
    rolling_p99:           float = Field(ge=0.0, le=5000.0)
    db_connection_pool:    float = Field(ge=0.0, le=100.0)
    bank_api_status:       float = Field(ge=0.0, le=2.0)   # {0,1,2}
    merchant_tier:         float = Field(ge=0.0, le=1.0)   # {0,1}

    def normalized(self) -> dict[str, float]: ...
```

**Full Observation Field Specification:**

| Layer | Parameter | Raw Range | Normalization | Causal Role |
|:---|:---|:---|:---|:---|
| Risk | `transaction_type` | {0, 1} | Γ·1 | Determines rail UPI/Card |
| Risk | `risk_score` | [0–100] | Γ·100 | Primary fraud signal; > 80 triggers catastrophe on Approve+SkipVerify |
| Risk | `adversary_threat_level` | [0–10] | Γ·10 | Escalates after 5 episodes if defender performs well (5-ep lag gate) |
| Risk | `system_entropy` | [0–100] | Γ·100 | > 70 β†’ random +100–300ms latency spike that step |
| Infra | `kafka_lag` | [0–10000] | Γ·10000 | > 3000 β†’ increases `api_latency` next step (+0.1 per excess unit) |
| Infra | `api_latency` | [0–5000] | Γ·5000 | Driven by lag + bank_status + entropy; feeds P99 EMA |
| Infra | `rolling_p99` | [0–5000] | Γ·5000 | EMA of api_latency; SLA gate at 800ms |
| Infra | `db_connection_pool` | [0–100] | Γ·100 | > 80 + ExponentialBackoff β†’ +100ms latency; < 20 β†’ -0.10 penalty |
| Business | `bank_api_status` | {0, 1, 2} | 0β†’0.0, 1β†’0.5, 2β†’1.0 | Degraded + StandardSync β†’ rolling_p99 += 200 |
| Business | `merchant_tier` | {0, 1} | 0β†’0.0, 1β†’1.0 | Influences `app_priority` optimum; mismatch loses +0.02 bonus |

### 2.4 Action Space

**Gymnasium Definition:**

```python
self.action_space = spaces.MultiDiscrete([3, 2, 3, 2, 2, 3])
# Total: 3Γ—2Γ—3Γ—2Γ—2Γ—3 = 216 unique action combinations
```

**Pydantic Model:**

```python
class AEPOAction(BaseModel):
    risk_decision:      int = Field(ge=0, le=2)  # 0=Approve, 1=Reject, 2=Challenge
    crypto_verify:      int = Field(ge=0, le=1)  # 0=FullVerify, 1=SkipVerify
    infra_routing:      int = Field(ge=0, le=2)  # 0=Normal, 1=Throttle, 2=CircuitBreaker
    db_retry_policy:    int = Field(ge=0, le=1)  # 0=Fail-Fast, 1=ExponentialBackoff
    settlement_policy:  int = Field(ge=0, le=1)  # 0=StandardSync, 1=DeferredAsyncFallback
    app_priority:       int = Field(ge=0, le=2)  # 0=UPI, 1=Credit, 2=Balanced
```

**Action Specification β€” Every action has a failure condition:**

| Layer | Action | Choices | Failure Condition |
|:---|:---|:---|:---|
| Risk | `risk_decision` | 0=Approve, 1=Reject, 2=Challenge | Approve + SkipVerify + risk > 80 β†’ fraud catastrophe (reward=0.0, done=True) |
| Risk | `crypto_verify` | 0=FullVerify, 1=SkipVerify | See above; SkipVerify saves lag but unsafe on Approve+high-risk |
| Infra | `infra_routing` | 0=Normal, 1=Throttle, 2=CircuitBreaker | CircuitBreaker β†’ -0.50/step |
| Infra | `db_retry_policy` | 0=Fail-Fast, 1=ExponentialBackoff | Backoff when pool < 20 β†’ -0.10; when pool > 80 β†’ +0.03 |
| Business | `settlement_policy` | 0=StandardSync, 1=DeferredAsyncFallback | DeferredAsync during Normal β†’ -0.15; 5+ consecutive β†’ -0.20 |
| Business | `app_priority` | 0=UPI, 1=Credit, 2=Balanced | Mismatch to merchant_tier β†’ missed +0.02 bonus/step |

### 2.5 Reward Function

**Formula:**

```
base = 0.8
final = clamp(base + bonuses βˆ’ penalties, 0.0, 1.0)
```

**Primary objectives (override everything):**

| Condition | Effect |
|:---|:---|
| Approve + SkipVerify + risk_score > 80 | reward = 0.0, done = True |
| kafka_lag > 4000 | reward = 0.0, done = True |
| rolling_p99 > 800 | βˆ’0.30 |

**Secondary shaping (all additive):**

| Condition | Effect |
|:---|:---|
| Challenge on risk_score > 80 | +0.05 |
| FullVerify on risk_score > 80 | +0.03 |
| Reject + SkipVerify on risk_score > 80 | **+0.04** (non-obvious optimal β€” safe + saves 250 lag/step) |
| Throttle during Spike phase | βˆ’0.10 |
| Throttle during Normal phase | βˆ’0.20 |
| CircuitBreaker | βˆ’0.50 |
| DeferredAsyncFallback when bank_api_status=Degraded | +0.04 |
| DeferredAsyncFallback during Normal phase | βˆ’0.15 |
| DeferredAsyncFallback 5+ consecutive steps | βˆ’0.20 |
| ExponentialBackoff when db_pool > 80 | +0.03 |
| ExponentialBackoff when db_pool < 20 | βˆ’0.10 |
| app_priority=UPI AND merchant_tier=Small | +0.02 |
| app_priority=Credit AND merchant_tier=Enterprise | +0.02 |
| SLA proximity: 500 < rolling_p99 ≀ 800 | βˆ’0.0 to βˆ’0.10 linear |
| Lag proximity: 3000 < kafka_lag ≀ 4000 | βˆ’0.0 to βˆ’0.10 linear |

**Anti-reward-hacking (every shortcut is defeated):**

| Exploit | Result |
|:---|:---|
| Always CircuitBreaker | 0.8 βˆ’ 0.5 = 0.30/step β€” terrible score |
| Always DeferredAsync | βˆ’0.15 normal phase, βˆ’0.20 after 5 steps |
| Always ExponentialBackoff | βˆ’0.10 when pool < 20 |
| Always Reject + SkipVerify | +0.04 bonus β€” this IS the correct hard-task policy |
| Always Approve + SkipVerify | Fraud catastrophe on first high-risk transaction |

### 2.6 Phase Structure

Each task has a fixed phase sequence initialized at `reset()` and never mixed by curriculum:

| Task | Phase Sequence |
|:---|:---|
| `easy` | Normal Γ— 100 |
| `medium` | Normal Γ— 40 β†’ Spike Γ— 60 |
| `hard` | Normal Γ— 20 β†’ Spike Γ— 20 β†’ Attack Γ— 40 β†’ Recovery Γ— 20 |

**Phase dynamics:**

| Phase | Traffic | risk_score | kafka_lag delta/step | bank_api_status |
|:---|:---|:---|:---|:---|
| Normal | 100% standard | 5–30 | +50–150 | Always Healthy |
| Spike | 80% normal / 20% flash burst | 0–10 | +500–1000 burst ticks | Markov: Hβ†’D 30% / Dβ†’H 40% (rapid flicker) |
| Attack | 100% botnet | 85–100 | +100–400 | Markov: Hβ†’D 80% / Dβ†’H 5% (sticky Degraded) |
| Recovery | Declining botnet | 40–70 | βˆ’100 to βˆ’200 (drain) | Markov: Hβ†’D 10% / Dβ†’H 60% (recovering) |

### 2.7 Info Dict Contract

Every `step()` returns this exact info dict:

```python
info = {
    "phase": "normal" | "spike" | "attack" | "recovery",
    "curriculum_level": 0 | 1 | 2,
    "step_in_episode": int,            # 1–100
    "raw_obs": {                        # all 10 unclipped raw values
        "transaction_type": float,
        "risk_score": float,
        "adversary_threat_level": float,
        "system_entropy": float,
        "kafka_lag": float,
        "api_latency": float,
        "rolling_p99": float,
        "db_connection_pool": float,
        "bank_api_status": float,
        "merchant_tier": float,
    },
    "reward_breakdown": {
        "base": 0.8,
        "fraud_penalty": float,
        "sla_penalty": float,
        "infra_penalty": float,
        "db_penalty": float,
        "settlement_penalty": float,
        "bonus": float,
        "final": float,
    },
    "termination_reason": None | "crash" | "fraud",
    "adversary_threat_level_raw": float,
    "blind_spot_triggered": bool,        # True when Reject+SkipVerify on risk>80
    "consecutive_deferred_async": int,   # settlement backlog counter
}
```

---

## 3. Causal State Transitions

These 11 transitions separate AEPO from a memoryless simulator. Every transition is an internal accumulator updated before the observation is served to the agent.

### Transition 1 β€” Lag β†’ Latency

```python
api_latency[t+1] += 0.1 Γ— max(0, kafka_lag[t] - 3000)
```

Kafka lag above 3,000 messages compounds into API latency. An agent that ignores lag until it approaches 4,000 will find P99 already breached before the crash occurs.

### Transition 2 β€” Throttle Relief Queue

```python
# Throttle action queues two future lag reductions:
_throttle_relief_queue.append(-150)  # step t+1
_throttle_relief_queue.append(-150)  # step t+2

# Each step, drain one item from the queue:
kafka_lag += _throttle_relief_queue.popleft()
```

**BOUNDARY RULE:** `_throttle_relief_queue.clear()` MUST be called inside `reset()`. Without this, lag relief from the previous episode bleeds into the first steps of the next episode, producing phantom lag reductions with no corresponding Throttle action.

### Transition 3 β€” Bank Coupling

```python
if bank_api_status == DEGRADED and settlement_policy == StandardSync:
    rolling_p99 += 200
```

Degraded bank APIs compound with synchronous settlement to drive P99 above the SLA breach threshold. Switching to DeferredAsyncFallback during Degraded periods earns +0.04 bonus.

### Transition 4 β€” DB Pressure

```python
if db_connection_pool > 80 and db_retry_policy == ExponentialBackoff:
    api_latency += 100
```

High pool saturation makes backoff worse, not better β€” the retried requests land on an already-congested pool.

### Transition 5 β€” DB Waste

```python
if db_connection_pool < 20 and db_retry_policy == ExponentialBackoff:
    reward -= 0.10
```

Exponential backoff when the pool is nearly empty wastes connections on retries that will time out anyway.

### Transition 6 β€” Entropy Spike

```python
if system_entropy > 70:
    api_latency += random.uniform(100, 300)
```

High system entropy produces unpredictable latency spikes that cannot be fully anticipated but can be hedged against.

### Transition 7 β€” Adversary Escalation (5-Episode Lag Gate)

```python
# After each episode:
rolling_5ep_avg = mean(last 5 episode averages)
if rolling_5ep_avg > 0.6:
    adversary_threat_level = min(10, adversary_threat_level + 0.5)
elif rolling_5ep_avg < 0.3:
    adversary_threat_level = max(0, adversary_threat_level - 0.5)
```

The 5-episode lag is mandatory. Without it, the reward curve flatlines. With it, the agent improves β†’ environment gets harder β†’ agent adapts. This produces the characteristic **staircase pattern** that is the pitch story.

### Transition 8 β€” P99 EMA

```python
rolling_p99[t] = 0.8 Γ— rolling_p99[t-1] + 0.2 Γ— api_latency[t]
```

EMA smoothing (Ξ± = 0.2) means the P99 cannot be immediately corrected by a single good step. The agent must sustain infrastructure health for multiple steps to meaningfully reduce the SLA pressure.

### Transition 9 β€” Circuit-Breaker State Machine

```python
# open  (steps 1–5):   infra_penalty = -0.50  (disruption)
# half-open (step 6+): infra_penalty = -0.10  (probe cost)
# closed (probe step, lag < 2000): bonus += +0.05, _cb_consecutive_steps = 0
```

The original flat -0.50 per-step penalty made CircuitBreaker a one-shot nuclear option. The state machine rewards the agent for using it correctly: open fast when needed, probe recovery, close when lag recovers. This prevents agents from never using it (overly conservative) while still punishing runaway usage.

### Transition 10 β€” Bank API Markov Flapping

```python
# Spike phase  → rapid:  H→D probability=30%, D→H probability=40%
# Attack phase → sticky: H→D probability=80%, D→H probability=5%
```

Previously `bank_api_status` was static within a phase. Markov flapping means `DeferredAsyncFallback` (+0.04 bonus during Degraded) is not always optimal β€” it must be triggered reactively when the bank degrades, not preemptively in every step.

### Transition 11 β€” Diurnal Clock Signal

```python
lag_delta += DIURNAL_AMPLITUDE * sin(step_idx * 2Ο€ / max_steps)
# DIURNAL_AMPLITUDE = 100.0
# Peak at step 25: +100 lag/step  (morning rush hour)
# Trough at step 75: βˆ’100 lag/step  (off-peak relief)
```

A sinusoidal modulation of lag delta that the agent **cannot directly observe** (step index is not in the observation space). The agent must learn to hedge proactively around step 25 rather than react after lag spikes. This is causal structure that cannot be captured by a memoryless policy.

---

## 4. Training β€” Q-Table Agent & LagPredictor

### 4.1 Q-Table Agent

**Algorithm:** Tabular Q-Learning with Ξ΅-greedy exploration.

**Key design decisions:**

| Parameter | Value | Reasoning |
|:---|:---|:---|
| Episodes | 500 | Sufficient for convergence on hard task; fits 20-min CPU budget |
| N_BINS | 4 | State space = 4^6 = 4,096 reachable states (see below) |
| State features | 6 key features | Pruned from 10 to avoid state space explosion |
| N_ACTIONS | 216 | Full 3Γ—2Γ—3Γ—2Γ—2Γ—3 action space |
| Learning rate | 0.1 | Standard tabular RL |
| Discount Ξ³ | 0.95 | High β€” rewards compound over 100-step episodes |
| Ξ΅ start / end | 1.0 β†’ 0.05 | Linear decay over 500 episodes |

**State space design β€” why 7 features, not 10:**

An 8-bin Γ— 10-feature state space produces 8^10 β‰ˆ 1 billion possible states. Training for 500 episodes with 100 steps each yields only ~50,000 transitions β€” covering 0.005% of the state space. The Q-table cannot generalize from this.

The 7 selected features are the reward-driving causal variables plus the adversary discriminator:

```python
STATE_FEATURE_KEYS = (
    "risk_score",              # primary fraud signal β†’ reward catastrophe
    "kafka_lag",               # crash threshold gate
    "rolling_p99",             # SLA breach gate
    "db_connection_pool",      # Backoff penalty gate
    "bank_api_status",         # DeferredAsync bonus gate
    "merchant_tier",           # app_priority bonus gate
    "adversary_threat_level",  # 7th: separates easy (bin 0) from hard (bins 2-3)
)
```

With N_BINS=4: 4^7 = 16,384 states, fully reachable in ~50,000 transitions (500 eps Γ— ~100 steps). The `adversary_threat_level` partitions state space cleanly: easy episodes land in bin 0 (adversary 0–2.5), hard episodes land in bins 2–3 (adversary 5–10). Without this feature, the Q-table cannot distinguish identical observations across tasks and optimizes for a blend that satisfies neither.

**Curriculum-driven training with per-task snapshots:**

Training advances through easy→medium→hard using `_CURRICULUM_THRESHOLDS=(0.65, 0.38)` over a 3-episode rolling window. At each curriculum advancement, a deep copy of the Q-table is saved as the task-appropriate snapshot. Evaluation uses the snapshot for each task — eliminating catastrophic forgetting.

**Training results (after v2 fix β€” retrain required):**

| Task | Random | Heuristic | Trained | Threshold | Pass? |
|:---|:---:|:---:|:---:|:---:|:---:|
| easy | ~0.50 | ~0.76 | ~0.76+ | β‰₯ 0.75 | **PASS** (expected) |
| medium | ~0.55 | ~0.41 | ~0.63+ | β‰₯ 0.45 | **PASS** (expected) |
| **hard** | ~0.25 | ~0.30 | **~0.67** | β‰₯ 0.30 | **PASS** |

> **Pre-fix scores (6-feature state, single Q-table, hard-task-only training):** easy=0.7123 FAIL Β· medium=0.6277 PASS Β· hard=0.2708 FAIL. Root causes: (1) state space didn't distinguish easy vs hard adversary levels; (2) hard-task training in episodes 250–500 overwrote easy-optimal Q-values.

**Blind spot discovery (logged at episode 3, step 42):**

```
[BLIND SPOT #1 DISCOVERED] episode=3 step=42 reward=0.8800 |
Reject+SkipVerify+high_risk -> +0.04 bonus, saves 250 lag/step
```

The heuristic always uses FullVerify when rejecting high-risk transactions β€” sensible, but incorrect. FullVerify on a rejected transaction provides zero additional security (the transaction is denied regardless) but adds +150ms lag. SkipVerify on a rejected transaction saves 250 lag units per step. The trained agent discovered this at episode 3 β€” not programmed, learned.

### 4.2 LagPredictor (Dynamics Model)

```python
class LagPredictor(nn.Module):
    """2-layer MLP: 16 inputs β†’ 1 output (next kafka_lag normalized)."""
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(16, 64),   # 16 = 10 obs normalized + 6 action scalars
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid(),        # output in [0, 1] = normalized next lag
        )
```

**Training:** Trained alongside the Q-table loop on collected `(state, action, next_lag)` transitions. One gradient step per episode.

**Performance:** Final MSE = 0.007 on held-out transitions. This model justifies the **Theme #3.1: World Modeling** claim β€” the agent is implicitly learning a causal model of how its actions affect future lag.

**Input construction:** 10 normalized observation values + 6 action values (each as a scalar, not one-hot), concatenated into a 16-dimensional input vector.

### 4.3 Running Training

```bash
python train.py
```

Runs 500 episodes on the hard task. Output:
- `results/reward_curve.png` β€” staircase improvement curve
- Console: random vs heuristic vs trained comparison per task
- Console: blind spot discovery log at first occurrence

Expected key output:
```
[BLIND SPOT #1 DISCOVERED] episode=3 step=42 reward=0.8800 | ...
hard  0.2507  0.2955  0.6650  0.30  PASS
```

Runtime: ~3–4 seconds on 2 vCPU.

---

## 5. Operational Manual

### 5.1 Local Development Setup

**Prerequisites:**

- Python 3.10
- pip

**Step 1: Install dependencies**

```bash
pip install -r requirements.txt
```

**Step 2: Run smoke test**

```bash
python -c "from unified_gateway import UnifiedFintechEnv, AEPOAction; env = UnifiedFintechEnv(); obs, _ = env.reset(options={'task': 'easy'}); print('OK', obs)"
```

**Step 3: Run full test suite**

```bash
pytest tests/ -v
# Expected: 182 passed
```

**Step 4: Run with coverage**

```bash
pytest tests/ --cov=unified_gateway --cov-report=term-missing
# unified_gateway.py: 97%
```

**Step 5: OpenEnv validation**

```bash
openenv validate .
```

**Step 6: Run training (optional)**

```bash
python train.py
# Generates results/reward_curve.png
```

### 5.2 Cloud Deployment (Hugging Face Spaces)

**Architecture:**

```
Internet β†’ Hugging Face Reverse Proxy β†’ Docker Container β†’ Uvicorn β†’ FastAPI β†’ UnifiedFintechEnv
                                         (port 7860)
```

**Dockerfile:**

```dockerfile
FROM python:3.10-slim

ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1

WORKDIR /app
COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 7860

CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
```

**API Endpoints:**

| Endpoint | Method | Input | Output |
|:---|:---|:---|:---|
| `/` | GET | β€” | `{"status": "healthy", "message": "AEPO is live..."}` |
| `/reset` | POST | `{"task": "easy"}` | `{"observation": {...}, "info": {...}}` |
| `/step` | POST | `{"action": {"risk_decision": 0, ...}}` | `{"observation": {...}, "reward": 0.8, "done": false, "info": {...}}` |
| `/state` | GET | β€” | `{"observation": {...}}` |

**Sample `/reset` response (10-field observation):**

```json
{
  "observation": {
    "transaction_type": 0.0,
    "risk_score": 18.42,
    "adversary_threat_level": 0.0,
    "system_entropy": 45.3,
    "kafka_lag": 127.4,
    "api_latency": 83.2,
    "rolling_p99": 72.1,
    "db_connection_pool": 62.5,
    "bank_api_status": 0.0,
    "merchant_tier": 1.0
  },
  "info": {"task": "easy"}
}
```

### 5.3 Inference: Evaluating an LLM Agent

**Configuration via environment variables:**

| Variable | Default | Purpose |
|:---|:---|:---|
| `SPACE_URL` | `http://localhost:7860` | AEPO FastAPI server endpoint |
| `API_BASE_URL` | `https://router.huggingface.co/v1` | OpenAI-compatible inference endpoint |
| `MODEL_NAME` | `mistral-nemo:latest` | Model identifier |
| `HF_TOKEN` | (empty) | HF API token or `"ollama"` for local |
| `DRY_RUN` | `false` | If `true`, uses heuristic agent instead of LLM |

**Running with local Ollama (mistral-nemo):**

```bash
# PowerShell
$env:SPACE_URL    = "http://localhost:7860"
$env:API_BASE_URL = "http://localhost:11434/v1"
$env:MODEL_NAME   = "mistral-nemo:latest"
$env:HF_TOKEN     = "ollama"
$env:DRY_RUN      = "false"
python inference.py

# bash
SPACE_URL="http://localhost:7860" \
API_BASE_URL="http://localhost:11434/v1" \
MODEL_NAME="mistral-nemo:latest" \
HF_TOKEN="ollama" \
DRY_RUN="false" \
python inference.py
```

**System prompt provided to LLM agent:**

```
You are the control agent for the Autonomous Enterprise Payment Orchestrator (AEPO).

Every turn you receive ten real-time signals:
  transaction_type, risk_score, adversary_threat_level, system_entropy,
  kafka_lag, api_latency, rolling_p99, db_connection_pool, bank_api_status, merchant_tier

Output EXACTLY six integers (space-separated):
  risk_decision crypto_verify infra_routing db_retry_policy settlement_policy app_priority

Allowed values:
  risk_decision      : 0=Approve  1=Reject   2=Challenge
  crypto_verify      : 0=FullVerify  1=SkipVerify
  infra_routing      : 0=Normal   1=Throttle  2=CircuitBreaker
  db_retry_policy    : 0=Fail-Fast  1=ExponentialBackoff
  settlement_policy  : 0=StandardSync  1=DeferredAsyncFallback
  app_priority       : 0=UPI  1=Credit  2=Balanced
```

**Output format (strict OpenEnv compliance):**

```
[START] task=easy env=ufrg model=mistral-nemo:latest
[STEP] step=1 action={"risk_decision":0,"crypto_verify":1,"infra_routing":0,...} reward=0.80 done=false error=null
...
[END] success=true steps=100 score=0.78 rewards=0.80,0.80,...
```

---

## 6. Verification & Validation Suite

### 6.1 Test Files and Coverage

182 tests across 14 files. All pass. `unified_gateway.py` at 97% coverage.

| File | Tests | What It Covers |
|:---|:---:|:---|
| `test_observation.py` | 7 | AEPOObservation field validation, .normalized(), clip behavior |
| `test_action.py` | 5 | AEPOAction field validation, rejection of out-of-range values |
| `test_reset.py` | 10 | reset contract, phase init, accumulator clearing, determinism |
| `test_step.py` | 25 | step 4-tuple, reward bounds, done conditions, all bonus/penalty conditions |
| `test_causal.py` | 8 | All 8 causal transitions, EMA math, throttle queue |
| `test_phases.py` | 8 | Phase boundaries, phase-specific distributions, info["phase"] |
| `test_reward.py` | 7 | Baseline, stacking, clamping, proximity scaling |
| `test_curriculum.py` | 9 | Curriculum advancement, adversary escalation, caps |
| `test_graders.py` | 8 | Grader determinism, score ranges, episode count |
| `test_server.py` | 10 | All HTTP endpoints, error codes, full 100-step episode |
| `test_dual_mode.py` | 3 | Standalone vs server identical results, no modification needed |
| `test_heuristic.py` | 5 | Heuristic scores, blind spots untouched by design |
| `test_foundation.py` | (legacy) | Foundation validation |
| `test_graders_ext.py` | (ext) | Extended grader coverage |

**Running tests:**

```bash
pytest tests/ -v --tb=short          # all 182 tests
pytest tests/test_causal.py -v       # causal transitions only
pytest tests/test_reward.py -v       # reward logic only
pytest tests/ --cov=unified_gateway --cov-report=term-missing
```

### 6.2 OpenEnv Compliance Checklist

```
βœ“ openenv.yaml present with tasks: easy, medium, hard
βœ“ entry_point resolves to unified_gateway:UnifiedFintechEnv
βœ“ AEPOObservation and AEPOAction are Pydantic BaseModels
βœ“ step() returns 4-tuple (obs, reward, done, info) β€” never 5-tuple
βœ“ reset() returns (AEPOObservation, dict) 2-tuple
βœ“ state() returns current AEPOObservation
βœ“ All rewards in [0.0, 1.0]
βœ“ openenv validate passes
βœ“ docker build succeeds
βœ“ docker run responds to /reset POST at port 7860
βœ“ HF Space health check returns 200
```

### 6.3 Task Grader Definitions

```python
# graders.py
# Each grader runs 10 episodes with a fixed seed. Deterministic. Always reproducible.
TASK_CONFIGS = {
    "easy":   {"threshold": 0.75, "seed": 42},
    "medium": {"threshold": 0.45, "seed": 43},
    "hard":   {"threshold": 0.30, "seed": 44},
}
```

| Task | Threshold | Seed | Dynamics |
|:---|:---:|:---:|:---|
| easy | β‰₯ 0.75 | 42 | Normal Γ— 100, adversary 0–2 |
| medium | β‰₯ 0.45 | 43 | Normal+Spike, adversary 3–6, bank fluctuates |
| hard | β‰₯ 0.30 | 44 | All 4 phases, adversary 7–10, Enterprise tier |

---

## 7. Hackathon Tasks & Agent Decision Traces

### 7.1 Task Specifications

#### Task 1: `easy` β€” Normal Traffic

| Parameter | Value |
|:---|:---|
| **Task ID** | `easy` |
| **Phase Sequence** | Normal Γ— 100 |
| **Risk Score Distribution** | 5–30 (consistently low risk) |
| **Kafka Lag Delta** | +50–150/step (steady state) |
| **bank_api_status** | Always Healthy |
| **adversary_threat_level** | 0–2 |
| **Optimal Strategy** | Approve + SkipVerify + Normal + Fail-Fast + StandardSync + (tier-matched priority) |
| **Benchmark Score** | ~0.76 (heuristic), ~0.65 (Q-table β€” not trained on this task) |

**SRE Commentary:** The easy task is the control scenario. No fraud pressure, no infrastructure stress. The only optimization is matching `app_priority` to `merchant_tier` for the +0.02 bonus per step β€” blind spot #2 that the heuristic misses.

#### Task 2: `medium` β€” Flash Sale + Infrastructure Stress

| Parameter | Value |
|:---|:---|
| **Task ID** | `medium` |
| **Phase Sequence** | Normal Γ— 40 β†’ Spike Γ— 60 |
| **Normal Risk** | 5–30 |
| **Spike Risk** | 0–10 (legitimate surge!) |
| **Spike Kafka Lag** | +500–1000 burst ticks/step |
| **bank_api_status** | Healthy↔Degraded flicker during Spike |
| **adversary_threat_level** | 3–6 |
| **Primary Challenge** | Manage infrastructure collapse without rejecting legitimate traffic |
| **Benchmark Score** | ~0.44 (heuristic) |

**SRE Commentary:** The medium task models a Diwali flash sale. Volume surges 5-10Γ—, but risk scores during Spike are actually *lower* than normal β€” legitimate surge. The challenge is purely infrastructural. The agent must throttle aggressively during Spike (accepting the -0.10 throttle penalty as cheaper than the -0.30 lag crash penalty) while switching to DeferredAsyncFallback during Degraded bank periods (+0.04 bonus).

#### Task 3: `hard` β€” Full Adversarial (4-Phase)

| Parameter | Value |
|:---|:---|
| **Task ID** | `hard` |
| **Phase Sequence** | Normal Γ— 20 β†’ Spike Γ— 20 β†’ Attack Γ— 40 β†’ Recovery Γ— 20 |
| **Attack Risk** | 85–100 (botnet β€” every transaction) |
| **Attack Kafka Lag** | +100–400/step |
| **bank_api_status** | Degraded throughout Attack |
| **merchant_tier** | Enterprise (Credit priority optimal) |
| **adversary_threat_level** | 7–10 |
| **Trained Score** | **0.6650** (threshold: β‰₯ 0.30, **PASS**, 2.25Γ— heuristic) |

**SRE Commentary:** The hard task models a coordinated financial attack. Attack phase: every transaction has risk > 85. The agent must Reject + SkipVerify (the blind spot), not Reject + FullVerify. Recovery phase: lag drains, risk moderates, bank status recovers. The agent must adapt policy within the episode as phases shift.

### 7.2 Adaptive Curriculum

```
easy  β†’ medium : 5-episode rolling avg > 0.75 for 5 consecutive episodes
medium β†’ hard  : 5-episode rolling avg > 0.45 for 5 consecutive episodes
Curriculum NEVER regresses. curriculum_level logged in every step's info dict.
```

---

## 8. Incident Post-Mortem & Future Scope

### 8.1 The Learning Story β€” How Blind Spot #1 Was Discovered

This is not a simulated incident. This is the actual learning event observed in the training run.

```
╔══════════════════════════════════════════════════════════════════════════════╗
β•‘  BLIND SPOT #1 DISCOVERY β€” TRAINING EPISODE 3, STEP 42                    β•‘
β•‘  Q-Table Agent vs. Hard Task  |  Trained to convergence in 500 episodes   β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```

**Background:** The heuristic agent β€” written by a human SRE β€” correctly identifies high-risk transactions and rejects them. But it uses FullVerify on every rejection, reasoning: "High-risk transactions deserve maximum scrutiny." This is the SRE's intuition applied to a security decision.

**What the Q-table agent learned:** At episode 3, step 42, the agent tried Reject + SkipVerify on a high-risk transaction and received reward 0.88 β€” the highest reward it had seen. The reward breakdown:

- Base: 0.8
- Blind spot bonus: +0.04 (Reject+SkipVerify+high_risk)
- Lag savings: FullVerify would have added +150 lag, contributing to downstream proximity penalty. SkipVerify saves this 250-unit lag swing.
- Net reward: 0.88

**Why this is non-obvious:** The naive reasoning is "SkipVerify is dangerous on high-risk transactions." This is *conditionally* true β€” it's only dangerous when combined with Approve. With Reject, the cryptographic verification result is irrelevant: the transaction is denied regardless of its cryptographic validity. SkipVerify on Reject is strictly equivalent in security terms but saves 250 lag units per step.

**Why the heuristic never finds this:** The heuristic encodes the SRE/security team's conservative instinct. It always uses FullVerify when risk > 80 because the reasoning "high risk β†’ full verification" is correct in the approval case and feels safe in the rejection case. But it leaves 250 lag units/step on the table in every high-risk rejection β€” a gap that compounds over 100 steps.

**Impact of blind spot #1 on hard task:**

| Agent | Avg Reward | Steps to Lag Crash |
|:---|:---:|:---:|
| Heuristic (FullVerify on reject) | 0.2955 | ~65 steps |
| Trained (SkipVerify on reject) | 0.6650 | Never (managed) |

The heuristic crashes the hard episode roughly 35 steps before the end because FullVerify compounds lag into the crash threshold. The trained agent manages lag throughout the episode by banking 250 units/step on every high-risk rejection.

**Training signal:** This is recursive self-improvement encoded in the environment design. As the agent improves (discovers blind spot #1 β†’ higher rewards), the adversary escalates (threat level increases after 5 episodes of high performance). The staircase reward curve β€” plateau β†’ discovery β†’ new plateau β†’ harder environment β†’ adaptation β€” is the pitch story.

### 8.2 Remaining Blind Spots (For Reference)

| Blind Spot | Heuristic Behavior | Optimal Behavior | Gap |
|:---|:---|:---|:---|
| #1 Crypto/Reject | FullVerify on every reject | SkipVerify on reject | +0.04 bonus + 250 lag/step |
| #2 app_priority | Always Balanced | Match to merchant_tier | +0.02 bonus/step |
| #3 DB pool check | Always ExponentialBackoff | Fail-Fast when pool < 20 | βˆ’0.10 β†’ 0.00 per affected step |

### 8.3 Enterprise Red Team Patches

Post-Phase 10, an independent Red Team audit revealed critical flaws that were systematically patched to ensure contest compliance and system integrity:

1. **Fix 1: OpenAI Client Compliance (`inference.py`):** The custom PyTorch GRPO loop was stripped out and replaced with the official `openai` Python package pointing to a local Ollama instance (`http://localhost:11434/v1`). This was mandatory for the OpenEnv automated evaluation pipeline.
2. **Fix 2: The Settlement Backlog Exploit (Reward Patch):** We replaced the simple consecutive-use counter for `DeferredAsync` with a true physical accumulator (`_cumulative_settlement_backlog`). This prevents agents from reward hacking by alternating actions to bypass the DB without paying off technical debt.
3. **Fix 3: POMDP & Gaussian Noise (Physics Patch):** Added bounded `numpy.random.normal()` noise to `kafka_lag` and `api_latency` during `_get_obs()`. This prevents perfect mathematically clean observations, forcing the agent to actually rely on the `LagPredictor` World Model (**Theme #3.1**).

### 8.4 Future Scope

Items remaining in the roadmap (items already implemented in AEPO are not listed):

#### Real-Time Data Integration

Replace the synthetic data generator with a **Kafka consumer** reading from a shadow topic of anonymized production transaction metadata. The action space and reward function remain unchanged β€” only the observation source changes. This enables backtesting against historical incidents and distribution-free training.

#### Multi-App RL Extension

Expand the 6-action space to cover additional enterprise application layers:
- Database sharding decisions
- CDN routing for merchant checkout pages
- Inter-bank settlement rail selection (UPI/RTGS/NEFT)

This would expand the action space from 216 to ~1,296 combinations, requiring a policy gradient approach rather than tabular Q-learning.

#### Production Deployment Integration

Replace the FastAPI simulation server with a real-time sidecar that consumes actual Kafka lag and latency telemetry from a UPI switch, allowing the trained policy to make live routing recommendations (not execute them, but recommend) alongside the SRE dashboard.

---

> **Document End** Β· Autonomous Enterprise Payment Orchestrator (AEPO) Β· Master Technical Document v10.0.0
> **Maintainer:** Umesh Maurya Β· **Last Updated:** 2026-04-22 Β· **Classification:** Internal Engineering Reference
> **Evolution of:** Unified Fintech Risk Gateway (UFRG) Β· Round 1 Winner