The categories are named after what a reader sees, not after any agent tool's log format: an agent tool reaches this model through an adapter, so the same few rows render a conversation whichever tool produced it.
A message carries Prose rather than the characters it was written in: people and agents write to each other in markdown, and reading it is the adapter's job, so every consumer of this model gets prose that is ready to show. detail on an activity is not prose — a tool's output is not writing, and is shown as the characters it is.
at is optional because not every source timestamps every row.
final case class Activity(at: Option[Moment], label: String, detail: Option[String])
Anything else the session shows: the agent's reasoning, a command the person invoked, an answer with no call on this page. label names the activity in a few words; detail carries the longer body a reader can expand, when there is one.
Anything else the session shows: the agent's reasoning, a command the person invoked, an answer with no call on this page. label names the activity in a few words; detail carries the longer body a reader can expand, when there is one.
final case class Hook(at: Option[Moment], name: String, call: Option[String], detail: Option[String])
A hook of the session's own that had something to say.
A hook of the session's own that had something to say.
A hook is not part of the conversation and nobody in it wrote what a hook says, but it changed what happened next — a warning before a command ran, the rules a session was started with — so a reader who does not see it reads the turn after it as arbitrary. It names the call it touched when that call is on this page.
Attributes
final case class ToolCall(at: Option[Moment], tool: String, call: String, input: ToolInput)
A tool the agent ran, and what it was called with. The input keeps the shape it was written in rather than being flattened to a line, because which value belongs to which name is what makes a long call readable.
A tool the agent ran, and what it was called with. The input keeps the shape it was written in rather than being flattened to a line, because which value belongs to which name is what makes a long call readable.
call is the token this exchange is known by. It says nothing on its own; it says that this row and the answer carrying the same token are one exchange, whether or not they ended up next to each other.
Attributes
final case class ToolResult(at: Option[Moment], tool: String, call: String, failed: Boolean, output: List[ToolOutput])
What a tool answered, and the call it answers.
What a tool answered, and the call it answers.
An answer is its own row rather than one more activity because it belongs to something: it carries the token of the call it came back from, and a host can join the two rows however it wants to. An answer whose call is not on this page belongs to nothing a reader can see, and is plain activity instead.
The answer itself is a sequence rather than a body, because a call answers with what it has: words, a picture, a file it wrote, in the order they came back.