Introduction
"Choose Strands when you would otherwise write your own agent loop: it runs in your process with no hosted control plane, and it covers the jobs a hand-rolled loop grows into."
This is the 191st article in the "One Open Source Project a Day" series. Today's project is Strands Agents (repository name harness-sdk).
Writing an AI agent that actually works in production is rarely as simple as "call the model, run the tool." In real usage you quickly run into a set of small but unavoidable problems: what happens when the conversation runs too long? What happens when tool results flood the context window? How do you resume after a session gets interrupted? Do you have to rewrite everything when switching model providers? Most teams' answer is to cobble together their own agent loop, then keep patching it as new problems surface — until that hand-rolled loop has grown session management, context compression, multi-model adapters, guardrails, and a pile of other things that should really be infrastructure, not bespoke code.
Strands Agents is an open-source agent SDK from the AWS team, shipping Python and TypeScript implementations, built to solve exactly this. It's organized in two layers: at the bottom is the Strands SDK, a model-driven, fully customizable agent loop, tool system, and multi-model support; on top of it is Strands Harness, which gives you a fully assembled, production-grade agent with a single create_harness() (or createHarness() in TypeScript) call — already configured with context management, session persistence, long-term memory, and a skill system. And the object you get back is still a plain strands.Agent, meaning everything Harness configures for you can be inspected, overridden, or replaced at will.
8,300+ Stars, 1,200+ Forks, Apache-2.0 License, developed and maintained primarily by the Strands team at AWS.
What You Will Learn
- The layered relationship between the Strands SDK and Strands Harness: "build your own agent loop" vs. "get a pre-configured production-grade agent"
- What
create_harness()gives you by default: context management, session persistence, long-term memory, skill loading - The built-in tool set: shell, file operations,
programmatic_tool_caller, andsubagentdelegation - Model portability: switching between Bedrock, Anthropic, OpenAI, and Gemini using the same code
- MCP integration, interventions (tool-call gating), and subagent delegation patterns
Prerequisites
- Basic development experience in Python or TypeScript
- A basic understanding of LLM agent concepts (tool calling, multi-turn conversations)
- Optional: a basic understanding of MCP (Model Context Protocol)
Project Background
What It Is
Strands Agents describes itself as "a model-driven approach to building AI agents in just a few lines of code." The README states the value proposition directly: "Choose Strands when you would otherwise write your own agent loop: it runs in your process with no hosted control plane, and it covers the jobs a hand-rolled loop grows into." This sentence captures the project's core pitch — it isn't just another thin skin over an "agent framework," it's the infrastructure that hand-rolled agent loops keep reinventing (lifecycle controls, tools, structured output, MCP, multi-agent patterns, memory, sessions, model portability, streaming, guardrails, tracing, evals), already built and ready to use.
This repository (harness-sdk) is a monorepo containing:
-
harness-py/,harness-ts/: the Python/TypeScript Strands Harness, assembled viacreate_harness()/createHarness() -
strands-cli/: a CLI to prototype and chat with a harness agent from the terminal -
strands-py/,strands-ts/: the underlying SDKs — the agent loop, model providers, tool system -
site/: source for the official documentation site (built with Astro/Starlight)
Team and Background
- Organization: strands-agents (led by the Strands team at AWS)
- License: Apache License 2.0
- Language support: Python 3.10+, TypeScript (Node.js 22+)
-
Distribution: PyPI (
strands-agents,strands-harness), npm (@strands-agents/sdk,@strands-agents/harness,@strands-agents/cli)
Project Stats
- ⭐ GitHub Stars: 8,300+
- 🍴 Forks: 1,246
- 📄 License: Apache-2.0
- 🐛 Open Issues: 837 (high activity level, frequent community feedback)
- 📅 Created: 2025-05
What It Does
The Problem It Solves
The typical evolution of a hand-rolled agent loop:
v1: a simple loop of model.generate() + tool calls
↓ conversations get longer → manually truncate history, risking dropped context
↓ tool results get too large → write custom summarization logic to fit the prompt
↓ need to switch model providers → rewrite the calling layer and parameter mapping
↓ need resumable sessions → implement your own session storage and recovery
↓ need to remember user preferences → implement your own "memory" storage and retrieval
↑ every step reinvents infrastructure, and each team's version is incompatible with everyone else's
Strands' approach:
Bottom layer SDK: a model-driven agent loop + tool system + multi-model support, ready to use
↓
Top layer Harness: one create_harness() call
↓
Get back an agent pre-configured with context management/sessions/memory/skills
↑ the return value is still a plain Agent — everything Harness configures
can be overridden, extended, or replaced
Use Cases
-
Quickly building a coding/ops agent that handles long-running tasks
- The kind of task shown in the official example —
agent("Find the slowest test in this repo and explain why it's slow")— requiring multi-step file reading, running shell commands, and analyzing results
- The kind of task shown in the official example —
-
Production systems that need model portability
- Teams may run Bedrock, Anthropic, and OpenAI simultaneously, or need to switch providers later; Strands'
provider/modelstring syntax makes that a one-parameter change
- Teams may run Bedrock, Anthropic, and OpenAI simultaneously, or need to switch providers later; Strands'
-
Assistant-style products that need long-term memory and session recovery
- Harness persists every conversation to disk by default and distills cross-conversation long-term memory via a background task, fitting scenarios like customer support or personal assistants that need to "remember" user preferences
-
Complex tasks that need to delegate subtasks and keep the main context from flooding with search results or multi-step exploration
- The built-in
subagenttool lets the main agent hand off tasks like "search across many files," "a multi-step change," or "open-ended exploration" to a clean child agent, returning only its final answer
- The built-in
Quick Start
Minimal SDK usage (Python):
pip install strands-agents strands-agents-tools
from strands import Agent
from strands_tools import calculator
agent = Agent(tools=[calculator])
agent("What is the square root of 1764")
Minimal SDK usage (TypeScript):
npm install @strands-agents/sdk
import { Agent } from '@strands-agents/sdk'
const agent = new Agent()
const result = await agent.invoke('What is the square root of 1764?')
console.log(result)
Getting a production-grade agent directly with Harness (Python):
pip install strands-harness
from strands_harness import create_harness
agent = create_harness()
agent("Find the slowest test in this repo and explain why it's slow")
Getting a production-grade agent directly with Harness (TypeScript):
npm install @strands-agents/harness
import { createHarness } from '@strands-agents/harness'
const agent = await createHarness()
await agent.invoke("Find the slowest test in this repo and explain why it's slow")
Prefer not to write code at all? Install the CLI with npm install -g @strands-agents/cli and you get a strands terminal command that chats directly with a Harness agent.
Core Features
1. Layered design: SDK provides the foundation, Harness provides the assembly
The underlying strands.Agent is a set of primitives you can fully assemble yourself; create_harness() composes those primitives into a ready-to-use agent using benchmarked defaults. There's no gap between the two layers — the agent Harness returns is a plain Agent, so every piece it configures can still be overridden after the fact.
2. Production-grade capabilities out of the box
| Capability | Default behavior |
|---|---|
| Model | Defaults to Claude Opus on Amazon Bedrock; supports Bedrock/Anthropic/OpenAI/Google and more |
| Tools | Comes with shell, file read/write/edit, web_fetch, and programmatic_tool_caller (a sandbox for tool orchestration) |
| Context management | Automatically summarizes older conversation turns and relocates bulky tool results to storage, leaving a short reference behind |
| Sessions | Every conversation is persisted to ./.agent/sessions by default, resumable by session id |
| Long-term memory | Distills user preferences and project facts across conversations, saved as markdown and folded back into context |
| Caching | System prompt, tool definitions, and history are cached automatically wherever the provider supports it |
3. A flexible model-selection syntax
A single provider/model string switches between providers using consistent syntax; reasoning effort is also unified into one set of levels (off/minimal/low/medium/high/xhigh/max), with the SDK mapping them to each provider's own parameters:
create_harness(model="anthropic/claude-opus-5")
create_harness(model="openai/gpt-5.6-sol")
create_harness(model="bedrock/global.anthropic.claude-opus-5") # the default
4. Fine-grained control over built-in tools
builtin_tools supports two shapes: a list pins exactly the tools named, while a mapping edits the default set — False removes a tool, True adds one, and a config dict both enables and configures it:
create_harness(builtin_tools=["read"]) # keep only read
create_harness(builtin_tools={"subagent": False}) # defaults minus subagent delegation
create_harness(builtin_tools={"web_fetch": {"model": "openai/gpt-5-mini"}}) # configure web_fetch's own model
5. Two subagent delegation patterns
The built-in subagent tool lets the main agent hand a subtask off to a full Harness child that inherits the parent's model, tools, skills, and intervention policies; alternatively, Agent.as_tool() wraps any dedicated agent into a tool — useful when you need a fixed role (like a "reviewer" agent that only reviews code). Both can coexist.
6. Native MCP and web access support
Point mcp_servers at a standard mcpServers config (a JSON file or an inline dict) and Harness connects each server, discovers its tools, and adds them to the tool list automatically; web_fetch is on by default, web_search turns on or off automatically depending on whether the model provider natively supports it, and can also be explicitly routed through Exa as a third-party search backend.
7. Agent Skills and interventions
Drop Agent Skills into ./.agent/skills and Harness loads them automatically; the interventions parameter supports "ask", "smart", a custom policy string, or even a .cedar policy file — a gate you can put in front of tool calls for approval or validation.
Project Advantages
| Dimension | Hand-rolling an agent loop from scratch | Using only the underlying SDK (strands-py/ts) | Strands Harness |
|---|---|---|---|
| Time to first result | Slow — reinventing infrastructure | Moderate — loop and tool primitives already in place | Fast — one call gets you a full default configuration |
| Customizability | Fully open | High | High (the return value is still a native Agent — override anything) |
| Context/session/memory management | Build it yourself | Wire it in yourself | Built in by default |
| Model portability | Build your own adapter layer | Multi-provider support already exists at the SDK layer | One string parameter switch |
| Production readiness | Depends on the team | Depends on how much you fill in yourself | Defaults are "benchmarked and tested" out of the box |
Why choose this project?
- Led by the AWS team, with active updates and issue response (837 open issues signals a real, actively-used user base)
- Consistent API design across Python and TypeScript — mixed-stack teams don't need two mental models
- A "layered but not locked in" philosophy — Harness gives you defaults, not a closed framework you're boxed into
A Deeper Look
What Harness Actually Assembles
The parameter list of create_harness() reads like a checklist of "what a production-grade agent needs":
create_harness(
model="bedrock/global.anthropic.claude-opus-5", # model provider and version
effort="auto", # reasoning effort
instructions=None, # domain instructions appended to the system prompt
tools=None, # custom tools
mcp_servers=None, # MCP server configuration
plugins=None, # custom plugins
builtin_tools=None, # add/remove/edit the built-in tool set
background_tasks=None, # background task policy
builtin_plugins=["todos", "environment"], # built-in feature plugins
caching="auto", # context caching
context_manager="auto", # context management policy
session=True, # session persistence
skills=True, # skill loading
memory=True, # long-term memory
interventions=None, # tool-call intervention policy
**agent_kwargs,
)
The logic behind this list is clear: every capability that a hand-rolled agent loop eventually grows into — lifecycle controls, context compression, session recovery, cross-conversation memory, approval gates — is already implemented with a benchmarked default, rather than being left for each user to rediscover on their own.
Context Management: Summarization Plus Externalized Storage
The easiest thing to lose control of in a long-running task is the context window. Harness's default strategy is two-pronged: automatically summarize older conversation turns, while relocating bulky tool results (say, the large volume of file content returned by a search) to external storage, leaving only a short reference in the context. This means the context doesn't grow linearly no matter how many turns a task runs for — when an old result is actually needed, the agent can pull it back by reference instead of every turn carrying the full history.
Subagent Delegation: Two Modes for Two Different Needs
The project distinguishes between two delegation patterns, reflecting two different intents:
-
The built-in
subagenttool: the model autonomously decides to delegate a task to a full Harness child that inherits the parent's entire configuration (model, built-in tools, plugins, skills, intervention policies). The child can narrow the tools granted by the parent, but never widen them — a deliberate safety boundary. Delegation depth is bounded by default to prevent unbounded recursive delegation. -
A dedicated agent wrapped via
Agent.as_tool(): suited for a fixed role with a single responsibility (like areviewerthat only does code review). Each call runs from a fresh conversation, making it a clean, focused delegate rather than a shared session.
The two can coexist: the built-in subagent handles general-purpose "exploratory" delegation, while as_tool() handles expert delegation with a fixed role and prompt. This distinction reflects two different understandings of "delegation" — one where the model judges when to hand off work itself, and one where the engineer pre-defines the role boundaries.
Built-in Tools and Sandboxed Orchestration: programmatic_tool_caller
One notable design choice in Harness's default tool set is programmatic_tool_caller — a sandbox where the agent writes code to chain, loop over, and parallelize its other tools, instead of going through a full "model generates call parameters → executes → model reads the result → decides next step" reasoning cycle for every single tool call. For batch, mechanical operations — like "read 50 files and count how often a pattern appears" — this kind of orchestration is far more efficient than calling tools one at a time, and it saves on unnecessary model reasoning overhead.
How Model Portability Is Implemented
The provider/model string is a thin alias mapping (bedrock, bedrock-mantle, anthropic, openai, google, ollama, litellm); for any provider outside that list, you simply pass a Model instance and the SDK uses it as-is. The effort parameter follows the same philosophy: unified into one set of enumerated levels, with the SDK mapping them to each provider's own native parameter names — but if a requested level isn't offered by a given provider, the SDK raises rather than silently degrading, avoiding the hidden failure mode of "thinking you turned on high-effort reasoning when it silently didn't take effect."
Compared to Building From Scratch: Layered but Not Locked In
Strands' design philosophy can be summarized as "layered but not locked in." Most agent frameworks give you either a set of fully free primitives that you have to assemble entirely yourself, or a tightly integrated black box that's hard to escape when you need custom behavior. Strands tries to have both: the Harness layer gives you a benchmarked, deploy-ready default configuration, but what it returns is a completely ordinary Agent — meaning you can bypass Harness's defaults at any time and reach directly into the underlying SDK's hooks, intervention mechanisms, and custom session backends, without paying the cost of "escaping the framework" through an expensive rewrite.
Project Links and Resources
Official Resources
- 🌟 GitHub: https://github.com/strands-agents/harness-sdk
- 📚 Documentation: https://strandsagents.com
- 📄 License: Apache License 2.0
- 🐛 Issue Tracker: GitHub Issues
- 💬 Community: Discord
Related Resources
- Model Context Protocol — The tool-integration standard Strands natively supports
- Agent Skills — The specification Harness's skill system follows
- Strands Samples — The official repository of practical examples
Summary
Key Takeaways
- A layered architecture: the underlying SDK provides a fully customizable agent loop and tool primitives, while Harness on top provides a one-call, production-grade default configuration, with no gap between the two layers
- Defaults cover the common pain points of hand-rolled loops: context management, session persistence, long-term memory, and caching optimizations all work out of the box
-
Model portability is a first-class concern: a unified
provider/modelsyntax and reasoning-effort levels let you switch model providers without rewriting business logic -
Two subagent delegation patterns serve different intents: the built-in
subagentsupports model-driven autonomous delegation, whileAgent.as_tool()supports engineer-defined expert roles - Led by the AWS team, with strong activity: 8,300+ Stars and 837 open issues indicate a project that's genuinely in use and actively iterated on
Who This Is For
- Teams about to write their own agent loop, but haven't yet gotten to sessions, memory, or context compression: can skip straight past the "reinvent infrastructure" phase
- Teams that need to stay flexible across multiple model providers: the unified model-switching syntax reduces provider lock-in risk
- Developers wanting to quickly build a coding/ops agent that handles long-running, multi-step operations: Harness's default tool set (shell, file operations, orchestration sandbox) covers most scenarios
- Teams mixing Python and TypeScript stacks: both SDKs keep a consistent API design, avoiding two separate mental models
One-Line Verdict
Strands Agents doesn't wrap "building an agent" into a closed black box — it first honestly builds out the infrastructure that a hand-rolled loop would eventually need to add anyway, then lets you decide whether to start from it.
Check out PrimeSkills — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.
Find more useful knowledge and interesting products on my Homepage
Top comments (0)