Skip to content
Agent Graph

List events

GET/graphs/{graph_id}/events

Returns a graph’s events, newest first by default, from its retained history.

Following a graph. Remember the newest event id you’ve seen, and list with order=asc and starting_after set to it: you get only what’s happened since, each event with its changes. No need to fetch the graph again. If you fall so far behind that the event is no longer retained, that’s 410 outside_retention_window, and the error’s first_event_id is the oldest event still held: fetch the graph again, and follow on from there.

Replaying history. List with order=asc from the start of retention, or from any event; expand[]=data.node adds each event’s node as it stood just after it.

Path ParametersExpand Collapse
graph_id: string
Query ParametersExpand Collapse
created: optional object { gt, gte, lt, lte }

Only events that happened in this range.

gt: optional number

After this time.

formatint64
gte: optional number

At or after this time.

formatint64
lt: optional number

Before this time.

formatint64
lte: optional number

At or before this time.

formatint64
ending_before: optional string

A cursor: an object id from the list. Returns the page before it. Pass the first id of the page you have to get the previous one.

expand: optional array of string

Fields to expand on each event: expand[]=data.node (the node just after the event), and its own fields, data.node.parent.

Send one expand[] per path. A path goes at most 4 levels deep; at most 20 paths per request. An expanded field appears beside its _id field, which is always there; one not expanded is absent.

limit: optional number

How many objects to return, from 1 to 1,000.

minimum1
maximum1000
node_id: optional string

Only events about this node.

order: optional "desc" or "asc"

desc: newest first. asc: oldest first.

One of the following:
"desc"
"asc"
root_id: optional string

Only events about nodes in this tree.

starting_after: optional string

A cursor: an object id from the list. Returns the page after it. Pass the last id of the page you have to get the next one.

type: optional array of string

Only events of these types. Send one type[] per type; a trailing * matches a family: type[]=agent.*.

Header ParametersExpand Collapse
"If-None-Match": optional string
ReturnsExpand Collapse
as_of_event_id: string

The event the list is as of. Keep it as as_of for every page.

data: array of Event { id, changes, created, 7 more }
id: string

Unique identifier for the event, evt_…. Sorts by time.

changes: array of NodeChange { created, fields, node_id }

Every node this event changed, starting with node_id. Empty when it changed nothing (a repeated status, say).

created: boolean

Whether the event created it. Its fields then go from null.

fields: map[FieldChange { from, to } ]

Each node field that changed, by name, from what to what. Derived fields count (child_count, descendant_count, blocked, headline, task_counts). stale doesn’t, as it changes with time rather than events, and nor do expandable fields.

from: unknown

The value before the event.

to: unknown

The value after it.

node_id: string
created: number

When it happened, in Unix milliseconds.

formatint64
graph_id: string
node_id: string

The node it’s about.

object: "event"

String representing the object’s type: always event.

payload: map[unknown]

What the agent tool reported, by type: see event types. Ids in it are the provider’s own. Text in it is untrusted.

source: EventSource { adapter, provider, provider_version }

What reported the event.

adapter: string

The Agent Graph adapter that recorded it, and its version, when it says.

provider: string
provider_version: string

The agent tool’s version, when it says.

type: "session.started" or "session.ended" or "agent.spawned" or 11 more

What happened. See event types. More may be added: ignore those you don’t recognise.

One of the following:
"session.started"
"session.ended"
"agent.spawned"
"agent.finished"
"status"
"tasks.updated"
"task.upserted"
"task.deleted"
"spawn.requested"
"spawn.returned"
"wait.started"
"wait.ended"
"message.sent"
"activity"
node: optional Node { id, agent_type, as_of_event_id, 39 more }

That node, as it stood just after this event. Present only when expanded.

id: string

Unique identifier for the node within its graph: node_ and the base64url of provider_ref.

agent_type: string

For an agent, its type, as the tool names it (Explore, general-purpose, …).

as_of_event_id: string

The event this node is shown as of.

attention: string

While input_required: what the person is being asked. Untrusted text.

background: boolean

Whether it was started in the background, so its requester carries on without waiting. null when unknown.

background_agent_count: number

Helpers its tool ran on its own between turns (memory extraction, summaries, prompt suggestions). They report nothing about what they’re for, so they’re counted, not made nodes.

blocked: Blocked { cycle, node_count, on_ids, 3 more }

What stands between a node and carrying on.

child_count: number

How many nodes sit directly under it.

created: number

When it started (or was first seen), in Unix milliseconds.

formatint64
cwd: string

The directory it runs in, when reported.

depth: number

How far below its root it is: 0 for a root.

minimum0
descendant_count: number

How many nodes sit under it, at any depth: the size of descendants.

ended: number

When it finished, in Unix milliseconds. null while it hasn’t.

formatint64
graph_id: string

The graph it belongs to.

headline: string

One line on what it’s doing now: summary, else the task in progress. Untrusted text.

kind: NodeKind

session: one run of a coding agent (Claude Code, Codex, Cursor, …). agent: an agent started inside a session, by it or by another agent.

One of the following:
"session"
"agent"
last_event: number

When it last had an event, in Unix milliseconds.

formatint64
One of the following:
object: "node"

String representing the object’s type: always node.

parent_id: string

The node it sits under: the agent or session that started it. null for a root.

provider: string

The agent tool: claude-code, codex, cursor, or another that reports to Agent Graph.

provider_ref: string

The id the agent tool gave it: <provider>:<session> for a session, <provider>:<session>/<agent> for an agent. Not an id in this API: use it to match the node to the tool’s own records.

purpose: string

Why it was started, from the request that started it. Untrusted text.

requested_by_id: string

The node whose spawn request started it, when known. Usually the parent; null for a session nothing started, or an agent no request was matched to.

root_id: string

The top of its tree: follow parent_id until it’s null. A session started by another session is in that session’s tree. A root’s root_id is its own id.

session_id: string

The session it runs in: itself, for a session; for an agent, the nearest session above it.

spawn_call_id: string

The tool call, in its requester’s session, that started it: matches a spawns[].call_id on requested_by.

stale: boolean

working, waiting on nothing, and silent for 30 minutes: it may have hung or crashed. Measured against as_of, not now.

state: NodeState

What a node is doing.

  • working: doing something.
  • input_required: waiting on a person (attention says what for).
  • idle: a session between turns, waiting for its next prompt.
  • completed, failed, canceled: finished.
One of the following:
"working"
"input_required"
"idle"
"completed"
"failed"
"canceled"
summary: string

Its own latest status line, if it reports one. Untrusted text.

task_counts: TaskCounts { completed, in_progress, open, 2 more }

Its tasks, by status.

completed: number
in_progress: number
open: number

Not completed: pending and in_progress.

pending: number
total: number
title: string

The session’s name, as its tool shows it. It can change. Untrusted text.

children: optional unknown

The nodes directly under it, oldest first: the first 100, and a url for the rest. Present only when expanded.

descendants: optional unknown

Every node under it, at any depth, as one flat list in tree order: each parent before its children, siblings oldest first. The first 1,000, and a url for the rest. Present only when expanded.

messages: optional array of Message { id, body, created, 5 more }

Messages it has sent and received, oldest first. Present only when expanded.

id: string

The tool’s id for the message.

body: string

The whole message, only if the share was made with body capture on. Untrusted text.

created: number

When it was sent, in Unix milliseconds.

formatint64
direction: "sent" or "received"
One of the following:
"sent"
"received"
peer_id: string

The node on the other side, when it’s one in this graph.

peer_name: string

The tool’s name for the other side, when it isn’t a node in this graph.

reply_to_message_id: string

The message it answers.

summary: string

Untrusted text.

parent: optional Node { id, agent_type, as_of_event_id, 39 more }

The parent node. Present only when expanded.

requested_by: optional Node { id, agent_type, as_of_event_id, 39 more }

The node that requested it. Present only when expanded.

root: optional Node { id, agent_type, as_of_event_id, 39 more }

The root node. Present only when expanded.

session: optional Node { id, agent_type, as_of_event_id, 39 more }

The session node. Present only when expanded.

spawns: optional array of Spawn { agent_type, background, call_id, 7 more }

The children it has asked for, oldest first. Present only when expanded.

agent_type: string
background: boolean

If false, the requester is blocked until the call returns.

call_id: string

The tool call that asked for it.

child_id: string

The node it started, once matched. Before the call returns this can be a best guess, corrected when it does.

kind: "agent" or "session"

agent: an agent in the same session. session: a separate session, e.g. codex exec run from a shell.

One of the following:
"agent"
"session"
purpose: string

Untrusted text.

requested: number

When it was asked for, in Unix milliseconds.

formatint64
returned: boolean

Whether the call has returned.

run: boolean

Whether it’s through agent-graph run, when known.

title: string

For a session, the name it was to be given.

tasks: optional array of Task { id, active_text, status, text }

Its task list, in the tool’s order. Present only when expanded.

id: string

The tool’s id for the task, unique within the node.

active_text: string

How the tool words it while it’s in progress (Running the tests). Untrusted text.

status: "pending" or "in_progress" or "completed"
One of the following:
"pending"
"in_progress"
"completed"
text: string

What the task is. Untrusted text.

waits: optional array of Wait { id, ended, kind, 4 more }

What it has waited on and is waiting on, oldest first. Present only when expanded.

id: string

The wait’s id, unique within the node.

ended: number

When it stopped, in Unix milliseconds.

formatint64
kind: "spawn" or "explicit"

spawn: waiting for a child it asked for, until the call returns. explicit: any other wait the tool reported.

One of the following:
"spawn"
"explicit"
on_id: string

The node waited on. null while a requested child hasn’t started.

open: boolean

Whether it’s still waiting.

reason: string

Untrusted text.

started: number

When it started waiting, in Unix milliseconds.

formatint64
has_more: boolean

Whether there are more after this page.

object: "list"
url: string

The URL for this list, with every parameter that defined it (including as_of). Add starting_after to get the next page.

List events

curl https://agentgraph.chofter.com/api/v1/graphs/$GRAPH_ID/events \
    -H "Authorization: Bearer $AGENT_GRAPH_API_KEY"
{
  "object": "list",
  "url": "/api/v1/graphs/gph_k3P9xQ2mWt7a/events",
  "as_of_event_id": "evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1",
  "has_more": true,
  "data": [
    {
      "id": "evt_01M4G3G0E4H6TW2C9Z5RB1KQ0E",
      "object": "event",
      "type": "status",
      "created": 1791541903812,
      "graph_id": "gph_k3P9xQ2mWt7a",
      "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
      "source": {
        "provider": "claude-code",
        "provider_version": "2.1.260",
        "adapter": "claude-code@1"
      },
      "payload": {
        "state": "working",
        "summary": "Reading the auth middleware"
      },
      "changes": [
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
          "created": false,
          "fields": {
            "summary": {
              "from": null,
              "to": "Reading the auth middleware"
            },
            "headline": {
              "from": "Searching for callers",
              "to": "Reading the auth middleware"
            }
          }
        }
      ]
    },
    {
      "id": "evt_01M4G2FMPN7KD3N1PZ0QX8RM4A",
      "object": "event",
      "type": "agent.spawned",
      "created": 1791540843221,
      "graph_id": "gph_k3P9xQ2mWt7a",
      "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
      "source": {
        "provider": "claude-code",
        "provider_version": "2.1.260",
        "adapter": "claude-code@1"
      },
      "payload": {
        "agent_type": "Explore",
        "purpose": "Find every caller of the old session store"
      },
      "changes": [
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
          "created": true,
          "fields": {
            "state": {
              "from": null,
              "to": "working"
            },
            "parent_id": {
              "from": null,
              "to": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEw"
            }
          }
        },
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEw",
          "created": false,
          "fields": {
            "child_count": {
              "from": 2,
              "to": 3
            },
            "descendant_count": {
              "from": 4,
              "to": 5
            },
            "blocked": {
              "from": null,
              "to": {
                "on_ids": [
                  "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE"
                ],
                "starting": 0,
                "node_count": 1,
                "open_tasks": 0,
                "cycle": false
              }
            }
          }
        }
      ]
    }
  ]
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "Invalid limit: must be between 1 and 1000.",
    "param": "limit",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "expand_too_deep",
    "message": "The path children.children.children.children.children goes 5 levels deep; at most 4 are allowed.",
    "param": "expand",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "authentication_error",
    "code": "api_key_invalid",
    "message": "Invalid API key: ag_sk_live_****Xa9T.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "permission_error",
    "code": "api_key_restricted",
    "message": "This restricted key can't read gph_k3P9xQ2mWt7a.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_missing",
    "message": "No such node: node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2I4MWMyMmUwOWY3YTNjNTU.",
    "param": "node_id",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "node_not_yet_created",
    "message": "The node node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE is created by evt_01M4G2FMPN7KD3N1PZ0QX8RM4A, after as_of.",
    "param": "as_of",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors",
    "first_event_id": "evt_01M4G2FMPN7KD3N1PZ0QX8RM4A"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "outside_retention_window",
    "message": "This graph holds events from evt_01M4FXYWR82B8XK0V5QJ7M1N3R to evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1.",
    "param": "as_of",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors",
    "first_event_id": "evt_01M4FXYWR82B8XK0V5QJ7M1N3R",
    "last_event_id": "evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1"
  }
}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit",
    "message": "Too many requests. Try again in 1 second.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "api_error",
    "code": "api_error",
    "message": "Something went wrong. Try again, and include the Request-Id if it keeps happening.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
Returns Examples
{
  "object": "list",
  "url": "/api/v1/graphs/gph_k3P9xQ2mWt7a/events",
  "as_of_event_id": "evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1",
  "has_more": true,
  "data": [
    {
      "id": "evt_01M4G3G0E4H6TW2C9Z5RB1KQ0E",
      "object": "event",
      "type": "status",
      "created": 1791541903812,
      "graph_id": "gph_k3P9xQ2mWt7a",
      "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
      "source": {
        "provider": "claude-code",
        "provider_version": "2.1.260",
        "adapter": "claude-code@1"
      },
      "payload": {
        "state": "working",
        "summary": "Reading the auth middleware"
      },
      "changes": [
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
          "created": false,
          "fields": {
            "summary": {
              "from": null,
              "to": "Reading the auth middleware"
            },
            "headline": {
              "from": "Searching for callers",
              "to": "Reading the auth middleware"
            }
          }
        }
      ]
    },
    {
      "id": "evt_01M4G2FMPN7KD3N1PZ0QX8RM4A",
      "object": "event",
      "type": "agent.spawned",
      "created": 1791540843221,
      "graph_id": "gph_k3P9xQ2mWt7a",
      "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
      "source": {
        "provider": "claude-code",
        "provider_version": "2.1.260",
        "adapter": "claude-code@1"
      },
      "payload": {
        "agent_type": "Explore",
        "purpose": "Find every caller of the old session store"
      },
      "changes": [
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE",
          "created": true,
          "fields": {
            "state": {
              "from": null,
              "to": "working"
            },
            "parent_id": {
              "from": null,
              "to": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEw"
            }
          }
        },
        {
          "node_id": "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEw",
          "created": false,
          "fields": {
            "child_count": {
              "from": 2,
              "to": 3
            },
            "descendant_count": {
              "from": 4,
              "to": 5
            },
            "blocked": {
              "from": null,
              "to": {
                "on_ids": [
                  "node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE"
                ],
                "starting": 0,
                "node_count": 1,
                "open_tasks": 0,
                "cycle": false
              }
            }
          }
        }
      ]
    }
  ]
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "Invalid limit: must be between 1 and 1000.",
    "param": "limit",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "expand_too_deep",
    "message": "The path children.children.children.children.children goes 5 levels deep; at most 4 are allowed.",
    "param": "expand",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "authentication_error",
    "code": "api_key_invalid",
    "message": "Invalid API key: ag_sk_live_****Xa9T.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "permission_error",
    "code": "api_key_restricted",
    "message": "This restricted key can't read gph_k3P9xQ2mWt7a.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_missing",
    "message": "No such node: node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2I4MWMyMmUwOWY3YTNjNTU.",
    "param": "node_id",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "node_not_yet_created",
    "message": "The node node_Y2xhdWRlLWNvZGU6NWYyYzFlOWEtOGQxYi00YjZlLTlhNTEtMGY2ZjJjN2QzZTEwL2EzZmQwN2MyYjE5ZTRkNjE is created by evt_01M4G2FMPN7KD3N1PZ0QX8RM4A, after as_of.",
    "param": "as_of",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors",
    "first_event_id": "evt_01M4G2FMPN7KD3N1PZ0QX8RM4A"
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "outside_retention_window",
    "message": "This graph holds events from evt_01M4FXYWR82B8XK0V5QJ7M1N3R to evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1.",
    "param": "as_of",
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors",
    "first_event_id": "evt_01M4FXYWR82B8XK0V5QJ7M1N3R",
    "last_event_id": "evt_01M4G3G4E8Q9R2M5X4T6W8Y0B1"
  }
}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit",
    "message": "Too many requests. Try again in 1 second.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}
{
  "error": {
    "type": "api_error",
    "code": "api_error",
    "message": "Something went wrong. Try again, and include the Request-Id if it keeps happening.",
    "param": null,
    "doc_url": "https://agentgraph.chofter.com/docs/reference#errors"
  }
}