Repository: ceylonai/ceylon (language: python)

Issue:
Create Examples
Create multiple examples to show what can be done with Ceylon.


Discussion / hints:

Fix patch:
diff --git a/bindings/ceylon/ceylon/agent/types/agent_request.py b/bindings/ceylon/ceylon/agent/types/agent_request.py
index 7a973159..10616678 100644
--- a/bindings/ceylon/ceylon/agent/types/agent_request.py
+++ b/bindings/ceylon/ceylon/agent/types/agent_request.py
@@ -8,7 +8,7 @@
 
 class AgentJobStepRequest(BaseModel):
     """ the agent job step request"""
-    id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias='_id')
+    id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias='_id', description="the id of the request")
     job_id: str = Field(None, description="the job id")
     worker: str = Field(description="the worker name")
     job_data: Any = Field(None, description="the job data")
@@ -16,8 +16,11 @@ class AgentJobStepRequest(BaseModel):
 
 
 class AgentJobResponse(BaseModel):
-    """ the agent job response"""
-    id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias='_id')
-    job_id: str = Field(None, description="the job id")
+    """
+    the agent job response
+    this is the response that is sent back to the agent
+    """
+    id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias='_id', description="the id of the response")
+    job_id: str = Field(None, description="the job id of the response")
     worker: str = Field(description="the worker name")
     job_data: Any = Field(None, description="the job data")
diff --git a/bindings/ceylon/ceylon/core/admin.py b/bindings/ceylon/ceylon/core/admin.py
index 2d692f05..1d831da5 100644
--- a/bindings/ceylon/ceylon/core/admin.py
+++ b/bindings/ceylon/ceylon/core/admin.py
@@ -3,6 +3,7 @@
 from typing import Any
 
 from ceylon.ceylon import AdminAgent, AdminAgentConfig, Processor, MessageHandler, EventHandler, AgentDetail
+from ceylon.ceylon.ceylon import uniffi_set_event_loop
 
 
 class Admin(AdminAgent, Processor, MessageHandler, EventHandler):
diff --git a/bindings/ceylon/src/workspace/admin_agent.rs b/bindings/ceylon/src/workspace/admin_agent.rs
index 90d63bbe..744da4c7 100644
--- a/bindings/ceylon/src/workspace/admin_agent.rs
+++ b/bindings/ceylon/src/workspace/admin_agent.rs
@@ -126,6 +126,11 @@ impl AdminAgent {
 
         if peer_.id == self._peer_id {
             info!("Admin peer created {}", peer_.id.clone());
+            println!("------------------------------------------------------------------");
+            println!("| Important");
+            println!("| ServerAdmin peer ID : {}", peer_.id.clone());
+            println!("| Use this ServerAdmin peer ID to connect to the server");
+            println!("-------------------------------------------------------------------");
         } else {
             panic!("Id mismatch");
         }
diff --git a/docs/examples/connect-through-network.md b/docs/examples/connect-through-network.md
new file mode 100644
index 00000000..a994e070
--- /dev/null
+++ b/docs/examples/connect-through-network.md
@@ -0,0 +1,151 @@
+# Tutorial: Setting Up Remote Admin and Worker Agents
+
+This tutorial will guide you through setting up a remote admin agent and a worker agent on separate computers using the
+Ceylon framework. We'll explain the process step-by-step and provide insights into how the system works.
+
+## Overview
+
+In this setup, we have two main components:
+
+1. **Server Admin (Admin Computer)**: This is the central control point that manages worker agents.
+2. **Worker Agent (Worker Computer)**: This is a separate computer that connects to the admin and performs tasks.
+
+The goal is to establish communication between these two computers over a network.
+
+## Step 1: Setting Up the Server Admin
+
+### 1.1 Prepare the Admin Script
+
+On the computer that will act as the server admin:
+
+1. Create a new file called `remote_admin.py`.
+2. Copy the following code into `remote_admin.py`:
+
+```python
+import pickle
+from loguru import logger
+from ceylon import CoreAdmin, Agent
+from ceylon.ceylon import AgentDetail
+
+
+class ServerAdminAgent(CoreAdmin):
+    async def on_agent_connected(self, topic: "str", agent: AgentDetail):
+        logger.info(f"ServerAdminAgent on_agent_connected {self.details().name}", agent.id, agent.name, agent.role)
+
+
+class WorkerAgent1(Agent):
+    async def run(self, inputs: "bytes"):
+        logger.info(f"WorkerAgent1 on_run  {self.details().name}", inputs)
+
+
+worker_1 = WorkerAgent1("worker_1", "server_admin", admin_port=8000)
+server_admin = ServerAdminAgent("server_admin", 8000, workers=[worker_1])
+server_admin.run_admin(pickle.dumps({}), [worker_1])
+```
+
+### 1.2 Understanding the Admin Script
+
+- We define two classes: `ServerAdminAgent` (which extends `CoreAdmin`) and `WorkerAgent1` (which extends `Agent`).
+- The `ServerAdminAgent` has a method to handle when an agent connects.
+- We create instances of both `WorkerAgent1` and `ServerAdminAgent`.
+- The admin is set to listen on port 8000.
+
+### 1.3 Run the Admin Script
+
+1. Open a terminal or command prompt.
+2. Navigate to the directory containing `remote_admin.py`.
+3. Run the script:
+
+```bash
+python remote_admin.py
+```
+
+4. The script will start and display a peer ID. It will look something like this:
+
+```
+ServerAdmin peer ID: 12D3KooWCLKVyiM5VkwYYAaDKL5rMW4WnSbMcVicMDqy23inFz3K
+```
+
+**Important:** Note down this peer ID. You'll need it for the worker script.
+
+## Step 2: Setting Up the Worker Agent
+
+### 2.1 Prepare the Worker Script
+
+On a different computer that will act as the worker:
+
+1. Create a new file called `remote_worker.py`.
+2. Copy the following code into `remote_worker.py`:
+
+```python
+import asyncio
+import pickle
+from loguru import logger
+from ceylon import Agent
+from ceylon.ceylon import uniffi_set_event_loop
+
+
+class WorkerAgent1(Agent):
+    async def run(self, inputs: "bytes"):
+        logger.info(f"WorkerAgent1 on_run  {self.details().name}", inputs)
+
+
+# Replace these with the actual values from your admin computer
+admin_peer_id = "12D3KooWCLKVyiM5VkwYYAaDKL5rMW4WnSbMcVicMDqy23inFz3K"
+admin_ip = "192.168.1.100"
+admin_port = 8000
+
+worker_1 = WorkerAgent1("worker_2", "server_admin", role="Whatever",
+                        admin_port=admin_port,
+                        admin_peer=f"{admin_peer_id}@{admin_ip}:{admin_port}")
+worker_1.run_worker(pickle.dumps({}))
+```
+
+### 2.2 Understanding the Worker Script
+
+- We define a `WorkerAgent1` class similar to the admin script.
+- The crucial part is setting up the `worker_1` instance with the correct `admin_peer` parameter.
+- The `admin_peer` parameter combines the peer ID, IP address, and port of the admin computer.
+
+### 2.3 Configuring the Worker Script
+
+Before running the worker script, you need to update three key pieces of information:
+
+1. `admin_peer_id`: Replace this with the peer ID you noted from the admin script.
+2. `admin_ip`: Replace this with the IP address of the computer running the admin script.
+3. `admin_port`: This should match the port the admin is listening on (8000 in our example).
+
+### 2.4 Run the Worker Script
+
+1. Open a terminal or command prompt on the worker computer.
+2. Navigate to the directory containing `remote_worker.py`.
+3. Run the script:
+
+```bash
+python remote_worker.py
+```
+
+## Step 3: Verifying the Connection
+
+If everything is set up correctly:
+
+1. On the admin computer, you should see a log message indicating that a worker agent has connected.
+2. On the worker computer, you should see log messages about task execution and successful connection to the admin.
+
+## Troubleshooting
+
+If the connection isn't working:
+
+1. **Check Network Connectivity**: Ensure both computers can reach each other over the network.
+2. **Firewall Settings**: Make sure the necessary ports (e.g., 8000) are open on both computers.
+3. **Double-check Configuration**: Verify that the peer ID, IP address, and port in the worker script match the admin's
+   details.
+
+## How It Works
+
+1. The admin script starts and generates a unique peer ID.
+2. The worker script uses this peer ID, along with the admin's IP and port, to establish a connection.
+3. Once connected, the admin can assign tasks to the worker, and the worker can execute these tasks and report back.
+
+This setup allows for distributed computing, where tasks can be offloaded to worker agents running on different
+machines, all coordinated by a central admin agent.
\ No newline at end of file
diff --git a/docs/examples/meeting-sechdular.md b/docs/examples/meeting-sechdular.md
new file mode 100644
index 00000000..22bd1616
--- /dev/null
+++ b/docs/examples/meeting-sechdular.md
@@ -0,0 +1,196 @@
+# Simplified Meeting Scheduler Tutorial
+
+## Introduction
+
+This tutorial will show you how to create a simple meeting scheduler using Python. The scheduler will help find a time
+when everyone can attend a meeting.
+
+## Step 1: Install the Required Tools
+
+First, we need to install some special tools (called libraries) that will help us build our scheduler. You can install
+these using a program called pip. Type this into your computer's command line:
+
+```
+pip install ceylon
+```
+
+## Step 2: Define the Building Blocks
+
+Now, we'll create some "blueprints" for the different parts of our scheduler. These blueprints are called classes, and
+they help us organize our code.
+
+```python
+from pydantic.dataclasses import dataclass
+
+
+@dataclass
+class Meeting:
+    name: str  # The name of the meeting
+    date: str  # The date of the meeting
+    duration: int  # How long the meeting will last
+    minimum_participants: int  # The smallest number of people needed for the meeting
+
+
+@dataclass
+class TimeSlot:
+    date: str  # The date of the time slot
+    start_time: int  # When the time slot starts
+    end_time: int  # When the time slot ends
+
+
+@dataclass
+class AvailabilityRequest:
+    time_slot: TimeSlot  # The time slot we're asking about
+
+
+@dataclass
+class AvailabilityResponse:
+    owner: str  # The name of the person responding
+    time_slot: TimeSlot  # The time slot they're responding about
+    accepted: bool  # Whether they can attend (True) or not (False)
+```
+
+## Step 3: Create the Participant
+
+Next, we'll create a "Participant" who can tell us if they're available for a meeting.
+
+```python
+from ceylon import Agent, on_message
+from typing import List
+
+
+class Participant(Agent):
+    name: str  # The participant's name
+    available_times: List[TimeSlot]  # A list of times when they're free
+
+    def __init__(self, name, available_times):
+        self.name = name
+        self.available_times = available_times
+        super().__init__(name=name, workspace_id="time_scheduling", admin_peer="Coordinator", admin_port=8000)
+
+    @on_message(type=AvailabilityRequest)
+    async def on_availability_request(self, data: AvailabilityRequest):
+        # Check if the participant is available at the requested time
+        if self.is_available(data.time_slot):
+            # If available, send a positive response
+            await self.broadcast_data(AvailabilityResponse(owner=self.name, time_slot=data.time_slot, accepted=True))
+        else:
+            # If not available, send a negative response
+            await self.broadcast_data(AvailabilityResponse(owner=self.name, time_slot=data.time_slot, accepted=False))
+
+    def is_available(self, requested_slot: TimeSlot) -> bool:
+        # Check if the requested time slot overlaps with any of the participant's available times
+        for available_slot in self.available_times:
+            if self.slots_overlap(available_slot, requested_slot):
+                return True
+        return False
+
+    @staticmethod
+    def slots_overlap(slot1: TimeSlot, slot2: TimeSlot) -> bool:
+        # Check if two time slots overlap
+        return slot1.start_time < slot2.end_time and slot2.start_time < slot1.end_time
+```
+
+## Step 4: Create the Coordinator
+
+The Coordinator is in charge of finding a time that works for everyone.
+
+```python
+from ceylon import CoreAdmin, on_message
+import pickle
+
+
+class Coordinator(CoreAdmin):
+    meeting: Meeting = None
+    agreed_slots = {}
+    next_time_slot = None
+
+    def __init__(self):
+        super().__init__(name="time_scheduling", port=8000)
+
+    async def run(self, inputs: "bytes"):
+        # Unpack the meeting details
+        self.meeting = pickle.loads(inputs)
+        print("Meeting Schedule request: ", self.meeting)
+
+    async def on_agent_connected(self, topic: "str", agent_id: "str"):
+        # When a new participant connects, start asking about availability
+        if self.next_time_slot is None and self.meeting is not None:
+            self.next_time_slot = TimeSlot(self.meeting.date, 0, self.meeting.duration)
+            await self.broadcast_data(AvailabilityRequest(time_slot=self.next_time_slot))
+
+    @on_message(type=AvailabilityResponse)
+    async def on_availability_request(self, data: AvailabilityResponse):
+        # Process responses from participants
+        if data.accepted:
+            # If the participant is available, add them to the list for this time slot
+            time_slot_key = f"{data.time_slot}"
+            if time_slot_key in self.agreed_slots:
+                self.agreed_slots[time_slot_key].append(data.owner)
+            else:
+                self.agreed_slots[time_slot_key] = [data.owner]
+
+            # Check if we have enough participants for this time slot
+            if len(self.agreed_slots[time_slot_key]) >= self.meeting.minimum_participants:
+                print(f"Meeting scheduled! Participants: {self.agreed_slots[time_slot_key]}, Time: {data.time_slot}")
+                await self.stop()
+                return
+
+        # If we haven't found a suitable time yet, try the next time slot
+        self.next_time_slot = TimeSlot(self.meeting.date, self.next_time_slot.start_time + 1,
+                                       self.next_time_slot.start_time + 1 + self.meeting.duration)
+        await self.broadcast_data(AvailabilityRequest(time_slot=self.next_time_slot))
+```
+
+## Step 5: Run the Scheduler
+
+Finally, we'll set up our participants and start the scheduler.
+
+```python
+import asyncio
+
+
+async def main():
+    # Create our participants with their available times
+    alice = Participant("Alice", [TimeSlot("2024-07-21", 9, 12), TimeSlot("2024-07-21", 14, 18)])
+    bob = Participant("Bob", [TimeSlot("2024-07-21", 10, 13), TimeSlot("2024-07-21", 15, 17)])
+    charlie = Participant("Charlie", [TimeSlot("2024-07-21", 11, 14), TimeSlot("2024-07-21", 16, 18)])
+
+    # Create our coordinator
+    coordinator = Coordinator()
+
+    # Start the scheduling process
+    meeting = Meeting(name="Team Meeting", duration=2, date="2024-07-21", minimum_participants=3)
+    await coordinator.arun_admin(
+        inputs=pickle.dumps(meeting),
+        workers=[alice, bob, charlie]
+    )
+
+
+if __name__ == '__main__':
+    asyncio.run(main())
+```
+
+## How It Works
+
+1. We set up our "Participants" (Alice, Bob, and Charlie) and tell the computer when they're free.
+2. We create a "Coordinator" who's in charge of finding a time that works for everyone.
+3. The Coordinator asks each Participant if they're free at a certain time.
+4. Each Participant checks their schedule and tells the Coordinator if they can make it.
+5. If enough people can make it, the Coordinator schedules the meeting.
+6. If not enough people can make it, the Coordinator tries a different time.
+7. This keeps going until a good time is found or we run out of options.
+
+## Real-World Improvements
+
+To make this work better in the real world, we could:
+
+1. Connect it to people's actual calendars (like Google Calendar).
+2. Send out email reminders about the meeting.
+3. Allow for scheduling multiple meetings at once.
+4. Let people join or leave the meeting after it's been scheduled.
+5. Give some meetings or people higher priority.
+6. Suggest different days if no time works for everyone.
+7. Create a user-friendly website for people to use the scheduler.
+
+These improvements would make our simple scheduler much more useful for real-life situations!
\ No newline at end of file
diff --git a/docs/examples/single-item-auction.md b/docs/examples/single-item-auction.md
new file mode 100644
index 00000000..55d0508d
--- /dev/null
+++ b/docs/examples/single-item-auction.md
@@ -0,0 +1,191 @@
+# Single-Item Auction System with Ceylon
+
+## Introduction
+
+In this guide, we will walk you through the process of building a single-item auction system using the Ceylon framework. The system simulates an auction where multiple bidders compete to purchase an item, with an auctioneer managing the entire process.
+
+## System Overview
+
+The auction system is composed of two primary components:
+
+1. **Auctioneer**: Manages the auction, collects bids, and determines the winner.
+2. **Bidders**: Participate in the auction by placing bids.
+
+Ceylon's agent-based architecture allows seamless communication and coordination between these components, making it ideal for such a system.
+
+## Prerequisites
+
+Before you dive into the code, ensure you have the following:
+
+- Python 3.7 or higher installed on your system.
+- The Ceylon framework installed (`pip install ceylon`).
+- A basic understanding of asynchronous programming in Python.
+
+## Step-by-Step Implementation
+
+### 1. Defining Data Structures
+
+The first step in building our auction system is to define the data structures that will be used for communication between the auctioneer and bidders. We use Pydantic dataclasses for this purpose:
+
+```python
+from pydantic.dataclasses import dataclass
+
+@dataclass(repr=True)
+class Item:
+    name: str
+    starting_price: float
+
+@dataclass(repr=True)
+class Bid:
+    bidder: str
+    amount: float
+
+@dataclass(repr=True)
+class AuctionStart:
+    item: Item
+
+@dataclass(repr=True)
+class AuctionResult:
+    winner: str
+    winning_bid: float
+
+@dataclass(repr=True)
+class AuctionEnd:
+    pass
+```
+
+- **Item**: Represents the item being auctioned, including its name and starting price.
+- **Bid**: Represents a bid placed by a bidder, including the bidder's name and the bid amount.
+- **AuctionStart**: Signals the start of the auction, carrying information about the item.
+- **AuctionResult**: Contains the result of the auction, including the winner's name and the winning bid.
+- **AuctionEnd**: Signals the end of the auction process.
+
+### 2. Implementing the Bidder Agent
+
+Next, we'll implement the bidder agent. Each bidder will listen for the start of the auction and place a bid if their budget allows.
+
+```python
+import random
+from ceylon import Agent, on_message
+
+class Bidder(Agent):
+    name: str
+    budget: float
+
+    def __init__(self, name: str, budget: float):
+        self.name = name
+        self.budget = budget
+        super().__init__(name=name, workspace_id="single_item_auction", admin_peer="Auctioneer", admin_port=8000, role="bidder")
+
+    @on_message(type=AuctionStart)
+    async def on_auction_start(self, data: AuctionStart):
+        if self.budget > data.item.starting_price:
+            random_i = random.randint(100, 1000)
+            bid_amount = min(self.budget, data.item.starting_price * random_i / 100)
+            await self.broadcast_data(Bid(bidder=self.name, amount=bid_amount))
+
+    @on_message(type=AuctionResult)
+    async def on_auction_result(self, data: AuctionResult):
+        if data.winner == self.name:
+            self.budget -= data.winning_bid
+            print(f"{self.name} won the auction for ${data.winning_bid:.2f}")
+```
+
+- **Initialization**: Each `Bidder` is initialized with a name and budget.
+- **Handling Auction Start**: The bidder listens for the `AuctionStart` message and places a random bid within their budget.
+- **Handling Auction Result**: The bidder listens for the `AuctionResult` message and deducts the winning bid amount from their budget if they win.
+
+### 3. Implementing the Auctioneer
+
+The auctioneer is responsible for managing the auction, including starting it, collecting bids, and determining the winner.
+
+```python
+from ceylon import CoreAdmin
+from typing import List
+
+class Auctioneer(CoreAdmin):
+    item: Item
+    bids: List[Bid] = []
+    expected_bidders: int
+    connected_bidders: int = 0
+
+    def __init__(self, item: Item, expected_bidders: int):
+        self.item = item
+        self.expected_bidders = expected_bidders
+        super().__init__(name="single_item_auction", port=8000)
+
+    async def on_agent_connected(self, topic: str, agent_id: str):
+        self.connected_bidders += 1
+        print(f"Bidder {agent_id} connected. {self.connected_bidders}/{self.expected_bidders} bidders connected.")
+        if self.connected_bidders == self.expected_bidders:
+            print("All bidders connected. Starting the auction.")
+            await self.start_auction()
+
+    async def start_auction(self):
+        print(f"Starting auction for {self.item.name} with starting price ${self.item.starting_price}")
+        await self.broadcast_data(AuctionStart(item=self.item))
+
+    @on_message(type=Bid)
+    async def on_bid(self, bid: Bid):
+        self.bids.append(bid)
+        print(f"Received bid from {bid.bidder} for ${bid.amount:.2f}")
+        await self.end_auction()
+
+    async def end_auction(self):
+        if not self.bids:
+            print(f"No bids received for {self.item.name}")
+        else:
+            winning_bid = max(self.bids, key=lambda x: x.amount)
+            result = AuctionResult(winner=winning_bid.bidder, winning_bid=winning_bid.amount)
+            await self.broadcast_data(result)
+            print(f"Auction ended. Winner: {result.winner}, Winning Bid: ${result.winning_bid:.2f}")
+            await self.stop()
+
+        await self.broadcast_data(AuctionEnd())
+```
+
+- **Initialization**: The `Auctioneer` is initialized with the auction item and the expected number of bidders.
+- **Handling Agent Connection**: The auctioneer waits for all bidders to connect before starting the auction.
+- **Starting the Auction**: The auctioneer broadcasts the `AuctionStart` message to all bidders.
+- **Handling Bids**: The auctioneer collects bids and ends the auction after receiving bids.
+- **Ending the Auction**: The auctioneer determines the highest bid and announces the winner.
+
+### 4. Running the Auction System
+
+Finally, we set up the auction environment by creating the auctioneer and bidders, and then running the system.
+
+```python
+if __name__ == '__main__':
+    item = Item("Rare Painting", 1000)
+
+    bidders = [
+        Bidder("Alice", 1500),
+        Bidder("Bob", 1200),
+        Bidder("Charlie", 2000),
+    ]
+
+    auctioneer = Auctioneer(item, expected_bidders=len(bidders))
+    auctioneer.run_admin(inputs=b"", workers=bidders)
+```
+
+- **Item Creation**: An item to be auctioned is created with a name and starting price.
+- **Bidder Creation**: Three bidders are created with different budgets.
+- **Auctioneer Setup**: An auctioneer is created with the item and the expected number of bidders.
+- **System Execution**: The auction system is run, with the auctioneer managing the process and bidders placing bids.
+
+## Key Ceylon Framework Features Used
+
+1. **Agent-based Architecture**: The system uses `Agent` for bidders and `CoreAdmin` for the auctioneer.
+2. **Message Handling**: The `@on_message` decorator handles specific message types, making it easy to react to different events in the auction process.
+3. **Asynchronous Programming**: Ceylon leverages Python's async capabilities, ensuring efficient communication and coordination between agents.
+4. **Broadcast Messaging**: The `broadcast_data()` method is used to send messages to all agents, ensuring everyone is informed of the auction's progress.
+
+## Potential Enhancements
+
+While the current implementation is functional, there are several ways to extend and enhance the system:
+
+1. Implement a more sophisticated bidding strategy for the bidders.
+2. Add support for multiple rounds of bidding.
+3. Introduce a time limit for the auction, adding urgency to the bidding process.
+4. Implement different auction types, such as Dutch auctions or silent auctions.
+5. Improve error handling and account for edge cases, such as network failures or disconnected agents.
\ No newline at end of file
diff --git a/docs/examples/task-manager.md b/docs/examples/task-manager.md
new file mode 100644
index 00000000..0d34dcca
--- /dev/null
+++ b/docs/examples/task-manager.md
@@ -0,0 +1,139 @@
+## Building a Distributed Task Management System with Ceylon
+
+### Overview
+
+In this tutorial, you'll learn how to create a distributed task management system using Python. This system will distribute tasks to worker agents based on their skill levels, execute the tasks asynchronously, and collect the results. We will use several Python libraries including `asyncio`, `pydantic`, and a custom distributed agent system called `ceylon`.
+
+### Prerequisites
+
+Before starting, ensure you have the following Python packages installed:
+
+- `asyncio`: For asynchronous task handling.
+- `pydantic`: For data validation and management.
+- `loguru`: For logging.
+- `ceylon`: For creating distributed agents.
+
+You can install the necessary packages using pip:
+
+```bash
+pip install pydantic loguru ceylon
+```
+
+### Step 1: Define Data Structures
+
+We'll start by defining the data structures that will represent tasks, task assignments, and task results.
+
+```python
+from pydantic.dataclasses import dataclass
+
+@dataclass
+class Task:
+    id: int
+    description: str
+    difficulty: int  # 1-10 scale
+
+
+@dataclass
+class TaskAssignment:
+    task: Task
+
+
+@dataclass
+class TaskResult:
+    task_id: int
+    worker: str
+    success: bool
+```
+
+### Step 2: Create the Worker Agent
+
+The `WorkerAgent` class represents a worker that performs tasks. Each worker has a name and a skill level that determines its ability to complete tasks of varying difficulties.
+
+```python
+import asyncio
+from ceylon import Agent, on_message
+from loguru import logger
+
+class WorkerAgent(Agent):
+    def __init__(self, name: str, skill_level: int):
+        self.name = name
+        self.skill_level = skill_level
+        super().__init__(name=name, workspace_id="task_management", admin_port=8000)
+
+    @on_message(type=TaskAssignment)
+    async def on_task_assignment(self, data: TaskAssignment):
+        logger.info(f"{self.name} received task: {data.task.description}")
+        # Simulate task execution
+        await asyncio.sleep(data.task.difficulty)
+        success = self.skill_level >= data.task.difficulty
+        await self.broadcast_data(TaskResult(task_id=data.task.id, worker=self.name, success=success))
+```
+
+### Step 3: Create the Task Manager
+
+The `TaskManager` class is responsible for assigning tasks to workers and collecting the results.
+
+```python
+from ceylon import CoreAdmin
+
+class TaskManager(CoreAdmin):
+    tasks = []
+    workers = []
+    task_results = []
+
+    def __init__(self, tasks, workers):
+        self.workers = workers
+        self.tasks = tasks
+        super().__init__(name="task_management", port=8000)
+
+    async def on_agent_connected(self, topic: str, agent_id: AgentDetail):
+        logger.info(f"Worker {agent_id} connected")
+        if len(self.workers) == len(self.tasks):
+            await self.assign_tasks()
+
+    async def assign_tasks(self):
+        for task, worker in zip(self.tasks, self.workers):
+            await self.broadcast_data(TaskAssignment(task=task))
+
+    @on_message(type=TaskResult)
+    async def on_task_result(self, result: TaskResult):
+        self.task_results.append(result)
+        logger.info(
+            f"Received result for task {result.task_id} from {result.worker}: {'Success' if result.success else 'Failure'}")
+        if len(self.task_results) == len(self.tasks):
+            await self.end_task_management()
+
+    async def end_task_management(self):
+        success_rate = sum(1 for result in self.task_results if result.success) / len(self.task_results)
+        logger.info(f"All tasks completed. Success rate: {success_rate:.2%}")
+        await self.stop()
+```
+
+### Step 4: Main Execution Script
+
+Finally, we need a script to create tasks, instantiate workers, and run the task manager.
+
+```python
+if __name__ == '__main__':
+    # Create tasks
+    tasks = [
+        Task(id=1, description="Simple calculation", difficulty=2),
+        Task(id=2, description="Data analysis", difficulty=5),
+        Task(id=3, description="Machine learning model training", difficulty=8),
+    ]
+
+    # Create workers
+    workers = [
+        WorkerAgent("Junior", skill_level=3),
+        WorkerAgent("Intermediate", skill_level=6),
+        WorkerAgent("Senior", skill_level=9),
+    ]
+
+    # Create and run task manager
+    task_manager = TaskManager(tasks, workers)
+    task_manager.run_admin(inputs=b"", workers=workers)
+```
+
+### Running the System
+
+To run the distributed task management system, simply execute the main script. The `TaskManager` will distribute tasks to workers based on their skill levels, and each worker will either succeed or fail in completing the task. The results will be collected and logged.
\ No newline at end of file


Test patch:
diff --git a/bindings/ceylon/tests/test_worker/meeting_schedular.py b/bindings/ceylon/tests/test_worker/meeting_schedular.py
index 550801a6..c4394158 100644
--- a/bindings/ceylon/tests/test_worker/meeting_schedular.py
+++ b/bindings/ceylon/tests/test_worker/meeting_schedular.py
@@ -1,6 +1,5 @@
 import asyncio
 import pickle
-import sys
 from typing import List
 
 from loguru import logger
diff --git a/bindings/ceylon/tests/test_worker/task_manager.py b/bindings/ceylon/tests/test_worker/task_manager.py
new file mode 100644
index 00000000..a974e2ef
--- /dev/null
+++ b/bindings/ceylon/tests/test_worker/task_manager.py
@@ -0,0 +1,100 @@
+# Distributed Task Management System
+
+import asyncio
+from typing import List
+from pydantic.dataclasses import dataclass
+from ceylon import Agent, CoreAdmin, on_message
+from loguru import logger
+
+from ceylon.ceylon import AgentDetail
+
+
+# Data structures
+@dataclass
+class Task:
+    id: int
+    description: str
+    difficulty: int  # 1-10 scale
+
+
+@dataclass
+class TaskAssignment:
+    task: Task
+
+
+@dataclass
+class TaskResult:
+    task_id: int
+    worker: str
+    success: bool
+
+
+# Worker Agent
+class WorkerAgent(Agent):
+    def __init__(self, name: str, skill_level: int):
+        self.name = name
+        self.skill_level = skill_level
+        super().__init__(name=name, workspace_id="task_management", admin_port=8000)
+
+    @on_message(type=TaskAssignment)
+    async def on_task_assignment(self, data: TaskAssignment):
+        logger.info(f"{self.name} received task: {data.task.description}")
+        # Simulate task execution
+        await asyncio.sleep(data.task.difficulty)
+        success = self.skill_level >= data.task.difficulty
+        await self.broadcast_data(TaskResult(task_id=data.task.id, worker=self.name, success=success))
+
+
+# Task Manager
+class TaskManager(CoreAdmin):
+    tasks: List[Task] = []
+    workers: List[WorkerAgent] = []
+    task_results: List[TaskResult] = []
+
+    def __init__(self, tasks: List[Task], workers: List[WorkerAgent]):
+        self.workers = workers
+        self.tasks = tasks
+        super().__init__(name="task_management", port=8000)
+
+    async def on_agent_connected(self, topic: str, agent_id: AgentDetail):
+        logger.info(f"Worker {agent_id} connected")
+        if len(self.workers) == len(self.tasks):
+            await self.assign_tasks()
+
+    async def assign_tasks(self):
+        for task, worker in zip(self.tasks, self.workers):
+            await self.broadcast_data(TaskAssignment(task=task))
+
+    @on_message(type=TaskResult)
+    async def on_task_result(self, result: TaskResult):
+        self.task_results.append(result)
+        logger.info(
+            f"Received result for task {result.task_id} from {result.worker}: {'Success' if result.success else 'Failure'}")
+        if len(self.task_results) == len(self.tasks):
+            await self.end_task_management()
+
+    async def end_task_management(self):
+        success_rate = sum(1 for result in self.task_results if result.success) / len(self.task_results)
+        logger.info(f"All tasks completed. Success rate: {success_rate:.2%}")
+        await self.stop()
+
+
+# Main execution
+if __name__ == '__main__':
+    # Create tasks
+    tasks = [
+        Task(id=1, description="Simple calculation", difficulty=2),
+        Task(id=2, description="Data analysis", difficulty=5),
+        Task(id=3, description="Machine learning model training", difficulty=8),
+    ]
+
+    # Create workers
+    workers = [
+        WorkerAgent("Junior", skill_level=3),
+        WorkerAgent("Intermediate", skill_level=6),
+        WorkerAgent("Senior", skill_level=9),
+    ]
+
+    # Create and run task manager
+    task_manager = TaskManager(tasks, workers)
+    task_manager.run_admin(inputs=b"", workers=workers)