from __future__ import annotations

from dataclasses import dataclass
from typing import (
    Any,
    AsyncIterator,
    Callable,
    Coroutine,
    Dict,
    Iterator,
    List,
    Literal,
    Optional,
    Sequence,
    Set,
    Type,
    Union,
    overload,
)

from pydantic import BaseModel

from agno.agent import (
    _cli,
    _default_tools,
    _init,
    _managers,
    _messages,
    _run,
    _session,
    _storage,
    _tools,
    _utils,
)
from agno.compression.manager import CompressionManager
from agno.culture.manager import CultureManager
from agno.db.base import AsyncBaseDb, BaseDb, ComponentType, UserMemory
from agno.db.schemas.culture import CulturalKnowledge
from agno.eval.base import BaseEval
from agno.filters import FilterExpr
from agno.guardrails import BaseGuardrail
from agno.knowledge.protocol import KnowledgeProtocol
from agno.learn.machine import LearningMachine
from agno.media import Audio, File, Image, Video
from agno.memory import MemoryManager
from agno.metrics import SessionMetrics
from agno.models.base import Model
from agno.models.message import Message
from agno.models.response import ToolExecution
from agno.registry.registry import Registry
from agno.run import RunContext, RunStatus
from agno.run.agent import (
    RunEvent,
    RunOutput,
    RunOutputEvent,
)
from agno.run.requirement import RunRequirement
from agno.session import AgentSession, SessionSummaryManager, TeamSession, WorkflowSession
from agno.session.summary import SessionSummary
from agno.skills import Skills
from agno.tools import Toolkit
from agno.tools.function import Function
from agno.utils.log import log_warning
from agno.utils.safe_formatter import SafeFormatter


@dataclass(init=False)
class Agent:
    # --- Agent settings ---
    # Model for this Agent
    model: Optional[Model] = None
    # Agent name
    name: Optional[str] = None
    # Agent UUID (autogenerated if not set)
    id: Optional[str] = None

    # --- User settings ---
    # Default user_id to use for this agent
    user_id: Optional[str] = None

    # --- Session settings ---
    # Default session_id to use for this agent (autogenerated if not set)
    session_id: Optional[str] = None
    # Default session state (stored in the database to persist across runs)
    session_state: Optional[Dict[str, Any]] = None
    # Set to True to add the session_state to the context
    add_session_state_to_context: bool = False
    # Set to True to give the agent tools to update the session_state dynamically
    enable_agentic_state: bool = False
    # Set to True to overwrite the stored session_state with the session_state provided in the run. Default behaviour merges the current session state with the session state in the db
    overwrite_db_session_state: bool = False
    # If True, cache the current Agent session in memory for faster access
    cache_session: bool = False

    search_session_history: Optional[bool] = False
    num_history_sessions: Optional[int] = None
    # If True, the agent creates/updates session summaries at the end of runs
    enable_session_summaries: bool = False
    # If True, the agent adds session summaries to the context
    add_session_summary_to_context: Optional[bool] = None
    # Session summary manager
    session_summary_manager: Optional[SessionSummaryManager] = None

    # --- Agent Dependencies ---
    # Dependencies available for tools and prompt functions
    dependencies: Optional[Dict[str, Any]] = None
    # If True, add the dependencies to the user prompt
    add_dependencies_to_context: bool = False

    # --- Agent Memory ---
    # Memory manager to use for this agent
    memory_manager: Optional[MemoryManager] = None
    # Enable the agent to manage memories of the user
    enable_agentic_memory: bool = False
    # If True, the agent creates/updates user memories at the end of runs
    update_memory_on_run: bool = False
    # Soon to be deprecated. Use update_memory_on_run
    enable_user_memories: Optional[bool] = None
    # If True, the agent adds a reference to the user memories in the response
    add_memories_to_context: Optional[bool] = None

    # --- Database ---
    # Database to use for this agent
    db: Optional[Union[BaseDb, AsyncBaseDb]] = None

    # --- Agent History ---
    # add_history_to_context=true adds messages from the chat history to the messages list sent to the Model.
    add_history_to_context: bool = False
    # Number of historical runs to include in the messages
    num_history_runs: Optional[int] = None
    # Number of historical messages to include in the messages list sent to the Model.
    num_history_messages: Optional[int] = None
    # Maximum number of tool calls to include from history (None = no limit)
    max_tool_calls_from_history: Optional[int] = None

    # --- Knowledge ---
    knowledge: Optional[Union[KnowledgeProtocol, Callable[..., KnowledgeProtocol]]] = None
    # Enable RAG by adding references from Knowledge to the user prompt.
    # Add knowledge_filters to the Agent class attributes
    knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
    # Let the agent choose the knowledge filters
    enable_agentic_knowledge_filters: Optional[bool] = False
    add_knowledge_to_context: bool = False
    # Retrieval function to get references
    # This function, if provided, is used instead of the default search_knowledge function
    # Signature:
    # def knowledge_retriever(agent: Agent, query: str, num_documents: Optional[int], **kwargs) -> Optional[list[dict]]:
    #     ...
    knowledge_retriever: Optional[Callable[..., Optional[List[Union[Dict, str]]]]] = None
    references_format: Literal["json", "yaml"] = "json"

    # --- Skills ---
    # Skills provide structured instructions, reference docs, and scripts for agents
    skills: Optional[Skills] = None

    # --- Agent Tools ---
    # A list of tools provided to the Model.
    # Tools are functions the model may generate JSON inputs for.
    # Can also be a callable factory that returns a list of tools at runtime.
    tools: Optional[Union[List[Union[Toolkit, Callable, Function, Dict]], Callable[..., List]]] = None

    # Maximum number of tool calls allowed.
    tool_call_limit: Optional[int] = None
    # Controls which (if any) tool is called by the model.
    # "none" means the model will not call a tool and instead generates a message.
    # "auto" means the model can pick between generating a message or calling a tool.
    # Specifying a particular function via {"type: "function", "function": {"name": "my_function"}}
    #   forces the model to call that tool.
    # "none" is the default when no tools are present. "auto" is the default if tools are present.
    tool_choice: Optional[Union[str, Dict[str, Any]]] = None

    # A function that acts as middleware and is called around tool calls.
    tool_hooks: Optional[List[Callable]] = None

    # --- Agent Hooks ---
    # Functions called right after agent-session is loaded, before processing starts
    pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
    # Functions called after output is generated but before the response is returned
    post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
    # If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
    _run_hooks_in_background: Optional[bool] = None

    # --- Agent Reasoning ---
    # Enable reasoning by working through the problem step by step.
    reasoning: bool = False
    reasoning_model: Optional[Model] = None
    reasoning_agent: Optional[Agent] = None
    reasoning_min_steps: int = 1
    reasoning_max_steps: int = 10

    # --- Default tools ---
    # Add a tool that allows the Model to read the chat history.
    read_chat_history: bool = False
    # Add a tool that allows the Model to search the knowledge base (aka Agentic RAG)
    # Added only if knowledge is provided.
    search_knowledge: bool = True
    # If True, add search_knowledge instructions to the system prompt
    add_search_knowledge_instructions: bool = True
    # Add a tool that allows the Agent to update Knowledge.
    update_knowledge: bool = False
    # Add a tool that allows the Model to get the tool call history.
    read_tool_call_history: bool = False
    # If False, media (images, videos, audio, files) is only available to tools and not sent to the LLM
    send_media_to_model: bool = True
    # If True, store media in run output
    store_media: bool = True
    # If True, store tool results in run output
    store_tool_messages: bool = True
    # If True, store history messages in run output.
    # When False (default): Each run stores only its own messages. History is reconstructed
    # on-the-fly by traversing previous runs. This results in linear storage growth.
    # When True: Each run stores all messages including history from previous runs.
    # This allows inspecting full context in stored runs but causes quadratic storage growth.
    store_history_messages: bool = False

    # --- System message settings ---
    # Provide the system message as a string or function
    system_message: Optional[Union[str, Callable, Message]] = None
    # Role for the system message
    system_message_role: str = "system"
    # Provide the introduction as the first message from the Agent
    introduction: Optional[str] = None
    # Set to False to skip context building
    build_context: bool = True

    # --- Settings for building the default system message ---
    # A description of the Agent that is added to the start of the system message.
    description: Optional[str] = None
    # List of instructions for the agent.
    instructions: Optional[Union[str, List[str], Callable]] = None
    # If True, wrap instructions in <instructions> tags. Default is False.
    use_instruction_tags: bool = False
    # Provide the expected output from the Agent.
    expected_output: Optional[str] = None
    # Additional context added to the end of the system message.
    additional_context: Optional[str] = None
    # If markdown=true, add instructions to format the output using markdown
    markdown: bool = False
    # If True, add the agent name to the instructions
    add_name_to_context: bool = False
    # If True, add the current datetime to the instructions to give the agent a sense of time
    # This allows for relative times like "tomorrow" to be used in the prompt
    add_datetime_to_context: bool = False
    # If True, add the current location to the instructions to give the agent a sense of place
    # This allows for location-aware responses and local context
    add_location_to_context: bool = False
    # Allows for custom timezone for datetime instructions following the TZ Database format (e.g. "Etc/UTC")
    timezone_identifier: Optional[str] = None
    # If True, resolve session_state, dependencies, and metadata in the user and system messages
    resolve_in_context: bool = True

    # --- Learning Machine ---
    # LearningMachine for unified learning capabilities
    learning: Optional[Union[bool, LearningMachine]] = None
    # Add learnings context to system prompt
    add_learnings_to_context: bool = True

    # --- Extra Messages ---
    # A list of extra messages added after the system message and before the user message.
    # Use these for few-shot learning or to provide additional context to the Model.
    # Note: these are not retained in memory, they are added directly to the messages sent to the model.
    additional_input: Optional[List[Union[str, Dict, BaseModel, Message]]] = None
    # --- User message settings ---
    # Role for the user message
    user_message_role: str = "user"
    # Set to False to skip building the user context
    build_user_context: bool = True

    # --- Agent Response Settings ---
    # Number of retries to attempt
    retries: int = 0
    # Delay between retries (in seconds)
    delay_between_retries: int = 1
    # Exponential backoff: if True, the delay between retries is doubled each time
    exponential_backoff: bool = False

    # --- Agent Response Model Settings ---
    # Provide an input schema to validate the input
    input_schema: Optional[Type[BaseModel]] = None
    # Provide a response model to get the response in the implied format.
    # You can use a Pydantic model or a JSON fitting the provider's expected schema.
    output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
    # Provide a secondary model to parse the response from the primary model
    parser_model: Optional[Model] = None
    # Provide a prompt for the parser model
    parser_model_prompt: Optional[str] = None
    # Provide an output model to structure the response from the main model
    output_model: Optional[Model] = None
    # Provide a prompt for the output model
    output_model_prompt: Optional[str] = None
    # If True, the response from the Model is converted into the output_schema
    # Otherwise, the response is returned as a JSON string
    parse_response: bool = True
    # Use model enforced structured_outputs if supported (e.g. OpenAIChat)
    structured_outputs: Optional[bool] = None
    # Intead of providing the model with the Pydantic output schema, add a JSON description of the output schema to the system message instead.
    use_json_mode: bool = False
    # Save the response to a file
    save_response_to_file: Optional[str] = None

    # --- Agent Streaming ---
    # Stream the response from the Agent
    stream: Optional[bool] = None
    # Stream the intermediate steps from the Agent
    stream_events: Optional[bool] = None

    # Persist the events on the run response
    store_events: bool = False
    events_to_skip: Optional[List[RunEvent]] = None

    # --- If this Agent is part of a team ---
    # If this Agent is part of a team, this is the role of the agent in the team
    role: Optional[str] = None
    # Optional team ID. Indicates this agent is part of a team.
    team_id: Optional[str] = None

    # --- If this Agent is part of a workflow ---
    # Optional workflow ID. Indicates this agent is part of a workflow.
    workflow_id: Optional[str] = None

    # Metadata stored with this agent
    metadata: Optional[Dict[str, Any]] = None

    # --- Experimental Features ---
    # --- Agent Culture ---
    # Culture manager to use for this agent
    culture_manager: Optional[CultureManager] = None
    # Enable the agent to manage cultural knowledge
    enable_agentic_culture: bool = False
    # Update cultural knowledge after every run
    update_cultural_knowledge: bool = False
    # If True, the agent adds cultural knowledge in the response
    add_culture_to_context: Optional[bool] = None

    # --- Context Compression ---
    # If True, compress tool call results to save context
    compress_tool_results: bool = False
    # Compression manager for compressing tool call results
    compression_manager: Optional[CompressionManager] = None

    # --- Debug ---
    # Enable debug logs
    debug_mode: bool = False
    debug_level: Literal[1, 2] = 1

    # --- Telemetry ---
    # telemetry=True logs minimal telemetry for analytics
    # This helps us improve the Agent and provide better support
    telemetry: bool = True

    # --- Callable factory settings ---
    # Enable caching of callable factory results
    cache_callables: bool = True
    # Custom cache key function for tools callable factory
    callable_tools_cache_key: Optional[Callable[..., Optional[str]]] = None
    # Custom cache key function for knowledge callable factory
    callable_knowledge_cache_key: Optional[Callable[..., Optional[str]]] = None

    def __init__(
        self,
        *,
        model: Optional[Union[Model, str]] = None,
        name: Optional[str] = None,
        id: Optional[str] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        add_session_state_to_context: bool = False,
        overwrite_db_session_state: bool = False,
        enable_agentic_state: bool = False,
        cache_session: bool = False,
        search_session_history: Optional[bool] = False,
        num_history_sessions: Optional[int] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        add_dependencies_to_context: bool = False,
        db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
        memory_manager: Optional[MemoryManager] = None,
        enable_agentic_memory: bool = False,
        update_memory_on_run: bool = False,
        enable_user_memories: Optional[bool] = None,  # Soon to be deprecated. Use update_memory_on_run
        add_memories_to_context: Optional[bool] = None,
        enable_session_summaries: bool = False,
        add_session_summary_to_context: Optional[bool] = None,
        session_summary_manager: Optional[SessionSummaryManager] = None,
        compress_tool_results: bool = False,
        compression_manager: Optional[CompressionManager] = None,
        add_history_to_context: bool = False,
        num_history_runs: Optional[int] = None,
        num_history_messages: Optional[int] = None,
        max_tool_calls_from_history: Optional[int] = None,
        store_media: bool = True,
        store_tool_messages: bool = True,
        store_history_messages: bool = False,
        knowledge: Optional[KnowledgeProtocol] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        enable_agentic_knowledge_filters: Optional[bool] = None,
        add_knowledge_to_context: bool = False,
        knowledge_retriever: Optional[Callable[..., Optional[List[Union[Dict, str]]]]] = None,
        references_format: Literal["json", "yaml"] = "json",
        skills: Optional[Skills] = None,
        metadata: Optional[Dict[str, Any]] = None,
        tools: Optional[Union[Sequence[Union[Toolkit, Callable, Function, Dict]], Callable[..., List]]] = None,
        tool_call_limit: Optional[int] = None,
        tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
        tool_hooks: Optional[List[Callable]] = None,
        pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
        post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
        reasoning: bool = False,
        reasoning_model: Optional[Union[Model, str]] = None,
        reasoning_agent: Optional[Agent] = None,
        reasoning_min_steps: int = 1,
        reasoning_max_steps: int = 10,
        read_chat_history: bool = False,
        search_knowledge: bool = True,
        add_search_knowledge_instructions: bool = True,
        update_knowledge: bool = False,
        read_tool_call_history: bool = False,
        send_media_to_model: bool = True,
        system_message: Optional[Union[str, Callable, Message]] = None,
        system_message_role: str = "system",
        introduction: Optional[str] = None,
        build_context: bool = True,
        description: Optional[str] = None,
        instructions: Optional[Union[str, List[str], Callable]] = None,
        use_instruction_tags: bool = False,
        expected_output: Optional[str] = None,
        additional_context: Optional[str] = None,
        markdown: bool = False,
        add_name_to_context: bool = False,
        add_datetime_to_context: bool = False,
        add_location_to_context: bool = False,
        timezone_identifier: Optional[str] = None,
        resolve_in_context: bool = True,
        learning: Optional[Union[bool, LearningMachine]] = None,
        add_learnings_to_context: bool = True,
        additional_input: Optional[List[Union[str, Dict, BaseModel, Message]]] = None,
        user_message_role: str = "user",
        build_user_context: bool = True,
        retries: int = 0,
        delay_between_retries: int = 1,
        exponential_backoff: bool = False,
        parser_model: Optional[Union[Model, str]] = None,
        parser_model_prompt: Optional[str] = None,
        input_schema: Optional[Type[BaseModel]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        parse_response: bool = True,
        output_model: Optional[Union[Model, str]] = None,
        output_model_prompt: Optional[str] = None,
        structured_outputs: Optional[bool] = None,
        use_json_mode: bool = False,
        save_response_to_file: Optional[str] = None,
        stream: Optional[bool] = None,
        stream_events: Optional[bool] = None,
        store_events: bool = False,
        events_to_skip: Optional[List[RunEvent]] = None,
        role: Optional[str] = None,
        culture_manager: Optional[CultureManager] = None,
        enable_agentic_culture: bool = False,
        update_cultural_knowledge: bool = False,
        add_culture_to_context: Optional[bool] = None,
        debug_mode: bool = False,
        debug_level: Literal[1, 2] = 1,
        telemetry: bool = True,
        cache_callables: bool = True,
        callable_tools_cache_key: Optional[Callable[..., Optional[str]]] = None,
        callable_knowledge_cache_key: Optional[Callable[..., Optional[str]]] = None,
    ):
        self.model = model  # type: ignore[assignment]
        self.name = name
        self.id = id
        self.introduction = introduction
        self.user_id = user_id

        self.session_id = session_id
        self.session_state = session_state
        self.overwrite_db_session_state = overwrite_db_session_state
        self.enable_agentic_state = enable_agentic_state
        self.cache_session = cache_session

        self.search_session_history = search_session_history
        self.num_history_sessions = num_history_sessions

        self.dependencies = dependencies
        self.add_dependencies_to_context = add_dependencies_to_context
        self.add_session_state_to_context = add_session_state_to_context

        self.db = db

        self.memory_manager = memory_manager
        self.enable_agentic_memory = enable_agentic_memory

        if enable_user_memories is not None:
            self.update_memory_on_run = enable_user_memories
        else:
            self.update_memory_on_run = update_memory_on_run
        self.enable_user_memories = self.update_memory_on_run  # Soon to be deprecated. Use update_memory_on_run

        self.add_memories_to_context = add_memories_to_context

        self.enable_session_summaries = enable_session_summaries

        if session_summary_manager is not None:
            self.session_summary_manager = session_summary_manager
            self.enable_session_summaries = True

        self.add_session_summary_to_context = add_session_summary_to_context

        # Context compression settings
        self.compress_tool_results = compress_tool_results
        self.compression_manager = compression_manager

        self.add_history_to_context = add_history_to_context
        self.num_history_runs = num_history_runs
        self.num_history_messages = num_history_messages
        if self.num_history_messages is not None and self.num_history_runs is not None:
            log_warning(
                "num_history_messages and num_history_runs cannot be set at the same time. Using num_history_runs."
            )
            self.num_history_messages = None
        if self.num_history_messages is None and self.num_history_runs is None:
            self.num_history_runs = 3

        self.max_tool_calls_from_history = max_tool_calls_from_history

        self.store_media = store_media
        self.store_tool_messages = store_tool_messages
        self.store_history_messages = store_history_messages

        self.knowledge = knowledge
        self.knowledge_filters = knowledge_filters
        self.enable_agentic_knowledge_filters = enable_agentic_knowledge_filters
        self.add_knowledge_to_context = add_knowledge_to_context
        self.knowledge_retriever = knowledge_retriever
        self.references_format = references_format

        self.skills = skills

        self.metadata = metadata

        # Component metadata (set by get_agents during DB loading)
        self._version: Optional[int] = None
        self._stage: Optional[str] = None

        from agno.utils.callables import is_callable_factory

        if tools is None:
            self.tools = []
        elif is_callable_factory(tools, excluded_types=(Toolkit, Function)):
            self.tools = tools  # type: ignore[assignment]
        else:
            self.tools = list(tools)  # type: ignore[arg-type]
        self.tool_call_limit = tool_call_limit
        self.tool_choice = tool_choice
        self.tool_hooks = tool_hooks

        self.pre_hooks = pre_hooks
        self.post_hooks = post_hooks

        self.reasoning = reasoning
        self.reasoning_model = reasoning_model  # type: ignore[assignment]
        self.reasoning_agent = reasoning_agent
        self.reasoning_min_steps = reasoning_min_steps
        self.reasoning_max_steps = reasoning_max_steps

        self.read_chat_history = read_chat_history
        self.search_knowledge = search_knowledge
        self.add_search_knowledge_instructions = add_search_knowledge_instructions
        self.update_knowledge = update_knowledge
        self.read_tool_call_history = read_tool_call_history
        self.send_media_to_model = send_media_to_model
        self.system_message = system_message
        self.system_message_role = system_message_role
        self.build_context = build_context
        self.description = description
        self.instructions = instructions
        self.use_instruction_tags = use_instruction_tags
        self.expected_output = expected_output
        self.additional_context = additional_context
        self.markdown = markdown
        self.add_name_to_context = add_name_to_context
        self.add_datetime_to_context = add_datetime_to_context
        self.add_location_to_context = add_location_to_context
        self.timezone_identifier = timezone_identifier
        self.resolve_in_context = resolve_in_context
        self.learning = learning
        self.add_learnings_to_context = add_learnings_to_context
        self.additional_input = additional_input
        self.user_message_role = user_message_role
        self.build_user_context = build_user_context

        self.retries = retries
        self.delay_between_retries = delay_between_retries
        self.exponential_backoff = exponential_backoff
        self.parser_model = parser_model  # type: ignore[assignment]
        self.parser_model_prompt = parser_model_prompt
        self.input_schema = input_schema
        self.output_schema = output_schema
        self.parse_response = parse_response
        self.output_model = output_model  # type: ignore[assignment]
        self.output_model_prompt = output_model_prompt

        self.structured_outputs = structured_outputs

        self.use_json_mode = use_json_mode
        self.save_response_to_file = save_response_to_file

        self.stream = stream
        self.stream_events = stream_events

        self.store_events = store_events
        self.role = role
        # By default, we skip the run response content event
        self.events_to_skip = events_to_skip
        if self.events_to_skip is None:
            self.events_to_skip = [RunEvent.run_content]

        self.culture_manager = culture_manager
        self.enable_agentic_culture = enable_agentic_culture
        self.update_cultural_knowledge = update_cultural_knowledge
        self.add_culture_to_context = add_culture_to_context

        self.debug_mode = debug_mode
        if debug_level not in [1, 2]:
            log_warning(f"Invalid debug level: {debug_level}. Setting to 1.")
            debug_level = 1
        self.debug_level = debug_level
        self.telemetry = telemetry

        # Internal use: _learning holds the resolved LearningMachine instance
        # use agent.learning_machine to access it.
        self._learning: Optional[LearningMachine] = None
        # Whether learning init has been attempted (prevents repeated attempts when db is None)
        self._learning_init_attempted: bool = False

        # If we are caching the agent session
        self._cached_session: Optional[AgentSession] = None

        self._tool_instructions: Optional[List[str]] = None

        self._formatter: Optional[SafeFormatter] = None

        self._hooks_normalised = False

        self._mcp_tools_initialized_on_run: List[Any] = []
        self._connectable_tools_initialized_on_run: List[Any] = []

        # Lazy-initialized shared thread pool executor for background tasks (memory, cultural knowledge, etc.)
        self._background_executor: Optional[Any] = None

        # Callable factory settings
        self.cache_callables = cache_callables
        self.callable_tools_cache_key = callable_tools_cache_key
        self.callable_knowledge_cache_key = callable_knowledge_cache_key
        self._callable_tools_cache: Dict[str, List[Any]] = {}
        self._callable_knowledge_cache: Dict[str, Any] = {}

        _init.get_models(self)

    # ---------------------------------------------------------------
    # Properties
    # ---------------------------------------------------------------

    @property
    def background_executor(self) -> Any:
        if self._background_executor is None:
            from concurrent.futures import ThreadPoolExecutor

            self._background_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="agno-bg")
        return self._background_executor

    @property
    def cached_session(self) -> Optional[AgentSession]:
        return self._cached_session

    @property
    def learning_machine(self) -> Optional[LearningMachine]:
        if (
            self._learning is None
            and not self._learning_init_attempted
            and self.learning is not None
            and self.learning is not False
        ):
            _init.set_learning_machine(self)
        return self._learning

    # ---------------------------------------------------------------
    # _init module delegates
    # ---------------------------------------------------------------

    def set_id(self) -> None:
        return _init.set_id(self)

    def initialize_agent(self, debug_mode: Optional[bool] = None) -> None:
        return _init.initialize_agent(self, debug_mode=debug_mode)

    def add_tool(self, tool: Union[Toolkit, Callable, Function, Dict]) -> None:
        return _init.add_tool(self, tool)

    def set_tools(self, tools: Union[Sequence[Union[Toolkit, Callable, Function, Dict]], Callable[..., List]]) -> None:
        return _init.set_tools(self, tools)

    def clear_callable_cache(
        self,
        kind: Optional[Literal["tools", "knowledge"]] = None,
        close: bool = False,
    ) -> None:
        from agno.utils.callables import clear_callable_cache

        clear_callable_cache(self, kind=kind, close=close)

    async def aclear_callable_cache(
        self,
        kind: Optional[Literal["tools", "knowledge"]] = None,
        close: bool = False,
    ) -> None:
        from agno.utils.callables import aclear_callable_cache

        await aclear_callable_cache(self, kind=kind, close=close)

    # ---------------------------------------------------------------
    # _tools module delegates
    # ---------------------------------------------------------------

    def get_tools(
        self,
        run_response: RunOutput,
        run_context: RunContext,
        session: AgentSession,
        user_id: Optional[str] = None,
    ) -> List[Union[Toolkit, Callable, Function, Dict]]:
        return _tools.get_tools(
            self, run_response=run_response, run_context=run_context, session=session, user_id=user_id
        )

    async def aget_tools(
        self,
        run_response: RunOutput,
        run_context: RunContext,
        session: AgentSession,
        user_id: Optional[str] = None,
        check_mcp_tools: bool = True,
    ) -> List[Union[Toolkit, Callable, Function, Dict]]:
        return await _tools.aget_tools(
            self,
            run_response=run_response,
            run_context=run_context,
            session=session,
            user_id=user_id,
            check_mcp_tools=check_mcp_tools,
        )

    @staticmethod
    def cancel_run(run_id: str) -> bool:
        return _run.cancel_run(run_id)

    @staticmethod
    async def acancel_run(run_id: str) -> bool:
        return await _run.acancel_run(run_id)

    # ---------------------------------------------------------------
    # _messages module delegates
    # ---------------------------------------------------------------

    def get_system_message(
        self,
        session: AgentSession,
        run_context: Optional[RunContext] = None,
        tools: Optional[List[Union[Function, dict]]] = None,
        add_session_state_to_context: Optional[bool] = None,
    ) -> Optional[Message]:
        return _messages.get_system_message(
            self,
            session=session,
            run_context=run_context,
            tools=tools,
            add_session_state_to_context=add_session_state_to_context,
        )

    async def aget_system_message(
        self,
        session: AgentSession,
        run_context: Optional[RunContext] = None,
        tools: Optional[List[Union[Function, dict]]] = None,
        add_session_state_to_context: Optional[bool] = None,
    ) -> Optional[Message]:
        return await _messages.aget_system_message(
            self,
            session=session,
            run_context=run_context,
            tools=tools,
            add_session_state_to_context=add_session_state_to_context,
        )

    def get_relevant_docs_from_knowledge(
        self,
        query: str,
        num_documents: Optional[int] = None,
        filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        validate_filters: bool = False,
        run_context: Optional[RunContext] = None,
        **kwargs,
    ) -> Optional[List[Union[Dict[str, Any], str]]]:
        return _messages.get_relevant_docs_from_knowledge(
            self,
            query=query,
            num_documents=num_documents,
            filters=filters,
            validate_filters=validate_filters,
            run_context=run_context,
            **kwargs,
        )

    async def aget_relevant_docs_from_knowledge(
        self,
        query: str,
        num_documents: Optional[int] = None,
        filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        validate_filters: bool = False,
        run_context: Optional[RunContext] = None,
        **kwargs,
    ) -> Optional[List[Union[Dict[str, Any], str]]]:
        return await _messages.aget_relevant_docs_from_knowledge(
            self,
            query=query,
            num_documents=num_documents,
            filters=filters,
            validate_filters=validate_filters,
            run_context=run_context,
            **kwargs,
        )

    def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> Agent:
        return _utils.deep_copy(self, update=update)

    # ---------------------------------------------------------------
    # _storage module delegates
    # ---------------------------------------------------------------

    def to_dict(self) -> Dict[str, Any]:
        return _storage.to_dict(self)

    @classmethod
    def from_dict(cls, data: Dict[str, Any], registry: Optional[Registry] = None) -> "Agent":
        return _storage.from_dict(cls, data=data, registry=registry)

    def save(
        self,
        *,
        db: Optional["BaseDb"] = None,
        stage: str = "published",
        label: Optional[str] = None,
        notes: Optional[str] = None,
    ) -> Optional[int]:
        return _storage.save(self, db=db, stage=stage, label=label, notes=notes)

    @classmethod
    def load(
        cls,
        id: str,
        *,
        db: "BaseDb",
        registry: Optional["Registry"] = None,
        label: Optional[str] = None,
        version: Optional[int] = None,
    ) -> Optional["Agent"]:
        return _storage.load(cls, id=id, db=db, registry=registry, label=label, version=version)

    def delete(
        self,
        *,
        db: Optional["BaseDb"] = None,
        hard_delete: bool = False,
    ) -> bool:
        return _storage.delete(self, db=db, hard_delete=hard_delete)

    def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
        return _storage.get_run_output(self, run_id=run_id, session_id=session_id)

    async def aget_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
        return await _storage.aget_run_output(self, run_id=run_id, session_id=session_id)

    def get_last_run_output(self, session_id: Optional[str] = None) -> Optional[RunOutput]:
        return _storage.get_last_run_output(self, session_id=session_id)

    async def aget_last_run_output(self, session_id: Optional[str] = None) -> Optional[RunOutput]:
        return await _storage.aget_last_run_output(self, session_id=session_id)

    def get_session(
        self,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
    ) -> Optional[Union[AgentSession, TeamSession, WorkflowSession]]:
        return _session.get_session(self, session_id=session_id, user_id=user_id)

    async def aget_session(
        self,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
    ) -> Optional[Union[AgentSession, TeamSession, WorkflowSession]]:
        return await _session.aget_session(self, session_id=session_id, user_id=user_id)

    def save_session(self, session: Union[AgentSession, TeamSession, WorkflowSession]) -> None:
        return _session.save_session(self, session=session)

    async def asave_session(self, session: Union[AgentSession, TeamSession, WorkflowSession]) -> None:
        return await _session.asave_session(self, session=session)

    def rename(self, name: str, session_id: Optional[str] = None) -> None:
        return _session.rename(self, name=name, session_id=session_id)

    def set_session_name(
        self,
        session_id: Optional[str] = None,
        autogenerate: bool = False,
        session_name: Optional[str] = None,
    ) -> AgentSession:
        return _session.set_session_name(
            self, session_id=session_id, autogenerate=autogenerate, session_name=session_name
        )

    async def aset_session_name(
        self,
        session_id: Optional[str] = None,
        autogenerate: bool = False,
        session_name: Optional[str] = None,
    ) -> AgentSession:
        return await _session.aset_session_name(
            self, session_id=session_id, autogenerate=autogenerate, session_name=session_name
        )

    def generate_session_name(self, session: AgentSession) -> str:
        return _session.generate_session_name(self, session=session)

    def get_session_name(self, session_id: Optional[str] = None) -> str:
        return _session.get_session_name(self, session_id=session_id)

    async def aget_session_name(self, session_id: Optional[str] = None) -> str:
        return await _session.aget_session_name(self, session_id=session_id)

    def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
        return _session.get_session_state(self, session_id=session_id)

    async def aget_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
        return await _session.aget_session_state(self, session_id=session_id)

    def update_session_state(self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None) -> str:
        return _session.update_session_state(self, session_state_updates=session_state_updates, session_id=session_id)

    async def aupdate_session_state(
        self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None
    ) -> str:
        return await _session.aupdate_session_state(
            self, session_state_updates=session_state_updates, session_id=session_id
        )

    def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[SessionMetrics]:
        return _session.get_session_metrics(self, session_id=session_id)

    async def aget_session_metrics(self, session_id: Optional[str] = None) -> Optional[SessionMetrics]:
        return await _session.aget_session_metrics(self, session_id=session_id)

    def delete_session(self, session_id: str, user_id: Optional[str] = None) -> None:
        return _session.delete_session(self, session_id=session_id, user_id=user_id)

    async def adelete_session(self, session_id: str, user_id: Optional[str] = None) -> None:
        return await _session.adelete_session(self, session_id=session_id, user_id=user_id)

    def get_session_messages(
        self,
        session_id: Optional[str] = None,
        last_n_runs: Optional[int] = None,
        limit: Optional[int] = None,
        skip_roles: Optional[List[str]] = None,
        skip_statuses: Optional[List[RunStatus]] = None,
        skip_history_messages: bool = True,
    ) -> List[Message]:
        return _session.get_session_messages(
            self,
            session_id=session_id,
            last_n_runs=last_n_runs,
            limit=limit,
            skip_roles=skip_roles,
            skip_statuses=skip_statuses,
            skip_history_messages=skip_history_messages,
        )

    async def aget_session_messages(
        self,
        session_id: Optional[str] = None,
        last_n_runs: Optional[int] = None,
        limit: Optional[int] = None,
        skip_roles: Optional[List[str]] = None,
        skip_statuses: Optional[List[RunStatus]] = None,
        skip_history_messages: bool = True,
    ) -> List[Message]:
        return await _session.aget_session_messages(
            self,
            session_id=session_id,
            last_n_runs=last_n_runs,
            limit=limit,
            skip_roles=skip_roles,
            skip_statuses=skip_statuses,
            skip_history_messages=skip_history_messages,
        )

    def get_chat_history(self, session_id: Optional[str] = None, last_n_runs: Optional[int] = None) -> List[Message]:
        return _session.get_chat_history(self, session_id=session_id, last_n_runs=last_n_runs)

    async def aget_chat_history(
        self, session_id: Optional[str] = None, last_n_runs: Optional[int] = None
    ) -> List[Message]:
        return await _session.aget_chat_history(self, session_id=session_id, last_n_runs=last_n_runs)

    def get_session_summary(self, session_id: Optional[str] = None) -> Optional[SessionSummary]:
        return _session.get_session_summary(self, session_id=session_id)

    async def aget_session_summary(self, session_id: Optional[str] = None) -> Optional[SessionSummary]:
        return await _session.aget_session_summary(self, session_id=session_id)

    def get_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
        return _managers.get_user_memories(self, user_id=user_id)

    async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
        return await _managers.aget_user_memories(self, user_id=user_id)

    def get_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
        return _managers.get_culture_knowledge(self)

    async def aget_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
        return await _managers.aget_culture_knowledge(self)

    # ---------------------------------------------------------------
    # _response module delegates
    # ---------------------------------------------------------------

    def save_run_response_to_file(
        self,
        run_response: RunOutput,
        input: Optional[Union[str, List, Dict, Message, List[Message]]] = None,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
    ) -> None:
        return _run.save_run_response_to_file(
            self, run_response=run_response, input=input, session_id=session_id, user_id=user_id
        )

    def add_to_knowledge(self, query: str, result: str) -> str:
        return _default_tools.add_to_knowledge(self, query=query, result=result)

    # ---------------------------------------------------------------
    # _cli module delegates
    # ---------------------------------------------------------------

    def print_response(
        self,
        input: Union[List, Dict, str, Message, BaseModel, List[Message]],
        *,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        user_id: Optional[str] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        stream: Optional[bool] = None,
        markdown: Optional[bool] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        add_session_state_to_context: Optional[bool] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_full_reasoning: bool = False,
        console: Optional[Any] = None,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        **kwargs: Any,
    ) -> None:
        return _cli.agent_print_response(
            self,
            input=input,
            session_id=session_id,
            session_state=session_state,
            user_id=user_id,
            run_id=run_id,
            audio=audio,
            images=images,
            videos=videos,
            files=files,
            stream=stream,
            markdown=markdown,
            knowledge_filters=knowledge_filters,
            add_history_to_context=add_history_to_context,
            add_dependencies_to_context=add_dependencies_to_context,
            dependencies=dependencies,
            add_session_state_to_context=add_session_state_to_context,
            metadata=metadata,
            debug_mode=debug_mode,
            show_message=show_message,
            show_reasoning=show_reasoning,
            show_full_reasoning=show_full_reasoning,
            console=console,
            tags_to_include_in_markdown=tags_to_include_in_markdown,
            **kwargs,
        )

    async def aprint_response(
        self,
        input: Union[List, Dict, str, Message, BaseModel, List[Message]],
        *,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        user_id: Optional[str] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        stream: Optional[bool] = None,
        markdown: Optional[bool] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_full_reasoning: bool = False,
        console: Optional[Any] = None,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        **kwargs: Any,
    ) -> None:
        return await _cli.agent_aprint_response(
            self,
            input=input,
            session_id=session_id,
            session_state=session_state,
            user_id=user_id,
            run_id=run_id,
            audio=audio,
            images=images,
            videos=videos,
            files=files,
            stream=stream,
            markdown=markdown,
            knowledge_filters=knowledge_filters,
            add_history_to_context=add_history_to_context,
            dependencies=dependencies,
            add_dependencies_to_context=add_dependencies_to_context,
            add_session_state_to_context=add_session_state_to_context,
            metadata=metadata,
            debug_mode=debug_mode,
            show_message=show_message,
            show_reasoning=show_reasoning,
            show_full_reasoning=show_full_reasoning,
            console=console,
            tags_to_include_in_markdown=tags_to_include_in_markdown,
            **kwargs,
        )

    def cli_app(
        self,
        input: Optional[str] = None,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
        user: str = "User",
        emoji: str = ":sunglasses:",
        stream: bool = False,
        markdown: bool = False,
        exit_on: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        return _cli.cli_app(
            self,
            input=input,
            session_id=session_id,
            user_id=user_id,
            user=user,
            emoji=emoji,
            stream=stream,
            markdown=markdown,
            exit_on=exit_on,
            **kwargs,
        )

    async def acli_app(
        self,
        input: Optional[str] = None,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
        user: str = "User",
        emoji: str = ":sunglasses:",
        stream: bool = False,
        markdown: bool = False,
        exit_on: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        return await _cli.acli_app(
            self,
            input=input,
            session_id=session_id,
            user_id=user_id,
            user=user,
            emoji=emoji,
            stream=stream,
            markdown=markdown,
            exit_on=exit_on,
            **kwargs,
        )

    # ---------------------------------------------------------------
    # Helper functions
    # ---------------------------------------------------------------

    def scrub_run_output_for_storage(self, run_response: RunOutput) -> None:
        return _run.scrub_run_output_for_storage(self, run_response=run_response)

    # ---------------------------------------------------------------
    # _run module delegates
    # ---------------------------------------------------------------

    @overload
    def run(
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Literal[False] = False,
        stream_events: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> RunOutput: ...

    @overload
    def run(
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Literal[True] = True,
        stream_events: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        yield_run_output: bool = False,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> Iterator[Union[RunOutputEvent, RunOutput]]: ...

    def run(
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Optional[bool] = None,
        stream_events: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        yield_run_output: Optional[bool] = None,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
        return _run.run_dispatch(
            self,
            input=input,
            stream=stream,
            stream_events=stream_events,
            user_id=user_id,
            session_id=session_id,
            session_state=session_state,
            run_context=run_context,
            run_id=run_id,
            audio=audio,
            images=images,
            videos=videos,
            files=files,
            knowledge_filters=knowledge_filters,
            add_history_to_context=add_history_to_context,
            add_dependencies_to_context=add_dependencies_to_context,
            add_session_state_to_context=add_session_state_to_context,
            dependencies=dependencies,
            metadata=metadata,
            output_schema=output_schema,
            yield_run_output=yield_run_output,
            debug_mode=debug_mode,
            **kwargs,
        )

    @overload
    def arun(
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Literal[False] = False,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        stream_events: Optional[bool] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        debug_mode: Optional[bool] = None,
        background: bool = False,
        **kwargs: Any,
    ) -> Coroutine[Any, Any, RunOutput]: ...

    @overload
    def arun(
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Literal[True] = True,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        stream_events: Optional[bool] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        yield_run_output: Optional[bool] = None,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]: ...

    def arun(  # type: ignore
        self,
        input: Union[str, List, Dict, Message, BaseModel, List[Message]],
        *,
        stream: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        run_context: Optional[RunContext] = None,
        run_id: Optional[str] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        stream_events: Optional[bool] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        add_history_to_context: Optional[bool] = None,
        add_dependencies_to_context: Optional[bool] = None,
        add_session_state_to_context: Optional[bool] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
        yield_run_output: Optional[bool] = None,
        debug_mode: Optional[bool] = None,
        background: bool = False,
        **kwargs: Any,
    ) -> Union[RunOutput, AsyncIterator[RunOutputEvent]]:
        return _run.arun_dispatch(
            self,
            input=input,
            stream=stream,
            user_id=user_id,
            session_id=session_id,
            session_state=session_state,
            run_context=run_context,
            run_id=run_id,
            audio=audio,
            images=images,
            videos=videos,
            files=files,
            stream_events=stream_events,
            knowledge_filters=knowledge_filters,
            add_history_to_context=add_history_to_context,
            add_dependencies_to_context=add_dependencies_to_context,
            add_session_state_to_context=add_session_state_to_context,
            dependencies=dependencies,
            metadata=metadata,
            output_schema=output_schema,
            yield_run_output=yield_run_output,
            debug_mode=debug_mode,
            background=background,
            **kwargs,
        )

    @overload
    def continue_run(
        self,
        run_response: Optional[RunOutput] = None,
        *,
        run_id: Optional[str] = None,
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        stream: Literal[False] = False,
        stream_events: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        yield_run_output: bool = False,
    ) -> RunOutput: ...

    @overload
    def continue_run(
        self,
        run_response: Optional[RunOutput] = None,
        *,
        run_id: Optional[str] = None,
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        stream: Literal[True] = True,
        stream_events: Optional[bool] = False,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        yield_run_output: bool = False,
    ) -> Iterator[RunOutputEvent]: ...

    def continue_run(
        self,
        run_response: Optional[RunOutput] = None,
        *,
        run_id: Optional[str] = None,  # type: ignore
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        stream: Optional[bool] = None,
        stream_events: Optional[bool] = False,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        run_context: Optional[RunContext] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        yield_run_output: bool = False,
        **kwargs,
    ) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
        return _run.continue_run_dispatch(
            self,
            run_response=run_response,
            run_id=run_id,
            updated_tools=updated_tools,
            requirements=requirements,
            stream=stream,
            stream_events=stream_events,
            user_id=user_id,
            session_id=session_id,
            run_context=run_context,
            knowledge_filters=knowledge_filters,
            dependencies=dependencies,
            metadata=metadata,
            debug_mode=debug_mode,
            yield_run_output=yield_run_output,
            **kwargs,
        )

    @overload
    def acontinue_run(
        self,
        run_response: Optional[RunOutput] = None,
        *,
        stream: Literal[False] = False,
        stream_events: Optional[bool] = None,
        run_id: Optional[str] = None,
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> Coroutine[Any, Any, RunOutput]: ...

    @overload
    def acontinue_run(
        self,
        run_response: Optional[RunOutput] = None,
        *,
        stream: Literal[True] = True,
        stream_events: Optional[bool] = None,
        run_id: Optional[str] = None,
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        **kwargs: Any,
    ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]: ...

    def acontinue_run(  # type: ignore
        self,
        run_response: Optional[RunOutput] = None,
        *,
        run_id: Optional[str] = None,  # type: ignore
        updated_tools: Optional[List[ToolExecution]] = None,
        requirements: Optional[List[RunRequirement]] = None,
        stream: Optional[bool] = None,
        stream_events: Optional[bool] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        run_context: Optional[RunContext] = None,
        knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
        dependencies: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        debug_mode: Optional[bool] = None,
        yield_run_output: bool = False,
        **kwargs,
    ) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
        return _run.acontinue_run_dispatch(
            self,
            run_response=run_response,
            run_id=run_id,
            updated_tools=updated_tools,
            requirements=requirements,
            stream=stream,
            stream_events=stream_events,
            user_id=user_id,
            session_id=session_id,
            run_context=run_context,
            knowledge_filters=knowledge_filters,
            dependencies=dependencies,
            metadata=metadata,
            debug_mode=debug_mode,
            yield_run_output=yield_run_output,
            **kwargs,
        )


# ---------------------------------------------------------------
# Module-level functions
# ---------------------------------------------------------------


def get_agent_by_id(
    db: "BaseDb",
    id: str,
    version: Optional[int] = None,
    label: Optional[str] = None,
    registry: Optional["Registry"] = None,
) -> Optional["Agent"]:
    """
    Get an Agent by id from the database (new entities/configs schema).

    Resolution order:
    - if label is provided: load that labeled version
    - else: load component.current_version

    Args:
        db: Database handle.
        id: Agent entity_id.
        label: Optional label.
        registry: Optional Registry for reconstructing unserializable components.

    Returns:
        Agent instance or None.
    """
    from agno.utils.log import log_error

    try:
        row = db.get_config(component_id=id, label=label, version=version)
        if row is None:
            return None

        cfg = row.get("config") if isinstance(row, dict) else None
        if cfg is None:
            raise ValueError(f"Invalid config found for agent {id}")

        agent = Agent.from_dict(cfg, registry=registry)
        agent.id = id

        return agent

    except Exception as e:
        log_error(f"Error loading Agent {id} from database: {e}")
        return None


def get_agents(
    db: "BaseDb",
    registry: Optional["Registry"] = None,
    exclude_component_ids: Optional[Set[str]] = None,
) -> List["Agent"]:
    """
    Get all agents from the database.

    Sets _version and _stage on each agent from the component metadata.
    """
    from agno.utils.log import log_error

    agents: List[Agent] = []
    try:
        components, _ = db.list_components(
            component_type=ComponentType.AGENT, exclude_component_ids=exclude_component_ids
        )
        for component in components:
            config = db.get_config(component_id=component["component_id"])
            if config is not None:
                agent_config = config.get("config")
                if agent_config is not None:
                    component_id = component["component_id"]
                    if "id" not in agent_config:
                        agent_config["id"] = component_id
                    agent = Agent.from_dict(agent_config, registry=registry)
                    agent.id = component_id
                    agent._version = component.get("current_version")
                    agent._stage = config.get("stage")
                    agents.append(agent)
        return agents

    except Exception as e:
        log_error(f"Error loading Agents from database: {e}")
        return []
