Fintech
Key points

Key points

  1. 1A decision log survives a model change when it records what the model saw (immutable input IDs, prompt template, sampling parameters) rather than what the model was.
  2. 2Version every reason code so old and new codes never collide, and never renumber an existing code.
  3. 3Store stable references to evidence instead of copying it, so retention and privacy rules stay where the data already lives.
  4. 4Replay recorded inputs against a new model and diff the reason codes; that diff is your audit, migration and regression test in one.

Why a decision log has to outlive the model

Most teams learn this the hard way. A support agent changes a renewal status, latency looks fine, and every dashboard stays green. Then someone asks the only question that matters: why was this action allowed, and what actually changed? A trace shows tokens and spans; an application log shows an HTTP 200; neither tells you the business intent or the policy verdict that permitted the action the decision log guide from Singularity. That gap is exactly where a schema earns its keep.

The same discipline applies to ordinary architecture decisions, where a decision log exists so that teams can see what was decided, in what context, and with what consequences, even as people change the Microsoft engineering playbook. Software decision logs track the reasoning of humans. A model decision log tracks the reasoning of a system, and its hardest test is the moment someone swaps the model, rewrites the prompt, or adds a feature.

In regulated credit decisions the stakes are concrete. ECOA and Regulation B require creditors to give applicants the specific principal reasons for an adverse action, and the CFPB has stated that complex algorithms, including AI and machine learning models, do not excuse that requirement the CFPB circular coverage. If your log cannot reconstruct which factors actually drove a denial under a specific model version, you cannot honestly produce that notice later.

The design principle that follows is blunt: store what the model saw, not what the model was. Model identity is metadata. Inputs, prompt and parameters are evidence.

The record you need: fields that hold across model swaps

A useful decision log is a small structured record per material decision, not a transcript and not an archive of hidden reasoning. Start with a decision boundary. Log when the agent crosses from interpretation into a consequential branch or side effect: selecting a tool, classifying a request into a risk tier, moving money, denying an action, escalating to a human the decision log guide from Singularity.

Here is a schema sketch that holds up when the model changes. It is deliberately additive: every block can gain fields without breaking readers.

decision_id            string, unique
occurred_at            RFC 3339 timestamp
tenant_id              string
subject               {type, subject_id}
decision_kind          enum: credit | fraud | route | allow | hold | block
policy                {policy_id, policy_version, verdict, reason_codes[]}
release               {prompt_template_id, prompt_version, model_id,
                       model_provider, sampling{...}, tool_manifest_id}
inputs[]               {input_id, value, source_system, as_of, hash}
outcome               {state: attempted|verified|failed|unknown, result_ref}
redaction_version     string

Three choices carry the weight. First, inputs[] stores immutable input IDs alongside the value the model actually saw, so a later model can be replayed against identical evidence. Second, release captures the prompt template version, model identifier, provider and sampling parameters together, because prompt behavior depends on more than the prompt text alone: the model, parameters, tool configuration and rationale all influence what comes back the prompt versioning guide. Third, policy.reason_codes[] holds versioned codes, never free text.

That last point matters for explainability. With AI and machine learning underwriting, meeting the specific-reasons requirement takes feature-level explainability methods such as SHAP or LIME, a governed process for mapping those outputs to Regulation B reason code language, and validation that the reasons are accurate and tied to a specific model version the Trussed lending guide. A versioned code library is the only way that mapping survives a retrain.

ElementSurvives model swap?Reason
Reason codes (versioned)Yes, if never renumberedOld decisions keep their meaning
Raw model name and versionMetadata onlyExplains history, cannot reproduce it
Prompt template versionYesShows exactly what instructions ran
Sampling parametersYesTemperature and top-p change outputs
Full prompt text copyFragileBloats storage, breaks retention rules
Input IDs with values and hashesYesEnables replay against any new model
Human-readable narrativeNoDrifts as language and model change

Immutable input IDs: log the values the model saw

The single most common failure is logging feature names without values, or values fetched at read time. A credit decision made in March cannot be explained in June if the bureau score, debt-to-income ratio and account age have all moved since. The record must freeze each value the model consumed, stamped with its source system and as-of time, and identified by an input ID that never changes.

Prefer references over copies for bulky evidence. In the support agent example, requestRef, targetRef and resultRef point to access-controlled data rather than duplicating it, so a transcript or invoice stays in its existing system under its existing retention rules the decision log guide from Singularity. For small scalar features, inline the value and a hash, so tampering is detectable.

Worked example, credit decision: an applicant is declined. The log's inputs[] holds bureau_score = 612 from crdbureau as of 2026-03-14T09:02:11Z, dti = 0.47 from income_service as of the same window, and months_since_oldest_trade = 9. The release block records prompt_version = 7, model_provider = A, temperature = 0, top_p = 1.0. Six months later, a new model is proposed. You replay the same inputs, get a new verdict, and compare reason codes. That comparison is the audit.

Worked example, fraud alert: a payment is held. inputs[] includes velocity_24h = 11, geo_distance_km = 3400, device_age_days = 2. The policy verdict is hold with reason code fraud.velocity.v2. The outcome block starts as attempted and is later appended as verified with a result_ref pointing at the payment event. Writing the planned record before the side effect gives you a durable idempotency key and distinguishes attempted, verified and unknown instead of silently turning uncertainty into success the decision log guide from Singularity.

The payoff is context. Monitoring systems that answer an alert in isolation cannot tell a routine payroll deposit from a laundering pattern; context about a customer's normal behavior is what separates a false positive from a true one the Flagright false-positive guide. A frozen input set is that context, captured at decision time and never rewritten.

Versioned reason codes: never renumber, only add

Reason codes are the part of the schema most likely to be quietly broken. A team renames insufficient_income to income_below_threshold, reuses the old code for a new factor, and suddenly two years of decisions mean different things depending on when they were logged. The fix is a rule, enforced in code review: a reason code is permanent. Any change to meaning creates a new versioned code.

Use a dotted, versioned namespace such as credit.income_insufficient.v3. The numeric suffix is the semantics version, not a date. Retiring a code means marking it deprecated in the registry while it stays valid for old records. Regulators take the same view: Regulation B's Appendix C sample reason checklists are illustrative rather than exhaustive, and lenders using AI models should treat them as a starting vocabulary and expand it to reflect the features the model actually uses the Trussed lending guide. A governed, version-controlled mapping process is what keeps disclosed reasons accurate as the model or its inputs change.

A reason-code registry is worth treating as its own artifact. It holds the code, its plain-language description, the owner, the model versions that emit it, and its status. That registry becomes the translator between a raw attribution and applicant-facing language. The mapping must be checked to confirm it reflects principal reasons rather than plausible-sounding but inaccurate explanations, and the CFPB circular notes creditors must be able to validate that disclosed codes correspond to what actually happened the Trussed explainability guide.

Record the prompt, the parameters and the tool manifest

Prompt changes are the silent regression in most agent systems. A small wording edit made to handle an edge case can break other scenarios without anyone noticing, and when quality drops, rolling back usually means guessing what the previous prompt looked like the prompt versioning guide. Logging the prompt template version next to every decision turns that guess into a lookup.

Store the template ID and version, not a full copy of the rendered prompt, unless the rendered prompt contains no personal data. Templates and variables need to be versioned together, because changes to template structure or variable handling affect behavior as much as changes to visible text the prompt versioning guide. The rendered prompt belongs in a trace with a stable ID that the decision log links, protected by normal access controls.

Sampling parameters belong in the decision record because they change the output. A decision made at temperature zero and one made with higher temperature are not the same evidence. Record temperature, top-p, max tokens and any seed, all together with the model identifier and provider, so a later replay can reproduce the original conditions or, deliberately, not reproduce them.

Version immutability is the underlying rule: once a prompt version exists, it should never be edited, because later references in logs and evaluations depend on it the prompt versioning guide. Apply the same rule to templates, tool manifests and policy bundles.

Additive migration: how to change the schema without rewriting history

Schemas evolve. The goal is that old records stay valid and new records get richer. That means every schema change must be readable by code that has never seen the new field, and every reader must tolerate records from before the field existed.

Practical rules:

  • Add optional fields, never repurpose existing ones. A new field defaults to absent. Old records simply lack it, and reports say "not captured" rather than inventing a value.
  • Namespace new blocks. If you add a retrieval block for RAG context, version it as retrieval_v1 so future versions coexist.
  • Backfill by replay, not by guess. To populate a new field on historical records, replay the frozen inputs against the original model, prompt version and parameters, and write the results as clearly labeled derived data with a derived_from marker. Never overwrite the original verdict.
  • Keep readers decoupled from writers. Readers should consume the versioned fields they need and treat unknown fields as absent.
  • Migrate policy and reason codes via the registry. A new model that emits fraud.velocity.v3 must not retire v2; both stay valid.

Validation checks belong in a small script that runs on every record before it is written and in a batch job over historical records. Run the checks on every insert, or you will discover the gap during an audit.

def validate(record):
    errors = []
    if not record.get("decision_id"):
        errors.append("missing decision_id")
    if not record.get("release", {}).get("prompt_version"):
        errors.append("missing prompt_version")
    if not record.get("release", {}).get("model_id"):
        errors.append("missing model_id")
    if not record.get("inputs"):
        errors.append("no inputs recorded")
    for i, inp in enumerate(record.get("inputs", [])):
        if not inp.get("input_id") or inp.get("as_of") is None:
            errors.append(f"input {i} lacks id or as_of")
        if inp.get("value") is None and inp.get("hash") is None:
            errors.append(f"input {i} has neither value nor hash")
    if record.get("policy", {}).get("verdict") in ("block", "hold"):
        for code in record["policy"].get("reason_codes", []):
            if not is_registered(code):
                errors.append(f"unregistered reason code: {code}")
    if record.get("outcome", {}).get("state") not in (
        "attempted", "verified", "failed", "unknown"
    ):
        errors.append("bad outcome state")
    return errors

The unknown state is not a nicety. A timeout or worker crash is itself important evidence, and distinguishing attempted, verified and unknown keeps uncertainty visible instead of folding it into success the decision log guide from Singularity.

Does the log pay for itself? A worked comparison

Yes, if you measure the right thing. The cost of a weak schema shows up as investigator time and as regulatory risk. Anti-money laundering systems built on static threshold rules routinely produce very high false-positive rates; one practitioner paper reports industry false-positive rates above ninety percent and proposes a hybrid architecture showing thirty to forty percent reductions in false positives with twenty to thirty percent improvements in detection precision the AI transaction monitoring paper. Another analysis cites research that in traditional AML systems ninety percent of alerts are false positives the Lucinity overview.

Take a worked example. Suppose you process 10,000 fraud alerts a month at a ninety percent false-positive rate, so 9,000 are cleared after review. If an average review costs 10 minutes, that is 90,000 minutes, or 1,500 hours, of investigator time per month. Now suppose a replay harness lets you test a candidate model against six months of frozen inputs before deployment and shows it would have flagged 40 percent fewer of those legitimate cases. The saving is 3,600 reviews a month, or 36,000 minutes, roughly 600 hours. The exact numbers depend on your base rate; the structure of the calculation is what you should copy.

The decision rule is simple. If a proposed change saves less investigator time than the schema work costs, skip the change; if it saves more, require that the replay evidence ships with the change request. Without frozen inputs and versioned reason codes, you cannot compute the first term at all, which is why the schema comes first.

This is also where explainability investment pays. Modern detection techniques and explainable models are what make reduced false positives defensible to regulators, since excessive alerts from outdated rule-based approaches signal weak systems the Facctum explanation. A log that captures evidence and reasons is the substrate for that defensibility.

Where agents and model routing fit

Agent systems make the schema problem harder, because one workflow may call several models through different providers. The log should treat model routing as part of the release metadata: record which provider and model handled each step, and keep the tool manifest version so the set of available tools is part of the evidence. If your agents call text, image and embedding models, keeping a single record per decision rather than per call prevents the log from fragmenting into thousands of rows nobody can read.

A useful pattern is one decision record with child spans. The parent holds the verdict, reason codes and frozen inputs. Child spans hold the individual model calls, their parameters and their trace IDs. Reviewers read the parent; engineers drill into the children. Teams that need to route many model types through one interface can hold that contract constant; Quanta Inference exposes text, image, video, audio and embedding models behind one OpenAI-compatible API key, which keeps the provider field meaningful as a metadata value rather than a scattering of SDKs.

FAQ

FAQ

Can I just log the full prompt and skip the input IDs?

No, that loses too much. The prompt text shows instructions but not the numeric feature values, their source systems or their as-of time, which are what you need to replay a decision against a new model. Log a prompt template version plus a trace link, and log the frozen inputs separately with IDs and hashes.

Do I need to store the model's chain of thought for an audit?

No, and you generally should not. A decision log should not be a hidden-reasoning archive; it should record the bounded evidence, the policy verdict and its explicit reason codes, the chosen action and the verified outcome. Store policy reasons that are safe to expose rather than internal text that may be unreliable or sensitive.

How do I handle a reason code whose meaning changes with a new model?

Create a new versioned code and leave the old one valid for historical records. Retire the old code only in the registry, never in the data. This keeps old and new codes from colliding and lets you diff reason codes between model versions during replay the Trussed lending guide.

What is the minimum viable schema?

One record per material decision with a unique ID, timestamp, frozen inputs with IDs and as-of times, a release block with prompt version, model, provider and sampling parameters, a policy verdict with registered reason codes, and an outcome state of attempted, verified, failed or unknown. Anything less and you cannot replay or explain.

How do I prove the log is accurate enough for compliance?

Validate the mapping from model attributions to reason codes, tie it to specific model versions, and recheck it whenever the model or inputs change. Creditors must be able to validate that disclosed reason codes correspond to the actual drivers of the decision, a point the CFPB circular makes explicit the Trussed explainability guide. A replay harness plus a written validation record is the practical evidence.

If you want help turning this schema into a working pipeline for your line of business, a free 30-minute discovery call with Quanta Consulting will scope your goals and give you a tailored recommendation: https://quantaintelligence.ai/calendar.

Sources

Sources

  1. AI Agent Decision Logs: Record Evidence Without Storing ...
  2. Design Decision Log - Engineering Fundamentals Playbook
  3. on Machine Learning Model Adverse Action Notices
  4. What is prompt versioning? Best practices for iteration without ...
  5. Specific Adverse Action Reasons for AI Lending Models
  6. Understanding False Positives in Transaction Monitoring - Flagright
  7. ECOA Adverse Action Notices for AI Credit Decisions
  8. AI-Driven Transaction Monitoring Systems (AI-TMS): Reducing False ...
  9. Reducing False Positives in Transaction Monitoring with AI
  10. False Positives In AML Compliance: Definition, Causes, And Solutions
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles