# the agent task log (AgentTask)

> Every piece of work an agent does is recorded as an `AgentTask` — which agent, what set it going, when it started and finished, and what came of it. Tasks nest: work an agent starts from inside another piece of work becomes a sub-task, so "what did this whole job involve" is one query however many agents it took. The rows are written by the platform and can never be created, edited or deleted by app code; your app decides who may READ them.

<!-- id: agent-task-log · area: agent · stability: stable · html: https://osysharp.com/reference/agent/task-log/ -->

## Summary        {#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 Osysharp.Agents;`, alongside the rest of the agent surface.

## Signature      {#signature}

```osy syntax
using Osysharp.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    {#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
Osysharp.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       {#examples}

An app that lets any signed-in person see the agent work log:

```osy title="agent-task-log-read-rule" test app=agent-task-log
using Osysharp.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       {#see-also}
- [what an agent hands back (AgentDeliverable)](https://osysharp.com/reference/agent/deliverables/) — what the work PRODUCED: the documents, files and records the agent hands back.
- [agent conversation memory (using Osysharp.Agents)](https://osysharp.com/reference/agent/conversation-memory/) — what the agent SAID, as against what it did.
- [running an agent from your code](https://osysharp.com/reference/agent/ask/) — running an agent from your own code.
