The agent11

Risk analyst

The Risk Analyst writes the parts of a verdict a human would want in prose, and code has the last word on every one of them. It never sees a way to change the score or the tier; it chooses limits inside a band, questions to ask, when to look again, whether a second hop is worth the cost, and the rationale.

What it receives and returns

Input: the request, the evidence bundle, the rules' score, tier and reasons, the prior tier and the current time. The system prompt fixes score and tier as inputs. Output is a single forced tool call:

ts
// The only way the model can answer: one forced tool call, zod-parsed, then guarded.
submit_decision({
  recommendedLimits: { maxPerTxUsdc: number, maxPerDayUsdc: number },
  rationale: string,
  eddRequested: string[],
  nextRescreenInSeconds: number,
  requestDepth: 2 | null,
  confidence: number            // 0–1, about limits/cadence, never about the tier
})

Guard rules

GuardRuleNote in the log
clampLimits0 ≤ tx ≤ band, 0 ≤ day ≤ band, day ≥ tx; HIGH is 0/0; non-numeric → band ceilinglimits clamped to MEDIUM band: {…} → {…}
clampRescreenSecondsinto the tier window (HIGH 5 min–1 h, MEDIUM 15 min–24 h, LOW 1 h–7 d); ceiling halved after a tier changerescreen clamped into LOW window (tightened: tier changed)
filterEddnone for LOW; strings only, trimmed, ≥ 8 chars, de-duplicated, max 3EDD dropped: LOW tier · EDD filtered: 5 → 3
sanitizeRationaleevery 0x hex (6–64 chars), NK-… OpenSanctions id or Q… Wikidata id must appear in the evidence, reasons or request (prefix ≥ 10 chars allowed); else replaced; then cut at 120 wordsstripped unverified identifiers: …
requestDepthhonoured once; ignored at depth 2 or when the client pinned depth 1requestDepth ignored: client pinned depth 1
clampConfidence0–1, default 0.5-

applyGuards() never throws. guardTriggered and the notes go into the decision core that is hashed and signed, so an overruled model is part of the record, not hidden by it.

Deterministic fallback

Without an LLM provider (LLM_PROVIDER=0g + ZEROG_API_KEY, or ANTHROPIC_API_KEY), on a model error, a timeout or a malformed tool call, fallbackOutput() produces the same shape and goes through the same guards. The verdict's analyst.fallback flag is true and the error is recorded.

text
Score 23 → MEDIUM. Driven by: funder 2hop sanctioned (+15, 0xSDN…); fresh wallet (+8). Limits set to the MEDIUM band default.
// eddRequested: "Please confirm the relationship with 0xSDN… and the source of funds behind it (FUNDER_2HOP_SANCTIONED)."
// nextRescreenInSeconds: top of the MEDIUM window (86,400 s, or 43,200 s after a tier change) · confidence 0.5
  • Limits - the band ceiling for the tier.
  • Rationale - the score, the tier and the top three reasons with their weights and one identifier each, all taken from the evidence.
  • EDD - none for LOW; otherwise one question per top reason (at most two), naming the path's last address.
  • Cadence - the top of the window; halved after a tier change.

The operator's model step follows the same pattern (The operator).