Most agent frameworks assume you want a hosted control plane. Strands Harness SDK assumes you want the control plane running in your own process. It exposes the plumbing that hand-rolled agent loops grow into: lifecycle controls, tool registration, structured output, evals, and observability. The architecture choice (local execution with optional cloud deployment) reveals how production agent infrastructure differs from prototyping frameworks.
The SDK supports Python and TypeScript, works with any model and any cloud, and includes MCP server integration for agent-tool communication. With 7,332 GitHub stars and trending #8 for Python, it is infrastructure-first tooling gaining traction as teams move from agent experiments to production deployments.
What In-Process Control Means
Strands runs the agent loop in your application process. There is no separate orchestration service, no API gateway for agent state, no hosted dashboard. You instantiate an agent, call run(), and the loop executes locally. This changes how lifecycle controls, tool boundaries, and observability work.
Lifecycle controls are synchronous function calls:
- Turn limits: maximum iterations before the loop exits
- Token budgets: cumulative token count across all turns
- Cancellation: cooperative cancellation via context or signal
- Stop reasons: explicit exit conditions (success, error, budget exhausted)
Because the loop runs in-process, you can interrupt it with standard concurrency primitives. You do not need to poll a remote API or wait for a webhook. You can wrap the agent call in a timeout, cancel it from another thread, or inspect intermediate state directly.
Tool registration happens at agent initialization. You pass a list of tool definitions (functions with type annotations or JSON schemas), and the SDK handles serialization, validation, and invocation. The agent loop calls your tools synchronously during execution. There is no tool registry service, no separate tool execution environment, and no network hop between agent and tool.
Observability is callback-based. You register hooks for lifecycle events (turn start, tool call, model response, error) and the SDK invokes them during execution. You can log to stdout, push metrics to a time-series database, or write traces to OpenTelemetry. The SDK does not assume a specific observability backend.
Architecture: Loop, Tools, and State
The core abstraction is the agent loop. You define an agent with a model, a system prompt, and a list of tools. The loop runs until it hits a stop condition: the model returns a final answer, the turn limit is reached, the token budget is exhausted, or an error occurs.
from strands_agents import Agent, Tool
def search_database(query: str) -> str:
"""Search the product database."""
return f"Results for: {query}"
agent = Agent(
model="gpt-4",
system_prompt="You are a product assistant.",
tools=[Tool(function=search_database)],
max_turns=10,
max_tokens=5000
)
result = agent.run("Find laptops under $1000")
print(result.output)
print(f"Turns: {result.turns}, Tokens: {result.tokens}")
The agent loop is a state machine:
- Prompt construction: Combine system prompt, conversation history, and user input
- Model call: Send prompt to LLM, receive response (text or tool calls)
- Tool execution: If the model requests tools, execute them and append results to history
- Stop check: Evaluate stop conditions (final answer, limits, error)
- Repeat: If not stopped, go to step 1
State lives in memory. The conversation history is a list of messages (user, assistant, tool) that grows with each turn. The SDK does not persist state to disk or a database by default. If you want durable state, you implement it yourself: serialize the conversation history, write it to S3, load it on restart.
Tool Boundaries and MCP Integration
Strands treats tools as first-class objects. A tool is a function with a schema (parameter types, return type, description). The SDK generates the schema from Python type hints or TypeScript interfaces, then serializes it to the format the model expects (OpenAI function calling, Anthropic tool use, etc.).
MCP server integration extends this model. The Model Context Protocol defines a standard for exposing tools, prompts, and resources over JSON-RPC. Strands includes an MCP server implementation that wraps your agent and exposes it as an MCP-compatible service. This lets you integrate Strands agents with MCP clients (Claude Desktop, Zed, other MCP-aware tools).
The MCP server runs in the same process as the agent. It listens on a local socket or stdio, receives tool call requests from the client, invokes the agent, and returns results. The agent loop still runs locally. The MCP server is a thin protocol adapter, not a separate orchestration layer.
Tool call flow with MCP:
- MCP client sends
tools/callrequest with tool name and arguments - MCP server deserializes request, invokes registered tool function
- Tool function executes in agent process, returns result
- MCP server serializes result, sends response to client
This architecture keeps tool execution inside your security boundary. The MCP client does not execute tools directly. It sends requests to your MCP server, which runs in your process and has access to your credentials, databases, and internal services.
Lifecycle Controls: Turn Limits, Token Budgets, Cancellation
Production agent loops need guardrails. Strands exposes three primary controls:
| Control | Purpose | Failure Mode |
|---|---|---|
| Turn limits | Prevent infinite loops | Agent exits before completing task |
| Token budgets | Control cost and latency | Agent runs out of budget mid-task |
| Cancellation | User-initiated stop | Partial results, inconsistent state |
Turn limits are simple: the loop exits after N iterations. This prevents runaway loops where the model repeatedly calls tools without making progress. The limit is a hard stop. If the agent hits the limit, it returns whatever state it has accumulated. You decide whether to treat this as an error or a partial success.
Token budgets are cumulative. The SDK tracks tokens consumed across all model calls (prompt tokens + completion tokens). When the budget is exhausted, the loop exits. This is useful for cost control: you can set a per-request budget and reject expensive queries early.
Cancellation is cooperative. You pass a cancellation token or signal to the agent, and the SDK checks it between turns. If the token is cancelled, the loop exits. This is not a hard interrupt. The agent finishes the current turn (model call + tool execution) before exiting. If you need immediate cancellation, you wrap the agent call in a timeout or kill the process.
Evals and Observability in Local Execution
Evals run in the same process as the agent. You define an eval function that takes the agent output and returns a score. The SDK runs the eval after each agent execution and logs the result.
def eval_accuracy(output: str, expected: str) -> float:
"""Score output accuracy."""
return 1.0 if output.strip() == expected.strip() else 0.0
agent = Agent(
model="gpt-4",
system_prompt="You are a calculator.",
evals=[eval_accuracy]
)
result = agent.run("What is 2 + 2?", expected="4")
print(f"Score: {result.eval_scores['eval_accuracy']}")
Evals are synchronous. They block the agent execution until they complete. This is fine for fast evals (string comparison, regex matching) but problematic for slow evals (model-graded, API calls). If you need async evals, you run them in a separate thread or process and correlate results later.
Observability is callback-based. You register hooks for lifecycle events:
-
on_turn_start: Called at the beginning of each turn -
on_model_call: Called before and after each model invocation -
on_tool_call: Called before and after each tool execution -
on_error: Called when an error occurs -
on_complete: Called when the loop exits
Hooks receive structured data (turn number, token count, tool name, error message). You decide what to do with it: log to stdout, push to a metrics backend, write to a trace collector.
Because the agent runs in-process, you have full access to execution state. You can inspect the conversation history, read intermediate tool results, or dump the entire agent state to disk. There is no API boundary between your observability code and the agent loop.
Deployment Shape: Local, Container, or Serverless
Strands agents run wherever your application runs. The SDK is a library, not a service. You import it, instantiate an agent, and call run(). The deployment shape depends on your application architecture.
Local execution: Run the agent in your web server, CLI tool, or desktop application. The agent loop runs in the same process as your application logic. This is the simplest deployment: no separate infrastructure, no network calls, no coordination.
Container deployment: Package the agent in a Docker container and deploy it to Kubernetes, ECS, or Cloud Run. The agent runs in a dedicated process, but it is still your process. You control the environment, the dependencies, and the lifecycle.
Serverless deployment: Wrap the agent in a Lambda function or Cloud Function. The agent runs on-demand, triggered by HTTP requests or events. This works if your agent execution time fits within the serverless timeout (15 minutes for Lambda, 60 minutes for Cloud Run).
The SDK does not prescribe a deployment model. It runs in any Python or TypeScript environment. You choose the deployment shape based on your latency, cost, and scaling requirements.
When the In-Process Model Breaks Down
Strands works well when the agent loop fits in a single process and completes in a reasonable time (seconds to minutes). It breaks down when:
- Long-running agents: If the agent runs for hours or days, you need durable state and checkpointing. Strands does not provide this. You implement it yourself or use a workflow orchestrator (Temporal, Prefect).
- Multi-agent coordination: If you have multiple agents that need to communicate, you need a coordination layer. Strands does not provide inter-agent messaging or shared state. You implement it with queues, databases, or a separate orchestration service.
- High concurrency: If you run thousands of agents concurrently, you need resource isolation and scheduling. Strands runs agents in threads or async tasks, but it does not provide process isolation or resource limits. You use a container orchestrator or a dedicated agent runtime.
The in-process model is a trade-off. You get simplicity and control at the cost of scalability and coordination. For many production use cases (customer support bots, data analysis agents, internal tools), this trade-off is acceptable. For others (large-scale multi-agent systems, long-running workflows), you need a different architecture.
Technical Verdict
Use Strands Harness SDK when:
- You want the agent loop running in your application process
- You need explicit lifecycle controls (turn limits, token budgets, cancellation)
- You want to avoid a hosted control plane or separate orchestration service
- Your agent execution time is measured in seconds to minutes
- You need MCP integration for tool communication
Avoid it when:
- You need durable state and checkpointing for long-running agents
- You require multi-agent coordination with shared state or messaging
- You need process isolation and resource limits for high-concurrency workloads
- You prefer a managed service with built-in observability and deployment
Strands exposes the plumbing production agent loops need without imposing a specific deployment model. It is infrastructure for teams that want control over the agent execution environment and are willing to handle state management, observability, and scaling themselves.
Top comments (0)