Summary#
You can see what an agent has said — that is the conversation. The task log is what it did.
Each piece of work an agent performs is one AgentTask row: whose work it was, what caused it, when it ran, and how
it ended. Because agent work sets off more agent work — an agent hands part of a job to another agent, or files a
record that starts a process which itself needs an agent — tasks form a tree. A task knows its parent and carries
its full ancestry, so you can ask about one step or about everything that step led to, at any depth.
AgentTask arrives with using Osyrin.Agents;, alongside the rest of the agent surface.
Signature#
using Osyrin.Agents;
partial entity AgentTask {
security { allow read when IsAuthenticated; }
}The fields you will read:
| field | what it holds |
|---|---|
Agent | whose work this was |
Type | what set it going — Workflow, Event, Schedule, Chat, or Spawn (another agent's task) |
Status | Running, Completed or Failed |
Title · Trigger | one line for a list; the full reason it ran |
Parent · Children · Path · RootTask | the tree — see below |
StartedAt · CompletedAt · Outcome | when, and how it ended |
WorkflowRun · ChatSession | the process or conversation it belongs to, when it belongs to one |
EntityTypeName · EntityId | the record it is about, when it is about one |
Principal · OnBehalfOf | who it ran as, and whose behalf it ran on |
Cost is not a field — see below.
Description#
The organising unit is the AGENT, not the process. The question the log answers is "what has this agent done" — a list of its recent work with its type, its timing and its outcome. A business process is one of the things that can cause agent work; plenty of processes never involve an agent at all, and those create nothing here.
Not every agent turn is work. A question the agent answers there and then — "what did we spend on travel in Q3?" — is a conversation, not a job, and it writes no task. A task exists when something set the agent to WORK: a process step, an event, a schedule, a request that turned into a piece of work, or another agent handing part of a job over. That is deliberate: a log that recorded every exchange would bury the work in chatter.
Tasks nest, across mechanisms. An agent's work can create a record that starts a process, whose step needs agent work, whose agent hands part of the job to another agent. Each of those is a sub-task of the one before it, and the chain is recorded the same way regardless of what made each hop. A sub-task means the work went to a different actor — another agent, or a person — not that the same agent moved on to its next step.
Path is what makes "and everything it led to" cheap. Every task carries its ancestry as a path ending in its own
id, so one filter selects a task together with all its descendants, however deep. Reading Children gives you the
immediate sub-tasks; matching on Path gives you the whole subtree.
Cost is not a field either, and that is the same decision. Every model call the task caused is recorded with the task's id and its path, so "what did this cost" is a sum over those calls, and "what did this cost all in" is a sum over every call whose path starts with this task's. Both are exactly the sum of the calls they summarise — a stored total can disagree with its own calls after a run that failed half-way, with nothing to reconcile it against.
The calls are LlmCallRecord rows, which your app can already query. AgentTask is the task each call belongs to,
TaskPath is that task's ancestry, and the same aggregate over InputTokens/OutputTokens gives tokens instead of
money.
⚑ It counts what the agent's TOOLS spent, not just its own turns. A tool that runs an extraction or a classification is doing model work the task caused, and it lands under the same task without anyone passing anything — because the attribution happens where every model call goes through, not where turns are counted.
Waiting is not recorded here. A task that is waiting for a person is waiting inside a process, which already records who it was assigned to, when it opened, when they picked it up and whether it ran late. The task points at that process rather than keeping a second copy of the same clock — two copies of one fact drift the day somebody fixes one of them.
The rows are written by the platform. App code can never create, change or delete an AgentTask: a fabricated row
would attribute work to an agent that never did it, and a deleted one would hide work that happened. Attempting a
write is refused.
Who may READ them is entirely yours. The platform ships no opinion about that, which means a plain using Osyrin.Agents; gives you a table nobody can read yet. Declare the rule you want, the same way you would for any
other entity — everyone signed in, only managers, only the person the work was done for. Building the queue screen,
the review list and the activity dashboard is your app's job too; the log is the material they are built from.
Examples#
An app that lets any signed-in person see the agent work log:
using Osyrin.Agents;
[Principal] entity User {
[Required, MaxLength(255)] string Email;
security { allow read when IsAuthenticated; }
}
partial entity AgentTask {
security { allow read when IsAuthenticated; }
}With that in place, "what has this agent been doing" is an ordinary query over an ordinary entity — and so is "show
me everything that came out of this one job", by matching descendants on their Path.
See also#
- what an agent hands back (AgentDeliverable) — what the work PRODUCED: the documents, files and records the agent hands back.
- agent conversation memory (using Osyrin.Agents) — what the agent SAID, as against what it did.
- running an agent from your code — running an agent from your own code.