Spaces:
Sleeping
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
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_VENDORwith one of three personas: honest, fraudulent, or confused - ManagerActor β reveals response to
ESCALATEbased on its budget authority and risk appetite; may be out-of-office, triggering a VP chain - ComplianceActor β reveals response to
HOLDwith 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
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)
| 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_matchimproved +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
| 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
- Temperature 1.1 β 55% format failures β model generated natural language instead of JSON; format reward Β±0.05 too weak to correct this
- Curriculum gating locked hard tasks β from epoch 3 onward
hard_policy_violationand other hard tasks were silently redirected to easy tasks; hard/long tasks stopped receiving any gradient signal - Entropy collapsed to 0.23 β model defaulted to REJECT for 59% of decisions; APPROVE_PARTIAL and QUERY_VENDOR nearly absent
- frac_reward_zero_std = 0.5 β half of all GRPO groups had identical rewards across 16 generations; zero learning signal for those steps
- 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.1added (prevents entropy collapse; waskl_coeffwhich 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 β 1PYTORCH_CUDA_ALLOC_CONF=expandable_segments:Trueto 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
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
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)
Optimal Ceiling vs Untrained Llama-3-8B (per task)
Untrained Qwen2.5-7B-Instruct baseline (17 tasks, 3 seeds each)
| 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-Instructwith no fine-tuning. Scores 0.811 overall but drops to 0.698 on hard tasks.Untrained Qwen2.5-7B β
Qwen/Qwen2.5-7B-Instructbefore 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 β REJECTis rewarded; skipping toREJECTwithout 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
Two independent reward functions (guide requirement: multiple signals, not one combined):
env_reward_fnβ calls/reset+/stepon 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
Dataset β built at runtime by calling
/resetfor 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).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).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
Model save β LoRA adapters saved directly (4-bit model, no naive upcast merge per guide point 16). Adapters uploaded to
Pathikreet/ap-commander-adapteron HF Hub after each run. Run artifacts auto-uploaded toruns/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











