How to Avoid Double-Charging AI Agent Query Costs
TLDR
A time-window join can match one warehouse query to two overlapping agent sessions and inflate six units of source cost into ten. Keep every query in the ledger, prefer direct request identifiers, and separate attributed, ambiguous, and unmatched cost before chargeback.
One warehouse query ran once. Your chargeback report billed it twice.
That is not an exotic accounting problem. It is usually a join problem.
When two AI agent sessions overlap for the same user, a broad time-window join can attach the same query to both sessions. The arithmetic looks tidy, but the total no longer matches the source ledger.
A six-unit ledger that turns into ten
Consider three warehouse queries and two overlapping agent sessions:
| Query | Evidence | Illustrative units |
|---|---|---|
| Q1 | Tagged to session A, but its timestamp also overlaps session B | 2 |
| Q2 | Untagged and inside both session windows | 3 |
| Q3 | Matches neither session window | 1 |
The source ledger contains 2 + 3 + 1 = 6 units. These are illustrative units, not Snowflake credits, prices, or claimed savings.
Now join queries to sessions using only the user and overlapping timestamps:
select
q.query_id,
q.units,
count(s.session_id) as candidate_sessions
from query_ledger q
left join agent_sessions s
on q.user_name = s.user_name
and q.start_time between s.start_time and s.end_time
group by q.query_id, q.units;
Q1 and Q2 each match sessions A and B. A naive inner join therefore reports 2 + 2 + 3 + 3 = 10 units. Q3 disappears because it matches no session at all.
The report is wrong in two directions at once: it duplicates the ambiguous work and drops the unmatched work.
The same failure is visible in the Mage fixture below. The source ledger holds six units, but the naive join reports ten, charges Q1 and Q2 twice, and drops Q3.
Select the expand icon to inspect the reconciliation at full resolution.
Classify first, allocate second
A safer result preserves each source query exactly once:
| Classification | Units | Budget-owner decision |
|---|---|---|
| Attributed to session A | 2 | Use as the lower-bound allocated amount |
| Ambiguous between A and B | 3 | Hold unresolved until stronger correlation evidence exists |
| Unmatched | 1 | Keep visible instead of treating missing attribution as zero |
The classified total remains 2 + 3 + 1 = 6. The report can explain all six source units without charging any unit twice.
In the corrected Mage result, each source query appears once. Q1 is attributed, Q2 stays ambiguous, and Q3 stays visible as unmatched. The source total and reconciled total both remain six.
If you model possible allocations, session A could own two or five units and session B could own zero or three. Those are alternate scenarios, not additive totals. Do not combine the upper bounds into another invoice.
Use the strongest identifier you have
Time overlap is useful for finding candidates. It is weak evidence of ownership.
Prefer correlation evidence in this order:
-
A direct request or interaction identifier that reaches the query record.
-
A controlled query tag that identifies the agent request or session.
-
A unique query ID captured by the calling workflow.
-
User, agent, warehouse, query shape, and time as a candidate set that may remain ambiguous.
A query hash is not an ownership key. Snowflake documents query_hash as a hash of canonicalized SQL text for grouping repeated queries. Two requests can run the same SQL and share a hash without being the same agent interaction.
Prefer native attribution when it is available
Snowflake's CORTEX_AGENT_USAGE_HISTORY includes request, user, and agent identifiers. Its metadata can also include SQL query credits and warehouse names for a request.
Use that direct attribution before inventing a weaker custom join. Then account for the documented boundaries:
-
The SQL cost-attribution fields can lag the rest of the row by up to eight hours. A temporary
NULLis not automatically zero. -
Those SQL credit fields exclude queries run on Adaptive Warehouses.
-
QUERY_HISTORY can itself lag by up to 45 minutes.
-
Keep agent token credits, AI function credits, and warehouse query credits as separate cost scopes unless the authoritative view explicitly tells you how they relate.
This example does not claim a complete Snowflake bill, prescribe a universal allocation policy, or demonstrate native Cortex attribution inside Mage. It shows the reconciliation control that should surround any fallback attribution logic.
A practical conservation contract
Before a chargeback report reaches a budget owner, require four checks:
-
Every source query is present in the reconciliation output.
-
Every source query is classified once as attributed, ambiguous, or unmatched.
-
The sum of those classifications equals the source-ledger total for the same window and cost scope.
-
Ambiguous work stays unresolved until a stronger identifier supports allocation.
The goal is not to force every cost into a team bucket. The goal is to produce a report that can admit uncertainty without inventing or losing spend.
One query should never become two charges just because two sessions happened to be open.




