Design decisions
The full set of platform-level decisions both products inherit, with the reasoning, and with the two that were reversed.
Both product specs carry a numbered decision table. These are the ones that belong to the platform (every project inherits them), written here with the why, because the why is the part that survives a rewrite.
Standing
| Decision | Why |
|---|---|
| LLMs extract, deterministic code decides. Presence, comparison, completeness, escalation, prioritisation are code. Models produce structured inputs: classifications, values, confidences. | Any conclusion with legal weight has to be explainable to an auditor and stable across model versions. A model proposes evidence; a rule decides. The essay. |
One shared platform, many orchestrators. shared/ holds tools, schemas, generic sub-agents. Each use case is a thin deterministic orchestrator composing them. | The products are the same shape. Sharing avoids two document-store integrations, two vision clients, two fetch engines. Domain difference lives in config and orchestration, not plumbing. |
The one import rule. shared/ imports nothing from any project. Projects import shared/ and themselves, never each other. | This is what lets two products ship on independent schedules from one repo. |
| Every conclusion carries a citation. A document, page and region, or a database reference. No citation → rendered as an unverified suggestion. | A reviewer signing off a legal placement or a verification must be able to see why. |
| Domain logic is versioned config, not code. Required-document sets, exception rows, comparison policies, thresholds, decision tables, keyword maps: YAML, validated by Pydantic, versioned with the pipeline. | Business owners revise rules without a release. Every rule is auditable and diffable. |
| The fetch registry is the SQL allow-list. Detail. | No model- or user-derived string ever reaches SQL. |
| Structured outputs everywhere. JSON-schema response format, Pydantic-validated, retried once, then dead-lettered. | Free text is where hallucinations hide and where parsers die. |
| Confidence-gated triage. Low confidence anywhere marks the component review rather than presenting a shaky pass or fail. | Protects the hard floors on false-accept and false-pass. |
| Deterministic orchestration on the batch path. No LLM routing. An interactive agent exists only as a colleague’s front door. | Batch behaviour has to be predictable, replayable and cheap to reason about. |
| Fail closed on every safety-critical check. Missing or low-confidence input defaults to the non-passing state; a model-matched document needs an explicit human tick. | A false “verified” or “complete” is a compliance or data-breach event. The essay. |
| Exceptions are explicit config, never inferred. “This seller’s accounts have no statements” is a row, not a model’s observation. | Prevents both false fails on legitimate exceptions and false passes from assumed universal rules. |
One work item, one idempotency key. hash(subject, use_case, pipeline_version); check before work; a claim table with in-progress / completed / failed. | Queues redeliver. Replays must be safe. |
| The flakiest dependency is isolated. Session reuse, retries with backoff and jitter, a circuit breaker, a defined degraded state. | The document store fails; the queue must not stall. Its auth model is changing; only its client should know. |
| Managed Identity to every dependency. Models, document reader, warehouse, queue, vault. Key Vault for residual secrets only; one fail-fast resolver at startup. | Least privilege per resource, and no credential in an environment variable that outlives the migration. |
| Governed retention. TTLs and automated purge on landing, artefacts, cache, dead-letter; only the audit log and the warehouse records retain long-term. A failed purge alerts. | Page images of customer correspondence are not something to keep by accident. |
Reversed
One decision in the first design did not survive contact with reality, and the reversal is worth recording.
A dense three-axis rule cube. The records product’s required-document model was first drawn as product × seller × jurisdiction, fully populated. The production rules export showed the real data was sparse (one axis mattered for one document type, another for one seller family) and the model became a state map with a small overlay. Detail. The lesson generalises: design the config shape after you have the data, or at least be ready to throw the first shape away.
Watched, not yet decided
Whether high-confidence passes can eventually clear without a human (post-pilot, on override data); whether the interactive agent should ever be allowed to call a write tool (currently no); and how much of the evaluation harness moves from merge-blocking to shadow-only as the golden set matures.