Skip to content

[FEATURE] Include last assistant message in AGENT_COMPLETE event #59

@galuszkm

Description

@galuszkm

Problem Statement

When the EventPublisher emits an AGENT_COMPLETE event it currently includes usage/metrics but not the assistant's final message. Consumers of these events (loggers, analytics, streaming UIs) must separately fetch or reconstruct the final assistant message, which is inconvenient and error-prone.

Proposed Solution

In event_publisher.py, inside _on_complete, extract both the flat text and the full Message TypedDict from AgentResult and include both in the AGENT_COMPLETE payload.

The AgentResult already exposes everything needed:

  • str(result) — flattens all text and citationsContent blocks into a plain string (also handles structured_output → JSON and interrupts → serialized list)
  • result.message — the full Message TypedDict: {"role": "assistant", "content": list[ContentBlock], "metadata": ...}

Example patch (conceptual):

# in event_publisher.py::_on_complete
result = event.result
if result:
    data["text"] = str(result)          # flat string, always safe
    data["message"] = result.message    # full Message TypedDict for rich consumers

This is fully self-contained to event_publisher.py — no SDK changes, no new imports, no new infrastructure. Backwards compatible (new fields added to payload, nothing removed).

Scope: AGENT_COMPLETE only. MULTIAGENT_COMPLETE can be addressed separately if desired.

Use Case

  • Logging & analytics: consumers can index and display the assistant output directly from AGENT_COMPLETE events using the flat text field, without any additional lookups.
  • Streaming UIs: show the final assistant message as soon as the event fires.
  • Reasoning / extended thinking: the full message field preserves reasoningContent blocks that are lost in str(result) — useful for auditing chain-of-thought.
  • Tool use inspection: the final content list may include toolUse blocks, allowing consumers to see which tools were called in the last turn.
  • RAG / citations: citationsContent is available as structured data in message, not just flattened text.
  • Per-message token usage: message.metadata carries usage and metrics for the final turn specifically, separate from the accumulated totals already in the event.

Alternatives Considered

  • Include only text (str(result)): simpler, but loses reasoning blocks, tool use, citations, and per-message metadata — not useful for richer consumers.
  • Include only message (result.message): richer, but requires all consumers to iterate content blocks themselves even for the simple text case.
  • Include both (proposed): covers the simple case with text and the rich case with message, at zero extra cost.
  • Leave events unchanged and require consumers to call into strands to fetch AgentResult: less ergonomic and increases coupling.

Additional Context

Implementation is self-contained to event_publisher.py with no public API or SDK changes required.

The Message TypedDict shape (from strands.types.content):

class Message(TypedDict):
    role: Role                           # always "assistant"
    content: list[ContentBlock]          # text, toolUse, reasoningContent, citationsContent, ...
    metadata: NotRequired[MessageMetadata]  # usage + metrics, stripped before model calls

ContentBlock fields relevant to a final assistant message: text, toolUse, reasoningContent, citationsContent.

Metadata

Metadata

Labels

enhancementNew feature or request
No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions