Dispatch logodispatch
‹ Sales & Revenue Ops
Dormant pipeline · daily

One query per rep.
Not one long guess.

Open opportunities don't die loudly — they just stop getting worked. Every morning this workflow runs a separate query for each rep, returning their highest-value accounts with no logged activity in a week or more, then opens each customer's website in a real browser to see what's actually running on it. The fan-out, the ranking, and the checks are tools. The model only writes the message.

Steps9 Model calls1 Cost / run$0 CadenceDaily 08:00 ET
SummaryBot APP
Dormant accounts by rep · sample
avery
• northwind-logistics.com
🌐 site up · competitor product detected
💰 $2,999/yr
⏱ 41 days since last activity
jordan
• harborview-clinic.org
🌐 site up · our product installed
💰 $2,369/yr
⏱ no activity on record
• pinecrest-outfitters.com
🌐 site down
💰 $799/yr
⏱ 63 days since last activity
Sample shape — every field pulled or measured by a tool
01

Two fan-outs, nine steps, one sentence written by a model

workflow · dormant-accounts-by-rep · DAG with two fan-outs (bounded concurrency, per-item isolation)

01

List the reps

crm.get_reps

Ask the CRM who the reps are. The list is data, not a constant — add a rep on Monday and Tuesday's run covers them with no edit.

deterministic
02

One query per rep

foreach · items: get-reps.output → crm.dormant_accounts(rep, min_days_inactive: 7, sort: value, limit: 10)

A separate query for each rep, four running at a time. Each returns that rep's ten highest-value open opportunities with no logged activity for a week or more — filtered, sorted and capped by the database. The number of iterations comes from step 01, never from a model's attention span.

∥ foreach ×4deterministic
03

Flatten to one table

utils.jq_query

Collapse the per-rep results into a single flat list, tagging each row with the rep it came from — the shape every downstream step expects.

deterministic
04

Open every customer's website

foreach · browser.inspect_site(domain)

A real headless browser loads each site, waits for tag-manager-injected scripts to settle, and reports three things: is it even up, is our product still installed, and is a competitor's running instead. An account that's live, dropped our product, and picked up a rival is a churn signal no CRM field will ever hold.

∥ foreach ×3deterministic
05

Join the findings back

utils.jq_query

Merge each site's verdict onto its lead row. Items whose check failed get an explicit "unknown" rather than a hole — a dead domain never corrupts the row next to it.

deterministic
06

Build the sheet

utils.json_to_csv

Turn the enriched rows into a CSV — the artifact a human actually works from.

deterministic
07

Save it to the box

assets.write · dormant-accounts-{{date}}.csv

Persist the day's list on your own hardware. Dated by runtime templating, so the pipeline accumulates its own history with no bookkeeping step.

write · auto
08

Write the digest

agent · dormant-accounts-digest · transform

The one generative step. It receives finished rows — already filtered, ranked, grouped and verified — and does the only thing a language model is needed for: turning them into something a rep will actually read at 8am. It is structurally unable to invent a figure, because it is handed every figure.

generative
09

Post it

slack.send_message

Straight into the channel the team already lives in. Unattended, every morning, no hand on the wheel.

write · auto
Nothing here is translation-specific. A list to fan out over, a per-item query, a real-browser check, a keyed join, a CSV, a digest — point the same nine steps at renewals, dormant accounts, failed payments, or expiring contracts and the pipeline is unchanged.
02

What this asks of a model — and why an agent loop buckles

Hand this whole job to an autonomous agent and every hard part lands on the model at once. The harness takes each one away and gives it to something that cannot get it wrong.

Knowing when it's done

Agent loop

The model has to remember how many reps there were, which it already did, and that it hasn't finished. It drifts — covers four of seven, repeats one, or quietly decides the rest are "similar".

Dispatch

foreach takes its list from the previous step. The count is data. Every item runs, the run records ok/failed per item, and nothing depends on the model keeping track.

One bad row

Agent loop

A dead domain or a timeout lands mid-loop. The turn derails: it apologises, restarts the batch, or fabricates a plausible result for the one that failed.

Dispatch

Failure is isolated per item. A broken site becomes a recorded error on that one row; the rest of the batch completes. The merge step has an explicit branch for it.

The context window

Agent loop

Every CRM response and every page inspection piles into the transcript. Long before the end the model is summarising its own inputs, and fields start disappearing.

Dispatch

The model is the second-to-last node and sees exactly one thing: the finished table. Raw tool output never enters a prompt, so the window is never the constraint.

Ranking and dates

Agent loop

"Top ten by value, per rep, no activity in 7+ days" is sorting and date arithmetic over hundreds of rows — precisely where a small local model is confidently wrong.

Dispatch

The database sorts, limits, and computes days-since-last-activity. When those numbers were wrong once, they were visibly wrong — which is how a real upstream bug surfaced instead of shipping a plausible number.

Actually seeing a website

Agent loop

A model can't look at a page, and fetching raw HTML misses anything a tag manager injects later — so "is our product still installed" becomes a guess dressed as a finding.

Dispatch

A real browser loads the page, waits for injection, and checks the live runtime — then names the competing vendor if one is there. Measured, not inferred.

What it costs to run

Agent loop

Every iteration is another model turn carrying the whole growing transcript. Run it daily across every rep and the token bill is the reason it gets switched off.

Dispatch

Eight deterministic steps cost nothing but electricity, and there is exactly one model call — served locally. Daily is free, so cadence stops being a budget decision.

03

Deterministic where it must be. Generative where it helps.

Tools do the work

Correctness lives here. Same input, same output, every time.

  • one query per rep
  • filter, rank & cap per rep
  • open and measure each site
  • join, tabulate & persist

The agent does the judgment

Tone, ordering, emphasis — what language models are actually good at.

  • group it by rep
  • lead with what's at risk
  • call out zero activity plainly
  • keep it scannable at 8am
The model in this pipeline never counts, never sorts, never subtracts a date, and never decides how many times to loop. Strip all of that away and what's left — turning a finished table into a sentence someone will read — is something a 30–70B model running on your own hardware does well. That is the whole trick behind $0 per run: not a cheaper model, a smaller job.
04

What the platform gives you — and what you wire up

Building this was composing primitives, not writing code. Worth seeing where the line sits when you scope your own.

Dispatch provides

Built-in capability — available to every workflow, on every box.

  • the foreach step kind
  • days-inactive filtering on the CRM tool
  • the browser inspection tool
  • scheduling, approvals, run history

You wire up

A row in your box's database. Live the moment you save it — no deploy, no rebuild.

  • the nine-step workflow
  • the digest agent & its prompt
  • the 08:00 schedule
  • which channel it posts to
Nothing on the left is specific to this pipeline. foreach is a step kind, not a feature of this report — any workflow that needs to run one query per rep, per region, per account, per anything uses the same one. That is what makes the right-hand column an afternoon rather than a project: the deterministic parts already exist, and the job is deciding how to arrange them.
The takeaway

The loop is a tool, so the model can be small.

Shipping today · Daily 08:00 ET → #pipeline