new-environment pipeline: scripted arms required, model arms captured when spark-1 is up
An environment ships on its scripted agents alone — they are what the reward flip is built on and they need no model. A recorded model attempt is captured once from spark-1 for the Watch tab when it is serving, and the environment does not block on it when it is not.
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
# alert-triage — specification
|
||||
|
||||
Vertical: `financial-crime-alert-triage` (`src/content/verticals.ts`, rank 4).
|
||||
Package: `envs/alert_triage/`. Demo: `src/demos/alert-triage/`. Traces:
|
||||
`public/traces/alert-triage/`.
|
||||
|
||||
This document is written before any code and is meant to be attacked. The two
|
||||
things it has to survive: a sceptic calling the grader subjective, and a
|
||||
sceptic finding a policy that maxes the reward while doing something a BSA
|
||||
officer would fire an analyst for.
|
||||
|
||||
**In one paragraph.** One episode is one transaction-monitoring alert on one
|
||||
synthetic customer. The agent sees what the analyst's screen shows — the alert,
|
||||
the triggering transactions, the KYC file, a twelve-month summary — and may pay
|
||||
analyst-hours for lookups (itemised history, a counterparty, prior alerts,
|
||||
documents on file). It ends the episode by *closing* the alert with a cited
|
||||
rationale or *escalating* it for a suspicious-activity filing with a typology
|
||||
and the transactions that constitute it. The generator planted the answer: the
|
||||
alert is either a benign scenario with a specific explaining fact, or a
|
||||
laundering typology with a specific set of transactions. The grader compares
|
||||
the disposition and the citations to what was planted, and charges the hours.
|
||||
Nothing in it reads prose.
|
||||
|
||||
---
|
||||
|
||||
## 1. The episode
|
||||
|
||||
**One alert.** Deterministic in the seed. The generator draws a scenario
|
||||
template from the seed (FNV-1a over the decimal seed selects the template and
|
||||
seeds a hand-written xorshift32 for its parameters — never a built-in RNG,
|
||||
see AGENTS.md), and produces:
|
||||
|
||||
- the **customer** (`C-####`): individual or business, occupation/business
|
||||
category, stated income or revenue, expected monthly cash, country, account
|
||||
opened, KYC last refreshed, PEP flag, prior alert and prior SAR counts;
|
||||
- twelve months of **account history**: a monthly summary table (cash in/out,
|
||||
wires in/out, ACH in/out, transaction count), and the itemised transactions
|
||||
behind it, each with a stable id `T-###`, a date, a channel, an amount, a
|
||||
branch or counterparty id `CP-##`;
|
||||
- the **alert** (`A-####`): the rule that fired (`R-STR-01` structuring,
|
||||
`R-VEL-02` rapid movement, `R-CASH-03` cash above KYC expectation,
|
||||
`R-WIRE-04` wires to a high-risk jurisdiction, `R-DORM-05` dormant-account
|
||||
reactivation), the fire date, and the triggering transactions itemised;
|
||||
- the **hidden truth**, never shown: `label ∈ {benign, suspicious}`, the
|
||||
`typology` if suspicious, and the `planted` citation set (§4).
|
||||
|
||||
**The free screen.** Turn 0 shows the alert, its triggering transactions, the
|
||||
KYC block and the monthly summary. This costs a fixed `SCREEN_HOURS` — the
|
||||
analyst has to read it whatever they do next.
|
||||
|
||||
**Turns.** Up to `MAX_TURNS = 8` replies. Each reply is one lookup or one
|
||||
disposition (§3). Each turn spent — accepted *or* rejected — costs
|
||||
`TURN_HOURS`; a lookup adds its own price.
|
||||
|
||||
**What ends it.** An accepted `close` or `escalate`, or the eighth turn
|
||||
without one. The latter is `outcome: aborted` and scores 0.000 on every
|
||||
component: an alert nobody disposed of is still in the queue, which is the
|
||||
worst outcome for throughput and, on a suspicious alert, is a miss.
|
||||
|
||||
**Scenario templates.** Six benign, five suspicious. Every template is written
|
||||
from public typology descriptions (FinCEN SAR narrative guidance, FATF
|
||||
typology reports); none is a real case.
|
||||
|
||||
| id | label | rule that fires | what the screen shows | where the answer lives |
|
||||
|---|---|---|---|---|
|
||||
| B1 seasonal cash | benign | R-CASH-03 | landscaping / tax-prep business, cash spikes in season | `kyc.expected_activity` says seasonal, `kyc.expected_monthly_cash` covers peak |
|
||||
| B2 processor settlement | benign | R-VEL-02 | business receives processor wires, pays contractors within 48h | `doc.processor_agreement` on file |
|
||||
| B3 property sale | benign | R-VEL-02 / R-DORM-05 | individual receives title-company wire, moves it to brokerage | `doc.closing_statement` |
|
||||
| B4 cash restaurant | benign | R-STR-01 | daily cash deposits, three happen to land in 8,000–9,999 | `kyc.expected_monthly_cash` ≥ observed; 12-month cash profile flat |
|
||||
| B5 family remittance | benign | R-WIRE-04 | monthly wire to one named individual in a high-risk country | `kyc.remittance_declared` names the destination |
|
||||
| B6 inheritance | benign | R-DORM-05 | dormant account receives estate wire | `doc.probate_letter` |
|
||||
| S1 structuring | suspicious, **visible** | R-STR-01 | 45k stated income; 8 cash deposits 9,200–9,900 in 12 days across 3 branches | the 8 deposits |
|
||||
| S3 money mule | suspicious, **visible** | R-VEL-02 | student/retiree; 4 inbound P2P from unrelated individuals, each out within 24h to an exchange | the in/out pairs |
|
||||
| S6 smurfing over time | suspicious, **visible** | R-CASH-03 | cash business; monthly cash tripled over 12 months and no single deposit ever exceeded 9,999 | the deposits in the last 3 months |
|
||||
| S2 funnel account | suspicious, **hidden** | R-STR-01 or R-CASH-03 | *generated by B4's template* — screen is identically distributed to a benign restaurant | `counterparty` lookup: its 3 outbound-wire counterparties are shared with 6–14 other alerted accounts; the wires are the evidence |
|
||||
| S5 serial closer | suspicious, **hidden** | R-VEL-02 | *generated by B2's template* — processor agreement on file, contractors paid | `prior_alerts` lookup: 3 prior alerts in 9 months, each closed by a different analyst with the same boilerplate; `documents` shows the agreement's counterparty is not the payer |
|
||||
|
||||
The **hidden** tier is the environment's whole argument. S2 and S5 are
|
||||
produced by *the same generator code* as B4 and B2 respectively, with the
|
||||
suspicious signal overlaid only in the data a lookup returns. The free screen
|
||||
for a hidden case is therefore not merely similar to a benign one; it is drawn
|
||||
from the same distribution. A policy that decides from the screen cannot tell
|
||||
them apart, by construction, not by our failing to try hard enough.
|
||||
|
||||
Mix by seed: 55% benign, 30% visible-suspicious, 15% hidden. This is
|
||||
deliberately **not** the real base rate — real programmes close the great
|
||||
majority of alerts as false positives, and at that rate a 24-seed probe would
|
||||
contain about one suspicious alert and the ladder would measure noise. The page
|
||||
must say this (§8, risk 1).
|
||||
|
||||
## 2. The data
|
||||
|
||||
**Synthetic, entirely, and labelled synthetic on the page — in the surface,
|
||||
not a footnote.** No real customer, account, transaction, counterparty or
|
||||
analyst exists behind any of it. Names come from a synthetic name list
|
||||
committed under `envs/alert_triage/names/` with its own `PROVENANCE.md`;
|
||||
business names are composed from committed word lists ("Harbor Lane Bistro
|
||||
LLC"). Amounts, dates, branch numbers and counterparties are generated.
|
||||
|
||||
Licence: the generator and its lists are ours, Apache-2.0 with the repository.
|
||||
The public documents the templates are *written from* — FinCEN's SAR narrative
|
||||
guidance and FATF typology reports — are cited as references on the evidence
|
||||
tab, not shipped, and not represented as data.
|
||||
|
||||
Why synthetic is the only honest option here: real alert data is customer
|
||||
financial data under BSA confidentiality; a SAR's existence may not be
|
||||
disclosed. There is no licensable real dataset for this task, and any page
|
||||
implying one would be lying.
|
||||
|
||||
## 3. The action set
|
||||
|
||||
Every reply is exactly **one JSON object** — the first `{…}` the parser can
|
||||
balance, or the contents of the first ```` ```json ```` fence if present. The
|
||||
system prompt says so and shows the four shapes.
|
||||
|
||||
| action | fields | cost | what comes back |
|
||||
|---|---|---|---|
|
||||
| `{"action":"lookup","what":"history","month":"2026-01"}` | a month inside the 12 | `LOOKUP_HOURS` | that month itemised, with `T-` ids and `CP-` ids |
|
||||
| `{"action":"lookup","what":"counterparty","id":"CP-7"}` | a `CP-` id that appeared on screen or in a fetched month | `LOOKUP_HOURS` | name, type, jurisdiction, first seen, **number of other alerted accounts sharing this counterparty in 12 months** |
|
||||
| `{"action":"lookup","what":"prior_alerts"}` | — | `LOOKUP_HOURS` | prior alerts: rule, date, disposition, closing analyst, rationale snippet |
|
||||
| `{"action":"lookup","what":"documents"}` | — | `DOC_HOURS` | documents on file with `doc.` ids and one-line descriptions |
|
||||
| `{"action":"close","reason":"CONSISTENT_WITH_PROFILE","cites":["kyc.expected_monthly_cash"],"note":"…"}` | `reason` ∈ {`CONSISTENT_WITH_PROFILE`, `DOCUMENTED_SOURCE_OF_FUNDS`, `PREVIOUSLY_REVIEWED`, `RULE_ARTEFACT`}; `cites` a non-empty list of ids; `note` free text | ends episode | — |
|
||||
| `{"action":"escalate","typology":"STRUCTURING","cites":["T-101","T-102"],"narrative":"…"}` | `typology` ∈ {`STRUCTURING`, `FUNNEL_ACCOUNT`, `MONEY_MULE`, `RAPID_MOVEMENT`, `UNKNOWN`}; `cites` non-empty; `narrative` free text | ends episode | — |
|
||||
|
||||
Citable ids: `kyc.<field>` for any field shown in the KYC block, `doc.<id>`,
|
||||
`T-<n>`, `CP-<n>`, `A-<n>` (a prior alert). An id the agent has not been
|
||||
shown is accepted and simply will not match anything planted — the grader does
|
||||
not need to know what the agent saw, only what it cited.
|
||||
|
||||
**Malformed replies.** No parseable object, an unknown `action`, an enum value
|
||||
outside its set, an empty `cites`, a month outside the window, a `CP-` id
|
||||
that does not exist, a lookup already made: all are **rejected**. A rejection
|
||||
costs a turn and `TURN_HOURS`, is counted in `rejected_replies`, returns a
|
||||
one-line reason in the format the prompt promised, and never raises. The
|
||||
engine is pure and cannot crash on input. A repeated lookup is rejected rather
|
||||
than re-charged so the transcript stays a record of distinct things learned.
|
||||
|
||||
**Hour constants** (the generator's config, shown on the page as such):
|
||||
`SCREEN_HOURS 0.50`, `TURN_HOURS 0.05`, `LOOKUP_HOURS 0.25`,
|
||||
`DOC_HOURS 0.10`. These are **assumptions, not measurements**, and the page
|
||||
must not present them as industry figures (§8, risk 2). The implement stage
|
||||
may tune them to make the ladder's ordering hold, and must record the values
|
||||
it ships in `reward.py` where the page quotes them.
|
||||
|
||||
## 4. The grader
|
||||
|
||||
Everything below is a pure function of `(world, transcript)`. No model, no
|
||||
human, no prose is read anywhere in it.
|
||||
|
||||
From the world (regenerated from the seed): `label`, `typology`, `planted`.
|
||||
From the transcript: the accepted actions in order, the count of rejected
|
||||
replies, and the final disposition.
|
||||
|
||||
```
|
||||
hours_spent = SCREEN_HOURS + TURN_HOURS × turns_spent + Σ lookup prices
|
||||
correct = (label == benign and disposition == close)
|
||||
or (label == suspicious and disposition == escalate)
|
||||
```
|
||||
|
||||
**`planted`** is the minimal set of ids that resolves the alert, fixed by the
|
||||
template at generation time:
|
||||
|
||||
- benign: the explaining fact(s) — one or two `kyc.`/`doc.` ids (table in §1);
|
||||
- suspicious: the transaction ids that constitute the typology (S1: the eight
|
||||
deposits; S2: the outbound wires to the shared counterparties; S3: the
|
||||
in/out pairs; and so on).
|
||||
|
||||
Where a template admits two reasonable rationales (B4 can be closed on the KYC
|
||||
expectation or on a prior closed alert with the same rationale), `planted` is
|
||||
a list of **alternates**, and the score takes the best-matching one. Still
|
||||
deterministic; less opinionated.
|
||||
|
||||
**`evidence`** = F1 between `cites` and the best alternate of `planted`. If
|
||||
`correct` is false, `evidence` is 0 — a false escalation is checked against an
|
||||
empty set, a missed one against facts that do not exist for that label.
|
||||
Cite-everything scores what its precision earns (one planted id among eight
|
||||
cited is F1 0.22); cite-nothing is rejected at parse.
|
||||
|
||||
**`reference_hours`** — the `hours` denominator — is *the hours of the cheapest
|
||||
shipped policy that reached the correct disposition on this seed*, computed by
|
||||
running the two shipped reference policies (§6) through the engine at
|
||||
generation time, exactly as wordle computes `reference_depth` by running the
|
||||
solver. On benign and visible seeds that is `fast`; on hidden seeds it is
|
||||
`thorough`. It is stored in the trace as a metric so the browser can verify
|
||||
without running the policies, and its absence makes a run *unverifiable*, never
|
||||
zero.
|
||||
|
||||
**Conformance.** Two digests, both gated in CI: (a) SHA-256 over the canonical
|
||||
JSON of the generated world for seeds 0–4095 — the browser must show the same
|
||||
alert for `?seed=` as the Python generated; (b) SHA-256 over the scorer applied
|
||||
to a fixed cross-product of synthetic dispositions (every template × every
|
||||
disposition × a grid of citation sets and hour totals). The generator has to be
|
||||
ported, not just the scorer, because the Play tab needs it.
|
||||
|
||||
## 5. The reward components
|
||||
|
||||
Weights sum to 1.0. One counterweight.
|
||||
|
||||
| key | label (exec-facing) | weight | role | definition |
|
||||
|---|---|---|---|---|
|
||||
| `caught` | Nothing slipped through | **0.55** | counterweight | 1.0 if a disposition was reached and either the alert was benign or it was escalated. 0.0 if the alert was suspicious and closed, or no disposition was reached. |
|
||||
| `hours` | Cleared it in the hours budgeted | **0.25** | objective | `min(1, reference_hours / hours_spent)` if `correct`, else 0.0. A fast wrong answer saves nothing. |
|
||||
| `evidence` | Named the facts that decide it | **0.20** | objective | F1 of `cites` against `planted` (best alternate), 0.0 if the disposition was wrong. The prose is shown, never scored. |
|
||||
|
||||
**Why this is the asymmetry the vertical promised.** A missed escalation zeroes
|
||||
`caught` (0.55), and because the call was wrong it also zeroes `hours` and
|
||||
`evidence`: the episode scores 0.000. The most any policy can gain by being
|
||||
fast is `hours`'s 0.25. One miss costs more than every hour saved, and you can
|
||||
read that off the weights rather than off a policy memo. The reward editor is
|
||||
where a BSA officer argues with it.
|
||||
|
||||
**The tension, named.**
|
||||
|
||||
- *The crude policy that maxes the objective:* close on turn one, read
|
||||
nothing. It takes `hours` (1.0 on every benign seed — it spent less than any
|
||||
reference) and is stopped by `caught`, which it scores at the benign base
|
||||
rate and no higher.
|
||||
- *The crude policy that maxes the counterweight:* escalate on turn one,
|
||||
everything. `caught` 1.0, stopped by `hours` (0 on every benign seed — wrong
|
||||
call) and `evidence` (0 on every benign seed).
|
||||
- *The good policy that pays the counterweight's price:* **`fast`** — decides
|
||||
from the screen and the KYC file, gets every benign and every visible case
|
||||
right, cites correctly, spends `SCREEN_HOURS` and one turn. It defines the
|
||||
`hours` denominator on 85% of seeds and scores 1.0 there. It structurally
|
||||
cannot see a funnel counterparty or a serial-closer history, so it scores
|
||||
`caught` ≈ 0.85 and is not 1.0.
|
||||
- *The good policy that takes the counterweight outright:* **`thorough`** —
|
||||
pulls documents, prior alerts, every counterparty on screen and the itemised
|
||||
peak months, then decides with everything. `caught` 1.0, `evidence` 1.0,
|
||||
`hours` ≈ 0.4 (it spends roughly four times the screen cost on the 85% of
|
||||
seeds where the screen was enough).
|
||||
|
||||
Neither dominates: `thorough` wins `caught` and `evidence`; `fast` wins
|
||||
`hours`. At the shipped weights `thorough` is intended to edge `fast` by a
|
||||
small margin (≈0.86 vs ≈0.85 on the rough arithmetic); moving `hours` to 0.35
|
||||
flips it. That the flip is one slider away is the demonstration.
|
||||
|
||||
**Metrics** (weight zero — rendered, never summed): `hours_spent`,
|
||||
`reference_hours`, `lookups`, `turns_used`, `rejected_replies`,
|
||||
`false_escalation` (0/1), `missed` (0/1), `typology_match` (1/0, `null` on a
|
||||
close), `label` (rendered as the reveal after the episode ends).
|
||||
|
||||
`typology` and `reason` are required for a well-formed action and are
|
||||
otherwise unscored. `typology_match` is a diagnostic. Scoring it would
|
||||
double-count `caught` with an enum the agent can guess.
|
||||
|
||||
## 6. The probe ladder
|
||||
|
||||
Run over 24 seeds; the probe asserts the seed set contains at least one seed of
|
||||
each tier (benign, visible, hidden) or aborts before measuring anything.
|
||||
|
||||
| policy | behaviour | expected |
|
||||
|---|---|---|
|
||||
| `inaction` | reasons in prose, never emits an object | **0.000** on every component, exactly |
|
||||
| `malformed` | emits an object every turn with an unknown action or an empty `cites` | **0.000** exactly; `rejected_replies` = 8 |
|
||||
| `close_blind` | turn one: `close`, `reason` CONSISTENT_WITH_PROFILE, cites `kyc.business_type` | `caught` = benign share (≈0.55); `hours` = benign share; `evidence` small; total ≈ 0.45 |
|
||||
| `close_cites_all` | turn one: `close` citing every KYC field and every triggering transaction | same as above with `evidence` ≈ 0.55 × 0.2; must score strictly below `fast` |
|
||||
| `escalate_blind` | turn one: `escalate`, typology from the rule's nominal mapping, cites all triggering transactions | `caught` 1.0; `hours` = suspicious share (≈0.45); `evidence` ≈ 0.3; total ≈ 0.7; must score strictly below both good policies |
|
||||
| `fast` | decides from the screen + KYC; closes when the KYC or a screen-visible fact explains the alert, escalates when the screen shows a typology; cites the explaining facts / constituting transactions | `hours` 1.0 on non-hidden seeds, 0 on hidden; `caught` ≈ 0.85 and **< 1.0**; `evidence` ≈ 0.85 |
|
||||
| `thorough` | lookups: documents, prior_alerts, every `CP-` on screen, itemised history for the two peak months; then decides with everything | `caught` **= 1.0 exactly**; `evidence` = 1.0; `hours` ≈ 0.4 and **< fast's** |
|
||||
| `thorough_wasteful` | pulls all seven lookups the turn budget allows — including months that carry nothing — then decides exactly as `thorough` | same `caught` and `evidence` as `thorough`; total **strictly below** `thorough` — proves the hour budget binds |
|
||||
|
||||
Assertions, in the style of `envs/probe.py`:
|
||||
|
||||
1. `inaction` and `malformed` total exactly 0.000.
|
||||
2. `thorough` scores exactly 1.0 on `caught` — the counterweight is attainable.
|
||||
3. `fast` scores strictly below 1.0 on `caught` — the counterweight bites the
|
||||
policy that defines the objective's denominator.
|
||||
4. `fast` strictly beats `thorough` on `hours` — paying the counterweight's
|
||||
price is a real price.
|
||||
5. Neither `fast` nor `thorough` dominates the other on every component.
|
||||
6. `escalate_blind`, `close_blind` and `close_cites_all` each total strictly
|
||||
below both good policies. A cheat the reviewers name gets a rung here.
|
||||
7. `thorough_wasteful` totals strictly below `thorough`.
|
||||
8. No weighted component is flat across the ladder.
|
||||
|
||||
Note `hours` is scored over turns **spent**, rejected replies included, via
|
||||
`TURN_HOURS`, so a policy that jams the parser and then decides does not read
|
||||
as cheaper than one that decided cleanly.
|
||||
|
||||
## 7. The held-out slice
|
||||
|
||||
Three cuts, all deterministic in the seed:
|
||||
|
||||
1. **Seed bucket.** `fnv1a(str(seed)) % 8 == 7` is held out — never captured,
|
||||
never probed, never in a training sweep. About 12.5% of seeds.
|
||||
2. **Moved threshold.** Within the held-out bucket, structuring and cash
|
||||
templates (B4, S1, S2, S6) are generated in a second currency with that
|
||||
jurisdiction's reporting threshold, so the band is not 8,000–9,999 USD, and
|
||||
the rule ids are renamed. An agent that learned "9,xxx and R-STR-01" rather
|
||||
than "just under the threshold, repeatedly, across branches" is caught
|
||||
here. This is the honest form of the wordle page's "nobody is pushing back"
|
||||
limit: the adversary has *moved*, not responded (§8, risk 4).
|
||||
3. **Re-ordered screen.** The held-out screen renders the KYC block before the
|
||||
alert and the summary in reverse chronological order. Format memorisation
|
||||
is not triage.
|
||||
|
||||
The page reports the training-slice and held-out numbers side by side, and
|
||||
never one as the other.
|
||||
|
||||
## 8. Risks — stated, not hidden
|
||||
|
||||
1. **The ground truth is what the generator planted, and that is easier than
|
||||
reality in three ways.** Every alert has a knowable answer; real SAR
|
||||
decisions are human judgements and a filed SAR is not a proven crime. Eleven
|
||||
templates stand in for a typology space in the hundreds. And the 55/45 mix
|
||||
is far from the real false-positive rate, which is widely reported to be
|
||||
above ninety percent; we do not re-quote a figure, we state that our mix is
|
||||
chosen so every arm meets both labels in a small seed set. The page must say
|
||||
that "hours per thousand alerts" here is a ratio inside this environment
|
||||
and not a production number.
|
||||
2. **The hour constants are assumptions.** 0.50h to read a screen, 0.25h per
|
||||
lookup, 0.05h per turn. They are shown as the generator's config. No
|
||||
industry figure is cited for them because we have not measured one.
|
||||
3. **The narrative prose is unscored.** Only the disposition, the citations and
|
||||
(as a metric) the typology are graded. A fluent wrong narrative and a terse
|
||||
right one score the same, and the page says so beside the narrative. This is
|
||||
the `legal-playbook-redline` move: score the checkable core, display the
|
||||
judgement half, never put a judge behind a bar.
|
||||
4. **The adversary is static.** The launderer shaped the data to pass the
|
||||
screen, and the held-out slice moves the threshold, but nothing responds to
|
||||
the agent's policy during training. The wordle page's limit "nobody is
|
||||
pushing back… no counterparty adapting" names this demo as its answer; the
|
||||
demo stage should claim only what §1 and §7 deliver — an adversary who
|
||||
designed the data to pass, and moved once — and the ship stage should
|
||||
consider softening that wordle limit's wording. (That file belongs to
|
||||
another stage; not edited here.)
|
||||
5. **The hidden tier is hidden only if the generator keeps it so.** If the
|
||||
implement stage lets any tell leak into the free screen — a different name
|
||||
distribution, a rounder amount, a branch number — a policy tuned to the
|
||||
leak catches S2/S5 from the screen and the tension collapses. Mitigation is
|
||||
structural (S2 and S5 are B4 and B2's generator with a lookup-only overlay)
|
||||
and assertion 3 catches a leak the shipped `fast` policy could exploit, but
|
||||
not one only an RL agent would find. A future review should try to
|
||||
classify hidden-vs-benign from the screen alone and expect chance.
|
||||
6. **`caught` is 1.0 on every benign alert by construction.** On a single
|
||||
benign seed, a close-everything run looks perfect. The page must argue the
|
||||
counterweight from the ladder over seeds, never from one run — the same
|
||||
caveat as a lucky first guess in wordle.
|
||||
7. **The planted set is an opinion about what "the" evidence is.** Alternates
|
||||
soften it; they do not remove it. Where a reviewer finds a rationale the
|
||||
grader refuses that a BSA officer would accept, the fix is another alternate
|
||||
in the template, not a judge.
|
||||
8. **Capture may find the weak arm cannot emit JSON.** Qwen3-8B without
|
||||
thinking may reject its way to 0.000 on most seeds. That is a finding to
|
||||
show, not a bug to hide, but a ladder with three arms at 0.000 teaches
|
||||
nothing — the capture stage must include `fast` and `thorough` as
|
||||
`generated` arms so the reward has something to rank.
|
||||
9. **Terminology is real, cases are not.** BSA, SAR, FinCEN, FATF and the
|
||||
typology names are public vocabulary and are used correctly. No scenario
|
||||
is drawn from a real case, and no institution is named or implied.
|
||||
|
||||
## 9. What the next stages need from this
|
||||
|
||||
- `envs/alert_triage/pyproject.toml` (`alert-triage`, workspace member),
|
||||
package `alert_triage/` with `generator.py` (templates, xorshift32, FNV-1a
|
||||
seed), `engine.py` (state, lookups, dispositions, never raises),
|
||||
`protocol.py` (JSON parse, renderers, system prompt), `reward.py` (with the
|
||||
single `# region: pig-demo/reward` pair), `policies.py` (`fast`,
|
||||
`thorough`), `taskset.py`, `tests/`, and `names/PROVENANCE.md`.
|
||||
- Probe policies registered for taskset `alert-triage`; eval command
|
||||
`uv run python envs/probe.py`.
|
||||
- The TypeScript port carries the generator, not only the scorer.
|
||||
- The demo's surface renders a **"Synthetic data — no real customer, account or
|
||||
transaction"** label on the board itself, the hour constants as config, the
|
||||
narrative with "not scored" beside it, and the label reveal only after a
|
||||
disposition.
|
||||
- Interactive mode: yes. A person can read the screen, click the four lookups
|
||||
(each showing its hour price), and close with a citation checklist or
|
||||
escalate with a typology and a transaction tick-list.
|
||||
- Suggested `caveat` for the vertical entry at ship time: "The alerts are
|
||||
synthetic and every one has a planted answer. The narrative prose is shown
|
||||
and not scored; the disposition and the cited facts are. The adversary
|
||||
designed the data and moved the threshold once — it does not adapt to the
|
||||
agent."
|
||||
Reference in New Issue
Block a user