A user sends one message. The agent takes seven seconds to answer. The tool itself only took 400ms. The database looks fine. The LLM endpoint is not down. The framework logs say the turn completed successfully.
The missing time is inside the runtime path: agent instantiation, state load, prompt construction, LLM planning, tool parsing, synchronous tool execution, synthesis, and state persistence. Microsoft’s Agent Framework makes the simple case easy because that pipeline is hidden. Production debugging starts when the hidden pipeline becomes the thing you have to measure.
The useful mental model is not “an agent is running.” It is: each turn creates a fresh execution context, loads state, calls a model, maybe calls tools, maybe calls the model again, writes state, and discards the instance.
This analysis applies to Microsoft Agent Framework 1.x as documented after the 1.0 production-ready release and checked against the public docs available on July 30, 2026. At that point the Python package line had reached agent-framework 1.12.0 and the core .NET packages listed under the MicrosoftAgentFramework NuGet profile had reached 1.15.0 for Microsoft.Agents.AI. The runtime details below should be read as the request path for normal agent runs using sessions, tools, middleware, and model calls, not as a guarantee about every provider-specific hosted service.
Microsoft’s own docs describe Agent Framework as a combination of agents, harnesses, and workflows, with foundational pieces for model clients, session-based state, context providers, middleware, MCP clients, and tool integration in the Agent Framework overview. The function tools guide documents the custom-code tool path, the runtime context guide separates per-run metadata from persistent session state, and the middleware guide shows where run, function, and chat middleware can intercept the pipeline.
Each Turn Starts Fresh
Agent definitions behave like templates. A request arrives, the framework resolves the definition, binds tools, loads conversation state, executes the turn, saves state, and drops the instance.
request
-> deserialize agent definition
-> resolve tools
-> instantiate agent
-> load conversation state
-> execute turn
-> persist updated state
-> discard instance
Constructors run on every turn. Expensive initialization in the agent constructor adds latency to every message. Loading models, opening connections, building caches, or reading configuration there means the user pays for it repeatedly.
Isolation is the upside. Warm per-agent memory is not part of the deal. Shared caches, persistent connections, and expensive dependencies need to live in tool initialization, external services, or explicitly managed state.
The Turn Pipeline Adds Up
A tool-using turn is usually at least two model calls.
The first call plans or decides whether a tool is needed. The framework parses the response for structured tool calls. It validates arguments against the tool schema. It invokes the tool synchronously. Then it often sends the tool result back to the model for a final natural-language response.
A typical turn can look like this:
state load 100ms
prompt construction 30ms
LLM planning call 3000ms
tool validation 5ms
tool execution 500ms
second tool 800ms
LLM synthesis call 2000ms
state save 100ms
That is already 6.5 seconds before retries, slow storage, network variance, or tool failure. The abstraction feels like one agent response. The runtime sees several serialized phases.
Streaming only helps when the model answers directly. If the turn includes tool calls, the framework must buffer enough output to identify the tool call, run the tool, and synthesize the final answer. The user may see nothing until the final synthesis is ready.
State Lives Outside the Agent
Conversation state is loaded at the start of the turn and saved at the end. Between those points, it exists in the current agent instance.
State is keyed by conversation ID. Two overlapping turns on the same conversation can load the same starting state and write back different endings.
T0: Turn A loads state version 10
T0: Turn B loads state version 10
T1: Turn A writes version 11
T2: Turn B writes version 11 from its own copy
Turn A’s changes are gone. The framework does not make the turn a transaction. If concurrent state access matters, the application needs optimistic locking, version checks, queues, or another serialization mechanism. The race is the same class of problem as agent turns as transaction boundaries.
State size also becomes latency, which is the agent-runtime version of RAG token consumption. Conversation history is commonly serialized as JSON. Long histories increase load time, save time, prompt size, and token cost. After enough turns, the failure changes from “slow” to “context overflow.”
The runtime will keep appending unless you prune, summarize, window, or store history elsewhere.
Runtime Request Lifecycle
The complete lifecycle is easier to reason about as a sequence than as a single run() call:
client
-> application endpoint
-> create turn trace and run options
-> resolve agent definition
-> attach middleware
-> open or create agent session
-> load session state
-> apply context providers
-> construct model request
-> model planning call
-> parse tool call candidates
-> validate tool arguments
-> invoke function middleware
-> execute tool
-> append tool result to messages
-> optional model synthesis call
-> update session state
-> persist session state
-> return response
The extension points are also latency points. Agent middleware wraps the run. Context providers inject memory or other prompt material before the model call. Function middleware wraps tool invocation. Chat middleware wraps the provider request. That shape matches Microsoft’s middleware documentation: it is a chain around the underlying agent, function, and chat operations.
Tool Calls Are Synchronous Boundaries
Tools register as callable functions with schemas. The model sees the schema, emits a function name and arguments, and the framework validates then invokes the callable.
LLM emits tool call
-> validate arguments
-> resolve function
-> execute with timeout
-> capture result or error
-> continue turn
A 5-second tool adds 5 seconds to the turn. A tool timeout returns an error to the model, which may trigger another model call for recovery. A schema mismatch can do the same. If the schema expects an integer and the model emits "42", strict validation can fail and send the turn through another loop.
Tools also receive only the context they are given. They do not automatically know the conversation ID, the original user message, previous tools called this turn, or the agent’s goal. Passing all of that through parameters makes schemas verbose and easier for the model to misuse. Injecting context through tool construction makes tools stateful. Both choices are real tradeoffs.
Shared tool instances must be thread-safe. Different conversations can invoke the same registered tool concurrently. Mutable tool state needs locks, external coordination, or removal.
Errors Cost Model Calls
Error handling looks graceful from the outside and expensive from the trace.
LLM calls tool A with bad arguments
validation fails
LLM receives error and retries
tool A runs and throws
the model tries tool B
tool B succeeds
Two failures can turn one user request into three model calls plus tool execution time. A buggy tool can consume the whole turn budget through retries. Tool-level timeouts stop one invocation, but they do not necessarily cancel downstream work unless the tool and transport are designed for that boundary. Turn-level timeout stops the entire pipeline.
Side effects complicate recovery, especially when tools behave like serverless functions with external state. A tool may succeed, write to an external system, and then the synthesis call times out. The user sees an error. A retry may run the tool again unless the tool is idempotent.
Cancellation and Overlap Edge Cases
Several production failures come from work continuing after the user experience has moved on.
A user closes the connection while a turn is still running. Tool calls complete. State may still save. Resources are consumed for a response nobody will receive.
A user sends a second message before the first turn finishes. Both turns may run against stale state. The later save wins.
A tool completes successfully and the final model call fails. External side effects exist, but the conversation does not contain the answer that explains them.
A state write partially fails. The next turn loads state that is valid JSON but semantically wrong.
The mitigation work sits in the application: idempotent tools, turn cancellation on disconnect, state validation, conflict detection, and serialization for overlapping turns.
Observability Has To Be Added
The framework may log events, but production debugging needs structured traces. A single turn should have one trace ID that follows state load, prompt construction, model calls, tool validation, tool execution, synthesis, and state save.
Useful metrics are phase-level:
- state load and save duration
- model call duration and retry count
- tool execution duration by tool
- validation failure rate by schema
- turn timeout rate
- state size and prompt token count
- concurrent turn conflicts
Without that instrumentation, latency analysis becomes timestamp archaeology. The logs say the turn was slow. They do not say which phase spent the time.
Middleware, tool wrappers, prompt modifiers, and custom state storage are the usual extension points. They are powerful because they sit inside the turn pipeline. They also add latency inside the same pipeline, so instrumentation should measure itself too.
Here is an anonymized OpenTelemetry-style trace from a production support assistant turn. It is not a framework benchmark. It is the sort of phase-level trace that makes the hidden runtime cost visible:
trace_id=7f4a2c1b9d2e4a71 user_turn=turn-48123 total=7248ms
span duration
http POST /agent/run 7248ms
agent.resolve_definition 18ms
agent.middleware.enter 11ms
session.load 146ms
context_provider.retrieve_customer 292ms
prompt.build 44ms
chat.model_call.plan 2818ms
tool.parse_and_validate 17ms
function.middleware.authorize 23ms
tool.crm_lookup 611ms
tool.ticket_search 843ms
chat.model_call.synthesize 2246ms
session.save 139ms
agent.middleware.exit 40ms
The two model calls dominate, but the non-model phases still account for more than two seconds. That matters operationally. If the team only measures provider latency, the dashboard explains 5.1 seconds of a 7.2-second turn and leaves the rest as folklore. For a deeper tracing primer, see distributed tracing and correlation trace IDs.
Scaling Moves the Bottleneck
The framework scales horizontally because instances are mostly stateless. More instances can serve more conversations.
State storage becomes the shared bottleneck. Every instance loads and saves conversation state. High traffic increases storage contention. Concurrent turns on the same conversation increase write conflicts. Large histories increase both storage I/O and model prompt cost.
Tools create their own bottlenecks. API-calling tools hit rate limits. Database tools need connection pools. CPU-heavy tools need compute. GPU-heavy tools need separate infrastructure. If tools overwhelm downstream services, failures return to the model and retries can amplify the load.
The framework does not automatically provide backpressure, request queues, downstream rate limiting, or circuit breakers, so retry behavior can reproduce the same amplification pattern described in AI outage retry policies. Those belong around the tool layer and the application boundary.
Comparison With Adjacent Frameworks
Agent Framework is not the only way to build this runtime shape. The tradeoff is where orchestration, state, hosting, and control live.
| Framework | Best fit | Runtime tradeoff |
|---|---|---|
| Microsoft Agent Framework | Microsoft-centered agent apps that need sessions, tools, middleware, model-provider flexibility, and workflows in one SDK. | Convenient request-turn abstraction, but hidden runtime phases still need explicit tracing, state discipline, and concurrency control. |
| Semantic Kernel Agents | Applications already built around Semantic Kernel plugins, planners, kernels, and enterprise integration patterns. | More control over kernel composition and SK ecosystem primitives, but agent orchestration features have historically moved through experimental and preview stages. |
| Azure AI Foundry Agents | Hosted or managed Azure agent deployments where platform integration, identity, governance, and managed infrastructure matter. | Less infrastructure to operate directly, but more runtime behavior is delegated to the platform boundary. Debugging depends on platform observability and deployment model. |
| LangGraph | Stateful, multi-step, graph-shaped workflows where explicit control flow, checkpointing, and resumability matter more than a simple agent facade. | More architectural control and durable graph semantics, but more application code owns the graph, state transitions, and operational surface. |
| AutoGen | Multi-agent conversations, team patterns, and research-friendly agent collaboration experiments. | Flexible team orchestration and agent chat patterns, but production state, tool boundaries, and hosting discipline still need explicit design. |
The practical distinction is simple: Agent Framework is attractive when you want an integrated Microsoft SDK with a conventional turn pipeline. LangGraph is attractive when the graph is the architecture. Foundry Agents are attractive when managed Azure hosting and governance dominate the decision. Semantic Kernel Agents fit teams already invested in SK’s plugin and kernel model. AutoGen remains useful when the core problem is multi-agent interaction design.
Where the Abstraction Stops Helping
The framework fits bounded conversational interfaces with moderate latency tolerance, simple state, and straightforward tool calls.
It becomes constraining when conversations are long, state needs transaction semantics, tools have dependencies on each other, latency must be sub-second, tool execution needs streaming or async orchestration, or multiple agents need to coordinate.
At that point the framework is still usable, but the application is doing more of the real architecture, closer to an orchestrator pattern than a simple agent wrapper: state pruning, locking, idempotency, tracing, rate limiting, backpressure, prompt management, and failure recovery.
Despite the name, the runtime model is turn-based request-response with tool calling. It does not provide autonomous goal execution, continuous operation, durable long-term memory, multi-agent coordination, or adaptive behavior by itself.
The agent is an instantiation pipeline, a model-call loop, synchronous tool boundaries, and externalized state. Once that is visible, the debugging questions get sharper: which phase is slow, which state write won, which tool side effect already happened, and which retry made the problem larger.





