Events
The event log every run writes, and how to read it.
Every run writes one ordered log of events. Studio's timeline, replay, evals, streaming and the HTTP API are all views of that log, so the log is the source of truth for what an agent did.
The shape of an event
{
"schemaVersion": 1,
"streamType": "run",
"streamId": "run-096c2fa2-814b-49a8-ac26-8b64c930d8a9",
"runId": "run-096c2fa2-814b-49a8-ac26-8b64c930d8a9",
"id": "…",
"sequence": 5,
"timestamp": "2026-11-09T12:07:17.039Z",
"type": "tool.call",
"payload": { "toolName": "create_ticket", "arguments": { "title": "VPN down" } }
}sequence orders events within a run and is what you pass as a cursor. schemaVersion is the
version of the saved event, not of the HTTP API. The types are in @plico-ai/contracts as
PlicoEvent.
Event types
| Event | When it is written |
|---|---|
run.started | A run begins. Carries the project name and any host metadata and context. |
instructions.composed | agent.md and the skills are combined. Records the exact text the run uses. |
tools.discovered | The project's tools are loaded, with their schemas and approval policy. |
input.received | The user message a model-backed run starts from. |
model.request | Plico sends messages and tool definitions to the model. |
model.attempt | A model call starts. A retry is a new attempt. |
model.delta | A streamed piece of the reply. |
model.response | The model's full reply, with token usage and any tool calls. |
assistant.output | Text the agent says to the user. |
tool.call | The agent calls a tool, with its arguments. |
tool.result | The tool's result. |
approval.requested | A tool needs a decision. See Approvals. |
approval.decided | A person approved or denied. May record who decided. |
artifact.workspace.created | The run gets an artifact workspace. |
artifact.file.written | A file version is written, with its path, hash and version. |
artifact.snapshot.created | A workspace snapshot is recorded, with its file count. |
run.waiting | The run is parked on a decision. |
run.completed | The run finished, with its final output. |
run.cancelled | The run was cancelled. |
run.error | Something went wrong, with its phase and a message. |
run.failed | The run ended in failure. Follows run.error. |
Not every run writes every type. A dry run has no model.* events, and a run with no tools has no
tool.* or approval.* events.
Read the log
From the command line:
plico runs . # list saved runs
plico events <run-id> . # print one run's eventsOver HTTP:
GET /v1/runs/<run-id>/eventsreturns the events as JSON. Add?after=<sequence>to get only newer ones.GET /v1/runs/<run-id>/events/streamis a Server-Sent Events stream. A reconnecting client sendsLast-Event-ID, or?after=, and gets the events it missed.
From your backend, plico.runs.events(runId, { after }) is an async iterator over the same
events. See Use Plico from your backend.
In Studio, the run view shows them as a timeline, with the raw JSON beside it and replay controls to step through a finished run. See Studio.
Stored data and stability
Runs are stored in SQLite at .plico/plico.sqlite in the project, or wherever --db points.
The log is append-only: Plico adds events and never edits one.
The HTTP API under /v1 takes additive changes only, so a new event type or payload field can
appear in a minor release. Write consumers that ignore types and fields they do not know. See
Upgrading for what else can change in a 0.x release.
Events can include tool arguments, results and host context. Keep secrets out of all three.