Skip to article frontmatterSkip to article content

Agent Lifecyle

In the previous chapter, we explored agents, messages, high level concept of runtime and how they effectively enable the passing of different type of messages between agents within the same or a distributed computing environment.

We explored the runtime environment, a system that lets programs run and supports AI agents designed with the actor model. We also built an example agent called PTOAgent and defined the runtime in our code. Below is a snippet of that code.

## snippet of the code
## PTO Agent Actor model

from autogen_core import RoutedAgent
class PTOAgent(RoutedAgent):
    def __init__(self) -> None:
        super().__init__("ERPPTOAgent")
    @message_handler(match=lambda msg, ctx: msg.source.startswith("Manager"))
        async def on_txt_message_1(self, message: PTOAgentMessages, ctx: MessageContext) -> None:
            # fetches available PTO for a given employee
            ## added agent ID
            print(f"{self.id.type} received message: {message.content} from : {message.source}")
...
...
...
## A runtime
from autogen_core import SingleThreadedAgentRuntime
runtime = SingleThreadedAgentRuntime()

This code imports the RoutedAgent class from autogen_core and defines a new PTOAgent class that inherits from it, initializing it with the name myPTOAgent.

Register an Agent

from autogen_core import SingleThreadedAgentRuntime
runtime = SingleThreadedAgentRuntime()

This code imports a SingleThreadedAgentRuntime class from the autogen_core module and creates a runtime instance that manages agents in a single-threaded environment.

Let’s attach (or register) the PTOAgent to this runtime.

## bad code
runtime.register("PTOAgent")

However, In a real-life situation, I can see multiple agents, like LLMAgent, ManagerAgent, or AnotherAgent, using this PTOAgent for various tasks, such as checking PTO availability for different employees.

How does registering an agent (using code runtime.register("PTOAgent")) with a runtime solve this? We need one PTOAgent that other agents can call repeatedly. If it’s not needed, it just stays idle.

Agent Type

If you want to reuse an agent like PTOAgent for various tasks without registering it repeatedly (e.g., runtime.register("PTOAgent") for each use), you can register an AgentType instead. This approach is more efficient and flexible.

Agent Type is not same as Agent class. In this above example, PTOAgent is an Agent class (Python class), initializing it with the name ERPPTOAgent.

So what is the Agent Type?

Again, The agent type is not an agent class. It associates an agent with a specific factory function, which produces instances of agents of the same agent type.

The job of Agent Type is to provide an instance of Python Agent class given a specific functions, this way, we can create many instance of same Agent Type.

AgentType: A Clearer Explanation

Key Concepts

What’s the Point of AgentType?

Instead of registering every agent instance individually (e.g., runtime.register(ERPPTOAgent)), you register the AgentType—the factory function. The runtime can then call this function whenever it needs a new agent of that type. This makes it easy to create multiple agents of the same kind without redundant registrations.

Example in Action

Here’s how it works:

# Define the agent class
class PTOAgent:
    def handle_pto(self):
        print("Processing PTO request")

# Define the factory function (AgentType)
def create_pto_agent():
    return PTOAgent()

# Register the AgentType once
runtime.register_agent_type("PTOAgentType", create_pto_agent)

# Runtime creates agents as needed
agent1 = runtime.get_agent("PTOAgentType")  # New PTOAgent instance
agent2 = runtime.get_agent("PTOAgentType")  # Another PTOAgent instance

agent1.handle_pto()  # Output: Processing PTO request
agent2.handle_pto()  # Output: Processing PTO request

Why It’s Better

Summary

The AgentType is a factory function that produces agent instances, not the agent class itself. By registering an AgentType, you let the runtime generate as many agents as needed from a single registration—way simpler than registering each one by hand!

Agent ID and LifeCycle

Final key concept introduced here is to put altogether, Agent, Agent Type, Agent ID and Agent Lifecycle.

Agent Types helps us create instances of an Agent Type, but how do we really associate a give Agent Type to a runtime to manage specific query requests from specific agents.

Agent ID

Agent ID uniquely identifies an agent instance within an agent runtime – including distributed runtime.

It is the “address” of the agent instance for receiving messages.

It has two components: agent type and agent key.

Agent ID = (Agent Type, Agent Key)

Agent Key

The agent key is an instance identifier for the given agent type

Agent Lifecycle

Agent Lifecycle

Registering Agents

The final agent register code creates a unique ID for the agent to send and receive messages in the runtime.

from autogen_core import SingleThreadedAgentRuntime

# create a standalong single thread runtime
runtime = SingleThreadedAgentRuntime()

# attach/register AI Agent to run time
await PTOAgent.register(runtime, "AgType_ERP_PTO_Agent", lambda: PTOAgent())
await TaskAgent.register(runtime, "AgType_ERP_Task_Agent", lambda: TaskAgent())

Executing Runtime

After registering an agent, you can message an Agent instance using its AgentId, and the runtime creates the instance on the first message delivery.

runtime.start()  # Start processing messages in the background.
await runtime.send_message(PTOAgentMessages("Query PTOs taken by Employee ID 512 in past 2 weeks.", "Employee"), AgentId("AgType_ERP_PTO_Agent", "default"))
await runtime.send_message(PTOAgentMessages("Query PTOs taken by Employee ID 512 in past 2 weeks", "Manager"), AgentId("AgType_ERP_PTO_Agent", "default"))
await runtime.send_message(TaskAgentMessages("I need a list of all tasks assigned!", "Employee"), AgentId("AgType_ERP_Task_Agent", "default"))
await runtime.send_message(TaskAgentMessages("I need a list of all tasks assigned!", "Manager"), AgentId("AgType_ERP_Task_Agent", "default"))
# Stop processing messages in the background.
await runtime.stop()
# This will block until the runtime is idle
await runtime.stop_when_idle()
# This will close the runtime
await runtime.close()