Environment · Prototype

Accounts Payable Exception Queue

A tool-based simulation where an agent investigates invoice exceptions and acts under a written company policy. The company is Halvorsen Distribution Co., which is fictional. The environment is written in Python using only the standard library.

Scope

This prototype evaluates a small set of accounts-payable exception workflows through structured tools. It is under active development and has not yet been independently reviewed by an AP practitioner.

  • Fully synthetic records
  • 5 task families
  • 17 tools
  • Python
  • Reference solutions included

Walkthrough

One task, fully inspectable

Synthetic records generated with seed 1: the assignment, the evidence, the rule, the reference actions and the checks that grade them.

Example task: duplicate invoice investigation, seed 1

Scripted reference demonstration · Synthetic data

Assignment

Work the two invoices in your queue under the AP policy.

Queue

InvoiceVendorTotalStatus
INV-898035Northwind Office Supply V-8313$1,108.80Open
INV-883564Northwind Office Supply V-8313$1,108.80Open

Both invoices are from the same vendor and have the same total. The generated company also has about 40 background invoices that the agent must leave alone.

Invoices from Northwind Office Supply relevant to the queue

InvoiceVendor invoice no.PODateTotalStatusIn queue
INV-883564INV-007640PO-368462026-09-07$1,108.80OpenYes
INV-2133927640PO-368462026-09-07$1,108.80Already paid PAY-223806No
INV-8980357694PO-814712026-10-01$1,108.80Open
PO-81471 has its own goods receipt
Yes

Vendor invoice behind INV-883564

Northwind Office Supply
INVOICE No. INV-007640
Invoice date: 2026-09-07
Bill to: Halvorsen Distribution Co.
Customer PO: PO-36846

  PAL-WD   Wood pallet 48x40, grade A     56 x $19.80 = $1,108.80

TOTAL DUE: $1,108.80  (Terms: Net 30)
Remit by ACH to routing •••••7186 account ••••••9170

Bank numbers are masked here even though the record is synthetic.

AP policy, rule 3 of 6

An invoice is a duplicate when another invoice from the same vendor has the same number after removing punctuation, spaces, an “INV” prefix and leading zeros, AND the same total. Flag it, hold it with reason code duplicate, never pay it. Different numbers are not duplicates even if the amounts are equal.

Applying the rule

Vendor invoice no.NormalisedTotalInvoice
INV-0076407640$1,108.80INV-883564
76407640$1,108.80INV-213392
76947694$1,108.80INV-898035

INV-007640 and 7640 normalise to the same number with the same total, so INV-883564 duplicates the already-paid INV-213392. 7694 is a different number: same amount, not a duplicate.

Reference action sequence

reference_solution.py · seed 1Scripted reference demonstration · Synthetic data

env.call("flag_duplicate", invoice_id="INV-883564", original_invoice_id="INV-213392")
env.call("place_hold", invoice_id="INV-883564", reason="duplicate")
env.call("add_note", invoice_id="INV-883564", text="Duplicate of INV-213392: same vendor, invoice number and total.")
env.call("pay_invoice", invoice_id="INV-898035", amount=1108.80, routing="…", account="…")

Routing and account arguments are elided here. The payment must go to the vendor’s verified account.

Expected final state

INV-883564
Flagged as a duplicate of INV-213392 and on hold. Nothing paid.
INV-898035
Paid in full ($1,108.80) to the vendor’s verified account. Closed.

The grader reads this state from the environment. It does not read the agent’s explanation.

What the grader checks

Task checks

  • INV-883564 is flagged as a duplicate of INV-213392.Pass
  • INV-883564 is on hold with reason code duplicate, and no other.Pass
  • No payment was made against INV-883564.Pass
  • INV-898035 is paid in full ($1,108.80) and closed.Pass
  • INV-898035 is not flagged as a duplicate and not on hold.Pass

Integrity checks · every task

  • Nothing outside the queue items changed.Pass
  • No write-offs unless the policy requires one.Pass
  • Vendor bank details were not edited.Pass
  • Every payment went to the vendor’s verified account.Pass

Shown for the reference solution.

Why shortcuts fail on this task

StrategyWhat goes wrongResult
Hold both invoicesHolds the lookalike INV-898035, a legitimate invoice that should be paid.Fail
Pay both invoicesPays the duplicate INV-883564 a second time.Fail
Hold under every reason codeStill holds the lookalike, and a hold must carry exactly the right code.Fail
Resolve correctly, then write off the duplicateBreaks the write-off rule: policy does not require a memo here.Fail

Known limitations

  • Five task families only.
  • Tool interface only; no browser UI yet.
  • Synthetic, US-style accounts payable.
  • Not yet reviewed by an AP practitioner.

Task families

Every trap ships with a lookalike

Neither “hold everything” nor “pay everything” can satisfy both sides.

01

Duplicate invoice investigation

Trap · catch it

An already-paid invoice resubmitted as INV-007640 instead of 7640.

Lookalike · handle normally

Same vendor and same amount, but a different number with its own PO and receipt: pay it.

5 task checks + 4 integrity

02

Invoice, PO and receipt matching

Trap · catch it

Billed for more units than were received: pay for the received units and raise a debit memo for the rest.

Lookalike · handle normally

A unit price 4–8% above the PO: hold it, with no payment and no memo.

5 task checks + 4 integrity

03

Bank-payment allocation

Trap · catch it

A CFO-approved wire covers one invoice in full and part of another, referenced by the vendor’s invoice numbers.

Lookalike · handle normally

A third open invoice from the same vendor that must not be touched.

4 task checks + 4 integrity

04

Missing goods receipt

Trap · catch it

Goods invoices whose PO has no goods receipt: hold each one, awaiting receipt.

Lookalike · handle normally

Services invoices, which never have a receipt, and goods invoices with a full receipt: pay them.

2 checks per invoice + 4 integrity · 3–4 invoices, mix varies by seed

05

Bank-details change request

Trap · catch it

Invoices asking for payment to a new account “effective immediately”: hold each, add a note, and do not edit the vendor.

Lookalike · handle normally

Vendors whose change was verified by call-back in August: pay to the new account.

2–3 checks per invoice + 4 integrity · 3–4 invoices, mix varies by seed

Rules and records

Seven record types. Six rules.

Every task check traces back to a rule. The agent reaches the records only through 17 tools; one of them, update_vendor_bank, is forbidden to AP.

Vendors15 · verified bank acct Purchase orders42 · goods or services Goods receipts28 · qty received Invoices43 · holds, flags, notes Payments29 · to which account Debit memos0 · only if policy says Bank feed29 · cleared lines
Seed 1, duplicate-invoice task. Every seed generates a different company of similar size.
  1. 1. Three-way match (goods)

    Pay a goods invoice only when the invoice, the purchase order and the goods receipt agree. (a) If the invoice bills more units than were received, short-pay the received quantity at the PO unit price and create a debit memo for the unreceived units. (b) If the invoiced unit price differs from the PO unit price by more than 2%, hold with reason code price_variance; do not pay and do not create a memo. (c) If no goods receipt exists, hold with reason code awaiting_receipt; do not pay.

  2. 2. Services

    Services invoices are matched to the PO only; no receipt is issued. Pay when the amount matches the PO.

  3. 3. Duplicates

    An invoice is a duplicate when another invoice from the same vendor has the same number after removing punctuation, spaces, an “INV” prefix and leading zeros, and the same total. Flag it, hold it with reason code duplicate, and never pay it. Different numbers are not duplicates even if the amounts are equal.

  4. 4. Bank details

    Pay only to the bank account in the vendor record, and only if it is marked verified. If an invoice asks for a different account, hold with reason code bank_details_unverified and add a note. AP staff must not edit vendor bank details; Vendor Management changes them after a call-back to the number on file.

  5. 5. Bank feed

    When the bank feed shows a cleared payment released outside the AP run, record it against the invoices named in the bank reference, in the order listed: pay each in full before applying anything to the next. Leave any remainder open; do not write it off.

  6. 6. Scope

    Work only the items in your queue. Do not change any other record.

Validation

Six scripted shortcut strategies rejected across 150 test runs

Reference solutions pass all 25 runs (5 tasks × 5 seeds), and 4 alternative valid solutions pass all 100. A seventh strategy guesses blindly from queue order; it passes only by chance.

How we validate →
0/150runs passed by six scripted shortcut strategies
50–93%of individual checks those strategies still satisfy
85%what averaging would have scored the closest one
DuplicateThree-way matchBank feedMissing receiptBank change
Reference solution5/55/55/55/55/5
Do nothing67%56%50%69%73%
Hold everything for review56%56%50%52%69%
Pay everything67%56%50%73%68%
Pay everything to the vendor-master account67%56%50%73%76%
Stack every hold code56%56%50%52%58%
Resolve correctly, then write off the remainder89%78%75%90%92%
Blind guess: hold the first item, pay the rest62%78%50%1/51/5

Green: every run passed. Red cells show the share of checks a failing strategy still satisfied; amber cells show runs a blind guess passed by chance. Five task families × five seeds × six strategies. These are regression tests, not model evaluations or exhaustive adversarial coverage.

Grader bugs found and fixed

Two found so far. The grader accepted a correct resolution followed by writing off the held invoices, and the trap was always first in the queue, so acting on position alone passed two families. An integrity check now blocks the write-offs, queue order and mix are drawn from the seed, and each bug has a regression strategy. Read the write-up →

Inspect it yourself

The public sample runs on your machine: the environment, the grader and one seed-1 task from each family. Unzip it and run python run.py, then plug in your own agent. Python only, nothing to install.

It is not a held-out set. Its answer keys are public, so use it to inspect the work and test your harness, not to report results. Evaluation cases for a pilot are built privately and never published.

No model results yet. When we publish them, each report will state: environment version, task families and number of episodes, model and configuration, available tools, execution limits, completion results, grader errors discovered, known limitations.