Quickstart — your first agent¶
An agent in apogee-ai is an Agent entity plus three resolvers that say, at
call time, which engine, which LLM client and which tools to use. Nothing is
hard-wired, which is why the same agent definition runs against the native
engine in tests and against LangGraph plus a real provider in production.
This example runs offline: EchoLLMClient and InMemoryAgentRepository
need no API key and no database.
import asyncio
from apogee_ai import (
Agent,
EngineBinding,
EngineKindEnum,
ExecuteAgentInputDTO,
ExecuteAgentUseCase,
ExecutionPatternEnum,
)
from apogee_ai.infrastructure.agent_runtime.engines.native.apogee_native_engine import (
ApogeeNativeEngine,
)
from apogee_ai.infrastructure.llm_providers.echo_llm_client import EchoLLMClient
from apogee_ai.infrastructure.persistence.in_memory_agent_repository import (
InMemoryAgentRepository,
)
async def main() -> None:
repository = InMemoryAgentRepository()
await repository.create(
Agent(
name="Weather bot",
slug="weather-bot",
execution_pattern=ExecutionPatternEnum.CONVERSATIONAL,
engine=EngineBinding(kind=EngineKindEnum.NATIVE),
)
)
execute = ExecuteAgentUseCase(
query_repo=repository,
engine_resolver=lambda _agent: ApogeeNativeEngine(),
llm_resolver=lambda _agent: EchoLLMClient(),
)
result = await execute.execute(
ExecuteAgentInputDTO(slug="weather-bot", input="What is the weather in Lisbon?")
)
print("response: ", result.final_response)
print("iterations:", result.iterations, "finish:", result.finish_reason)
asyncio.run(main())
What each piece does¶
| Piece | Role |
|---|---|
Agent |
The definition: name, slug, execution pattern, engine binding, model config |
EngineBinding |
Which runtime executes the loop — EngineKindEnum.NATIVE or LANGGRAPH |
ExecutionPatternEnum |
CONVERSATIONAL, REACT, and the other loop shapes |
IAgentQueryRepository |
Where agent definitions live; InMemoryAgentRepository for tests |
engine_resolver / llm_resolver |
Called per execution, so one agent can run on different engines and models |
ExecuteAgentUseCase |
Loads the agent, resolves its dependencies, runs the loop |
Going to production¶
Swap the two resolvers; the agent definition and the use case stay as they are.
from apogee_ai_providers import Provider, ProviderCredentials, ProviderFactory
execute = ExecuteAgentUseCase(
query_repo=sql_agent_repository,
engine_resolver=lambda agent: engine_for(agent.engine.kind),
llm_resolver=lambda agent: ProviderFactory.build(
Provider.ANTHROPIC,
ProviderCredentials(api_key=settings.anthropic_key),
),
tool_resolver=lambda agent: tool_registry,
guardrail_resolver=lambda agent: guardrails,
trace_repo=trace_repository,
observability_emitter=otel_emitter,
)
The optional arguments are the extension points: tool_resolver supplies the
callable tools, guardrail_resolver filters input and output, and trace_repo
plus observability_emitter record what happened.
Streaming¶
StreamAgentUseCase takes the same constructor arguments and yields
AgentEvents instead of returning a single DTO.
async for event in stream_use_case.execute(
ExecuteAgentInputDTO(slug="weather-bot", input="Count to five.")
):
print(event)
Read next¶
- Reference — the 97 exported contracts and use cases.
- apogee-ai-providers — the LLM clients the resolver returns.
- apogee-ai-rag — retrieval to ground the agent's answers.