Per-execution vs per-record tracking: when each makes sense
A flow that runs hourly but acts on 3 records is a different tracking problem than a webhook that fires once per record. The decision rule, the failure modes, and what each does to your dashboard.
- engineering
- metrics
A scheduled flow wakes up every hour, queries a table, and finds three new rows to process. Over a day that is 24 executions and 72 records. A webhook on the same table fires once per insert: 72 executions, 72 records. Same work, same human time replaced, two execution counts that differ by 3x. If your savings event is tied to the wrong one of these, your dashboard is wrong by the same factor.
This is the per-execution vs per-record decision, and it is the most common reason a tracking number drifts from reality without anyone noticing.
The two boundaries
An execution is one run of a workflow: one trigger fires, the flow runs to completion or error, the platform logs a row. A scheduled trigger produces one execution per interval regardless of how many items it touches. A webhook produces one execution per event.
A record is one unit of work inside that run: one ticket, one invoice, one lead, one row in the batch. A single execution can process zero records (nothing matched the query) or hundreds (the nightly catch-up run).
The execution count is what your automation platform hands you for free. n8n's Insights documentation ties its Time Saved metric to production runs of parent workflows, which is an execution-shaped boundary: it counts runs, not items inside them. That boundary is convenient for the platform's accounting. It is not always where human work begins and ends.
The decision rule
Track per record when one execution can replace a variable amount of human work. Track per execution when one execution always replaces a fixed amount.
That is the whole rule. The rest is recognising which case you are in.
A webhook that fires once per inbound support ticket and routes it: one execution, one record, fixed mapping. Per execution and per record are the same number here, so track per execution because it is simpler and free.
A scheduled flow that runs hourly and processes every unhandled row since the last run: variable records per execution. Three rows at 2pm, forty rows after the overnight backlog. If you log one savings event per execution, the overnight run that cleared forty invoices counts the same as the quiet 2pm run that cleared three. Track per record.
A batch job that runs once a night and reconciles the full day's transactions: variable, and the variance is the entire point. Track per record.
The failure modes
Per-execution tracking on a batch flow undercounts, badly. The flow that clears a 200-record overnight backlog logs one event. A human would have processed 200 items. Your dashboard shows one task saved where 200 happened. This is the failure mode that quietly deletes the value of your highest-throughput automations, which are exactly the ones doing the most work.
Per-record tracking on a fixed flow can double-count. If a webhook fires per record and the flow internally loops over a sub-list, naive per-record logging inside the loop can fire multiple events for one inbound item. The fix is to log at the task boundary, once per replaced human task, not once per iteration of an internal loop.
The empty-run trap. A scheduled flow that finds nothing to do still produces an execution. If you log a flat savings value per execution, every empty hourly run books phantom savings. Twenty-four runs a day, most of them processing zero rows, each claiming a fixed baseline, is how a dashboard inflates while the agent sits idle. Per-record tracking is immune to this: zero records, zero events, zero claimed savings.
At realistic agent volumes, between one thousand and one hundred thousand executions a month, these errors are not rounding noise. A 3x execution-to-record ratio on a flow doing 40,000 monthly executions is the difference between a defensible number and a fiction.
What this does to the CFO conversation
The number a finance partner signs off on has to answer one question: how much human work did this replace? Records map to that question. Executions map to platform load.
When you report "this flow saved 180 hours last month," the implicit claim is 180 hours of work a person would otherwise have done. If that number came from execution counts on a batch flow, it is understated and you are leaving budget defence on the table. If it came from per-execution counts on a chatty scheduled trigger with empty runs, it is overstated and it collapses the moment someone audits a sample of runs that processed nothing.
Per-record tracking with a per-record baseline survives the audit because every event maps to one thing a human would have done. The reconciliation is direct: events times baseline equals hours, and you can pull any single event and point at the record it replaced.
How to implement it
Log one savings event per replaced human task, at the task boundary. For a per-record flow, that means one HTTP call per record processed, fired inside the item loop at the point the work is done, not at the start of the run.
curl -X POST https://humanhours.dev/api/v1/track \
-H "Authorization: Bearer hh_live_..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "invoice-reconciler",
"task_type": "invoice_data_extraction",
"outcome": "success",
"human_baseline_minutes": 6,
"metadata": { "record_id": "inv_88213", "execution_id": "exec_4471" }
}'Carry the execution_id in metadata even when you track per record. That keeps the platform-load view available for capacity planning while the record count drives the savings number. You get both boundaries from one event: records for the CFO, executions for the ops dashboard.
For a genuinely fixed flow, fire once per execution and skip the loop entirely. The test is the decision rule, not the trigger type: does one run always replace the same amount of human work? If yes, per execution. If the amount moves with how many records showed up, per record.