Skip to content

Latest commit

 

History

History
1478 lines (1210 loc) · 49.6 KB

File metadata and controls

1478 lines (1210 loc) · 49.6 KB

A2A StoryLab - Architecture & User Guide

Version: 2.0 (A2A Protocol Compliant)

Table of Contents

  1. System Architecture
  2. A2A Protocol Implementation
  3. Message Flow with A2A
  4. test_system.sh Flow
  5. How to Provide Custom Content
  6. API Reference
  7. A2A Troubleshooting

System Architecture

ASCII Architecture Diagram

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                 │
└─────────────────────────────────────────────────────────────────────────┘

Component Details

1. Creative Director (Orchestrator) - Port 8000

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)

2. Creator Agent ("The Remixer") - Port 8001

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

3. Critic Agent ("The Analyst") - Port 8002

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 Protocol Implementation

Overview

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.

Key Components

1. A2A Message Utilities (src/common/a2a_utils.py)

Comprehensive utilities for A2A message handling:

Message Creation Functions:

  • create_request_message() - Create A2A request messages
  • create_response_message() - Create A2A response messages
  • create_error_message() - Create A2A error messages
  • create_health_check_message() - Create health check messages

Message Processing Functions:

  • extract_payload() - Extract payload from A2A message
  • unwrap_response() - Unwrap response and extract result
  • unwrap_request() - Unwrap request and extract action/parameters
  • validate_a2a_message() - Validate message structure
  • validate_message_chain() - Validate request-response chains

Message Conversion:

  • message_to_dict() - Convert message to JSON
  • dict_to_message() - Parse JSON to message

Logging & Tracking:

  • log_message_sent() - Log outgoing messages
  • log_message_received() - Log incoming messages
  • track_message() - Track message in conversation manager

2. Conversation Manager (src/common/conversation_manager.py)

Tracks conversations and message chains:

Core Classes:

  • ConversationRecord - Individual conversation tracking
  • ConversationManager - Global conversation management (singleton)

Key Functions:

  • track_conversation() - Start tracking new conversation
  • add_message() - Add message to conversation history
  • get_message_history() - Get all messages in conversation
  • get_message_by_id() - Look up specific message
  • get_message_chain() - Get reply chains
  • get_conversation_stats() - Get statistics

3. Agent Identity Registry

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"
)

A2A Message Structure

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"
    }
  }
}

Message Types

REQUEST Message

Used to request actions from agents:

{
  "message_type": "request",
  "payload": {
    "action": "remix_story" | "refine_story" | "evaluate_story",
    "parameters": {...},
    "context": {...}
  }
}

RESPONSE Message

Used to return results:

{
  "message_type": "response",
  "in_reply_to": "msg_request_id",
  "payload": {
    "status": "success",
    "result": {...}
  }
}

ERROR Message

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": {...}
  }
}

Conversation Threading

Each adaptation session creates a unique conversation:

  1. conversation_id: Generated when session starts (e.g., conv_abc123)
  2. message_id: Unique ID for each message (e.g., msg_xyz789)
  3. 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)

Message Audit Trail

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

Breaking Changes from v1.0

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.

Error Handling with A2A

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 found
  • SESSION_NOT_FOUND - Session ID not found
  • REMIX_ERROR - Error during story remix
  • REFINE_ERROR - Error during story refinement
  • EVALUATE_ERROR - Error during story evaluation
  • VALIDATION_ERROR - Message validation failed

Message Flow with A2A

Complete Iteration Flow with A2A Messages

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 Flow

Complete Execution Flow

┌──────────────────────────────────────────────────────────────────────────┐
│                      ./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 │
                        └─────────────────┘

What test_system.sh Validates

  1. Service Health: All three services are running and responding
  2. Data Access: Base stories are loaded and accessible
  3. Agent Communication: Creator and Critic can communicate with Orchestrator
  4. Iterative Refinement: The feedback loop works correctly
  5. Quality Gates: Stories are evaluated and approved/rejected appropriately
  6. Data Persistence: Sessions and variations are saved correctly
  7. Session Tracking: Session status can be queried

How to Provide Custom Content

There are 3 methods to provide your own content for validation:

Method 1: Add New Base Stories (Recommended)

Add your own stories to the base collection that the system can adapt.

Step 1: Edit the Stories File

File: data/stories/stories.json

Step 2: Add Your Story

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
      }
    }
  ]
}

Step 3: Test Your Story

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"
  }'

Example: Adding a Business Fable

{
  "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
  }
}

Method 2: Direct API Call with Custom Variation

Use the existing base stories but provide your own variation request.

Basic Usage

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
    }
  }'

Examples of Custom Variations

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"
  }'

Method 3: Python Script for Batch Testing

Create a test script to validate multiple variations at once.

Create Test Script

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()

Run the Batch Test

# Make executable
chmod +x test_custom_stories.py

# Run tests
./test_custom_stories.py

Example Output

======================================================================
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...]

API Reference

Orchestrator Endpoints (Port 8000)

POST /adapt-story

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 /session/{session_id}

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
}

GET /variations/{story_id}

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
}

GET /health

Check health of all services.

Response:

{
  "orchestrator": "healthy",
  "creator_agent": "healthy",
  "critic_agent": "healthy",
  "overall": "healthy",
  "timestamp": "2025-10-29T14:30:00Z"
}

Available Base Stories

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

Best Practices for Custom Content

1. Writing Good Variation Requests

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"

2. Setting Appropriate Thresholds

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+

3. Monitoring and Debugging

Check Logs:

# Watch orchestrator activity
tail -f logs/orchestrator.log

# Watch agent interactions
tail -f logs/creator_agent.log
tail -f logs/critic_agent.log

Common Issues:

  1. Low scores: Story may not preserve moral well

    • Solution: Adjust variation_request to be closer to original theme
  2. Max iterations reached: Story doesn't meet threshold

    • Solution: Lower approval_threshold or increase max_iterations
  3. Timeout errors: Processing taking too long

    • Solution: Increase timeout_seconds in options

Conclusion

This system provides a flexible framework for adapting stories through iterative agent collaboration. Users can:

  1. Use existing base stories with custom variations
  2. Add their own base stories
  3. Batch test multiple variations
  4. 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 Troubleshooting

Common A2A Issues and Solutions

1. Invalid Message Format

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
)

2. Message Chain Validation Errors

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={...}
)

3. Cannot Unwrap Response

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']}")

4. Message Not Being Tracked

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)

5. Audit Log Not Writing

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.log

6. Agent Communication Failure

Error: 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.py

7. Protocol Version Mismatch

Error: 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"

Debugging Tips

View Message Flow

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.log

Validate Message Structure

from common import validate_a2a_message

is_valid, error = validate_a2a_message(message)
if not is_valid:
    print(f"Validation error: {error}")

Check Conversation History

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")

Inspect Message Content

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_story

Migration from v1.0 to v2.0

If 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"
    }
)

Testing A2A Implementation

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

Additional Resources

  • A2A Protocol Guide - Complete guide to A2A implementation
  • Testing Guide - Testing strategies and examples
  • src/common/a2a_utils.py - Source code with comprehensive docstrings
  • src/common/conversation_manager.py - Conversation tracking source code

Document Version: 2.0 (A2A Protocol Compliant) Last Updated: 2025-10-29