matter_type — walkthroughAtlas's job is the issue-framework layer of Early Case Assessment — the lens every downstream tool reads documents through. The frame for an arbitration is a different shape from a matrimonial matter, which is a different shape from a criminal appeal: different live issues, different applicable-law family, different relief category. Get the frame's nature wrong and everything built on it leans the wrong way.
Until now Atlas guessed the dispute nature from counsel's free text alone (proceeding_types was sent empty). The firm had already declared what kind of matter this is — that signal just never reached Atlas. Spike-1274 / ADR-115 added a real coarse axis (business.matter_types, one per matter, set at create). This Story is the consumer half: pipe the firm's own matter_type.name into Atlas so the prior is grounded input, not a guess.
WHY a prior, not a rule: the naive fix is "branch on type → emit a fixed template." That's brittle and wrong — counsel's raw text is the truth, and a declared type can be mislabelled. So matter_type only shapes the framework and vocabulary; Atlas still reasons over the raw words, and if the text contradicts the type, it trusts the text and flags the mismatch.
functions (S2/S3) broker envelope Atlas brain
matters.matter_type_id ──→ input.matter_type ──→ AtlasIntakeInput.matter_type (string?)
(firm's matter_type.name) (the .name string) │
▼
prompt grounds the prior:
arbitration ≠ matrimonial ≠ criminal appeal
│
present ─→ frame on that dispute nature (Other if unrecognised)
absent ─→ infer from free text, exactly as before
The whole change rides one optional string. No functions-side mapping table — Atlas owns coarse bucketing: an unrecognised firm-custom type collapses to a generic Other matter framed purely from counsel's words. The anti-hallucination rule is untouched: a declared type never licenses inventing clause numbers, rates, or citations.
| Change | Where | What & WHY |
|---|---|---|
| Soul: new input field | agents/atlas/baml_src/atlas.baml | matter_type string? added to AtlasIntakeInput — the firm's own matter_type.name verbatim. Optional so older matters (pre-catalog) omit it and Atlas falls back cleanly (ADR-115 D1). |
| Soul: grounding rule | atlas.baml system prompt | New "GROUND THE FRAME ON THE DECLARED MATTER TYPE" block: steers issue framework / applicable-law family / relief category per dispute nature (arbitration / matrimonial / criminal appeal / civil), with explicit "PRIOR not a fact", "trust the raw text on conflict + flag it", "unrecognised → Other", and "type never licenses fabricated specifics". When absent → infer from text as before. |
| Soul: render the field | atlas.baml user prompt | {% if input.matter_type %}Matter type … {{ input.matter_type }}{% endif %} — only rendered when present, so the prompt is byte-identical to pre-1282 when absent. |
| Regenerated client | agents/atlas/baml_client/ | baml-cli generate --from agents/atlas/baml_src, committed. AtlasIntakeInput.matter_type: Optional[str] = None now in the typed model. |
| No Python wiring needed | agent.py / runtime.py | build_intake_input already does AtlasIntakeInput.model_validate(request["input"]). An optional field flows through automatically: present key → carried; absent key → None. Graceful fallback is structural, not branchy. |
matter_type is an input prior, not part of the refined-vs-raw promotion contract, so it is deliberately not added to source_snapshot. The frozen snapshot (core_dispute / outcome / value) stays byte-identical — proven by test_matter_type_not_in_source_snapshot and the unchanged test_source_snapshot_is_frozen_verbatim.matter_type key) validates unchanged, inp.matter_type is None, the prompt's {% if %} renders nothing, and Atlas infers the dispute nature from free text exactly as before. Proven by test_build_intake_input_matter_type_absent_is_none plus the BAML AtlasNoMatterTypeFallsBackCleanly case (same sparse intake, no type, still a clean all_clear frame).| Test | Proves |
|---|---|
test_build_intake_input_carries_matter_type | functions' matter_type.name rides the typed input verbatim |
test_build_intake_input_matter_type_absent_is_none | backward compat — absent key → None, payload accepted unchanged |
test_matter_type_not_in_source_snapshot | the prior never leaks into the frozen truth-boundary snapshot |
AtlasMatterTypeGroundsMatrimonialFrame (BAML) | same sparse intake + matter_type "Matrimonial" → matrimonial framing (divorce/family proceeding), not commercial damages — live-model check |
AtlasNoMatterTypeFallsBackCleanly (BAML) | identical intake with NO type still frames cleanly from free text |
$ (cd agents/atlas && pytest tests -q) 45 passed # 42 existing (zero regressions) + 3 new unit tests $ ruff check agents/ All checks passed! $ baml-cli generate --from agents/atlas/baml_src Wrote 14 files to agents/atlas/baml_client # committed, reproducible # ADR-110 §6 statelessness intact: no sqlalchemy / psycopg / boto3 s3·sqs·dynamodb added