unknown1321's picture
Clean deploy to HF Space
e9ce6e9
|
Raw
History Blame Contribute Delete
44.2 kB

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
  2. Architecture & Implementation Deep-Dive
  3. Causal State Transitions
  4. Training β€” Q-Table Agent & LagPredictor
  5. Operational Manual
  6. Verification & Validation Suite
  7. Hackathon Tasks & Agent Decision Traces
  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:

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:

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:

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

Pydantic Model:

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:

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

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

# 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

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

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

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

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)

# 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

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

# 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

# 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

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:

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)

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

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

pip install -r requirements.txt

Step 2: Run smoke test

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

pytest tests/ -v
# Expected: 182 passed

Step 4: Run with coverage

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

Step 5: OpenEnv validation

openenv validate .

Step 6: Run training (optional)

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:

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):

{
  "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):

# 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:

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

# 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