Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 36 — The Intake Agent

Jordan Lee emailed about order O-1001 — headphones still “in transit” after eight days. Maya opened the case in ShopOps. Before Resolution can propose a reship, refund, or status email, something has to turn OMS and carrier records into a case the pipeline can trust.

That first step is Intake. It reads order and shipment systems and emits a typed intake pack: facts with evidence IDs, inferences with confidence, and explicit UNKNOWN where the records are silent.

Maya opens O-1001 → Intake reads OMS + shipments → facts / inferences / unknowns → typed pack → Resolution

Skip strict Intake and the rest of the chain guesses. If language preference is missing, a sloppy step invents English so the workflow keeps moving — and Outreach later sends the wrong template with high confidence. If a stalled shipment is narrated as a fact instead of an inference, Policy cannot tell what is proven versus assumed.

This chapter adds Intake: the agent that starts every case from records, not from chat prose, and passes a compact contract downstream instead of a raw OMS dump.

First principles

  1. Start with records, not narration. Order and shipment systems supply facts and evidence IDs.
  2. Keep reasoning visible. A stalled-shipment assessment is an inference, never a fact in disguise.
  3. Make absence explicit. UNKNOWN tells the next step to block or escalate instead of guessing (Ch 13).
  4. Stop at the boundary. Intake reads and organizes; it has no customer-contact capability (Ch 15, 27).
  5. Pass a compact contract downstream. Resolution receives the typed intake pack, not a raw OMS dump.
  6. Do not let confidence make decisions. Confidence helps Resolution choose a track; Policy still decides what is permitted.

Concrete example — O-1001 intake pack

FieldValueKindProvenance
customer_idC-7781factoms.get_customer
order_total_cents12900factoms.get_customer
preferred_channelemailfactoms.get_customer
opted_outfalsefactoms.get_customer
exception_likelytrueinference (0.7)shipment_pattern
languagenullunknownmissing

Resolution (Ch 37) may proceed on exception track using inference + facts. If preferred_channel were unknown, Resolution blocks contact proposals.

Diagram

flowchart LR
  R[Order and shipment records] --> I[Intake.build]
  I --> F[Facts]
  I --> N[Inferences]
  I --> U[Unknowns]
  F & N & U --> P[Typed intake pack]
  P --> S[Resolution]
  P --> M[Optional memory write]
 raw OMS JSON ──► classify field ──┬── FACT (+ evidence_id)
                                   ├── INFERENCE (+ confidence)
                                   └── UNKNOWN (value=null)

Caption: Notice UNKNOWN is an output, not a failure to parse — it is a decision to not invent.

Implementation

Module: shopops/agents/intake.py.

from shopops.agents.intake import IntakeAgent, FactKind


class FakeOms:
    def get_customer(self, case_id: str) -> dict:
        return {
            "customer_id": "C-7781",
            "order_total_cents": 12_900,
            "preferred_channel": "email",
            "opted_out": False,
            # language intentionally missing
        }

    def list_shipments(self, case_id: str) -> list[dict]:
        return [
            {"id": "s1", "status": "in_transit_stalled"},
            {"id": "s2", "status": "failed_delivery"},
            {"id": "s3", "status": "delivered"},
        ]


agent = IntakeAgent(oms=FakeOms())
profile = agent.build("O-1001", tenant_id="store_northline")
assert profile.get("language").kind == FactKind.UNKNOWN
assert profile.get("exception_likely").kind == FactKind.INFERENCE
missing = agent.require_known(profile, ["preferred_channel", "language"])
assert missing == ["language"]

Authz: run under agent:intake scopes only (intake:read, order_pii:read, memory:write_intake). Never attach outreach:send.

What “unknown” forces upstream

language = UNKNOWN
    → Resolution may still propose if language not required
    → OR product rule: require_known(["language"]) before any draft
    → Orchestrator escalates to human data repair — model does not invent "en"

The Intake agent’s highest-value output is often the list of unknowns. That list is how you stop the rest of the pipeline from improvising.

Memory write (optional)

If you persist profile facts via the memory controller (Ch 10), write only FactKind.FACT with provenance. Inferences can be stored as inferences with decay; unknowns are usually omitted rather than stored as null spam.

Failure modes

FailureResultFix
Fill unknowns with model guessesWrong channel / languageFactKind.UNKNOWN + escalate
Raw tool JSON as memoryPoison / bloatMemory controller (Ch 10)
Outreach from profileSoD breakNo channel tools in process
Inference as factOverconfident strategyKind checks downstream
Missing provenanceUnauditableRequire evidence_ids

Production considerations

  • Snapshot golden profiles in eval fixtures (Ch 20).
  • PII minimization: Outreach may receive a redacted view, not full order + payment PII.
  • Refresh policy: when do we rebuild vs reuse checkpointed profile?
  • Trace every CRM read with tool spans; link evidence ids into audit packs.

Chapter summary

  • Intake agent produces typed facts/inferences/unknowns.
  • Provenance is mandatory.
  • Unknowns must not be hallucinated away.
  • No customer contact from this agent.
  • Downstream blocks on missing required keys.
  • Inferences carry confidence and evidence.
  • Least-privilege authz applies.
  • O-1001 profile pack is the contract for later chapters.

Exercises

  1. Mechanical. Remove preferred_channel from FakeOms; assert require_known lists it.
  2. Inference. Tune the stalled-shipment threshold; show an exception signal flip between UNKNOWN and INFERENCE.
  3. Design. Specify which profile fields Outreach is allowed to see vs only Intake/Policy.

References

  • Chapters 9–10, 13, 15, 27
  • Provenance / evidence patterns in knowledge bases. [VERIFY]