Agentica Python SDK

repository·main·Indexed 41 days ago

https://github.com/symbolica-ai/agentica-python-sdk

A type-safe AI framework that enables LLM agents to integrate directly with Python code, including functions, classes, and live objects. Version 0.4.1 provides tools for managing agent lifecycles, enforcing runtime type constraints, and integrating with the Symbolica Platform or local servers via the Model Context Protocol (MCP).

Tokens
15.6K
Snippets
58
Records
65
Agent score
52%

What's inside Agentica

  1. Overview of Agentica Production Security Architecture

    main

    Agentica uses a multi-layered approach to secure arbitrary code execution:

    1. Warp: Enables remote object proxying, allowing agents to interact with objects across network boundaries as if they were local, while constraining them to well-typed operations.
    2. MicroVMs: Provides full OS-level isolation for groups of agents, offering the highest standard of security for arbitrary code execution.
    3. WASM: Provides per-agent isolation for high-performance scenarios, protecting the filesystem and network from agent code.

    This security model is applied by default when using an API key.

  2. How Agentica uses code instead of JSON for tool calling

    main

    Agentica moves away from the industry standard of calling tools via verbose JSON objects. Instead, it uses Code mode, allowing models to use language primitives like iteration and conditionals. This makes tool calls more expressive, compact, and allows intermediate results to flow by reference rather than through repetitive data serialization.

    # Instead of repetitive JSON tool calls:
    # [ { "tool": "weather", "args": { "location": "San Francisco", "date": "2025-11-01" } }, ... ]
    
    # Agentica uses expressive Python code:
    [get_weather("San Francisco", f"2025-11-{day}") for day in range(1, 13)]
  3. Orchestrate multiple agents

    main

    Complex tasks can be managed by coordinating multiple agents. A 'Lead' agent can be initialized with a scope that includes other specialized agent classes. This allows the lead agent to spawn and delegate tasks to sub-agents to complete a larger workflow.

    from agentica.agent import Agent
    
    class LeadResearcher:
        def __init__(self):
            self._brain = Agent(
                premise="Coordinate research tasks across subagents.",
                scope={"SubAgent": ResearchAgent}
            )
    
        async def call(self, query: str) -> str:
            return await self._brain.call(str, query)
    
    # The lead researcher can spawn and coordinate subagents
    lead = LeadResearcher()
    report = await lead("Research companies building AI agents")
  4. Specify return types for agent calls

    main

    When calling an agent via agent.call(), you can control the output format by specifying the desired type:

    • Default: If no type is provided, it returns a str.
    • Structured Output: Pass types like int, dict[str, int], or custom classes to get structured data.
    • Side-effects: Pass None if the call is intended only for side-effects (e.g., sending a message) and requires no return value.
    # Returns str
    result = await agent.call("What is 2+2?")
    
    # Returns int
    result: int = await agent.call(int, "What is 2+2?")
    
    # Returns dict
    result: dict[str, int] = await agent.call(dict[str, int], "Count items")
    
    # Side-effect only
    await agent.call(None, "Send a message to John")
  5. Understand Agentica Scope and Object Discovery

    main

    In Agentica, an agent's scope is a dynamic collection of objects provided at initialization. Rather than a static list of functions, agents interact with rich objects. When an agent calls a method that returns an object, that new object (and its methods) becomes part of the agent's evolving scope. This allows capabilities to emerge naturally from the data structure of your codebase.

    database = Database(...)
    # The agent starts with 'database' in its scope
    agent = await spawn("You are a helpful assistant.", database)
    
    # If database.get_user(id) returns a User object,
    # the agent can then discover and call User.update(), User.delete(), etc.
  6. Perform stateful data analysis with agents

    main

    Agents in Agentica maintain context across multiple calls. This allows them to manipulate variables by reference and remember previous results. You can pass datasets (e.g., converted to dictionaries) in an initial call, and subsequent calls can operate on that same state without re-providing the data.

    from agentica import spawn
    import pandas as pd
    
    agent = await spawn()
    
    # First analysis: provide the dataset
    result = await agent.call(
        dict[str, int],
        "Count movies by genre",
        dataset=pd.read_csv("movies.csv").to_dict()
    )
    
    # Agent remembers previous result and can perform follow-up operations
    filtered = await agent.call(
        dict[str, int],
        "Keep only genres with more than 1000 movies"
    )
  7. Stream agent responses with StreamLogger

    main

    To see live text generation, use StreamLogger. This creates an async generator that yields text chunks.

    Crucial Pattern: You must consume the stream before awaiting the final result. If you await the result first, you will miss the live output.

    import asyncio
    from agentica import spawn
    from agentica.logging.loggers import StreamLogger
    
    agent = await spawn(premise="You are a truth-teller.")
    stream = StreamLogger()
    
    with stream:
        # Create the task for the call
        result_task = asyncio.create_task(
            agent.call(str, "Write a long poem")
        )
        
        # 1. Consume stream FIRST for live output
        async for chunk in stream:
            print(chunk.content, end="", flush=True)
        
        # 2. Then await the final result
        final_result = await result_task
  8. Import existing libraries directly into Agentica

    main

    You do not need to wrap existing libraries into custom tool definitions. Any Python or TypeScript library with proper docstrings and type annotations can be passed directly into the agent's scope by including the client instance in the scope dictionary.

    from twelvedata import TDClient
    import os
    
    # Initialize the real library client
    market_data = TDClient(os.getenv("TWELVEDATA_API_KEY"))
    
    # Pass it directly to the agent scope
    agent = await spawn("You are a financial analysis assistant", { 'market_data': market_data })
  9. Create custom agent classes

    main

    You can wrap the core Agent class within a custom Python class to provide domain-specific functionality and cleaner interfaces. In this pattern, the custom class holds an instance of Agent (often referred to as _brain) and exposes high-level methods that call the agent with specific premises and scopes.

    from agentica.agent import Agent
    
    class ResearchAgent:
        def __init__(self, web_search_fn):
            self._brain = Agent(
                premise="You are a research assistant.",
                scope={"web_search": web_search_fn}
            )
    
        async def research(self, topic: str) -> str:
            return await self._brain(str, f"Research: {topic}")
    
        async def summarize(self, text: str) -> str:
            return await self._brain(str, f"Summarize: {text}")
    
    # Usage
    researcher = ResearchAgent(web_search)
    findings = await researcher.research("AI agents in 2025")
    summary = await researcher.summarize(findings)
  10. Create AI-powered functions with the @agentic decorator

    main

    Use the @agentic() decorator to transform standard Python functions (sync or async) into AI-powered ones. The LLM implements the logic defined by the function's signature and docstring. You can pass tools, models, and token limits directly into the decorator.

    Key features:

    • Persistence: Use persist=True to allow the function to maintain state/conversation history between calls.
    • Scope: Pass objects, functions, or classes into the decorator to provide them as tools to the AI.
    • Model Selection: Specify a model (e.g., openai:gpt-5) to control which LLM is used.
    from agentica import agentic
    
    # Defines an AI-powered function
    @agentic(model="openai:gpt-5", max_tokens=1000, persist=True)
    async def add(a: int, b: int) -> int:
        """Returns the sum of a and b"""
        ...
    
    # Calls the AI-powered function
    result = await add(1, 2)
  11. Spawn and call AI agents with spawn()

    main

    The spawn() function is the primary way to create an autonomous agent. Agents created via spawn are always awaitable.

    Calling an agent: When using agent.call(), you must provide the return type as the first argument, followed by the prompt as a str.

    Configuration:

    • Premise vs System: Use premise to add context to the default system prompt, or system for full control. You cannot use both simultaneously.
    • Scope: Pass a dictionary to scope to provide tools/objects to the agent.
    • MCP: Pass a path to an MCP configuration JSON file to the mcp parameter to give the agent access to Model Context Protocol servers.
    from agentica import spawn
    
    # Create an agent with a premise
    agent = await spawn(premise="You are a truth-teller.")
    
    # Call the agent: (return_type, prompt)
    result: bool = await agent.call(bool, "The Earth is flat")