ap-clerk-env / README.md
Pathikreet's picture
Add blog/writeup link to README header
5a8800d verified
|
Raw
History Blame Contribute Delete
26.5 kB
metadata
title: AP Commander
emoji: πŸ›οΈ
colorFrom: blue
colorTo: indigo
sdk: docker
pinned: true
license: mit
tags:
  - openenv
  - reinforcement-learning
  - multi-agent
  - fleet-ai
  - long-horizon
  - finance
  - enterprise
  - oversight

AP Commander β€” Multi-Agent RL Environment for Enterprise Financial Operations

Hackathon: Meta PyTorch OpenEnv Γ— Scaler School of Technology Grand Finale
Team: Pathikreet Chowdhury, Anubhav Bhattacharya, Radhika Ravi
Live environment UI: https://pathikreet-ap-clerk-env.hf.space
Environment API + Swagger: https://pathikreet-ap-clerk-env.hf.space/docs
Training Space (Gradio UI): https://huggingface.co/spaces/Pathikreet/ap-commander-training
Training script (HF Space): training/train.py β€” runs on the Training Space (A10G)
Colab notebook: training/colab_training.ipynb β€” self-contained GRPO loop, T4-compatible
Training logs: runs/grpo/ β€” timestamped per run, nothing overwritten
Baseline logs: runs/baselines/ β€” scripted agent + untrained Llama/Qwen evaluations
Blog / Writeup: BLOG.md β€” full writeup: problem, environment, training evidence across 4 runs, reward design
Presentation (slide deck): https://canva.link/k7f87ccul4fznaf
Technical documentation: TECHNICAL.md β€” architecture, reward design, agent interactions, flowchart, API reference

AP Commander β€” interactive environment website


Hackathon Theme Coverage

Theme Implementation Bonus Target
#1 Multi-Agent AP Clerk agent + Fleet AI Oversight agent (separate action/obs spaces, /oversight/* endpoints) + VendorActor / ManagerActor / ComplianceActor responding dynamically to QUERY_VENDOR / ESCALATE / HOLD Fleet AI Β· Halluminate
#2 Long-Horizon 7 tasks with max 10–16 steps requiring sustained multi-step reasoning: dispute resolution, fraud investigation, manager OOO escalation chain, SOX audit trail Scale AI Labs
#3.1 Professional World Modeling ERP-style documents (Invoice, PO, GRN, paid ledger), dynamic company policy, multi-app actor interactions, randomised per episode Scaler AI Labs
#4 Self-Improvement Adaptive curriculum (/curriculum/next_task) that escalates difficulty based on session history + HYPOTHETICAL action for counterfactual self-play exploration during training Snorkel AI

The Problem

Every enterprise processes thousands of vendor invoices every month. Each one requires a human to cross-reference purchase orders, verify delivery receipts, apply company policy, and decide whether to pay β€” and how much. A wrong approval costs money. A wrong rejection damages a vendor relationship. A missed duplicate is fraud.

LLMs fail at this in specific, measurable ways. They hallucinate PO numbers, ignore policy caps, approve duplicates, and cannot chain multi-step workflows like query vendor β†’ get response β†’ escalate to manager β†’ reject. They treat each step as independent rather than as part of an investigation. There is no RL environment that exposes these failures with a reward signal designed to close the loopholes.

AP Commander is that environment: a multi-agent system that trains an LLM to reason through enterprise Accounts Payable workflows with the rigor a CFO would require, and deploys a second agent to monitor and catch what the first one misses.


What the Agent Learns

An AP Clerk agent receives a structured observation β€” invoice, purchase orders, goods receipt notes, company policy β€” and must decide:

APPROVE_FULL | APPROVE_PARTIAL | REJECT | ESCALATE | QUERY_VENDOR | HOLD

It must also justify its decision with specific dollar amounts and a reason code. Getting the decision right but the amount wrong is still penalized. Citing "policy violation" without identifying the violated clause is still penalized.

A second Oversight agent (Fleet AI) monitors batches of completed clerk decisions, identifies fraudulent approvals, and explains its reasoning with numeric evidence. It is penalized for false positives.

Three simulated workplace actors generate contextual responses at episode start, revealed progressively as the clerk takes intermediate actions:

  • VendorActor β€” reveals response to QUERY_VENDOR with one of three personas: honest, fraudulent, or confused
  • ManagerActor β€” reveals response to ESCALATE based on its budget authority and risk appetite; may be out-of-office, triggering a VP chain
  • ComplianceActor β€” reveals response to HOLD with a SOX / GDPR / Internal Policy verdict

Episodes run up to 16 steps on long-horizon tasks β€” fraud investigations, audit trails, multi-vendor splits β€” requiring sustained multi-step reasoning to reach the correct terminal decision.


How the Data Works

Every episode is synthetically generated at runtime β€” there is no static dataset. When the agent calls /reset, the environment produces a fresh, unique financial scenario from scratch using a seeded RNG.

POST /reset { task_id: "medium_quantity_shortfall", seed: 42 }
  └── tasks.py: generate_medium_quantity_shortfall(seed=42)
        └── Builds everything from scratch: vendor, item, quantities, prices, PO, GRN

The agent receives a structured APObservation:

APObservation
β”œβ”€β”€ invoice              ← vendor name, line items, unit prices, freight, total
β”œβ”€β”€ purchase_orders      ← 1 real OPEN PO + 1–2 distractor CLOSED POs (noise)
β”œβ”€β”€ goods_receipts       ← 1 real GRN + 1 wrong-vendor distractor GRN (noise)
β”œβ”€β”€ company_policy       ← text with randomised freight cap and price tolerance
β”œβ”€β”€ freight_cap          ← randomised each episode: $30 / $50 / $75 / $100
β”œβ”€β”€ price_tolerance      ← randomised each episode: 0.5% – 3.0%
└── paid_invoice_ids     ← ledger of already-paid invoices (duplicate detection)
What Fixed or random? Why
Task type (e.g. quantity shortfall) Fixed by task_id Defines the skill being trained
Vendor, item, amounts, IDs Random per seed Agent cannot memorise β€” must reason
Freight cap & price tolerance Random Agent must read policy each episode
Distractor POs and GRNs Always present Forces genuine 3-way matching

Same seed β†’ identical episode. This makes training and evaluation reproducible. Different seeds across training episodes prevent the agent from memorising amounts β€” it must learn the underlying reasoning pattern.


Results

Run 1 β€” Qwen2.5-7B-Instruct, 3 Epochs GRPO (2026-04-25)

Parameter Value
Model Qwen/Qwen2.5-7B-Instruct
Quantization 4-bit NF4 (BitsAndBytes)
LoRA r=16, alpha=16, no dropout
Algorithm GRPO (TRL β‰₯ 0.15)
Epochs 3
Generations / prompt 8
Training samples 50 (10 tasks Γ— 5 seeds)
Hardware A10G Small (HF Spaces)
Elapsed 59.5 min
Reward calls 1 200
Format rate 91.2%
Parse failures 106 / 1 200 (8.8%)

Live training metrics

Live reward curve and stats Decision distribution and per-task rewards

Recent mean reward 0.746 at step 150. Single-step REJECT tasks learned quickly (Price Discrepancy 0.96, Vendor Mismatch 0.94, Tax Discrepancy 0.92). Multi-step tasks still failing (Duplicate Invoice 0.07, Policy Violation 0.09) β€” correct action sequences (QUERY_VENDOR β†’ REJECT, ESCALATE β†’ REJECT) require more epochs to discover.

Before / After evaluation (10 tasks, seed=99)

GRPO Before vs After

Task Before GRPO After GRPO Ξ”
easy_perfect_match 0.500 0.990 +0.490
easy_no_po_found 0.990 0.990 0.000
medium_quantity_shortfall 0.860 0.860 0.000
medium_price_discrepancy β€” β€” β€”
medium_split_delivery β€” β€” β€”
medium_vendor_mismatch β€” β€” β€”
hard_policy_violation 0.010 0.010 0.000
hard_duplicate_invoice β€” β€” β€”
hard_partial_po_match β€” β€” β€”
hard_tax_discrepancy β€” β€” β€”

easy_perfect_match improved +0.490 (Qwen was getting the amount or reason code wrong before GRPO). Hard multi-step tasks need more epochs. Full 10-task eval runs from epoch 2 onward.


Run 2 β€” Qwen2.5-7B, 17 tasks, 160 prompts (stopped early at step 235/420)

Hardware: A10G Large | Stopped: Epoch 3.35 / 6

Run 2 Dashboard at stop

Metric Value
Steps completed 235 / 420
Recent mean reward 0.516
Format rate 44.4% (critical failure)
Parse failures 8 343 / 7 520 reward calls
Elapsed 255.6 min

Per-task means at stop

Task Score Task Score
easy_no_po_found 0.99 hard_policy_violation 0.45
easy_perfect_match 0.82 medium_price_discrepancy 0.45
medium_vendor_mismatch 0.55 long_policy_migration 0.42
hard_tax_discrepancy 0.50 long_manager_chain 0.38
long_fraud_investigation 0.49 long_audit_trail 0.34
hard_duplicate_invoice 0.48 medium_quantity_shortfall 0.33
long_invoice_dispute 0.33
long_batch_reconciliation 0.28
long_split_delivery 0.24
long_multi_vendor_split 0.22
hard_partial_po_match 0.22

Issues that caused early stop

  1. Temperature 1.1 β†’ 55% format failures β€” model generated natural language instead of JSON; format reward Β±0.05 too weak to correct this
  2. Curriculum gating locked hard tasks β€” from epoch 3 onward hard_policy_violation and other hard tasks were silently redirected to easy tasks; hard/long tasks stopped receiving any gradient signal
  3. Entropy collapsed to 0.23 β€” model defaulted to REJECT for 59% of decisions; APPROVE_PARTIAL and QUERY_VENDOR nearly absent
  4. frac_reward_zero_std = 0.5 β€” half of all GRPO groups had identical rewards across 16 generations; zero learning signal for those steps
  5. Negative loss (-0.011) with zero clip_ratio β€” policy drifted past reference without PPO correction

Run 3 fixes applied

  • Temperature: 1.1 β†’ 0.7
  • beta=0.1 added (prevents entropy collapse; was kl_coeff which is not a valid TRL param)
  • Format reward: Β±0.05 β†’ Β±0.15
  • Curriculum gating disabled β€” all 20 tasks train from step 1
  • NUM_GENERATIONS: 16 (32 caused CUDA OOM on 7B/A10G during backward pass)
  • gradient_accumulation_steps: 2 β†’ 1
  • PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to reduce fragmentation
  • 3 missing hard tasks registered: hard_currency_conversion, hard_manager_preapproval, hard_credit_memo β†’ restores 322 training prompts
  • System prompt updated with concrete JSON example

Run 3 β€” Qwen2.5-1.5B-Instruct, G=16, 322 prompts (paused β€” insufficient compute)

Hardware: A10G Small | Model: Qwen/Qwen2.5-1.5B-Instruct | Generations/prompt: 16

Run 3 β€” Training dashboard at step 112 Run 3 β€” Metrics at step 113: mean 0.722, format 94.9%

Mean reward 0.722 at step 113 (up from 0.486 untrained baseline), format rate 94.9% β€” entropy collapse and format failures from Run 2 fully resolved. Paused at step 113 due to insufficient compute allocation. See per-task breakdown in BLOG.md.


Run 4 β€” Qwen2.5-1.5B-Instruct, G=8, parallel comparison (ongoing)

Hardware: A10G Small | Model: Qwen/Qwen2.5-1.5B-Instruct | Generations/prompt: 8

Run 4 β€” Training dashboard at step 160 Run 4 β€” Metrics at step 179: mean 0.709, format 93.7%

Mean reward 0.709 at step 179, format rate 93.7%. Running as a G=8 ablation against Run 3 (G=16) β€” smaller groups train faster per step; larger groups provide more contrastive signal per prompt. Currently ongoing.


Baselines

Untrained Qwen2.5-7B-Instruct (4-bit, no LoRA)

Run training/eval_baseline.py on the HF Training Space to generate this. Results saved to runs/baselines/qwen2.5-7b-instruct-DATETIME/.

Task Difficulty Score (mean, 3 seeds)
easy_perfect_match easy 0.500
easy_no_po_found easy 0.990
medium_quantity_shortfall medium 0.860
medium_price_discrepancy medium β€”
medium_split_delivery medium β€”
medium_vendor_mismatch medium β€”
hard_policy_violation hard 0.010
hard_duplicate_invoice hard 0.010
hard_partial_po_match hard β€”
hard_tax_discrepancy hard β€”

Partial results from the Run 1 pre-training evaluation (seeds 99 only). Full 3-seed baseline generated by training/eval_baseline.py. Hard multi-step tasks score near 0.01 — the model cannot discover the ESCALATE→REJECT / QUERY_VENDOR→REJECT sequences without training.

Optimal Ceiling β€” scripted agent (all 20 tasks)

Scripted Agent Reward Curves

Optimal Ceiling vs Untrained Llama-3-8B (per task)

Llama-3-8B vs Optimal Ceiling

Untrained Qwen2.5-7B-Instruct baseline (17 tasks, 3 seeds each)

Qwen2.5-7B Baseline

Task Category Optimal Ceiling Untrained Llama-3-8B Untrained Qwen2.5-7B After GRPO 3ep
Easy (2 tasks) 0.990 0.990 0.721 0.990
Medium (4 tasks) 0.907 0.712 0.691 0.860
Hard (4 tasks) 0.843 0.698 0.468 β€”
Long-horizon (7 tasks) 0.989 0.832 0.432 β€”
Overall 0.921 0.811 0.535 β€”

Optimal ceiling β€” a hardcoded scripted agent (baseline.py) that applies the exact correct rule for every task. Not 1.0 because explanation quality, seed-dependent actor responses, and partial-credit graders penalise even perfect decisions.

Untrained Llama-3-8B β€” meta-llama/Meta-Llama-3-8B-Instruct with no fine-tuning. Scores 0.811 overall but drops to 0.698 on hard tasks.

Untrained Qwen2.5-7B β€” Qwen/Qwen2.5-7B-Instruct before GRPO, 17 tasks Γ— 3 seeds. Mean 0.535 overall; 7/51 parse failures. Hard tasks (0.468) and long-horizon tasks (0.432) near floor β€” multi-step sequences undiscovered without training.

After GRPO β€” Qwen2.5-7B after 3 epochs. Easy tasks match the ceiling. Hard multi-step tasks need more epochs.

Detailed breakdowns: runs/baselines/qwen2-5-7b-instruct-2026-04-25/ | runs/baselines/scripted-agent-2026-04-25/ | runs/grpo/qwen-2.5-7b-3ep-2026-04-25/


Why It Matters

Enterprise AP automation is a $10B+ market. Current LLM deployments fail silently β€” a model that confidently approves a duplicate invoice looks identical to one that correctly rejects it, until the reconciliation audit three months later.

The reward signal in AP Commander is specifically engineered to close the shortcuts an untrained model exploits:

  • 3-way matching: Invoice ↔ PO ↔ GRN β€” amounts, quantities, vendor names must all align
  • Policy compliance: Freight caps, approval authority limits, and tax rates change per episode; the agent must read policy, not memorise it
  • Multi-step investigation: QUERY_VENDOR β†’ ESCALATE β†’ REJECT is rewarded; skipping to REJECT without the investigation is not
  • Scalable oversight: A second agent monitors completed clerk decisions, flags fraud with numeric evidence, and is penalised for false positives β€” making oversight trainable, not just bolted on

An agent cannot score well by guessing. It must cite specific dollar amounts, choose the correct reason code, and follow the right sequence. There is no shortcut.


Environment Design

Reward Signal (AP Clerk)

Scores are partial-credit across five components β€” composable, not monolithic:

Component Weight What it measures
Decision accuracy 38–55% Correct terminal action
Amount accuracy 20–45% Within 1% = full credit, within 8% = partial
Reason code 10–30% Correct classification of why
Explanation quality 10–20% Specific $ / % citations required
Process bonus 0–15% Correct intermediate steps before terminal

An agent that always outputs APPROVE_FULL at $0 scores near zero. An agent that gets the decision right but cites the wrong amount scores ~0.40. Full credit requires all five.

Reward Signal (Oversight Agent)

Condition Score
Correctly flag fraudulent episode with numeric evidence +0.90
Flag fraudulent episode without specific signal +0.70
False positive (flag a clean episode) βˆ’0.25
Correctly clear a clean episode +0.01

Task Library (24 tasks)

Easy / Medium / Hard (13 tasks, max 1–3 steps)

Task Difficulty Correct Decision
easy_perfect_match easy APPROVE_FULL
easy_no_po_found easy REJECT
medium_quantity_shortfall medium APPROVE_PARTIAL
medium_price_discrepancy medium REJECT
medium_split_delivery medium APPROVE_FULL
medium_vendor_mismatch medium REJECT
hard_policy_violation hard ESCALATE β†’ REJECT
hard_duplicate_invoice hard QUERY_VENDOR β†’ REJECT
hard_partial_po_match hard APPROVE_PARTIAL
hard_tax_discrepancy hard REJECT
hard_currency_conversion hard APPROVE_FULL or REJECT
hard_manager_preapproval hard ESCALATE β†’ APPROVE_FULL
hard_credit_memo hard APPROVE_PARTIAL or REJECT

Long-horizon (7 tasks, max 10–16 steps)

Task Steps Optimal Sequence
long_invoice_dispute 12 QUERY_VENDOR β†’ ESCALATE β†’ REJECT
long_policy_migration 10 HOLD β†’ compliance reveals new cap β†’ APPROVE_FULL
long_batch_reconciliation 15 3-way match in batch context β†’ APPROVE_FULL
long_manager_chain 14 ESCALATE (OOO) β†’ ESCALATE again (VP) β†’ APPROVE_FULL
long_fraud_investigation 16 QUERY_VENDOR β†’ ESCALATE β†’ REJECT
long_audit_trail 14 HOLD β†’ SOX review β†’ APPROVE_FULL with citations
long_multi_vendor_split 12 3 GRNs, first tranche only β†’ APPROVE_PARTIAL

Oversight tasks (4 tasks, via /oversight/*)

oversight_fraud_detection Β· oversight_pattern_recognition Β· oversight_false_positive_trap Β· oversight_explanation_quality

Adaptive Curriculum

easy (mean β‰₯ 0.70) β†’ medium (β‰₯ 0.65) β†’ hard (β‰₯ 0.68) β†’ long-horizon (β‰₯ 0.72) β†’ oversight

The /curriculum/next_task endpoint tracks performance history and recommends the next task automatically. No manual task selection needed during training.


Training

Algorithm: GRPO (Group Relative Policy Optimization)
Model: Qwen2.5-7B-Instruct, 4-bit NF4 quantized, LoRA (r=16) via PEFT
Framework: TRL β‰₯ 0.15 (standard stack β€” Unsloth dropped due to Python 3.10 / llm_blender dependency conflict on HF Spaces)
Environment: Live HF Space serves rewards over HTTP β€” no static dataset

HF Training Space (A10G)            HF Environment Space
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Qwen2.5-7B + LoRA       │─(HTTP)β–Ίβ”‚  AP Commander FastAPI server β”‚
β”‚  GRPOTrainer             β”‚β—„reward─│  24 tasks Β· graders Β· actors β”‚
β”‚  [env_reward, fmt_reward]β”‚        β”‚  seeded RNG Β· no static data β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

How it works

  1. Two independent reward functions (guide requirement: multiple signals, not one combined):

    • env_reward_fn β€” calls /reset + /step on the live environment, returns the grader score (0.01–0.99)
    • format_reward_fn β€” checks JSON validity and enum values (+0.05 / βˆ’0.05), independent of task correctness
  2. Dataset β€” built at runtime by calling /reset for each task Γ— seed combination. No static dataset; every prompt is a fresh synthetically-generated invoice scenario. Run 3: 322 prompts (easyΓ—5, mediumΓ—8, hardΓ—20, longΓ—20 seeds across 20 tasks).

  3. GRPO loop β€” for each prompt, 16 completions are sampled. The two reward functions score them independently. Group-relative advantages drive the policy update. per_device_train_batch_size = num_generations = 16 (TRL requirement).

  4. Reward hacking mitigations already in the environment:

    • _explanation_coherence() penalises keyword dumps (>40% keyword density)
    • _has_numeric_citation() requires actual dollar amounts, not vague language
    • Forged curriculum rejected β€” server-side history only
    • Oversight false-positive penalty is real negative (βˆ’0.25), not clamped to zero
  5. Model save β€” LoRA adapters saved directly (4-bit model, no naive upcast merge per guide point 16). Adapters uploaded to Pathikreet/ap-commander-adapter on HF Hub after each run. Run artifacts auto-uploaded to runs/grpo/MODEL-NEP-DATETIME/ in this repo β€” each run gets its own folder, nothing is overwritten.

Monitoring tracked per step

reward Β· format_rate Β· parse_failures Β· env_errors Β· decision_counts Β· per_task_mean Β· elapsed_min β€” written to metrics_live.json every reward call; Gradio UI polls every 15s.

The training Space is at Pathikreet/ap-commander-training. Open it, paste your HF token (needed for gated models like Llama-3), and click Start Training. The notebook at training/colab_training.ipynb uses the identical training loop for Colab (T4 GPU).


API Reference

AP Clerk

Endpoint Method Description
/reset POST Start episode: { task_id, seed? }
/step POST Submit action: { session_id, action }
/state GET Session state: ?session_id=...

Oversight Agent

Endpoint Method Description
/oversight/reset POST Start batch: { num_episodes?, seed? }
/oversight/step POST Submit verdict: { session_id, action }
/oversight/state GET Session state

Curriculum + Meta

Endpoint Method Description
/curriculum/next_task POST Get next task given session history
/tasks GET List all 24 tasks
/health GET Health check
/docs GET Swagger UI

Run It

# Local environment server
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 7860

# Docker
docker build -t ap-commander .
docker run -p 7860:7860 ap-commander

# Optimal scripted-agent baseline (all 20 tasks β†’ runs/baselines/scripted-agent-DATETIME/)
python baseline.py

# LLM baseline β€” untrained model eval (GPU required, run on HF Space or Colab)
# Results β†’ runs/baselines/MODEL-DATETIME/
MODEL_NAME=Qwen/Qwen2.5-7B-Instruct HF_TOKEN=hf_... python training/eval_baseline.py

# GRPO training (GPU required)
# Results β†’ runs/grpo/MODEL-NEP-DATETIME/
MODEL_NAME=Qwen/Qwen2.5-7B-Instruct NUM_EPOCHS=3 HF_TOKEN=hf_... python training/train.py

Project Structure

β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ main.py                 # FastAPI: all endpoints
β”‚   β”œβ”€β”€ environment.py          # APClerkEnvironment: reset/step/state
β”‚   β”œβ”€β”€ tasks.py                # 24 task generators + graders
β”‚   β”œβ”€β”€ models.py               # Pydantic models
β”‚   └── actors/
β”‚       β”œβ”€β”€ vendor_actor.py     # VendorActor (honest/fraudulent/confused)
β”‚       β”œβ”€β”€ manager_actor.py    # ManagerActor (budget authority, OOO chain)
β”‚       └── compliance_actor.py # ComplianceActor (SOX/GDPR/Internal Policy)
β”œβ”€β”€ oversight_environment.py    # Fleet AI OversightEnvironment
β”œβ”€β”€ training/
β”‚   β”œβ”€β”€ train.py                # GRPO training script (TRL, 4-bit, LoRA)
β”‚   β”œβ”€β”€ eval_baseline.py        # LLM baseline eval (no fine-tuning)
β”‚   └── colab_training.ipynb    # Colab notebook (identical pipeline)
β”œβ”€β”€ runs/
β”‚   β”œβ”€β”€ baselines/
β”‚   β”‚   β”œβ”€β”€ scripted-agent-YYYY-MM-DD/   # Optimal scripted agent results
β”‚   β”‚   β”œβ”€β”€ qwen2.5-7b-instruct-*/       # Untrained Qwen eval
β”‚   β”‚   └── llama-3-8b-*/                # Untrained Llama eval
β”‚   └── grpo/
β”‚       └── MODEL-NEP-YYYY-MM-DD_HHMM/  # Each GRPO run (timestamped, never overwritten)
β”‚           β”œβ”€β”€ training_results.json
β”‚           β”œβ”€β”€ results.png
β”‚           β”œβ”€β”€ reward_curve.png
β”‚           β”œβ”€β”€ metrics_live.json
β”‚           └── adapter/                 # LoRA weights (also on HF Hub)
β”œβ”€β”€ baseline.py                 # Scripted optimal agent (saves to runs/baselines/)
β”œβ”€β”€ inference.py                # LLM inference runner
β”œβ”€β”€ sim_run.py                  # Demo all tasks
β”œβ”€β”€ openenv.yaml                # Environment manifest
└── Dockerfile