Skip to content

basic_agent

Level 1 · Fundamentals — the high-level SymfonicAgent in under 10 lines.

Where minimal_agent wires the low-level symfonic.core primitives by hand, this example uses the symfonic.agent orchestration layer. SymfonicAgent composes the graph, the 5-layer memory system, and the model provider for you — you construct it, call run(), and read a typed AgentResponse.

  • Lines: ~43
  • Prerequisites: none to run as-is (uses MockModelProvider); a real provider needs pip install symfonic-core[anthropic] + an API key
  • Key concepts: SymfonicAgent, FrameworkConfig, FrameworkTenantScope, AgentResponse

Run it

Installed via pip? Copy this example into your project with the CLI (no checkout needed):

pip install "symfonic-core[cli]"
symfonic examples add basic_agent
python basic_agent/basic_agent.py

Or, from a source checkout (run from the repo root):

python -m examples.agent.basic_agent

Expected output:

Response: I remember our conversation!
Run ID: <uuid>
Duration: <n>ms
Memory entries used: 0

Full code

"""An agent that remembers, in about ten lines.

Usage:
    python -m examples.agent.basic_agent

Requires:
    pip install symfonic-core[anthropic]
    export ANTHROPIC_API_KEY=sk-...    # only for the real provider

Public surface used, and nothing else::

    from symfonic import Agent
    from symfonic.agent import FrameworkTenantScope
    from symfonic.capabilities.memory import GraphBackedHms, memory_capabilities
    from symfonic.capabilities.prompting import PromptingCapability
    from symfonic.memory.backends import InMemoryGraphBackend

The previous version of this file was titled "multi-memory agent" and turned
memory off -- ``auto_hydrate``, ``auto_consolidate`` and ``lazy_tooling`` all
False, because nothing registered a layer for them to use. It demonstrated
construction.

Composing two capabilities is what makes the title true. ``memory_capabilities``
records and recalls against a store; ``PromptingCapability`` is what renders a
recollection into the next prompt. Either alone is a no-op you cannot see:
without prompting the agent stores perfectly and the model reads none of it.

The scope is the isolation boundary and it is passed, not discovered. Every
read and write below is checked against it.
"""

from __future__ import annotations

import asyncio

from symfonic import Agent
from symfonic.agent import FrameworkTenantScope
from symfonic.capabilities.memory import GraphBackedHms, memory_capabilities
from symfonic.capabilities.prompting import PromptingCapability
from symfonic.core.testing import MockModelProvider
from symfonic.memory.backends import InMemoryGraphBackend


def build(store: GraphBackedHms, scope: FrameworkTenantScope) -> Agent:
    """One agent, bound to one tenant, that remembers its own turns."""
    # Swap MockModelProvider for AnthropicProvider() and nothing else changes:
    #   from symfonic.core.providers import AnthropicProvider
    return Agent(
        MockModelProvider(response="You mentioned the walnut allergy."),
        instructions="You are a helpful assistant. Be brief.",
        capabilities=[
            *memory_capabilities(store, scope, limit=5),
            PromptingCapability(sources=[]),
        ],
    )


async def main() -> None:
    store = GraphBackedHms(InMemoryGraphBackend())
    scope = FrameworkTenantScope.root("org", "demo-tenant")

    await build(store, scope).run("Remember that I am allergic to walnuts.")
    second = await build(store, scope).run("What do you remember?")

    print(f"Response: {second.text}")
    print(f"Duration: {second.duration_ms:.1f}ms")
    # No token count here on purpose: MockModelProvider reports none, and
    # an example printing "0 tokens" teaches that accounting is broken.
    # Rung 9 (journey_observability) measures spend with a provider that
    # reports real usage.

    # Two agents, one store: the second turn recalled what the first was told,
    # and nothing in this file retrieved it. The capability did.


if __name__ == "__main__":
    asyncio.run(main())

Step by step

1. Choose a provider

provider = MockModelProvider(response="I remember our conversation!")

Same ModelProvider seam as minimal_agent. To go live, swap the two commented lines:

from symfonic.core.providers import AnthropicProvider
provider = AnthropicProvider()   # reads ANTHROPIC_API_KEY from env

Nothing else on the page changes — that is the point of the provider protocol.

2. Construct the agent

agent = SymfonicAgent(
    model_provider=provider,
    config=FrameworkConfig(auto_hydrate=False, auto_consolidate=False),
)

SymfonicAgent is the batteries-included orchestrator. FrameworkConfig layers framework behavior (memory hydration, consolidation, lazy loading) on top of the core AgentConfig. Here auto_hydrate=False and auto_consolidate=False keep the demo self-contained — no memory backends are read or written. Flip them on once you attach real stores.

3. Define a tenant scope

scope = FrameworkTenantScope(tenant_id="demo-tenant")

Every run is scoped to a tenant. FrameworkTenantScope is how the framework isolates one customer's memory, documents, and skills from another's. Multi-tenant isolation is a first-class concept — see Hierarchical Tenant Scope.

4. Run and read the typed response

response = await agent.run("What do you remember?", scope=scope)
print(f"Response: {response.final_response}")
print(f"Run ID: {response.run_id}")
print(f"Duration: {response.duration_ms:.1f}ms")
print(f"Memory entries used: {response.memory_entries_used}")

Unlike the low-level runtime (which returns a raw state dict), SymfonicAgent.run returns a typed AgentResponse. Beyond final_response it carries observability fields (run_id, duration_ms, memory_entries_used) and — when you ask for it — a validated structured payload. See Structured Output.

Low-level vs high-level

minimal_agent (core) basic_agent (agent)
Entry type AgentRuntime SymfonicAgent
You wire graph, deps, stores just the provider
Memory layers none by default 5-layer HMS available
Return state dict typed AgentResponse
Best for learning internals, custom graphs building applications

What to try next

See also