Summary#
A task tells you an agent did some work. This tells you what that work cost and what it consisted of.
var spend = task.AllCalls.Sum(c => c.Cost); // what the whole job cost
var slow = task.AllCalls.Where(c => c.DurationMs > 5000);
var broke = task.AllCalls.Where(c => c.Error != null);Each entry is one call to a model. Because the cost is on each call, every total you might want is a Sum you
write yourself — there is no stored figure that could disagree with the calls behind it.
Signature#
task.Calls // → List<AgentLlmCall> this task's own calls
task.AllCalls // → List<AgentLlmCall> this task and everything beneath itAgentLlmCall | |
|---|---|
Model | the model that answered — the tier actually used, not the one declared |
Turn | which turn of the run this was (1-based) |
InputTokens · OutputTokens | tokens sent and generated |
CacheCreationTokens · CacheReadTokens | cache written, and cache read — reads are the ones that save money |
Cost | what this call cost, in your currency units, as a decimal |
DurationMs | wall-clock time for the provider call |
Truncated | the model hit its token ceiling and was cut off mid-answer |
Error | the failure, or null. A task whose calls carry errors spent money without delivering |
At | when the request went out |
Task | which task in the subtree made the call — so a total can be broken down by step |
Description#
AllCalls is almost always the one you want#
⚠ A task driven by a loop makes no model calls itself. The loop's task is the parent; the agent run it starts is a child task, and the calls are recorded against the child. So on a loop-driven task:
task.Calls.Sum(c => c.Cost) // 0.00 — the parent spent nothing
task.AllCalls.Sum(c => c.Cost) // what the job actually costThat is why they are two members rather than one with a flag: a reader must not have to work out which they are
holding. Reach for Calls when you specifically want this step's spend and not its children's.
The totals are yours to write#
There is no task.Cost. The task deliberately stores no total, because a stored number and the rows it summarises
disagree the first time something fails halfway — and then nothing can say which is right. Summing the calls cannot
drift, and it gives you every other question for free:
var spend = task.AllCalls.Sum(c => c.Cost);
var turns = task.AllCalls.Count;
var cached = task.AllCalls.Sum(c => c.CacheReadTokens);
var priciest = task.AllCalls.OrderByDescending(c => c.Cost).FirstOrDefault();⚑ Cost is converted once from the platform's internal whole-number units, so summing a list of these is exact. A per-call rounded figure would not be.
Who can read it#
If you can read the task, you can read its calls. There is no second rule to declare and none to forget: you can
only ask about a task you are holding, and you could only be holding one your app's own rule on
AgentTask allowed you.
So the decision is the one you already made:
partial entity AgentTask {
security { allow read when IsFinance; } // …and only finance sees what anything cost
}⚠ Prompts and responses are NOT here. These entries say what a call cost, never what it contained. That is
deliberate: an agent often runs with more authority than the person reading the screen, so its prompts can hold data
that reader is not entitled to. Exposing content through the same member as cost would make one read rule the only
thing standing between them. Content is task.Transcript, behind a gate the agent
declares for itself.
Examples#
A review screen — the spend, and the calls behind it:
using Osyrin.Agents;
[Principal] entity User {
[Required, MaxLength(255)] string Email;
security { allow read when IsAuthenticated; }
}
entity ReviewTask : AgentTask {
security { allow read when IsAuthenticated; }
}
/// What one task spent, and what it did to spend it.
string Spend(Guid taskId) {
var task = ReviewTask.Where(t => t.Id == taskId).FirstOrDefault();
if (task == null) { return "no such task"; }
var calls = task.AllCalls;
var cost = calls.Sum(c => c.Cost);
var failed = calls.Where(c => c.Error != null).Count();
return calls.Count.ToString() + " calls, " + cost.ToString("C")
+ (failed > 0 ? " (" + failed.ToString() + " failed)" : "");
}See also#
- the agent task log (AgentTask) — the task itself: what caused it, when it ran, how it ended
- what an agent hands back (AgentDeliverable) — what the agent chose to hand back, as opposed to what it spent
- what the agent saw (task.Transcript) — what those calls contained, and who may read it
- the agent loop (app.Agent, Loop) — why a loop-driven task's own calls are empty