Version: 2.0 (A2A Protocol Compliant)
- System Architecture
- A2A Protocol Implementation
- Message Flow with A2A
- test_system.sh Flow
- How to Provide Custom Content
- API Reference
- A2A Troubleshooting
Note: All agent-to-agent communication uses A2A protocol message envelopes.
┌─────────────────────────────────────────────────────────────────────────┐
│ USER REQUEST │
│ "Adapt bear_loses_roar story as │
│ scientist who lost his formulas" │
└──────────────────────────────────┬──────────────────────────────────────┘
│ HTTP POST /adapt-story
│ (Simplified JSON - converted to A2A internally)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ CREATIVE DIRECTOR (Orchestrator) │
│ Port 8000 (FastAPI) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Responsibilities: │ │
│ │ • Check cache for existing variations │ │
│ │ • Create & manage sessions │ │
│ │ • Coordinate iterative refinement loop │ │
│ │ • Enforce quality gates (8.0/10 threshold) │ │
│ │ • Cache approved variations │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└────────────┬────────────────────────────────────────┬───────────────────┘
│ │
ITERATION LOOP (Max 5 times) │
│ │
┌───────▼───────┐ ┌────────▼────────┐
│ STEP 1: │ │ STEP 2: │
│ GENERATE │ │ EVALUATE │
└───────┬───────┘ └────────┬────────┘
│ │
│ A2A REQUEST (remix/refine) │ A2A REQUEST (evaluate)
│ POST /remix (v1) or /refine (v2+) │ POST /evaluate
│ wrapped in A2A envelope │ wrapped in A2A envelope
▼ ▼
┌────────────────────────────────────┐ ┌───────────────────────────────────┐
│ CREATOR AGENT ("The Remixer") │ │ CRITIC AGENT ("The Analyst") │
│ Port 8001 (FastAPI) │ │ Port 8002 (FastAPI) │
│ ┌──────────────────────────────┐ │ │ ┌─────────────────────────────┐ │
│ │ Capabilities: │ │ │ │ Evaluation Dimensions: │ │
│ │ • /remix - Initial adapt │ │ │ │ • Moral Preservation (30%) │ │
│ │ • /refine - Feedback-based │ │ │ │ • Structure Quality (25%) │ │
│ │ improvements │ │ │ │ • Creativity (25%) │ │
│ │ │ │ │ │ • Coherence (20%) │ │
│ │ Uses: Ollama LLM (gemma3:1b) │ │ │ │ │ │
│ └──────────────────────────────┘ │ │ │ Uses: Ollama LLM (gemma3:1b)│ │
│ │ │ └─────────────────────────────┘ │
│ Returns: story_text + version │ │ │
└────────────────────────────────────┘ │ Returns: score + suggestions │
└───────────────────────────────────┘
│ │
│ │
└──────────┬──────────────────────────┘
│ Repeat if score < 8.0
│ and iterations < 5
▼
┌────────────────────────┐
│ DECISION POINT: │
│ Score >= 8.0? │
│ ✓ YES → Cache & Return│
│ ✗ NO → Next Iteration│
└────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ DATA PERSISTENCE (JSON) │
│ ┌───────────────────┬──────────────────────┬────────────────────────┐ │
│ │ data/stories/ │ data/variations/ │ data/sessions/ │ │
│ │ stories.json │ variations.json │ sessions.json │ │
│ │ (Base stories │ (Cached approved │ (Session history & │ │
│ │ from SimpleMCP) │ story adaptations) │ iteration details) │ │
│ └───────────────────┴──────────────────────┴────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ OLLAMA LLM BACKEND │
│ localhost:11434 (gemma3:1b model) │
│ Provides AI capabilities to both agents │
└─────────────────────────────────────────────────────────────────────────┘
File: src/orchestrator/main.py
Core Responsibilities:
- Receives user requests to adapt stories
- Checks cache for existing variations (performance optimization)
- Creates and manages adaptation sessions
- Orchestrates the iterative refinement loop between Creator and Critic
- Enforces quality gates (default: 8.0/10 threshold)
- Caches approved variations for reuse
- Tracks session history and metadata
Key Configuration:
MAX_ITERATIONS: 5 (maximum refinement attempts)APPROVAL_THRESHOLD: 8.0/10.0 (minimum score for approval)TIMEOUT_SECONDS: 300 (5 minutes)
File: src/creator_agent/main.py
Core Capabilities:
/remix: Creates initial story adaptation from base story + variation request/refine: Improves story based on Critic feedback- Preserves moral lessons from original stories
- Maintains version history per session
- Uses Ollama LLM (gemma3:1b) for generation
Agent ID: creator-agent-001
File: src/critic_agent/main.py
Evaluation Framework:
| Dimension | Weight | Criteria |
|---|---|---|
| Moral Preservation | 30% | Original lesson maintained, theme consistency |
| Structure Quality | 25% | Clear narrative arc, pacing, word count (150-250) |
| Creativity | 25% | Originality, engagement, fresh perspective |
| Coherence | 20% | Logical flow, character consistency, grammar |
Scoring: 0-10 scale, calculated as weighted sum of dimensions Approval Threshold: 8.0/10
A2A StoryLab implements core Google A2A protocol concepts for all inter-agent communication, including message envelopes, conversation threading, agent cards, task states, and typed message parts. This is an educational implementation - see the official A2A specification for full protocol details.
Comprehensive utilities for A2A message handling:
Message Creation Functions:
create_request_message()- Create A2A request messagescreate_response_message()- Create A2A response messagescreate_error_message()- Create A2A error messagescreate_health_check_message()- Create health check messages
Message Processing Functions:
extract_payload()- Extract payload from A2A messageunwrap_response()- Unwrap response and extract resultunwrap_request()- Unwrap request and extract action/parametersvalidate_a2a_message()- Validate message structurevalidate_message_chain()- Validate request-response chains
Message Conversion:
message_to_dict()- Convert message to JSONdict_to_message()- Parse JSON to message
Logging & Tracking:
log_message_sent()- Log outgoing messageslog_message_received()- Log incoming messagestrack_message()- Track message in conversation manager
Tracks conversations and message chains:
Core Classes:
ConversationRecord- Individual conversation trackingConversationManager- Global conversation management (singleton)
Key Functions:
track_conversation()- Start tracking new conversationadd_message()- Add message to conversation historyget_message_history()- Get all messages in conversationget_message_by_id()- Look up specific messageget_message_chain()- Get reply chainsget_conversation_stats()- Get statistics
Predefined agent identities:
ORCHESTRATOR_AGENT = AgentInfo(
agent_id="orchestrator-001",
agent_type="orchestrator",
instance="http://localhost:8000"
)
CREATOR_AGENT = AgentInfo(
agent_id="creator-agent-001",
agent_type="creator",
instance="http://localhost:8001"
)
CRITIC_AGENT = AgentInfo(
agent_id="critic-agent-001",
agent_type="critic",
instance="http://localhost:8002"
)All messages follow this structure:
{
"protocol": "google.a2a.v1",
"message_id": "msg_abc123",
"conversation_id": "conv_xyz789",
"timestamp": "2025-10-29T10:00:00Z",
"sender": {
"agent_id": "orchestrator-001",
"agent_type": "orchestrator",
"instance": "http://localhost:8000"
},
"recipient": {
"agent_id": "creator-agent-001",
"agent_type": "creator",
"instance": "http://localhost:8001"
},
"message_type": "request",
"in_reply_to": null,
"payload": {
"action": "remix_story",
"parameters": {
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas"
}
}
}Used to request actions from agents:
{
"message_type": "request",
"payload": {
"action": "remix_story" | "refine_story" | "evaluate_story",
"parameters": {...},
"context": {...}
}
}Used to return results:
{
"message_type": "response",
"in_reply_to": "msg_request_id",
"payload": {
"status": "success",
"result": {...}
}
}Used to report errors:
{
"message_type": "error",
"in_reply_to": "msg_request_id",
"payload": {
"error_code": "STORY_NOT_FOUND",
"error_message": "Story 'xyz' not found",
"details": {...}
}
}Each adaptation session creates a unique conversation:
- conversation_id: Generated when session starts (e.g.,
conv_abc123) - message_id: Unique ID for each message (e.g.,
msg_xyz789) - in_reply_to: Links responses to requests, creating message chains
Example Message Chain:
User Request → Session Start
↓ conversation_id: conv_001
Iteration 1:
msg_001: Orchestrator → Creator (remix)
msg_002: Creator → Orchestrator (story v1.0)
msg_003: Orchestrator → Critic (evaluate)
msg_004: Critic → Orchestrator (score 6.5)
Iteration 2:
msg_005: Orchestrator → Creator (refine)
msg_006: Creator → Orchestrator (story v2.0)
msg_007: Orchestrator → Critic (evaluate)
msg_008: Critic → Orchestrator (score 8.5 - APPROVED)
All messages are logged to /logs/a2a_messages.log:
Format:
timestamp | event_type | message_type | msg_id | conv_id | sender | recipient | in_reply_to
Example:
2025-10-29T10:30:45.123 | SENT | request | msg_001 | conv_abc | orchestrator-001 | creator-agent-001 | None
2025-10-29T10:30:46.456 | RECEIVED | response | msg_002 | conv_abc | creator-agent-001 | orchestrator-001 | msg_001
Version 1.0 (Plain JSON):
POST /remix
{
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas"
}Version 2.0 (A2A Protocol):
POST /remix
{
"protocol": "google.a2a.v1",
"message_id": "msg_abc123",
"conversation_id": "conv_xyz789",
"sender": {...},
"recipient": {...},
"message_type": "request",
"payload": {
"action": "remix_story",
"parameters": {
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas"
}
}
}Migration Note: The user-facing /adapt-story endpoint still accepts simplified JSON for backward compatibility, but all internal agent communication uses A2A protocol.
All errors return A2A error messages:
try:
result = process_story(story_id)
except StoryNotFoundError as e:
error_message = create_error_message(
request_message=request,
sender=CREATOR_AGENT,
error_code="STORY_NOT_FOUND",
error_message=f"Story '{story_id}' not found",
details={"story_id": story_id}
)
return message_to_dict(error_message)Common Error Codes:
STORY_NOT_FOUND- Story ID not foundSESSION_NOT_FOUND- Session ID not foundREMIX_ERROR- Error during story remixREFINE_ERROR- Error during story refinementEVALUATE_ERROR- Error during story evaluationVALIDATION_ERROR- Message validation failed
Session Start: conversation_id = "conv_abc123"
┌────────────────────────────────────────────────────────────────────┐
│ ITERATION 1 │
└────────────────────────────────────────────────────────────────────┘
1. Orchestrator creates REQUEST to Creator:
{
"message_id": "msg_001",
"conversation_id": "conv_abc123",
"message_type": "request",
"sender": ORCHESTRATOR_AGENT,
"recipient": CREATOR_AGENT,
"in_reply_to": null,
"payload": {
"action": "remix_story",
"parameters": {
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas",
"session_id": "sess_xyz789"
}
}
}
2. Creator returns RESPONSE to Orchestrator:
{
"message_id": "msg_002",
"conversation_id": "conv_abc123",
"message_type": "response",
"sender": CREATOR_AGENT,
"recipient": ORCHESTRATOR_AGENT,
"in_reply_to": "msg_001",
"payload": {
"status": "success",
"result": {
"version": "1.0",
"story_text": "Dr. Sarah Chen was a brilliant scientist...",
"metadata": {...}
}
}
}
3. Orchestrator creates REQUEST to Critic:
{
"message_id": "msg_003",
"conversation_id": "conv_abc123",
"message_type": "request",
"sender": ORCHESTRATOR_AGENT,
"recipient": CRITIC_AGENT,
"in_reply_to": "msg_002",
"payload": {
"action": "evaluate_story",
"parameters": {
"story_text": "Dr. Sarah Chen was a brilliant scientist...",
"base_story_id": "bear_loses_roar",
"session_id": "sess_xyz789"
}
}
}
4. Critic returns RESPONSE to Orchestrator:
{
"message_id": "msg_004",
"conversation_id": "conv_abc123",
"message_type": "response",
"sender": CRITIC_AGENT,
"recipient": ORCHESTRATOR_AGENT,
"in_reply_to": "msg_003",
"payload": {
"status": "success",
"result": {
"score": 6.5,
"approved": false,
"suggestions": ["Strengthen moral preservation", ...]
}
}
}
┌────────────────────────────────────────────────────────────────────┐
│ ITERATION 2 │
└────────────────────────────────────────────────────────────────────┘
5. Orchestrator creates REQUEST to Creator (refine):
{
"message_id": "msg_005",
"conversation_id": "conv_abc123",
"message_type": "request",
"sender": ORCHESTRATOR_AGENT,
"recipient": CREATOR_AGENT,
"in_reply_to": "msg_004",
"payload": {
"action": "refine_story",
"parameters": {
"story_id": "bear_loses_roar",
"previous_story": "Dr. Sarah Chen was a brilliant scientist...",
"feedback": "Strengthen moral preservation...",
"session_id": "sess_xyz789"
}
}
}
6. Creator returns RESPONSE (v2.0):
{
"message_id": "msg_006",
"conversation_id": "conv_abc123",
"in_reply_to": "msg_005",
"payload": {
"status": "success",
"result": {
"version": "2.0",
"story_text": "Dr. Sarah Chen had always been brilliant...",
"metadata": {...}
}
}
}
7-8. Evaluation continues with msg_007 and msg_008
Final score: 8.5 - APPROVED ✓
Result: Session completed with 8 messages in conversation conv_abc123
┌──────────────────────────────────────────────────────────────────────────┐
│ ./test_system.sh EXECUTION │
└──────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ TEST 1: Health Checks │
│ curl http://localhost:8000/health │
│ │
│ Verifies: │
│ ✓ Orchestrator is running (port 8000) │
│ ✓ Creator Agent is reachable (port 8001) │
│ ✓ Critic Agent is reachable (port 8002) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ TEST 2: Available Stories │
│ curl http://localhost:8000/variations/bear_loses_roar │
│ │
│ Lists: │
│ • Base story: "The Bear Who Lost His Roar" │
│ • All cached variations (if any) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ TEST 3: Story Adaptation │
│ curl -X POST http://localhost:8000/adapt-story │
│ { │
│ "base_story_id": "bear_loses_roar", │
│ "variation_request": "scientist who lost formulas", │
│ "options": { │
│ "max_iterations": 3, │
│ "approval_threshold": 7.5 │
│ } │
│ } │
│ │
│ This triggers the FULL ITERATION LOOP: │
│ │
│ Iteration 1: │
│ 1. Orchestrator → Creator: /remix │
│ Creator generates adapted story v1.0 │
│ 2. Orchestrator → Critic: /evaluate │
│ Critic scores v1.0 (e.g., 6.5/10) ❌ │
│ Provides feedback: "Strengthen moral connection" │
│ │
│ Iteration 2: │
│ 3. Orchestrator → Creator: /refine │
│ Creator improves based on feedback v2.0 │
│ 4. Orchestrator → Critic: /evaluate │
│ Critic scores v2.0 (e.g., 8.5/10) ✓ APPROVED │
│ │
│ Result: │
│ • Story cached to data/variations/ │
│ • Session saved to data/sessions/ │
│ • Returns final story + metadata │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Extracts session_id from response │
│ (e.g., "sess_abc123xyz") │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ TEST 4: Check Session Status │
│ curl http://localhost:8000/session/sess_abc123xyz │
│ │
│ Returns: │
│ • Session status: "completed" │
│ • Current version: "2.0" │
│ • Iteration count: 2 │
│ • Final score: 8.5 │
│ • Processing time │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ Testing Complete │
└─────────────────┘
- Service Health: All three services are running and responding
- Data Access: Base stories are loaded and accessible
- Agent Communication: Creator and Critic can communicate with Orchestrator
- Iterative Refinement: The feedback loop works correctly
- Quality Gates: Stories are evaluated and approved/rejected appropriately
- Data Persistence: Sessions and variations are saved correctly
- Session Tracking: Session status can be queried
There are 3 methods to provide your own content for validation:
Add your own stories to the base collection that the system can adapt.
File: data/stories/stories.json
Add your story in this format:
{
"stories": [
... existing stories ...,
{
"id": "your_custom_story",
"title": "Your Story Title",
"text": "Once upon a time... [your complete story text here]",
"metadata": {
"theme": ["friendship", "courage"],
"characters": ["protagonist", "helper"],
"moral": "The lesson your story teaches",
"reading_time_minutes": 3,
"age_range": "5-10",
"word_count": 200
}
}
]
}curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "your_custom_story",
"variation_request": "your custom context here"
}'{
"id": "startup_founder_journey",
"title": "The Founder Who Found Her Team",
"text": "Sarah built amazing products, but worked alone in her garage. One day, her app crashed and she couldn't fix it alone. She reached out to other founders, sharing her struggles. Together, they helped each other grow. Sarah learned that success isn't built alone, but through collaboration and community.",
"metadata": {
"theme": ["collaboration", "community", "vulnerability"],
"characters": ["Sarah", "fellow founders"],
"moral": "Great achievements require great teams and community support",
"reading_time_minutes": 2,
"age_range": "adult",
"word_count": 180
}
}Use the existing base stories but provide your own variation request.
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "bear_loses_roar",
"variation_request": "YOUR CUSTOM CONTEXT HERE",
"options": {
"max_iterations": 5,
"approval_threshold": 8.0
}
}'Tech Industry Examples:
# Developer losing debugging skills
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "bear_loses_roar",
"variation_request": "developer who lost their debugging skills"
}'
# Junior developer learning from mentor
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "squirrel_and_owl",
"variation_request": "junior developer learning from senior mentor"
}'Professional Development Examples:
# Teacher losing passion
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "bear_loses_roar",
"variation_request": "teacher who lost their passion for teaching"
}'
# Artist wanting to learn coding
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "turtle_wants_to_fly",
"variation_request": "artist wanting to learn coding"
}'Business Examples:
# Chef forgetting recipe
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "bear_loses_roar",
"variation_request": "chef who forgot their secret recipe"
}'
# Startup founder feeling isolated
curl -X POST http://localhost:8000/adapt-story \
-H "Content-Type: application/json" \
-d '{
"base_story_id": "lonely_firefly",
"variation_request": "startup founder feeling isolated"
}'Create a test script to validate multiple variations at once.
File: test_custom_stories.py
#!/usr/bin/env python3
"""
Batch test script for custom story variations
"""
import requests
import json
import time
from datetime import datetime
ORCHESTRATOR_URL = "http://localhost:8000"
# Define your custom test cases
custom_tests = [
{
"base_story_id": "lonely_firefly",
"variation_request": "startup founder feeling isolated",
"expected_theme": "community"
},
{
"base_story_id": "squirrel_and_owl",
"variation_request": "junior developer learning from senior mentor",
"expected_theme": "mentorship"
},
{
"base_story_id": "turtle_wants_to_fly",
"variation_request": "artist wanting to learn coding",
"expected_theme": "learning"
},
{
"base_story_id": "bear_loses_roar",
"variation_request": "developer who lost debugging skills",
"expected_theme": "rediscovery"
},
{
"base_story_id": "rabbit_and_carrot",
"variation_request": "entrepreneur discovering their niche",
"expected_theme": "discovery"
}
]
def test_story_adaptation(test_case):
"""Test a single story adaptation"""
print(f"\n{'='*70}")
print(f"Test: {test_case['variation_request']}")
print(f"Base Story: {test_case['base_story_id']}")
print(f"Expected Theme: {test_case['expected_theme']}")
print('='*70)
try:
# Make request
start_time = time.time()
response = requests.post(
f"{ORCHESTRATOR_URL}/adapt-story",
json={
"base_story_id": test_case["base_story_id"],
"variation_request": test_case["variation_request"],
"options": {
"max_iterations": 5,
"approval_threshold": 8.0
}
},
timeout=120
)
elapsed_time = time.time() - start_time
# Parse result
result = response.json()
# Display results
print(f"\n✓ Status: {result['status']}")
print(f"✓ Final Score: {result['result']['final_score']}/10.0")
print(f"✓ Iterations: {result['result']['iteration_count']}")
print(f"✓ Processing Time: {result['result']['processing_time_ms']}ms")
print(f"✓ Request Time: {elapsed_time:.2f}s")
print(f"✓ Session ID: {result['session_id']}")
if result.get('result', {}).get('variation_id'):
print(f"✓ Variation ID: {result['result']['variation_id']}")
print(f"✓ Cached: Yes")
# Show story preview
story_text = result['result']['story_text']
print(f"\nStory Preview ({len(story_text)} chars):")
print("-" * 70)
print(story_text[:300] + "..." if len(story_text) > 300 else story_text)
print("-" * 70)
# Show iterations
if 'iterations' in result:
print(f"\nIteration History:")
for i, iteration in enumerate(result['iterations'], 1):
print(f" v{iteration['version']}: Score {iteration['score']:.1f}/10.0")
if iteration.get('feedback_summary'):
print(f" Feedback: {iteration['feedback_summary'][:80]}...")
return {
"success": True,
"score": result['result']['final_score'],
"iterations": result['result']['iteration_count'],
"time_ms": result['result']['processing_time_ms']
}
except requests.exceptions.Timeout:
print("\n✗ ERROR: Request timeout")
return {"success": False, "error": "timeout"}
except requests.exceptions.ConnectionError:
print("\n✗ ERROR: Connection failed - is the server running?")
return {"success": False, "error": "connection"}
except Exception as e:
print(f"\n✗ ERROR: {str(e)}")
return {"success": False, "error": str(e)}
def main():
"""Run all test cases"""
print("\n" + "="*70)
print("STORY WORKSHOP A2A - CUSTOM CONTENT VALIDATION")
print(f"Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*70)
# Check health
print("\nChecking system health...")
try:
health_response = requests.get(f"{ORCHESTRATOR_URL}/health", timeout=5)
health = health_response.json()
print(f"✓ Orchestrator: {health['orchestrator']}")
print(f"✓ Creator Agent: {health['creator_agent']}")
print(f"✓ Critic Agent: {health['critic_agent']}")
print(f"✓ Overall: {health['overall']}")
except Exception as e:
print(f"✗ Health check failed: {e}")
print("Please ensure all services are running with ./start_all.sh")
return
# Run tests
results = []
for i, test_case in enumerate(custom_tests, 1):
print(f"\n\nTest {i}/{len(custom_tests)}")
result = test_story_adaptation(test_case)
results.append(result)
# Brief pause between tests
if i < len(custom_tests):
time.sleep(2)
# Summary
print("\n\n" + "="*70)
print("TEST SUMMARY")
print("="*70)
successful = [r for r in results if r.get("success")]
failed = [r for r in results if not r.get("success")]
print(f"\nTotal Tests: {len(results)}")
print(f"✓ Passed: {len(successful)}")
print(f"✗ Failed: {len(failed)}")
if successful:
avg_score = sum(r["score"] for r in successful) / len(successful)
avg_iterations = sum(r["iterations"] for r in successful) / len(successful)
avg_time = sum(r["time_ms"] for r in successful) / len(successful)
print(f"\nAverage Score: {avg_score:.2f}/10.0")
print(f"Average Iterations: {avg_iterations:.1f}")
print(f"Average Processing Time: {avg_time:.0f}ms")
print("\n" + "="*70)
print(f"Completed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*70 + "\n")
if __name__ == "__main__":
main()# Make executable
chmod +x test_custom_stories.py
# Run tests
./test_custom_stories.py======================================================================
STORY WORKSHOP A2A - CUSTOM CONTENT VALIDATION
Started at: 2025-10-29 14:30:00
======================================================================
Checking system health...
✓ Orchestrator: healthy
✓ Creator Agent: healthy
✓ Critic Agent: healthy
✓ Overall: healthy
Test 1/5
======================================================================
Test: startup founder feeling isolated
Base Story: lonely_firefly
Expected Theme: community
======================================================================
✓ Status: success
✓ Final Score: 8.5/10.0
✓ Iterations: 2
✓ Processing Time: 4523ms
✓ Request Time: 4.78s
✓ Session ID: sess_abc123xyz
✓ Variation ID: var_xyz789abc
✓ Cached: Yes
[Story preview and iteration history...]
Adapt a base story to a new context.
Request Body:
{
"base_story_id": "string",
"variation_request": "string",
"options": {
"max_iterations": 5,
"approval_threshold": 8.0,
"timeout_seconds": 300,
"return_all_versions": false
}
}Response:
{
"status": "success",
"session_id": "sess_xyz123",
"result": {
"variation_id": "var_abc456",
"title": "Story Title",
"story_text": "The adapted story...",
"final_score": 8.5,
"iteration_count": 2,
"processing_time_ms": 4523,
"metadata": {
"word_count": 189,
"approved": true
}
},
"iterations": [
{
"version": "1.0",
"score": 7.2,
"feedback_summary": "Strengthen moral connection"
},
{
"version": "2.0",
"score": 8.5,
"feedback_summary": "Excellent adaptation"
}
]
}Get status of an adaptation session.
Response:
{
"session_id": "sess_xyz123",
"status": "completed",
"created_at": "2025-10-29T14:30:00Z",
"updated_at": "2025-10-29T14:30:45Z",
"current_version": "2.0",
"iteration_count": 2,
"current_score": 8.5
}List all cached variations for a base story.
Response:
{
"base_story_id": "bear_loses_roar",
"base_story_title": "The Bear Who Lost His Roar",
"variations": [
{
"variation_id": "var_abc123",
"title": "The Scientist Who Lost His Formulas",
"variation_request": "scientist who lost his formulas",
"final_score": 8.5,
"created_at": "2025-10-29T14:30:00Z"
}
],
"total_variations": 1
}Check health of all services.
Response:
{
"orchestrator": "healthy",
"creator_agent": "healthy",
"critic_agent": "healthy",
"overall": "healthy",
"timestamp": "2025-10-29T14:30:00Z"
}The system comes with 5 base stories from SimpleMCP:
| Story ID | Title | Moral Lesson |
|---|---|---|
squirrel_and_owl |
The Curious Squirrel and the Wise Owl | Value of asking questions and seeking wisdom |
bear_loses_roar |
The Bear Who Lost His Roar | Inner strength and rediscovering yourself |
turtle_wants_to_fly |
The Turtle Who Wanted to Fly | Appreciating your unique abilities |
lonely_firefly |
The Lonely Firefly | Finding your community and place |
rabbit_and_carrot |
The Rabbit and the Magical Carrot | Discovery and wonder in simple things |
Good Examples (specific, clear context):
- "developer who lost their debugging skills"
- "teacher rediscovering passion after burnout"
- "entrepreneur finding their first customer"
Poor Examples (too vague):
- "a person at work"
- "someone sad"
- "generic story"
| Use Case | Max Iterations | Approval Threshold |
|---|---|---|
| Quick draft | 2-3 | 7.0-7.5 |
| Standard quality | 4-5 | 8.0 |
| High quality | 5-7 | 8.5+ |
Check Logs:
# Watch orchestrator activity
tail -f logs/orchestrator.log
# Watch agent interactions
tail -f logs/creator_agent.log
tail -f logs/critic_agent.logCommon Issues:
-
Low scores: Story may not preserve moral well
- Solution: Adjust variation_request to be closer to original theme
-
Max iterations reached: Story doesn't meet threshold
- Solution: Lower approval_threshold or increase max_iterations
-
Timeout errors: Processing taking too long
- Solution: Increase timeout_seconds in options
This system provides a flexible framework for adapting stories through iterative agent collaboration. Users can:
- Use existing base stories with custom variations
- Add their own base stories
- Batch test multiple variations
- Monitor quality through detailed scoring
The architecture ensures quality through:
- Multi-dimensional evaluation (moral, structure, creativity, coherence)
- Iterative refinement with feedback
- Configurable quality gates
- Session tracking and caching
For more details, see:
- A2A Protocol Guide - Complete A2A implementation guide
- README - Quick start guide
- Testing Guide - Testing patterns and examples
src/common/models.py- Data model definitions
Error: ValidationError or Invalid A2A message format
Cause: Message missing required A2A fields
Solution:
# Ensure all required fields are present
from common import create_request_message, ORCHESTRATOR_AGENT, CREATOR_AGENT
message = create_request_message(
conversation_id="conv_123", # Required
sender=ORCHESTRATOR_AGENT, # Required
recipient=CREATOR_AGENT, # Required
payload={...} # Required
)Error: Message chain validation failed: conversation_id mismatch
Cause: Response has different conversation_id than request
Solution:
# Always use create_response_message() which automatically matches conversation_id
from common import create_response_message
response = create_response_message(
request_message=original_request, # Automatically copies conversation_id
sender=CREATOR_AGENT,
payload={...}
)Error: ValueError: Response status is 'error'
Cause: Trying to unwrap an ERROR message as a success response
Solution:
from common import unwrap_response, extract_payload
try:
result = unwrap_response(response_message)
except ValueError as e:
# Handle error response
payload = extract_payload(response_message)
if payload.get("error_code"):
print(f"Error: {payload['error_message']}")Error: Conversation history is empty
Cause: Not calling track_message() after creating messages
Solution:
from common import track_message, log_message_sent
# After creating message
message = create_request_message(...)
# Track it
track_message(message, session_id="sess_123")
log_message_sent(message)Error: /logs/a2a_messages.log is empty
Cause: Log directory doesn't exist or permissions issue
Solution:
# Create logs directory
mkdir -p $PROJECT_ROOT/logs
# Check permissions
ls -la $PROJECT_ROOT/logs/
# Verify log is being written
tail -f $PROJECT_ROOT/logs/a2a_messages.logError: ConnectionError when calling agent endpoints
Cause: Agents not running or incorrect endpoint URL
Solution:
# Check if all services are running
curl http://localhost:8000/health
curl http://localhost:8001/health
curl http://localhost:8002/health
# Start services if needed
./start_all.sh
# Verify agent instance URLs match
grep "instance" src/common/a2a_utils.pyError: Protocol version mismatch
Cause: Using wrong protocol version
Solution:
# Always use google.a2a.v1
# This is automatically set by create_*_message() functions
message = create_request_message(...)
assert message.protocol == "google.a2a.v1"Check the A2A message audit log:
# Watch messages in real-time
tail -f logs/a2a_messages.log
# Filter by conversation
grep "conv_abc123" logs/a2a_messages.log
# Filter by message type
grep "request" logs/a2a_messages.log
# Find a specific message
grep "msg_xyz789" logs/a2a_messages.logfrom common import validate_a2a_message
is_valid, error = validate_a2a_message(message)
if not is_valid:
print(f"Validation error: {error}")from common import get_conversation_manager
manager = get_conversation_manager()
# Get all messages in conversation
history = manager.get_message_history("conv_abc123")
for msg in history:
print(f"{msg.message_id}: {msg.sender.agent_id} → {msg.recipient.agent_id}")
# Get conversation stats
stats = manager.get_conversation_stats("conv_abc123")
print(f"Messages: {stats['message_count']}")
print(f"Participants: {stats['participants']}")
print(f"Duration: {stats['duration_seconds']}s")from common import get_message_summary
# Get human-readable summary
summary = get_message_summary(message)
print(summary)
# Output: REQUEST from orchestrator-001 to creator-agent-001: remix_storyIf you have existing v1.0 clients that send plain JSON:
Old v1.0 Code:
import requests
response = requests.post(
"http://localhost:8001/remix",
json={
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas"
}
)New v2.0 Code:
import requests
from common import create_request_message, message_to_dict, dict_to_message, unwrap_response
# Create A2A request
request = create_request_message(
conversation_id="conv_test123",
sender={"agent_id": "my-client", "agent_type": "client", "instance": "http://localhost"},
recipient={"agent_id": "creator-agent-001", "agent_type": "creator", "instance": "http://localhost:8001"},
payload={
"action": "remix_story",
"parameters": {
"story_id": "bear_loses_roar",
"variation": "scientist who lost formulas",
"session_id": "sess_test456"
}
}
)
# Send request
response = requests.post(
"http://localhost:8001/remix",
json=message_to_dict(request)
)
# Parse response
response_message = dict_to_message(response.json())
result = unwrap_response(response_message)Simplified Option: Use the user-facing /adapt-story endpoint which still accepts simplified JSON:
# This endpoint handles A2A wrapping internally
response = requests.post(
"http://localhost:8000/adapt-story",
json={
"base_story_id": "bear_loses_roar",
"variation_request": "scientist who lost formulas"
}
)Run the comprehensive test suite:
# Run all tests
pytest tests/ -v
# Run A2A-specific tests
pytest tests/ -k "a2a" -v
# Run with coverage
pytest tests/ --cov=src/common/a2a_utils --cov=src/common/conversation_manager- A2A Protocol Guide - Complete guide to A2A implementation
- Testing Guide - Testing strategies and examples
src/common/a2a_utils.py- Source code with comprehensive docstringssrc/common/conversation_manager.py- Conversation tracking source code
Document Version: 2.0 (A2A Protocol Compliant) Last Updated: 2025-10-29