Principle:OpenHands OpenHands Agent Summary Callback
| Knowledge Sources | |
|---|---|
| Domains | Platform_Integration, Webhook_Processing |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Agent summary callback is the post-execution hook pattern that generates a summary of the agent's work upon completion and posts the results back to the originating platform.
Description
After an AI agent finishes working on a task (whether successfully or not), the system must close the loop by informing the user of the outcome. The agent summary callback pattern addresses this by registering a callback function that fires when the agent's conversation reaches a terminal state. The callback:
- Detects completion -- Listens for terminal events (agent finish, timeout, error) in the conversation's event stream.
- Generates a summary -- Condenses the agent's actions, findings, and results into a human-readable summary, often using an LLM to synthesize the raw event log into a concise narrative.
- Posts to the platform -- Delivers the summary to the originating GitHub issue, PR, or review thread as a comment, providing closure to the user.
This pattern separates the execution concern (running the agent) from the reporting concern (summarizing and posting results), enabling each to evolve independently.
Usage
Apply this pattern when:
- Agent execution is decoupled from result reporting (different services or processes)
- A human-readable summary is needed (raw agent logs are too verbose for end users)
- The reporting logic varies by platform (GitHub comments vs. Slack messages vs. email)
- Post-execution hooks are needed for cleanup, billing, or analytics beyond just reporting
Theoretical Basis
1. Callback Processor Pattern
The callback processor pattern registers functions that are invoked when specific events occur in an event stream. The processor subscribes to a conversation's event bus and is called for each event:
class CallbackProcessor:
def __call__(event) -> Result | None:
if is_terminal(event):
summary = generate_summary(event.conversation)
post_summary(summary)
return Result(status=COMPLETED)
return None # not a terminal event, continue
The processor returns None for non-terminal events (indicating no action taken) and a Result for terminal events (indicating the callback has been executed).
2. Event-Driven Post-Execution Hooks
In event-driven architectures, post-execution hooks decouple the executor from the reporter. The executor emits events; the hook listens for specific event types:
EventStream:
AgentStarted -> ... -> ToolCalled -> ... -> AgentFinished
|
v
CallbackProcessor fires
This decoupling has several benefits:
- The executor does not need to know about the reporting mechanism
- Multiple hooks can be registered (e.g., one for GitHub posting, one for analytics, one for billing)
- Hooks can be added or removed without modifying the executor
3. Summary Generation via LLM
The summary is typically generated by an LLM that reads the agent's conversation history and produces a concise, user-facing narrative:
generate_summary(conversation_history):
prompt = SUMMARY_TEMPLATE.format(
actions=conversation_history.actions,
results=conversation_history.results,
errors=conversation_history.errors,
)
return LLM(prompt)
The summary prompt is engineered to:
- Highlight the key actions taken (files modified, tests run, PRs created)
- Report the outcome (success, partial success, failure)
- Include actionable next steps for the user
4. Conversation Lookup and State Recovery
The callback processor must recover the full conversation state from just a conversation ID, because the callback may execute in a different process or container than the original agent:
recover_state(conversation_id):
metadata = lookup_metadata(conversation_id)
events = fetch_event_history(conversation_id)
view = reconstruct_view(metadata)
return (metadata, events, view)
This requires that all conversation state is persisted durably (not just held in memory), enabling the callback to reconstruct the context needed for summary generation and posting.
5. Exactly-Once Posting
The callback must ensure the summary is posted exactly once, even if the terminal event is delivered multiple times (e.g., due to event stream replays):
process_terminal(event):
if already_posted_summary(event.conversation_id):
return # idempotent guard
summary = generate_summary(event.conversation_id)
post_summary(summary)
mark_summary_posted(event.conversation_id)