Level 1: Your First Agent¶
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¶
Your First Agent (10 Lines)¶
Create a file called my_agent.py:
import asyncio
from symfonic.agent.config import FrameworkConfig
from symfonic.agent.engine import SymfonicAgent
from symfonic.agent.types import FrameworkTenantScope
from symfonic.core.testing import MockModelProvider
async def main():
agent = SymfonicAgent(
model_provider=MockModelProvider(response="Hello! I'm your first symfonic agent."),
config=FrameworkConfig(auto_hydrate=False, auto_consolidate=False),
)
scope = FrameworkTenantScope(tenant_id="quickstart")
response = await agent.run("Hi there!", scope=scope)
print(response.final_response)
print(f"Duration: {response.duration_ms:.1f}ms")
asyncio.run(main())
Run it:
Output:
What Just Happened¶
The 10 lines above wire a complete agent pipeline:
User query
-> FrameworkTenantScope (tenant isolation)
-> AgentGraph (state machine with react_loop topology)
-> ModelProvider (LLM call -- mocked here)
-> AgentResponse (final_response, duration_ms, node_execution_log)
Each component plays a specific role:
| Component | Role |
|---|---|
SymfonicAgent |
High-level facade composing graph runtime + memory |
FrameworkConfig |
All configuration in one frozen Pydantic model |
FrameworkTenantScope |
Multi-tenant isolation (every operation is scoped) |
MockModelProvider |
Deterministic test double for the LLM |
AgentResponse |
Structured result with response text, timing, and metadata |
The two config flags we disabled:
auto_hydrate=False-- Skip memory context injection (no memory backends yet)auto_consolidate=False-- Skip memory extraction after the response
Switch to a Real LLM¶
Install the Anthropic extra and set your API key:
Replace the mock provider with a real one:
import asyncio
from symfonic.agent.config import FrameworkConfig
from symfonic.agent.engine import SymfonicAgent
from symfonic.agent.types import FrameworkTenantScope
from symfonic.core.providers import AnthropicProvider
async def main():
agent = SymfonicAgent(
model_provider=AnthropicProvider(), # uses ANTHROPIC_API_KEY env var
config=FrameworkConfig(auto_hydrate=False, auto_consolidate=False),
)
scope = FrameworkTenantScope(tenant_id="quickstart")
response = await agent.run("What is dependency injection?", scope=scope)
print(response.final_response)
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.
Fluent construction with AgentBuilder (9.2+)¶
AgentBuilder is a chainable facade over the same primitives
(FrameworkConfig, SubAgentSpec, HMSFactory) — no new behaviour, just a
more readable assembly for agents with several knobs. It's the recommended way
to construct anything beyond the two-argument SymfonicAgent(...) form.
import asyncio
from symfonic.agent import AgentBuilder, SubAgentSpec
from symfonic.core.providers import AnthropicProvider
from symfonic.core import symfonic_tool
@symfonic_tool()
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
async def main():
agent = (
AgentBuilder()
.provider(AnthropicProvider())
.model("claude-sonnet-4-5", temperature=0.2)
.domain("calc", "A calculator agent.")
.tools(add)
.sub_agent_spec(SubAgentSpec(
name="researcher", description="Deep research.", temperature=0.4,
))
.build()
)
async with agent:
reply = await agent.run("What is 17 + 25?")
print(reply.final_response)
asyncio.run(main())
Chainable methods (each returns the builder): .provider(), .model(name,
temperature=, max_tokens=), .domain(name, description), .tools(*tools),
.enable_hms(), .lazy_tooling(), .sub_agent(name, agent=) /
.sub_agent_spec(spec), .option(**framework_config_overrides), and
.memory(embeddings=…) (builds an in-memory HMS for you). .build()
re-validates the assembled FrameworkConfig and returns a ready SymfonicAgent.
Three tiers, same engine — pick the smallest that fits:
| Tier | Use it when |
|---|---|
SymfonicAgent(model_provider=…, config=…) |
A couple of arguments; the common case |
AgentBuilder()....build() |
Several knobs, sub-agents, or you prefer a fluent chain |
Core AgentGraph / AgentRuntime (below) |
You need the graph runtime without the memory-aware facade |
Using the Low-Level Core API¶
If you want direct access to the graph runtime without the high-level
SymfonicAgent wrapper, 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? |
|---|---|---|---|
SymfonicAgent |
Production apps, full HMS memory, plugins | Yes | Yes |
AgentRuntime |
Custom graph topologies, fine-grained DI | Manual | Manual |
Most users should start with SymfonicAgent 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.