Repository navigation
Replies: 1 comment
|
This ties into #1160 - sending a AG-UI MESSAGE_SNAPSHOT via a defined endpoint would be the first and foremost use case of this. If this endpoint was included in the standard as well, even better. As discussed in the other thread, it would need to include additional metadata on conversation (threadID) level. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
AG-UI defines an optional
timestampon every event. I would like to propose an optionaltimestampon every durableMessageas well, primarily so a frontend can preserve and display message time after history compaction.Short summary: Why and what?
Knowing when a question was asked / answered and when an action was taken is often important for users, especially in the business context. The most prominent case is data agents - users must be able to see when a conversation took place to judge whether the data in the conversation might already be outdated.
Currently, when loading an old conversation or sharing a conversation with another user using AG-UI's MESSAGE_SNAPSHOT, this information is lost. I therefore propose adding a canonical, optional and fully backwards compatible timestamp field to
BaseMessage:Further details follow below - not sure what the process is here in AG-UI since this is the first time I am posting here - I am happy to write a PR / go into even more detail once maintainers agree this is something that makes sense in principle?
Why
BaseEvent.timestampis not enoughBaseEvent.timestampis useful on the raw stream. For example, a producer can timestampTEXT_MESSAGE_START, eachTEXT_MESSAGE_CONTENT, andTEXT_MESSAGE_END. The final event is a reasonable time for an assistant reply to be considered complete.However, it is event metadata, not message metadata:
{ "type": "TEXT_MESSAGE_END", "messageId": "assistant-1", "timestamp": 1786096860000 }The information becomes difficult to use after compaction. A compact history normally replaces a sequence of text, tool, and state events with a standard AG-UI
MESSAGES_SNAPSHOTevent:{ "type": "MESSAGES_SNAPSHOT", "timestamp": 1786100000000, "messages": [ { "id": "user-1", "role": "user", "content": "Show material costs for July." }, { "id": "assistant-1", "role": "assistant", "content": "July material costs were..." } ] }MESSAGES_SNAPSHOTis already an AG-UI standard state-management event. Its event-leveltimestampsays only when this snapshot was emitted. It does not say whenuser-1was asked or whenassistant-1was completed. The originalTEXT_MESSAGE_*events may no longer be retained in the compact projection, so a frontend cannot reconstruct those per-message times.This is important for more than chat chronology. A data-agent user needs to judge whether a table, chart, or dataframe was produced before or after an upstream data refresh. It is also useful for exports, audit views, and chat channels.
Proposed semantics
Add optional
timestamp?: numbertoBaseMessage, using Unix epoch milliseconds to match the existingBaseEvent.timestampencoding.MESSAGES_SNAPSHOT: time the response is finalized and made durable for replay.Example:
{ "type": "MESSAGES_SNAPSHOT", "messages": [ { "id": "user-1", "role": "user", "content": "Show material costs for July.", "timestamp": 1786096800000 }, { "id": "assistant-1", "role": "assistant", "content": "July material costs were...", "timestamp": 1786096860000 } ] }Why use the name
timestampThe existing protocol already uses
timestampfor the same encoding and broad concept: a time associated with a protocol object. Reusing it makes the field easier to learn and avoids introducing competingcreatedAt,sentAt, andcompletedAtvocabularies.The type makes the scope unambiguous:
BaseEvent.timestampBaseMessage.timestampFor live streaming, implementations may continue to expose event start/end timing through the existing event field. A compact snapshot contains completed durable messages, so one message-level timestamp is enough for the history-display use case.
Why a first-class field rather than a workaround
CUSTOMevents,rawEvent, and adapter-specific metadata can carry this information today. They require a second correlation object or per-adapter convention, and consumers cannot rely on one portable shape.AG-UI currently has no
metadataorcustomfield onBaseMessage. Adding a generic bag would be less precise and typed SDKs may strip unknown fields. A narrow optional field is easier to specify and test across implementations.This is backward compatible at the protocol level: existing producers omit it, and clients that do not display timestamps ignore it. It still requires a coordinated SDK update so typed parse-and-reserialize paths retain the field, especially the TypeScript Zod message schemas.
Suggested implementation after consensus
timestamp?: numbertoBaseMessagein TypeScript, Python, .NET, proto, and community SDK schemas.BaseEvent.timestampin the event documentation and recommend it for producers that support raw replay or audit/history displays.MESSAGES_SNAPSHOT, retain message timestamps through decode/re-encode, and accept older snapshots without them.There is a related question in #2022 about
BaseEvent.timestamp; I see this as complementary rather than a replacement. The maintainers' proposal discussion #2161 also explicitly weighs optional first-class fields againstCUSTOM-event conventions.All reactions