The data question, mapped

Where does the record live at 10,000 accounts?

Raw data stays where it is.
The account record moves to Snowflake.
Four agents become one incremental loop.

01 The recommendation
Two tiers, not one

Raw stays. The record moves.

Totango and Salesforce extracts keep landing in S3 as an append-only archive — cheap, auditable, reprocessable. The account record is the part that outgrows S3. That moves to Snowflake, which the org already owns and the data team already governs.

Derived, not stored

The record is a view over events

Agents don’t overwrite one blob. They append facts to an event log, and the account row is rebuilt from that log by code. Every field traces to a real event. Auditable, replayable, rebuildable. That’s the governance moat, not a policy nobody reads.

One loop, not four scripts

Incremental, per account

Incremental updates — new tags today, next steps tomorrow — fall out naturally. A change to one account triggers that account’s steps only — signal, narrative, QA, action — not a whole-book rerun. Four steps in one loop with a shared log.

Nothing new to approve

Built on the stack you have

Reuses S3 + Snowflake + the governed AWS runtime. The LLM only narrates; the risk math stays in code. In a cybersecurity org where every key needs a blessing, the least-exciting answer is the right one.

Four questions, short answers.
“Snowflake?”Yes — for the record and the history. Raw stays in S3. Both, not either-or.
“Four automations?”Four steps in one loop with a shared event log. Decoupled, but consistent.
“Historic trends?”The event log is the history. Query it over time — no separate trend pipeline.
“Pre-process in Snowflake?”Yes, via the data-analytics team you already have. It fans data in; you read and score it.
02 The architecture the whole pipeline in one view
Storage · where the data lives The loop · incremental, per account Source don’t store here Snowflake governed warehouse Loop runs on one record Totango post-sales CRM Salesforce sales-side reference S3 · raw landing append-only extracts account one row per account score_history trends account_event append-only log CSM review UI reads the record, human confirms historic trends the churn signal signal + tag flags & tags narrative the story QA cites signals action next step LLM narrates only fan in materialized query history reads writes
Storage — source systems, S3, Snowflake Compute — the per-account loop Model — the LLM Consumer — review UI, trends
03 The mechanics how it fits together
AThe two tiers, in depth

The decision isn’t “S3 or Snowflake.” It’s which data lives in each. Confusing the two is how you end up storing the whole world in the wrong place.

Tier 1 — raw landing (S3)
  • Append-only extracts from Totango and Salesforce
  • Cheap, immutable, auditable — reprocess anytime
  • This is the current setup, and it’s fine to keep for this
  • Fan-in and field creation handled by the data-analytics team
Tier 2 — account record (Snowflake)
  • One row per account, updated incrementally
  • Per-record random access, SQL, and history
  • Where S3 JSONs fail: rewrite the whole blob to change one field
  • Aligned with the org’s governed warehouse — nothing to buy

The rule of thumb: if you append and archive it, it’s Tier 1. If you update and query it, it’s Tier 2.

BThe per-account loop and the four steps

The weekly whole-book run is the trap. The target is an event-driven loop: a change to one account triggers that account’s steps only.

StepWhat it doesEngine
signal + tagTurns raw signals into flags and tags on the accountDeterministic code
narrativeWrites the plain-language story the CSM can defendLLM, grounded in the flags
QAChecks the narrative cites real signals, kills driftRules + LLM check
next step / actionRoutes a playbook and a suggested action, human confirmsCode-routed + HITL

Each step is idempotent and resumable. A step fails mid-account, you retry that step, not the book. The risk math lives in code; the LLM only narrates and is QA’d. That’s what keeps a 10,000-account run cheap and trustworthy.

CWhere it runs, and why it’s guardrail-friendly
Run in the stack you have
  • AWS runtime — Lambda per loop step, already governed
  • Snowflake — the account record, already owned
  • S3 — raw landing, unchanged
LLM kept to one place
  • Narrative and QA only, at the Bedrock runtime
  • Cheaper models (Llama) fit here — the score is code
  • Customer data stays in the governed runtime, not in unblessed tools

The pitch to Mark: no new data store, no new provider, no new approval. You already own every piece. The skill set is wiring them and making the loop reliable — which is the role, not a rebuild.

04 A worked example the data records, row by row

Here’s what the whole thing actually looks like, end to end, using three example accounts: Meridian (at risk), Granite (churn-imminent), and Sunrise (healthy).

The data question: with 10,000 customer accounts, where does the account record live? Customer information is scattered across the tools the team already uses — Totango (post-sales) and Salesforce. You can’t act on a customer until those facts are pulled into one current record.

Tier one — the raw data stays put. Copies of the raw data land in an S3 bucket, a simple append-only archive. Nothing is ever edited there, so you can always re-check the original. This is what the team already runs today, and it stays.

Tier two — the account record moves to Snowflake. This is where the real work happens, in three tables: account (one current row per customer), account_event (the append-only log every observation gets added to), and score_history (snapshots over time). The account row is derived from the event log, never overwritten directly — so every number traces back to a real observation.

The account record — one row per customer

account_idcustomerarrhealth_scorestatusrisk_flagsnext_action
ACC-1002Meridian$240k42at_riskusage_down, renewal_60dcall about renewal
ACC-1006Granite$510k18churn_imminentchurn_signalescalate to director
ACC-1005Sunrise$90k88healthyoffer expansion

The event log — every loop step appends a row

event_idaccount_idtsevent_typepayloadsource
8411ACC-100208-28usage_drop{wau: -22%}usage_weekly
8412ACC-100208-28ticket_spike{tickets: +3}support_tickets
8413ACC-100208-29narrative_writtenusage down three straight weeksnarrative_agent
8414ACC-100208-29qa_verdict{pass: true}qa_agent
8415ACC-100208-30next_stepschedule renewal check-inaction_agent
8416ACC-100608-30churn_signal{renewal_likelihood: low}scoring
8417ACC-100608-30escalation{level: churn_imminent}escalation_engine

The history — a snapshot per run, so you see the trend

account_idsnapshot_datehealth_scoreflags
ACC-10022026-08-1768{usage_down}
ACC-10022026-08-2455{usage_down, ticket_spike}
ACC-10022026-08-3142{usage_down, ticket_spike, renewal_60d}

How they connect:
the account row isn’t stored independently — it’s derived from account_event.
Each observation (a usage drop, a narrative, a QA verdict, a next step) is appended; a deterministic layer recomputes the current account row from the log and pushes a fresh row into score_history.
Every number on Meridian’s record traces back to events 8411–8415 — nothing guessed, nothing overwritten.
The payload columns are JSON; shown here as {key: value} for readability.

05 What we don’t know yet the blockers that shape the plan
PriorityWhat we need to knowWhy it matters / what it blocks
Blocks go/no-go
Can the pipeline write to Snowflake?
Snowflake “should work well” — that’s hope, not approval. Does the loop get a role, or must writes route through the data-engineering team?
Blocked by: the security-review guardrail on every integration.
This is the single biggest unknown. If the loop can’t write the record, the whole Tier 2 collapses and you’re back to S3. Everything else depends on the answer.
Blocks go/no-go
What does “quick” actually mean?
“On demand, operating quick” — minutes, hours, or daily? The freshness SLA decides the trigger design: push-on-event vs. pull-on-cadence.
Different architecture for near-real-time vs. overnight.
Near-real-time means event triggers and CDC from source systems. Daily means a batch that still updates one record per account. One is much harder in a governed org.
Blocks go/no-go
Account count and event volume
“Thousands / 10,000 accounts.” But how many source events per account per week? Sizing drives Snowflake credits and LLM cost.
Blocked by: volume data only they have.
A 1,000-account pilot and a 10,000-account rollout differ in cost and capacity by an order of magnitude. You can’t cost the plan without the number.
Blocks go/no-go
Which LLM runtimes are approved?
Cybersecurity data-residency rules on customer data. Is Bedrock the only sanctioned runtime, or is a cheaper Llama path cleared?
Blocked by: the security review + procurement.
Gates the narrative step. If only premium models are approved, per-account cost rises and the cheap-model play dies.
Refines the plan
Schema ownership and governance
The data-analytics team owns Snowflake. Is the EWS schema inside their governed warehouse or isolated? Who reviews DDL?
Blocks: the approval workflow, not the approach.
Determines who signs off schema changes and how fast you can iterate. Know the boundary before you design the tables.
Refines the plan
The fourth agent
The roster is signals+tag, narrative, QA, and one more. We can’t finalize the loop without the full roster and their I/O.
One quick follow-up to confirm.
Every step changes the event schema. Locking the roster early avoids a rework of the loop definition.
Refines the plan
Canonical account identity
One customer, one ID across Totango and Salesforce. Is there a reconciled account key, or does the loop have to build one?
Blocks: accurate records, not the architecture.
Score on split identity and you score wrong. This is the plumbing problem the repo’s “The Fix” page names — solve it before the loop matters.
Refines the plan
History for backfill
To show historic trends now, you need history. Does existing S3/Snowflake data go back far enough, or do trends start at go-live?
Blocks: the trend feature’s launch date.
“Historic trends” was a stated goal. If there’s no past data, score_history starts empty and trends only build over weeks — set the expectation now.
Refines the plan
Cost per account at 10,000
Unit economics of 4 steps × 10,000 accounts × weekly. Especially with the consumption-based pivot 18 months out.
Blocks: budget approval.
A board-facing metric. Keeping the score in code and the LLM narrow is what holds per-account cost down — quantify it to defend it.
Assumption to confirm
The churn metric and attribution
How is EWS success measured and tied to churn? The repo’s outcome tracker logs renewed / churned / expanded per intervention.
Confirm the North Star with the stakeholders.
The org mandate is churn. Without a defined outcome metric, you can’t prove ROI to the two people who matter most.
06 The working assumptions defaults that make it buildable today, swapped when we know better
Finishable now, correctable later. The unknowns don’t block the plan — we build against these defaults and change each one the moment it resolves. Every assumption has a known correction path.
Assumption we build againstCorrection we can make later
The loop gets a Snowflake write path
A dedicated service role and its own namespace inside the governed warehouse, writes routed through the data-analytics team’s standard process.
If not blessed: the loop writes to a data-team-staged area and they fan it in. Same schema, one-hop difference.
Daily incremental, not near-real-time
Source data isn’t streaming and the weekly run already works. “New tags today, next steps tomorrow” fits a daily tick.
If the mandate needs faster: move to event-triggered. The event log makes that an additive upgrade, not a rewrite.
10,000 accounts, proved at 1,000
Size for 10,000 accounts, four steps each, weekly. Measure real unit cost on a 1,000-account pilot before committing.
After pilot data lands: resize capacity and re-budget from measured, not assumed, per-account cost.
Bedrock runtime, cheap model where it binds
Narrative and QA run on the approved Bedrock runtime; a cheaper model (Llama) where cost is the constraint. The score stays in code either way.
As procurement clears: swap the model tier for a cheaper approved path. The loop definition doesn’t change.
Totango is the master account key
Totango holds the canonical account ID — it’s the post-sales system of record. Salesforce maps in as the sales-side reference.
If they have a golden-record system: reconcile to it once the data team confirms. One identity map, not a loop change.
History seeds from the S3 JSONs
Existing S3 extracts backfill roughly 8–12 weeks of score_history at go-live, so trends are usable immediately.
If they don’t: trends start empty and build over weeks. Set that expectation with stakeholders now.
The fourth agent is the action step
The loop is the three named steps plus a next-step/action agent — the gap the repo already closes.
If the fourth differs: confirm the roster and rework the loop. Early, cheap; later, expensive.
Churn saved is the North Star
Success = at-risk contracts saved and interventions acknowledged, tracked through the outcome log.
Lock the exact metric with the leadership: so attribution is real, not hoped for.
07 Where this plan could be wrong the honest risks, hardest first
This is a strong architecture, not a finished plan. The risks below are the ones that could sink it — not the ones we already flagged as unknowns. Read these before you build.
01 · the premise is unverified

The whole loop hinges on a trigger we haven’t confirmed

The per-account incremental loop is event-driven: a change on one account triggers that account’s steps. But nothing confirms Totango or Salesforce can push that change. If they don’t expose webhooks or CDC — common behind a security review — there’s no real-time trigger, and the elegant loop quietly becomes a scheduled poll. Same architecture, different reality.

02 · the problem may not be storage

We answered “where does the data live” before confirming that’s the problem

The mandate is churn — and churn is about what CSMs do, not where bytes sit. The existing S3 + weekly run already works at their actual scale. This plan licenses a lot of machinery for a scaling problem they may never hit. The outcome loop that ties to churn is a side note, not the spine.

03 · the hard part is hand-waved

Event-sourcing at 10,000 accounts is the piece we sized last

account_event → materialize account is elegant for 12 accounts. At 10,000 it’s a real distributed problem: event ordering, idempotency, exactly-once, partial failures, a materialized view that stays in sync. And the four agents are a sequential pipeline — the record stalls on the slowest step, with no queue or backpressure.

04 · HITL doesn’t scale

Human-confirms-everything breaks at 10,000 accounts

The demo’s “CSM confirms every flag and draft” works at 12. At 10k no human reviews every signal. The plan never defines the triage threshold — what auto-routes, what escalates, what waits. That’s a product decision, not a storage one, and it’s the real scaling problem.

05 · cost is unsized

The LLM is the cost driver, and we have no number for it

Narrative + QA per account per change, ×10,000 ×365. “Keep the score in code, LLM narrow” doesn’t settle it — we never put a unit number on the part that actually spends. With a consumption-based pivot in 18 months, this is board-visible.

06 · the assumptions are load-bearing

We’re proceeding on guesses we can’t yet make

Snowflake write access is blessable, Totango is the master key, Bedrock is approved for customer data — each is an assumption treated as a default. Identity is the sharpest: if one-customer-one-ID isn’t reconciled first, every account record is wrong at the foundation, and that’s hard to retrofit.

What to change first: spend two days validating the trigger and the Snowflake write path before designing anything else — that spike confirms or kills the event-loop premise. If the trigger isn’t there, the honest architecture is simpler: keep S3, do a daily incremental per-account update, and materialize to Snowflake only when analytics earns it. The plan’s elegance is its risk.
© 2025–2026 Charlie Fuller The agent →