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.
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.
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.
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.
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