xml-pipeline/handlers/hello.py
dullfig ad507cd54a Fix TUI console response display
- Add thread-safe invalidation for cross-task updates
- Fix response handler to properly route to TUI output
- Add debug output if console hook fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:30:36 -08:00

146 lines
4.7 KiB
Python

"""
hello.py — Multi-agent hello world handlers for testing the message pump.
This module demonstrates a multi-agent flow:
user -> greeter -> shouter -> user
Payload classes:
- Greeting: Initial request with a name
- GreetingResponse: Greeter's response
- ShoutedResponse: Shouter's ALL CAPS version
Handlers:
- handle_greeting: Receives Greeting, sends GreetingResponse to shouter
- handle_shout: Receives GreetingResponse, sends ShoutedResponse to original sender
Usage in organism.yaml:
listeners:
- name: greeter
payload_class: handlers.hello.Greeting
handler: handlers.hello.handle_greeting
- name: shouter
payload_class: handlers.hello.GreetingResponse
handler: handlers.hello.handle_shout
"""
from dataclasses import dataclass
from third_party.xmlable import xmlify
from agentserver.message_bus.message_state import HandlerMetadata, HandlerResponse
@xmlify
@dataclass
class Greeting:
"""Incoming greeting request."""
name: str
@xmlify
@dataclass
class GreetingResponse:
"""Greeter's response - will be forwarded to shouter."""
message: str
original_sender: str # Track who started the conversation
@xmlify
@dataclass
class ShoutedResponse:
"""Shouter's ALL CAPS response - sent back to original sender."""
message: str
async def handle_greeting(payload: Greeting, metadata: HandlerMetadata) -> HandlerResponse:
"""
Handle an incoming Greeting and forward GreetingResponse to shouter.
Flow: console-router -> greeter -> shouter -> response-handler
Demonstrates TodoUntil pattern:
1. Register a watcher for ShoutedResponse from shouter
2. Send GreetingResponse to shouter
3. When ShoutedResponse appears, eyebrow is raised
4. On next invocation, greeter sees nudge and can close the todo
NOTE: This handler uses platform.complete() for LLM calls.
The system prompt is managed by the platform (from organism.yaml).
The handler cannot see or modify the prompt.
"""
from agentserver.platform import complete
from agentserver.message_bus.todo_registry import get_todo_registry
# Check for any raised todos and close them
todo_registry = get_todo_registry()
if metadata.todo_nudge:
# We have raised todos - check and close them
raised = todo_registry.get_raised_for(metadata.thread_id, metadata.own_name or "greeter")
for watcher in raised:
todo_registry.close(watcher.id)
# Register a todo watcher - we want to know when shouter responds
todo_registry.register(
thread_id=metadata.thread_id,
issuer=metadata.own_name or "greeter",
wait_for="ShoutedResponse",
from_listener="shouter",
description=f"waiting for shouter to process greeting for {payload.name}",
)
# Use platform.complete() for LLM call
# The platform assembles: system prompt (from registry) + peer schemas + history + user message
# The handler only provides the user message - no prompt building!
llm_response = await complete(
agent_name=metadata.own_name or "greeter",
thread_id=metadata.thread_id,
user_message=f"Greet {payload.name} enthusiastically. Respond with ONLY a short greeting sentence.",
temperature=0.9,
)
# Return clean dataclass + target - pump handles envelope
return HandlerResponse(
payload=GreetingResponse(
message=llm_response,
original_sender="response-handler",
),
to="shouter",
)
async def handle_shout(payload: GreetingResponse, metadata: HandlerMetadata) -> HandlerResponse:
"""
Handle GreetingResponse by shouting it back to original sender.
Flow: greeter -> shouter -> original_sender (response-handler)
"""
# Return clean dataclass + target - pump handles envelope
return HandlerResponse(
payload=ShoutedResponse(message=payload.message.upper()),
to=payload.original_sender,
)
async def handle_response_print(payload: ShoutedResponse, metadata: HandlerMetadata) -> None:
"""
Print the final response to the console.
Routes output to the TUI console if available, otherwise prints to stdout.
"""
console = None
try:
from run_organism import get_console
console = get_console()
except ImportError:
pass
if console and hasattr(console, 'on_response'):
try:
console.on_response("shouter", payload)
return # Success - don't fall through to print
except Exception as e:
# Debug: show error but continue to fallback
print(f"\n\033[31m[console error] {e}\033[0m")
# Fallback: print to stdout (only if console not available or failed)
print(f"\n\033[36m[response] {payload.message}\033[0m")
return None