user.message vs user.define_outcome: Event Types in Anthropic Managed Agents
The user.message event sends conversational input to trigger standard agent responses, while user.define_outcome declares a desired final state with evaluation criteria to enable automated iteration loops.
When building applications with Anthropic Managed Agents using the anthropics/cwc-workshops reference implementation, developers must choose between two distinct event types for client-to-agent communication. Understanding the difference between user.message and user.define_outcome is critical for controlling whether a session operates in standard chat mode or outcome-driven evaluation mode.
Core Functional Differences
user.message serves as the standard mechanism for conversational interaction. According to the source code in research-desk/src/lib/sessions.ts, this event type triggers normal agent responses through the sendUserMessage() function, allowing users to ask questions, provide instructions, or continue dialogue threads.
user.define_outcome initiates a structured evaluation loop. As implemented in research-desk/solutions/src/lib/sessions.ts, the defineOutcome() function sends this event to declare what "done" looks like, supply a grading rubric, and optionally set iteration limits. This switches the session into outcome-driven mode where the platform automatically evaluates agent outputs against defined criteria.
Implementation in the Source Code
Sending Standard Messages with sendUserMessage()
In research-desk/src/lib/sessions.ts (lines 53-57), the sendUserMessage() function constructs and transmits a user.message event:
// research-desk/src/lib/sessions.ts
export async function sendUserMessage(
client: Anthropic,
sessionId: string,
text: string,
) {
await client.beta.sessions.events.send(sessionId, {
events: [{ type: "user.message", content: [{ type: "text", text }] }],
});
}
This pattern appears consistently across the codebase, including in production-ready-agent/solution/app/api/steer/[id]/route.ts (lines 17-18), where the same event structure forwards user text to the session.
Defining Outcomes with defineOutcome()
The solution implementation in research-desk/solutions/src/lib/sessions.ts (lines 66-78) provides the full defineOutcome() function that sends a user.define_outcome event:
// research-desk/solutions/src/lib/sessions.ts (implemented version)
export async function defineOutcome(
client: Anthropic,
sessionId: string,
description: string,
rubric: string,
maxIterations = 2,
) {
await client.beta.sessions.events.send(sessionId, {
events: [
{
type: "user.define_outcome",
description,
rubric: { type: "text", content: rubric },
max_iterations: maxIterations,
},
],
});
}
Note that the main repository contains only a stub implementation that throws a TODO error, while the solutions directory provides this complete working version.
Payload Structure and Schema
The JSON structures for these events differ significantly:
user.message payload:
{
"type": "user.message",
"content": [{ "type": "text", "text": "Your prompt here" }]
}
user.define_outcome payload:
{
"type": "user.define_outcome",
"description": "What 'done' looks like",
"rubric": {
"type": "text",
"content": "Evaluation criteria for the grader"
},
"max_iterations": 2
}
The user.define_outcome event requires description and rubric fields, with max_iterations as an optional parameter that defaults to 2 in the reference implementation.
Session Behavior and Routing
When you send a user.message event, the orchestrator processes it as a normal chat event and streams back agent.message responses. This creates the standard interactive back-and-forth typical of conversational AI applications.
Sending a user.define_outcome event triggers the platform's grader evaluation loop. The system watches agent outputs, compares them against the provided rubric, and may request additional iterations until the rubric is satisfied or max_iterations is reached. This generates span.outcome_evaluation_end events that indicate the grader's verdict on whether the outcome requirements were met.
UI Rendering Differences
The production-ready-agent/starter/lib/chat.ts file (lines 40-49) demonstrates how the UI distinguishes between these events in its toChatMessages() function:
// production-ready-agent/starter/lib/chat.ts
if (e.type === "user.message") {
out.push({ id: e.id, role: "user", text: textOf(e) });
} else if (e.type === "user.define_outcome") {
out.push({
id: e.id,
role: "user",
text: "",
outcome: {
result: "defined",
explanation: (e as { description?: string }).description,
},
});
}
While user.message renders as a standard text chat bubble, user.define_outcome produces a special outcome marker that displays the defined objective to the user, creating a visual distinction between conversational turns and evaluation setup.
Dynamic Selection in API Endpoints
The steering endpoint in production-ready-agent/solution/app/api/steer/[id]/route.ts (lines 10-20) illustrates how applications can dynamically select between these event types based on request parameters:
// production-ready-agent/solution/app/api/steer/[id]/route.ts
const event =
kind === "outcome"
? {
type: "user.define_outcome",
description: text,
rubric: { type: "text", content: text },
}
: { type: "user.message", content: [{ type: "text", text }] };
await client.beta.sessions.events.send(id, { events: [event] });
This pattern allows a single API route to handle both standard messaging and outcome definition by checking the request kind and constructing the appropriate event payload.
Summary
user.messagesends standard conversational input that triggers immediate agent responses, suitable for interactive chat flows.user.define_outcomedeclares a target state with evaluation criteria, enabling the platform to automatically iterate and grade agent outputs against a rubric.- Source implementations reside in
research-desk/src/lib/sessions.tsfor messages andresearch-desk/solutions/src/lib/sessions.tsfor outcomes, with UI handling inproduction-ready-agent/starter/lib/chat.ts. - Payload requirements differ: messages require only text content, while outcomes require description, rubric, and optionally
max_iterations. - Session effects vary significantly: standard messaging creates a simple request-response loop, whereas outcome events trigger the grader's evaluation cycle with potential for multiple iterations.
Frequently Asked Questions
Can I mix user.message and user.define_outcome events in the same session?
Yes, you can send user.message events for standard conversational turns and later send a user.define_outcome event to switch the session into outcome-driven evaluation mode. However, once the outcome is defined, subsequent interactions typically focus on achieving that outcome rather than casual conversation, as the grader will evaluate all agent outputs against the established rubric.
What happens if the rubric is never satisfied within max_iterations?
According to the implementation in research-desk/solutions/src/lib/sessions.ts, the platform will stop iterating once max_iterations is reached, even if the rubric criteria remain unmet. The session generates a final span.outcome_evaluation_end event indicating the result, which the UI renders as a completed outcome bubble, allowing the application to handle the incomplete status programmatically.
Is the max_iterations parameter required for user.define_outcome?
No, max_iterations is optional. The reference implementation in research-desk/solutions/src/lib/sessions.ts sets a default value of 2 if not specified. This parameter controls the maximum number of revision cycles the grader will attempt before concluding the evaluation, preventing infinite loops when the rubric cannot be satisfied.
How does the grader evaluate agent outputs against the rubric?
The platform's grader automatically compares agent outputs to the rubric.content field provided in the user.define_outcome event. As implemented in the anthropics/cwc-workshops codebase, this evaluation generates span.outcome_evaluation_end events that indicate whether the agent's work meets the defined criteria. The UI then renders these verdicts through the conversion logic in production-ready-agent/starter/lib/chat.ts (lines 40-49), displaying the evaluation results alongside the conversation history.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →