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.
Problem Statement
When the
EventPublisheremits anAGENT_COMPLETEevent 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 fullMessageTypedDict fromAgentResultand include both in theAGENT_COMPLETEpayload.The
AgentResultalready exposes everything needed:str(result)— flattens alltextandcitationsContentblocks into a plain string (also handlesstructured_output→ JSON and interrupts → serialized list)result.message— the fullMessageTypedDict:{"role": "assistant", "content": list[ContentBlock], "metadata": ...}Example patch (conceptual):
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_COMPLETEonly.MULTIAGENT_COMPLETEcan be addressed separately if desired.Use Case
AGENT_COMPLETEevents using the flattextfield, without any additional lookups.messagefield preservesreasoningContentblocks that are lost instr(result)— useful for auditing chain-of-thought.contentlist may includetoolUseblocks, allowing consumers to see which tools were called in the last turn.citationsContentis available as structured data inmessage, not just flattened text.message.metadatacarriesusageandmetricsfor the final turn specifically, separate from the accumulated totals already in the event.Alternatives Considered
text(str(result)): simpler, but loses reasoning blocks, tool use, citations, and per-message metadata — not useful for richer consumers.message(result.message): richer, but requires all consumers to iteratecontentblocks themselves even for the simple text case.textand the rich case withmessage, at zero extra cost.AgentResult: less ergonomic and increases coupling.Additional Context
Implementation is self-contained to
event_publisher.pywith no public API or SDK changes required.The
MessageTypedDict shape (fromstrands.types.content):ContentBlockfields relevant to a final assistant message:text,toolUse,reasoningContent,citationsContent.