The Core Decision: LangGraph, Not n8n

Sentinel was built on LangGraph + Python from the outset, following the same architectural principle established on prior ANR builds: workflow tools like n8n execute pre-written logic. Client relationship risk assessment is not deterministic — detecting that a late invoice, a cancelled meeting, and a shorter email reply together constitute a serious compound risk pattern requires an LLM with genuine reasoning capability, not a pre-written IF/THEN branch.

LangGraph provides a shared state object that persists across every agent invocation, conditional routing based on what a prior agent concluded, and full Python expressiveness for the deterministic logic (identity resolution, score damping) that should never cost an LLM token.


The Shared State Model

Every agent reads from and writes to a single SentinelState TypedDict — the architectural whiteboard every stage shares.

SentinelState:
  client_id: str
  trigger_type: str
  client_data: dict
  existing_signals: list[dict]
  new_signals_generated: list[dict]
  concerning_signals: list[dict]
  signals_written: bool
  health_score: int
  health_state: str
  score_delta: int
  scoring_rationale: str
  expansion_signals_detected: bool
  root_cause_analysis: str
  risk_classification: str
  confidence_level: str
  drafted_actions: list[dict]
  requires_diagnostic: bool
  error: str | None
  current_agent: str

Agent 1 writes new_signals_generated. Agent 2 reads it and writes health_score + health_state. The conditional router reads health_state to decide whether Agent 3 runs at all. Agent 4 reads everything Agents 2 and 3 concluded before drafting a single word of outreach.


The Two-Model Strategy

Sentinel routes work across two Groq models based on task complexity, not cost alone.

llama-3.1-8b-instant handles high-volume, low-complexity work: email summarization and raw-event classification. These are extraction tasks, not reasoning tasks — pattern matching text into structured fields.

llama-3.3-70b-versatile is reserved for genuine reasoning: health scoring (compound-pattern detection across signal history), root-cause diagnosis, and outreach drafting.

This split roughly triples effective daily capacity on Groq's free tier — the highest-volume calls (summarization, classification) run on a separate quota from the reasoning-heavy calls, and a rate-limit hit on one no longer blocks the other.


Why Agents Are Separated

The five stages could theoretically be one large prompt. This was explicitly rejected, for the same reasons it's rejected on every ANR agentic build: each stage can be tested and fail in isolation, each stage's output is independently inspectable, and the deterministic work (identity resolution, score damping, routing) runs as pure Python — zero LLM cost for logic that doesn't need a model's judgment.


Agent Execution Order

START
  → signal_collector
  → health_scorer
  → [conditional edge]
       ── error present → END (no garbage score persisted)
       ── at_risk / drifting → diagnostician → action_drafter → END
       ── healthy / expansion_ready → action_drafter → END