Skip to content

Level 1: Your First Agent

This walkthrough uses symfonic.Agent. The Simple Agent is the full reference for it; Moving to 11.0 covers the transition boundaries.

SymfonicAgent and AgentBuilder: removed in 11.3.0

From 11.3.0, SymfonicAgent(...) and AgentBuilder().build() raise EntryPointRemovedError, a migration error, before anything is built, with no compatibility shim. Do not start new code on either. symfonic-core==9.12.* is the newest public PyPI line on which both still construct (private 11.0 to 11.2.6 did too), but it predates Agent and the other 11.x features, so pinning it does not migrate an 11.x application. Earlier versions of this page used both; their arguments do not carry over to Agent by renaming, so use Guide 22 to migrate.

Build a running symfonic agent in under 10 lines of code. No external services, no API keys, no infrastructure.

Prerequisites

  • Python 3.11 or later
  • pip (or uv, poetry, etc.)

Install

pip install symfonic-core

Public PyPI tops out at the 9.12 line, which does not ship symfonic.Agent. Install the 11.x line from your approved private registry to run this page.

Your First Agent (10 Lines)

Create a file called my_agent.py:

import asyncio
from symfonic import Agent
from symfonic.core.testing import MockModelProvider

async def main():
    agent = Agent(MockModelProvider(response="Hello! I'm your first symfonic agent."))
    result = await agent.run("Hi there!")
    print(result.text)
    print(f"Tokens: {result.usage.total_tokens}")

asyncio.run(main())

Run it:

python my_agent.py

Output:

Hello! I'm your first symfonic agent.
Tokens: 0

The mock provider reports no token usage; a real provider does.

What Just Happened

User query
  -> Agent.run() (one turn, no session)
  -> ModelProvider (LLM call -- mocked here)
  -> AgentResult (text, messages, tool_calls, usage, stop_reason)
Component Role
Agent The facade: one provider, optional instructions, tools and capabilities
MockModelProvider Deterministic test double for the LLM
AgentResult The turn's text, transcript, tool calls and token usage

There is no configuration object, tenant scope or memory here. Memory, delegation and the other capabilities are opt-in; see Guide 22. To serve one agent per tenant, compose Agents under symfonic.platform.AgentHost; the package README shows a minimal host and Moving to 11.0 a host with durable human continuation.

Switch to a Real LLM

Install the Anthropic extra and set your API key:

pip install symfonic-core[anthropic]
export ANTHROPIC_API_KEY="sk-ant-..."

Replace the mock provider with a real one:

import asyncio
from symfonic import Agent
from symfonic.core.providers import AnthropicProvider

async def main():
    agent = Agent(AnthropicProvider())  # uses ANTHROPIC_API_KEY env var
    result = await agent.run("What is dependency injection?")
    print(result.text)

asyncio.run(main())

Everything else stays the same. The ModelProvider protocol means you can swap backends (Anthropic, OpenAI, local models) without changing any other code.

Tools

Agent takes its tools at construction. This replaces the AgentBuilder().tools(...) chain earlier versions of this page showed:

import asyncio
from symfonic import Agent
from symfonic.core import symfonic_tool
from symfonic.core.providers import AnthropicProvider

@symfonic_tool()
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

async def main():
    agent = Agent(
        AnthropicProvider(),
        instructions="You are a calculator agent.",
        tools=[add],
    )
    result = await agent.run("What is 17 + 25?")
    print(result.text)

asyncio.run(main())

The builder's other knobs (.sub_agent_spec(), .enable_hms(), .lazy_tooling(), .memory(), .option()) have no keyword on Agent. Guide 22 maps the legacy patterns it covers to capabilities; do not assume a knob carries over by name.

Using the Low-Level Core API

If you want direct access to the graph runtime without a facade, use the core API:

import asyncio
from symfonic.core import (
    AgentConfig,
    AgentGraph,
    AgentRuntime,
    BaseAgentDeps,
    ModelProvider,
)
from symfonic.core.documents.in_memory import InMemoryDocumentStore
from symfonic.core.models import Document
from symfonic.core.protocols import DocumentStore
from symfonic.core.testing import MockModelProvider

async def main():
    config = AgentConfig()
    provider = MockModelProvider(response="Hello from the core API!")
    doc_store = InMemoryDocumentStore()
    await doc_store.write(Document(id="greeting", content="Welcome to symfonic."))

    deps = BaseAgentDeps()
    deps.register(ModelProvider, provider)
    deps.register(DocumentStore, doc_store)

    graph = AgentGraph()
    runtime = AgentRuntime(graph=graph, deps=deps, config=config)
    result = await runtime.run("Hello, agent!")
    print(f"Response: {result['final_response']}")
    print(f"Nodes executed: {result['node_execution_log']}")

asyncio.run(main())

The core API gives you full control over dependency injection via BaseAgentDeps and protocol-based service registration.

Choosing Your API Level

API Best for Memory? Streaming?
Agent New agents; capabilities composed explicitly Opt-in capability Yes (stream())
AgentHost One Agent per tenant scope over shared resources Per composed agent Per composed agent
AgentRuntime Custom graph topologies, fine-grained DI Manual Manual

Most users should start with Agent and drop to the core API only when they need custom graph topologies.

Next Steps

Your agent works, but it forgets everything between runs. Level 2 adds persistent memory with Postgres.

Next: Memory Persistence

Ready to ship a real app instead of a script? symfonic init scaffolds a full FastAPI + Postgres + auth + Docker project wired to symfonic-core โ€” see Scaffold a Production App.

For a grouped overview of every feature across the v6.0-v7.0.3 sprint, see the Feature Catalog โ€” 11 capability clusters each with opt-in examples.