Strands harness: de un import a un agente en producción
Un harness es todo lo que rodea al modelo: herramientas, contexto, memoria, permisos. Este curso te hace correr el de Strands, entender cada default y decidir cuáles cambiar antes de ponerlo a trabajar.
- módulos
- 8
- ejercicios
- 16
- proyecto final
- 1
- Teoría: Corta, con un insight y un antipatrón por módulo.
- Ejercicios: Dos por módulo. Se corren en tu terminal y tienen un resultado que puedes verificar.
- Entregable: Una checklist por módulo. Tu progreso queda guardado en este navegador.
- Proyecto final: Un agente con sesión, memoria, gate de aprobación y estado durable, con criterios medibles.
Basado en la documentación oficial de Strands harness (strandsagents.com/docs/user-guide/harness). Cubre overview, quickstart, subagentes, sesiones y memoria, contexto y caching, interventions, producción y la referencia de configuración. Skills, MCP, tareas en segundo plano y herramientas integradas quedan en los siguientes pasos. Nombres de modelos y versiones salen de la doc a octubre de 2026: verifícalos antes de usarlos.
Módulo 1 · Fundamentos
Qué es un harness y qué trae por defecto
Un agente es un modelo con herramientas dentro de un loop. El harness es todo lo demás: el prompt afinado, el manejo de contexto, la memoria, las herramientas base. Strands lo trae armado en una sola importación.
Al terminar: Correr create_harness() sin argumentos, listar de memoria sus defaults y ubicar cada uno en la referencia de configuración.
Una importación, un agente listo
create_harness() (createHarness() en TypeScript) devuelve un Agent estándar de Strands. No hay wrapper ni abstracción escondida: lo que cambia son los defaults con los que viene armado.
Qué trae por defecto
- Un modelo con razonamiento activo. Amazon Bedrock es el proveedor por defecto.
- Un prompt afinado: explorar antes de actuar, confirmar antes de lo irreversible, verificar antes de dar algo por terminado.
- Herramientas de shell y archivos (read, write, edit) y acceso web.
- Manejo de la ventana de contexto y prompt caching.
- Memoria de largo plazo y sesiones que se retoman con un id.
- Un subagente generalist y un checklist de tareas (todos).
- Agent Skills si existen y llamado programático de herramientas.
Opinionado, no restrictivo
Cada default se puede acotar, cambiar o apagar. Cuando tu caso se vuelve específico, sobrescribes lo que importa y dejas el resto. Si quieres construir tu propio harness desde cero, el Strands Harness SDK acepta la misma configuración.
Referencia de configuración
| Opción (Python) | TypeScript | Default | Qué hace |
|---|---|---|---|
model | model | bedrock/global.anthropic.claude-opus-5 | Un string provider/name, un id de Bedrock o una instancia de Model. |
effort | effort | "auto" | Esfuerzo de razonamiento: auto, low, medium, high u off. Se ignora si pasas una instancia de Model. |
instructions | instructions | ninguno | Bloque de dominio que se agrega después del contrato del harness. Se ignora si pasas un system prompt completo. |
tools | tools | ninguno | Tus herramientas, sumadas a las integradas. |
plugins | plugins | ninguno | Plugins del SDK, sumados a los plugins integrados. |
mcp_servers | mcpServers | ninguno | Servidores MCP a conectar: ruta a un JSON o un mapping. |
builtin_tools | builtinTools | shell, read, write, edit, web_fetch, web_search, programmatic_tool_caller, subagent | Herramientas integradas a habilitar, o [] para ninguna. |
builtin_tools={"web_fetch": {"model": ...}} | builtinTools: { web_fetch: { model } } | el del proveedor | Modelo resumidor sobre el que corre web_fetch. |
caching | caching | "auto" (activo) | Prompt caching donde el proveedor lo soporta. Apagarlo desactiva lo que el harness configura. |
context_manager | contextManager | "auto" | auto, agentic u off. Activa el manejo de contexto y el offloading. |
session={"id": ...} | session: { id } | ninguno | Persiste y retoma esta conversación por id. |
session={"dir": ...} | session: { dir } | ./.agent/sessions | Dónde viven el estado de sesión y los artefactos descargados del contexto. |
skills | skills | ./.agent/skills | Directorio (o lista) que se escanea en busca de Agent Skills. Off lo desactiva. |
builtin_plugins | builtinPlugins | ["todos", "environment"] | Plugins integrados a habilitar, o [] para ninguno. |
memory | memory | activa | Memoria de largo plazo basada en archivos. Off la desactiva. |
memory={"dir": ...} | memory: { dir } | ./.agent/memory | Dónde viven los archivos del store de memoria por defecto. |
memory={"stores": [...]} | memory: { stores } | ninguno | Cambia el backend de memoria y conserva la política del harness. |
interventions | interventions | ninguno | Pone las llamadas a herramientas detrás de aprobación o de una política. |
background_tasks | backgroundTasks | { agentic: ['*'] } | Política de tareas en segundo plano. |
Como el resultado es un Agent común, todo lo que sepas del SDK (hooks, streaming, observabilidad) sigue valiendo. El harness es una fábrica de configuración, no un framework aparte.
Tratarlo como caja negra. Los defaults escriben en disco (./.agent), pueden ejecutar shell, editar archivos y usan Claude Opus 5 en Bedrock. Léelos antes de ponerlo frente a datos reales.
Primer run sin configurar nada
- Crea un entorno con Python 3.10 o superior e instala strands-harness.
- Dale credenciales de Bedrock (
AWS_BEARER_TOKEN_BEDROCKo credenciales AWS) y habilita el modelo en la consola de Bedrock. - Corre el código y espera a que termine.
- Mira qué apareció en el directorio de trabajo.
# pip install strands-harness
from strands_harness import create_harness
agent = create_harness()
agent("Research the top three vector databases, compare pricing and limits, and write it up in comparison.md")import { createHarness } from '@strands-agents/harness'
const agent = await createHarness()
await agent.invoke("Research the top three vector databases, compare pricing and limits, and write it up in comparison.md") Cómo saber que salió bien: Existe comparison.md escrito por el agente y una carpeta ./.agent/sessions. La carpeta ./.agent/memory aparece cuando corre la extracción de memoria (cada pocos turnos). Si no tienes Bedrock, usa model="anthropic/claude-sonnet-5" y sigue con el módulo 2.
Mapa de defaults contra la referencia
- Abre la tabla de referencia de configuración de este módulo.
- En un archivo defaults.md, escribe para cada línea de "qué trae por defecto" qué opción la controla y cómo se apaga.
- Comprueba tu tabla armando un agente con todo lo apagable apagado.
- Pídele que cree un archivo y observa qué responde.
from strands_harness import create_harness
agent = create_harness(
builtin_tools=[], # no shell, files, web, subagent
builtin_plugins=[], # no todos, no environment
memory=False,
session=False,
context_manager=False,
caching=False,
)
agent("Create a file named hello.txt with the word hi.")import { createHarness } from '@strands-agents/harness'
const agent = await createHarness({
builtinTools: [],
builtinPlugins: [],
memory: false,
contextManager: false,
caching: false,
// session off: see the "Persist sessions" page for your version
})
await agent.invoke('Create a file named hello.txt with the word hi.') Cómo saber que salió bien: Sin herramientas integradas el agente no puede crear hello.txt: solo conversa. Después vuelve a encender una opción por vez y mira cuál habilita qué.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Qué devuelve create_harness()?
Un Agent estándar de Strands, sin wrapper ni abstracción oculta.
Nombra cuatro cosas que el agente tiene sin que las pidas.
Por ejemplo: shell y herramientas de archivos, acceso web, memoria de largo plazo, manejo de contexto, el subagente generalist y el checklist todos.
Entregable del módulo
Va al proyecto final: Guarda defaults.md. En el proyecto final decides qué defaults conservas y cuáles cambias, y lo justificas en el README.
Módulo 2 · Fundamentos
Quickstart: CLI, librería y elección de modelo
Hay tres formas de empezar: pedirle a tu asistente de código que te guíe, armar el agente en la CLI o usarlo como librería. Las tres terminan en el mismo agente.
Al terminar: Correr el mismo agente por CLI y por código, cambiar de proveedor con una línea y retomar una conversación por id.
Tres caminos
- Con tu agente de código: pegas el prompt de la doc en Codex, Claude Code o Kiro y te guía paso a paso.
- CLI:
npm install -g @strands-agents/cli, ejecutas strands y eliges Quickstart. Lo único que configuras es el proveedor de modelo. - Librería:
pip install strands-harness(Python 3.10 o superior) o el paquete@strands-agents/harnessen TypeScript.
Elegir modelo
El modelo se pasa como provider/name. Amazon Bedrock es el default. La doc lista Anthropic, OpenAI, Google y Ollama para correr en local. En la CLI, strands --model anthropic/claude-sonnet-5 cambia el modelo solo para esa ejecución.
De la CLI al código
Cuando quieras embeber el agente, /export dentro del chat de la CLI escribe un proyecto Python o TypeScript con tus elecciones puestas en create_harness(...). La CLI es una rampa: construyes de forma interactiva y después pasas a código.
Sesiones desde el primer día
Las sesiones vienen activas: cada conversación se guarda en ./.agent/sessions con un id generado. Si eliges tú el id, una ejecución posterior retoma la misma conversación. En la CLI: strands --session-id api-design.
Con Ollama el modelo corre en tu máquina y no necesitas credenciales de nube: es la forma más barata de iterar la configuración. Un modelo chico puede fallar más al usar herramientas, así que pruébalo con tu tarea real antes de confiar en él.
Pegar una API key dentro del código. La CLI guarda una key pegada solo para la sesión actual; para reutilizarla, ponla en el perfil de tu shell. En código, léela del entorno.
Mismo agente, tres proveedores
- Pon la key de cada proveedor en el entorno y corre Ollama con ollama pull llama3.1.
- Corre el script: usa la misma tarea con tres modelos y guarda un archivo por modelo.
- Anota en una línea cuál usó mejor las herramientas y cuál te sirve para iterar barato.
from strands_harness import create_harness
MODELS = ["anthropic/claude-sonnet-5", "openai/gpt-5.4", "ollama/llama3.1"]
for model in MODELS:
name = model.split("/")[0]
try:
agent = create_harness(model=model, session=False)
agent(
"Research the three most common strategies for versioning a REST API, "
f"compare their tradeoffs, and write a recommendation to api-versioning-{name}.md"
)
except Exception as err:
print(f"{model} failed: {err}")import { createHarness } from '@strands-agents/harness'
const models = ['anthropic/claude-sonnet-5', 'openai/gpt-5.4', 'ollama/llama3.1']
for (const model of models) {
const name = model.split('/')[0]
try {
const agent = await createHarness({ model })
await agent.invoke('Research the three most common strategies for versioning a REST API, compare their tradeoffs, and write a recommendation to api-versioning-' + name + '.md')
} catch (err) {
console.error(model + ' failed:', err)
}
} Cómo saber que salió bien: Tienes un archivo api-versioning-*.md por cada proveedor que funcionó. Si uno falló, el mensaje te dice qué credencial falta.
CLI, export y sesión retomable
- Instala la CLI y ejecuta strands. Elige Quickstart, un proveedor y un modelo, y Save and Launch.
- Sal y entra de nuevo con un id elegido por ti. Pídele una tarea corta.
- Cierra, reabre con el mismo id y pregunta "¿qué te pedí antes?".
- Dentro del chat ejecuta
/exporty elige Python. Abre el proyecto y buscacreate_harness(...).
npm install -g @strands-agents/cli
strands # setup, then chat
strands --model anthropic/claude-sonnet-5
strands --session-id curso-m2 # resume by id Cómo saber que salió bien: Al reabrir con el mismo id el agente recuerda la conversación. El proyecto exportado importa create_harness y trae tus opciones.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Cuál es el formato del argumento model y cuál es el default?
provider/name, por ejemplo anthropic/claude-sonnet-5. El default es bedrock/global.anthropic.claude-opus-5.
¿Qué hace /export en la CLI?
Escribe un proyecto Python o TypeScript con tus elecciones puestas en create_harness(...), con un agent listo para importar.
Entregable del módulo
Va al proyecto final: Elige el modelo y el proveedor del proyecto final y anótalos en el README junto con la variable de entorno que necesita.
Módulo 3 · Configurar el agente
Instrucciones, herramientas y subagentes
El harness trae un prompt afinado y herramientas base. Tu trabajo es agregar el contexto de tu dominio, tus propias herramientas y, si hace falta, especialistas a los que delegar.
Al terminar: Agregar instructions, sumar un especialista con as_tool() y usar el subagente generalist para no inundar el contexto principal.
instructions no reemplaza el prompt
El parámetro instructions agrega un bloque de dominio después del contrato del harness (explorar antes de actuar, confirmar antes de lo irreversible, verificar antes de terminar). Si pasas un system prompt completo, instructions se ignora y pierdes ese contrato.
Tus herramientas conviven con las integradas
tools suma las tuyas a shell, read, write, edit, web_fetch, web_search, programmatic_tool_caller y subagent. builtin_tools elige cuáles integradas quedan: una lista vacía no deja ninguna.
Subagentes: delegar para proteger el contexto
Un subagente es un agente al que el principal llama como a una herramienta. Su trabajo intermedio queda fuera de la conversación principal: solo vuelve la respuesta final.
- Tus especialistas: un Agent con name, description y
system_prompt, pasado conas_tool(). Cada llamada arranca de cero, sin estado acumulado, y el nombre debe ser único entre las herramientas. - generalist: viene activo. Hereda modelo, razonamiento, caching, contexto, herramientas, plugins, subagentes, interventions y sandbox, pero usa un prompt de rol genérico en vez de tus instructions. Arranca en blanco: la llamada tiene que llevar todo lo que necesita.
Cuándo delegar
Cuando una subtarea inundaría el contexto: buscar en muchos archivos, un cambio de varios pasos o una exploración abierta donde solo te importa la conclusión. El generalist corre en segundo plano, así que el agente principal puede seguir trabajando.
Un subagente hereda interventions y sandbox. Por diseño no puede ser una puerta trasera para saltarte la aprobación que pusiste en el agente principal.
Un especialista con descripción vaga. El modelo decide cuándo llamar a una herramienta por su nombre y su descripción: "helper" no dice nada. Escribe qué hace, qué recibe y qué devuelve.
Un especialista con nombre y descripción
- Define un Agent researcher con name, description y
system_prompt. - Pásalo con
as_tool()y suma un bloque instructions que diga cuándo delegar. - Pide una tarea que requiera investigar y redactar y mira la llamada a researcher en la salida.
- Experimento: cambia la description por "helper" y repite.
from strands import Agent
from strands_harness import create_harness
researcher = Agent(
name="researcher",
description="Researches a topic and returns a concise, sourced summary.",
system_prompt="Research the given topic and return a concise, sourced summary.",
)
agent = create_harness(
instructions=(
"You write short technical briefs. "
"Delegate research to the researcher tool, then write the brief to brief.md."
),
tools=[researcher.as_tool()],
)
agent("Brief me on prompt caching in LLM APIs, in under 300 words.")import { Agent } from '@strands-agents/sdk'
import { createHarness } from '@strands-agents/harness'
const researcher = new Agent({
name: 'researcher',
description: 'Researches a topic and returns a concise, sourced summary.',
systemPrompt: 'Research the given topic and return a concise, sourced summary.',
})
const agent = await createHarness({
instructions: 'You write short technical briefs. Delegate research to the researcher tool, then write the brief to brief.md.',
tools: [researcher.asTool()],
})
await agent.invoke('Brief me on prompt caching in LLM APIs, in under 300 words.') Cómo saber que salió bien: Se creó brief.md y viste la llamada a la herramienta researcher. Con la descripción vaga, anota si el agente deja de delegar o delega peor.
Generalist: delegar para no inundar el contexto
- Pon una carpeta
./reposcon varios README.md (pueden ser repos tuyos o copias). - Corre el script: la misma tarea con el generalist activo y con
builtin_tools={"subagent": False}. Cada corrida usa su propio directorio de sesión. - Compara el tamaño de cada directorio de sesión y anota qué viste en la salida.
from strands_harness import create_harness
TASK = "Read every README.md under ./repos, then write overview.md with one line per project."
with_delegate = create_harness(
session={"id": "m3-with", "dir": "./.s-with"},
)
without_delegate = create_harness(
session={"id": "m3-without", "dir": "./.s-without"},
builtin_tools={"subagent": False},
)
with_delegate(TASK)
without_delegate(TASK)
# then, in the terminal: du -sh ./.s-with ./.s-without Cómo saber que salió bien: Existen dos overview.md y dos directorios de sesión. Es esperable que la corrida sin delegado deje más contenido en la conversación principal; si no lo ves, anota por qué (por ejemplo, el offloader movió resultados voluminosos).
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Qué pasa con instructions si pasas system_prompt?
Se ignora: system_prompt reemplaza el prompt que el harness arma y pierdes su contrato.
¿Qué hereda el generalist y qué no?
Hereda modelo, razonamiento, caching, contexto, herramientas, plugins, subagentes, interventions y sandbox. No hereda tus instructions (usa un prompt de rol genérico) ni la conversación (arranca en blanco).
Entregable del módulo
Va al proyecto final: El proyecto final lleva un especialista propio (por ejemplo un verificador de fuentes) y un bloque instructions con las reglas de tu dominio.
Módulo 4 · Configurar el agente
Estado: sesiones, checkpoints y memoria
Un agente puede recordar de dos maneras distintas y no son lo mismo. Una sesión retoma una conversación exacta. La memoria lleva hechos durables entre conversaciones. Confundirlas produce agentes que olvidan lo que no deberían o arrastran lo que no corresponde.
Al terminar: Decidir entre sesión, memoria o ambas, y demostrarlo con una corrida que sobrevive a un reinicio y otra que no deja rastro.
Dos preguntas distintas
Una sesión (checkpoint) persiste una conversación para retomar esa tarea exacta tras un reinicio. La memoria de largo plazo destila hechos durables y los recupera en cualquier conversación, haya o no sesión. Son ortogonales: puedes correr con ninguna, una o ambas. Por defecto las dos están activas, pero solo retomas una conversación si das un id de sesión.
- Retomar una tarea tras un reinicio: una sesión, con
session={"id": ...}. - Llevar hechos, preferencias o decisiones entre corridas no relacionadas: memoria, que ya viene activa.
- Las dos cosas: id de sesión y memoria encendida.
- Una tarea de una sola vez sin rastro: session=False y memory=False.
Cómo funciona la memoria
El harness destila hechos durables en archivos bajo ./.agent/memory, los busca antes de cada turno y suma los mejores resultados al contexto. El agente también recibe una herramienta search_memory para recordar a demanda. La extracción corre en segundo plano cada pocos turnos con un modelo chico, así que mantenerla cuesta poco.
Backends: qué viene y qué no
LocalFileStorage(default, escrituras atómicas),S3Storage(producción y multi-instancia) eInMemoryStorage(tests).- Un backend propio implementa cuatro métodos async: write, read, delete y list.
- SQLite y PostgreSQL no son first-party: los escribes tú contra Storage o
MemoryStore. Redis o Valkey solo existen vía el paquete comunitariostrands-valkey-session-manager(Python).
Concurrencia, aislamiento y borrado
- Un solo escritor por conversación. Los managers no toman un lock distribuido: dos invocaciones sobre el mismo id se pisan y ninguna falla (gana la última escritura). Si abres en paralelo, agrega tu lock o rutea cada id a un solo worker.
- Aislamiento por namespace y por store con scope: la memoria admite un store por tenant en vez de uno compartido.
- Borrado: borrar una sesión elimina su directorio raíz (o el prefijo en S3, que necesita
s3:DeleteObject). La memoria son archivos planos: borrar la de un tenant es borrar su directorio o store. - El directorio de sesión es un store de confianza: restringe sus permisos al proceso del agente. El SDK no bloquea symlinks dentro.
La sesión sirve para continuar una tarea; la memoria, para que el conocimiento sobreviva a la tarea. Si te preguntas "¿esto lo quiero mañana en otra conversación?", la respuesta decide cuál usar.
Dos workers sobre el mismo id de sesión. No hay error: el segundo pisa los turnos del primero y lo descubres cuando la conversación ya está rota.
Retomar tras un reinicio
- Corre el primer bloque con el id m4-api y deja que termine.
- Detén el proceso por completo. Ese es el reinicio.
- En un proceso nuevo corre el segundo bloque con el mismo id.
- Mira
./.agent/sessionspara ver dónde quedó el estado.
# run 1
from strands_harness import create_harness
agent = create_harness(session={"id": "m4-api"})
agent("List three strategies for versioning a REST API.")
# run 2, in a brand new process
agent = create_harness(session={"id": "m4-api"})
agent("Which of those would you pick for an API with external customers, and why?")// run 1
import { createHarness } from '@strands-agents/harness'
const agent = await createHarness({ session: { id: 'm4-api' } })
await agent.invoke('List three strategies for versioning a REST API.')
// run 2, in a brand new process
const again = await createHarness({ session: { id: 'm4-api' } })
await again.invoke('Which of those would you pick for an API with external customers, and why?') Cómo saber que salió bien: La segunda respuesta habla de las tres estrategias sin que se las repitas.
Memoria entre conversaciones no relacionadas
- En la conversación A, pídele que recuerde un dato y haz tres o cuatro turnos más para dar tiempo a la extracción.
- En la conversación B (otro id de sesión) pregunta por ese dato.
- En la conversación C, con memory=False, haz la misma pregunta.
- Mira los archivos de
./.agent/memory.
from strands_harness import create_harness
a = create_harness(session={"id": "m4-a"})
for turn in [
"Remember: my stack is FastAPI and Postgres, and I prefer answers as short tables.",
"Give me one tip about queues.",
"Give me one tip about caching.",
"Give me one tip about retries.",
]:
a(turn)
b = create_harness(session={"id": "m4-b"}) # new conversation
b("What stack do I use?")
c = create_harness(session={"id": "m4-c"}, memory=False) # memory off
c("What stack do I use?") Cómo saber que salió bien: B responde con el stack y C no. Si B no lo sabe, puede que la extracción en segundo plano no haya corrido todavía: suma turnos en A y mira ./.agent/memory.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Qué diferencia hay entre una sesión y la memoria?
La sesión persiste una conversación para retomarla por id. La memoria destila hechos durables y los recupera en cualquier conversación.
¿Qué pasa si dos procesos escriben en la misma sesión?
Se pisan: no hay lock distribuido y gana la última escritura, sin error. Usa un escritor por id.
Entregable del módulo
Va al proyecto final: El proyecto final usa un id de sesión por tarea y deja la memoria encendida con su directorio en almacenamiento durable. Anota en el README quién escribe cada id.
Módulo 5 · Configurar el agente
Contexto y caching
Un modelo solo lee un tramo limitado de texto a la vez. El harness mantiene la conversación dentro de ese límite y reutiliza lo que no cambia entre turnos, para que cada paso sea más barato y rápido.
Al terminar: Entender qué hacen context_manager y caching, cambiar su modo y saber dónde terminan los resultados voluminosos.
Manejo de contexto
Con context_manager activo, el harness resume los turnos viejos a medida que la conversación crece y agrega un offloader: los resultados voluminosos de herramientas pasan a almacenamiento y se reemplazan por una vista previa corta y una referencia que el agente puede seguir para recuperar el contenido completo cuando de verdad lo necesita.
- auto (default) y agentic eligen la estrategia de contexto del SDK. Ambos dejan el offloader activo.
- Apagarlo (False o null, u off en la CLI) apaga también el offloading: el historial completo queda en la ventana y el tamaño lo manejas tú.
- Con una sesión activa, los artefactos descargados persisten bajo el directorio de sesión. Sin sesión van a un directorio temporal que no sobrevive al proceso.
Prompt caching
Reutiliza lo que no cambia entre turnos (system prompt, definiciones de herramientas y conversación previa), así el prefijo estable de una conversación larga se procesa más barato y rápido. Está activo por defecto.
- En Bedrock y en Anthropic directo, el harness configura puntos de caché y definiciones de herramientas cacheadas.
- En OpenAI, Google y bedrock-mantle el caching es automático del lado del servidor: no hay nada que configurar.
- Apagar caching no tiene efecto donde ya es automático. Habilitarlo explícitamente en una instancia de Model ya construida se ignora con una advertencia: configúralo en la propia instancia.
El offloader cambia lo que ve el modelo, no lo que se pierde: el contenido completo sigue guardado y el agente puede recuperarlo. Por eso conviene tener una sesión activa si quieres auditar esos artefactos después.
Apagar context_manager "para ver todo" en una tarea larga. Sin él el historial completo queda en la ventana y el límite lo manejas tú: la tarea puede chocar con ese límite.
Qué ve el modelo y qué queda guardado
- Corre una tarea que genere resultados voluminosos (páginas largas), una vez con el contexto manejado y otra con
context_manager=False. Cada corrida usa su propio directorio de sesión. - Busca en cada directorio un subdirectorio de contexto: la doc indica que el stash del context manager vive bajo context/.
- Anota qué contiene y cuánto pesa cada directorio.
from strands_harness import create_harness
TASK = "Fetch three long documentation pages about HTTP caching and write a comparison to caching.md"
managed = create_harness(session={"id": "m5-on", "dir": "./.s-on"})
unmanaged = create_harness(session={"id": "m5-off", "dir": "./.s-off"}, context_manager=False)
managed(TASK)
unmanaged(TASK)
# terminal: find ./.s-on ./.s-off -type d -name 'context*' ; du -sh ./.s-on ./.s-off Cómo saber que salió bien: En ./.s-on debería haber un directorio de contexto con artefactos descargados y en ./.s-off no. Si tu tarea no produjo resultados lo bastante grandes, no habrá offloading: prueba con páginas más largas.
Caching: qué configura el harness y qué no
- Para cada proveedor que probaste en el módulo 2, anota si el caching lo configura el harness (Bedrock, Anthropic) o es automático (OpenAI, Google, bedrock-mantle).
- Corre la misma conversación de varios turnos con caching por defecto y con caching=False y compara el tiempo total.
- Si tu proveedor reporta tokens cacheados, anota esa cifra.
import time
from strands_harness import create_harness
TURNS = [
"Explain HTTP caching in 3 bullets.",
"Now ETag versus Last-Modified.",
"Now the Cache-Control directives.",
"Summarize our whole chat in 2 lines.",
]
for label, extra in [("caching auto", {}), ("caching off", {"caching": False})]:
agent = create_harness(session=False, **extra)
start = time.time()
for turn in TURNS:
agent(turn)
print(label, round(time.time() - start, 1), "s") Cómo saber que salió bien: Tienes dos tiempos y una nota por proveedor. No esperes una diferencia fija: depende del proveedor y del largo del prefijo. Lo que importa es saber quién configura el caching en tu caso.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Qué hace el offloader?
Mueve resultados voluminosos de herramientas a almacenamiento y los reemplaza por una vista previa corta con una referencia para recuperarlos.
¿Qué pasa con context_manager=False?
Se apaga también el offloading: el historial completo queda en la ventana y tú manejas su tamaño.
Entregable del módulo
Va al proyecto final: El proyecto final deja context_manager en auto, la sesión con directorio durable (los artefactos viven ahí) y documenta en el README si el caching lo configura el harness o el proveedor.
Módulo 6 · Control y producción
Interventions: poner un gate a las herramientas
Por defecto toda llamada a una herramienta se ejecuta. Una intervention decide si una llamada corre, y es la forma documentada de poner aprobación humana o una política delante del shell y de la edición de archivos.
Al terminar: Gatear llamadas con los presets ask y smart, con una regla en lenguaje natural y con una política Cedar, y comprobar que el subagente no se salta el gate.
Cuatro formas de gatear
"ask": pide aprobación en cada llamada."smart": usa el clasificador de riesgo del SDK y solo frena las llamadas riesgosas.- Un string que no es preset ni termina en .cedar es una política de riesgo en lenguaje natural: se vuelve el prompt del clasificador, como smart con tu propia rúbrica.
- Un string que termina en .cedar carga una política Cedar (requiere
strands-agents[cedar]en Python o@cedar-policy/cedar-wasmen TypeScript). El texto Cedar inline no se autodetecta: pasa una instancia deCedarAuthorization.
Capas y handlers propios
Para lo que los presets no cubren, construye el handler del SDK y pásalo: una instancia pasa sin tocar. También puedes pasar una lista para combinar, por ejemplo una política Cedar con una compuerta de aprobación humana. Un agente registra a lo sumo un handler por nombre, así que combina tipos distintos, no duplicados.
Pausar y reanudar
Cuando un gate pide aprobación, la ejecución se interrumpe. Con el Agent del SDK el patrón es: si result.stop_reason es "interrupt", respondes con interruptResponse y el id de la interrupción para reanudar. Como el harness devuelve un Agent estándar, confirma en tu versión cómo se expone antes de construir una interfaz encima.
El generalist hereda la política
El subagente integrado hereda lo que pongas en interventions, así que no puede saltarse el gate del agente principal.
Una regla en lenguaje natural es flexible, pero la evalúa un modelo: sirve para frenar lo dudoso, no para garantizar lo imposible. Lo que nunca debe ocurrir va a una política Cedar o a una sandbox, que son deterministas.
Dejar interventions sin configurar en un agente con shell y edición de archivos que recibe entrada de terceros. El default no aplica ninguna: toda llamada procede.
Presets y reanudación
- Crea un agente con interventions=
"ask"y pídele crear y borrar un archivo. - Cuando se interrumpa, aprueba la primera llamada y niega la de borrado.
- Repite con
"smart"y anota qué llamadas frenó y cuáles dejó pasar.
from strands_harness import create_harness
agent = create_harness(interventions="ask") # then try "smart"
result = agent("Create temp.txt with the word hi, then delete it.")
# pattern from the SDK human-in-the-loop docs: verify it on your version
while result.stop_reason == "interrupt":
interrupt = result.interrupts[0]
print("Approval needed:", interrupt)
answer = input("approve? (yes/no) ")
result = agent([{"interruptResponse": {"interruptId": interrupt.id, "response": answer}}]) Cómo saber que salió bien: Con ask, cada herramienta pide aprobación. Si respondes "no" al borrado, temp.txt sigue existiendo. Con smart anota lo que observas: el clasificador es un modelo y puede decidir distinto en cada corrida.
Regla natural y gate heredado
- Crea un agente con la regla "Ask before deleting files or making any network request."
- Pídele algo que requiera la web y verifica que
result.stop_reasonsea"interrupt". - Pídele una tarea que delegue al generalist (leer muchos archivos y luego borrar uno) y verifica que el borrado también se frene.
- Escribe una acción que quisieras bloquear siempre y por qué merece Cedar o una sandbox en vez de lenguaje natural.
from strands_harness import create_harness
agent = create_harness(
interventions="Ask before deleting files or making any network request.",
)
result = agent("Search the web for the latest Python release and write it to python.txt")
print(result.stop_reason) # expect "interrupt" when a network call is gatedimport { createHarness } from '@strands-agents/harness'
const agent = await createHarness({
interventions: 'Ask before deleting files or making any network request.',
})
const result = await agent.invoke('Search the web for the latest Python release and write it to python.txt')
console.log(result.stopReason) // expect "interrupt" when a network call is gated Cómo saber que salió bien: stop_reason es "interrupt" cuando se intenta una llamada de red. En el punto 3 el borrado dentro de la delegación también pide aprobación, porque el generalist hereda la política.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Cuál es el default de interventions?
Ninguno: toda llamada a una herramienta se ejecuta.
¿Cuándo una regla en lenguaje natural y cuándo Cedar?
La regla en lenguaje natural la evalúa un modelo: sirve para escalar lo dudoso. Cedar es una política programática: va para lo que nunca debe ocurrir.
Entregable del módulo
Va al proyecto final: El proyecto final lleva un gate: una regla en lenguaje natural para lo dudoso y, si tu entorno lo permite, un .cedar para lo prohibido. El criterio medible es que una acción destructiva negada no se ejecute.
Módulo 7 · Control y producción
Producción: qué escribe, qué ejecuta y dónde vive el estado
El harness devuelve un Agent normal, así que desplegarlo, observarlo y protegerlo sigue las guías del SDK. Lo específico del harness son sus defaults: escriben a disco y pueden ejecutar código.
Al terminar: Hacer el inventario de riesgos de los defaults, mover el estado a almacenamiento durable y empaquetar el agente en un contenedor.
Desplegar: igual que cualquier Agent
Todos los destinos de las guías de despliegue del SDK (Lambda, Fargate, EKS, Bedrock AgentCore, Docker y más) funcionan sin cambios: donde la guía construye Agent(), tú construyes create_harness().
Estado sobre almacenamiento efímero
Sesiones y memoria escriben por defecto en directorios bajo ./.agent. En un contenedor o en serverless, apunta session={"dir": ...} y memory={"dir": ...} a almacenamiento durable (un volumen montado) o a un backend propio, vía memory={"stores": [...]} o un session manager, para que el estado sobreviva a una sola instancia.
Observar
Es un Agent estándar: la telemetría del SDK (trazas, métricas y logs) funciona sin cambios y no hay nada del harness que conectar. Para medir calidad, el Evals SDK prueba y puntúa corridas del harness como las de cualquier agente.
Dos defaults que piden una decisión de seguridad
programmatic_tool_callerejecuta código escrito por el modelo en Monty: aísla el código, pero no las herramientas que ese código llama. Si el agente maneja entrada no confiable, córrelo dentro de una sandbox del SDK o quita la herramienta.- El agente por defecto puede ejecutar comandos y editar archivos: controla lo que hace con interventions y limita a qué llega con una sandbox.
- Las guías de seguridad del SDK (guardrails, redacción de PII, historial de mensajes confiable) aplican directamente.
Una lista permitida es más fácil de auditar que una lista de bloqueo: en vez de preguntarte qué apagar, declaras con builtin_tools exactamente qué herramientas existen.
Desplegar en un contenedor sin montar los directorios de sesión y memoria. Todo funciona hasta el primer redeploy: ahí el agente pierde su conversación y lo que aprendió.
Inventario de superficie de riesgo
- Para cada herramienta integrada (shell, read, write, edit,
web_fetch,web_search,programmatic_tool_caller, subagent) escribe qué daño podría hacer con entrada no confiable. - Decide cuáles se quedan y arma el agente con esa lista en
builtin_tools. - Pídele algo fuera de la lista y verifica que no puede hacerlo.
from strands_harness import create_harness
# example allowlist for a research agent that must not touch the filesystem
agent = create_harness(
builtin_tools=["web_search", "web_fetch"],
builtin_plugins=["todos"],
interventions="smart",
)
agent("Write a file named x.txt with the word hi.") # it should be unable to Cómo saber que salió bien: El agente responde que no puede escribir archivos. Tu tabla de riesgos explica por qué cada herramienta que dejaste es necesaria.
Contenedor con estado durable
- Crea los tres archivos: agent.py, Dockerfile y compose.yaml. El estado vive en el volumen /data.
- Corre una tarea con un
SESSION_IDfijo. - Destruye el contenedor con
docker compose down. El volumen se conserva. - Corre otra tarea con el mismo
SESSION_IDy verifica que retoma.
# agent.py
import os
import sys
from strands_harness import create_harness
agent = create_harness(
model=os.environ.get("HARNESS_MODEL", "anthropic/claude-sonnet-5"),
session={"id": os.environ["SESSION_ID"], "dir": "/data/sessions"},
memory={"dir": "/data/memory"},
)
agent(sys.argv[1])FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir strands-harness
COPY agent.py .
ENTRYPOINT ["python", "agent.py"]services:
agent:
build: .
environment:
- SESSION_ID=${SESSION_ID:-demo}
- HARNESS_MODEL=${HARNESS_MODEL:-anthropic/claude-sonnet-5}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} # use the variable your provider expects
volumes:
- agent-data:/data
volumes:
agent-data:docker compose run --rm agent "List three API versioning strategies."
docker compose down # the volume stays
docker compose run --rm agent "Which of those would you pick, and why?"
# to prove the volume matters: docker compose down -v and repeat Cómo saber que salió bien: La segunda corrida retoma la conversación aunque el contenedor original ya no exista. Con docker compose down -v (que borra el volumen) vuelve a empezar de cero: esa es la prueba de que el estado vive en el volumen.
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Por qué mover session y memory fuera de ./.agent en un contenedor?
Porque por defecto escriben en ./.agent, y en un contenedor o en serverless eso es almacenamiento efímero: el estado se pierde con la instancia.
¿Qué aísla Monty y qué no?
Aísla el código escrito por el modelo, pero no las herramientas que ese código llama.
Entregable del módulo
Va al proyecto final: El proyecto final se entrega como compose.yaml con un volumen para sesión y memoria, una lista permitida de herramientas y el README con los riesgos que aceptaste.
Módulo 8 · Más allá de los defaults
Salir de los defaults: bajar al SDK
El harness es una fábrica con opiniones. Cuando una opción no alcanza, cualquier argumento que la fábrica no nombra pasa al constructor del Agent, y puedes bajar al SDK sin perder la configuración que ya tienes.
Al terminar: Saber qué se sobrescribe con un argumento pasado al Agent, qué piezas exporta el harness y cuándo conviene construir tu propio harness con el SDK.
Passthrough al Agent
Todo argumento que la fábrica no nombra se reenvía al constructor de Agent. Un valor explícito pasado así gana sobre el default que le corresponde: pasar system_prompt reemplaza el prompt que el harness arma desde instructions, y pasar memory_manager reemplaza memory. En TypeScript, HarnessAgentOptions extiende AgentConfig del SDK (menos los campos que el harness maneja), así que otros campos como retryStrategy pasan tal cual.
Piezas exportadas
Además de la fábrica, el harness exporta lo que compone: HARNESS_CONTRACT, build_system_prompt (buildSystemPrompt en TypeScript), resolve_memory y resolve_interventions (solo Python). Sirven para inspeccionar o recomponer en vez de reescribir desde cero.
Qué nivel de override usar
- Cambia un parámetro: un argumento de
create_harness. - Cambia el prompt completo:
system_prompt, sabiendo que pierdes el contrato y que instructions se ignora. - Cambia el backend de memoria:
memory={"stores": [...]}conserva la política del harness;memory_managerla reemplaza. - Cambia el backend de sesión: pasa tu propio session manager, que gana sobre el que el harness arma desde session.
- Cambia todo: el Strands Harness SDK directo, con la misma configuración que ya conoces.
Elige el nivel más bajo que resuelva tu problema. memory={"stores": [...]} cambia dónde se guarda y conserva la política del harness; memory_manager reemplaza la política entera.
Pasar system_prompt para "agregar una regla". Para sumar contexto de dominio existe instructions: con system_prompt reemplazas todo y pierdes el contrato de explorar, confirmar y verificar.
instructions contra system_prompt
- Crea un archivo notes.txt en la carpeta de trabajo.
- Arma dos agentes con la misma regla: uno con instructions y otro con
system_prompt. - Pídele a cada uno que borre notes.txt (restaura el archivo entre corridas) y anota si el agente confirma antes de actuar.
from strands_harness import create_harness
RULE = "Always answer in exactly two sentences."
a = create_harness(instructions=RULE, session=False) # keeps the harness contract
b = create_harness(system_prompt=RULE, session=False) # replaces it entirely
for agent in (a, b):
agent("Delete notes.txt from the current folder.") Cómo saber que salió bien: A sigue el contrato del harness (confirmar antes de lo irreversible); B ya no lo tiene. Si ambos se comportan igual, anótalo: el modelo puede confirmar por su cuenta, pero con system_prompt ya no hay garantía de contrato.
Leer el contrato y cambiar un store
- Importa
HARNESS_CONTRACTe imprímelo. Subraya las reglas que reconoces de tu defaults.md. - Cambia solo el directorio de memoria con
memory={"dir": ...}. - Después de algunos turnos, verifica que los archivos de memoria aparecen en el directorio nuevo y no en
./.agent/memory. - Escribe en qué caso usarías stores en vez de dir.
# if the import path differs in your version, see "Compose with the Strands Harness SDK"
from strands_harness import create_harness, HARNESS_CONTRACT
print(HARNESS_CONTRACT)
agent = create_harness(memory={"dir": "./my-memory"}, session={"id": "m8"})
for turn in [
"Remember that my deploy target is a single VPS with Docker Compose.",
"Give me one tip about volumes.",
"Give me one tip about backups.",
]:
agent(turn)
# terminal: ls ./my-memory Cómo saber que salió bien: Pudiste leer el contrato que el harness agrega y viste que ./my-memory se llena en lugar de ./.agent/memory (puede tardar unos turnos por la extracción en segundo plano).
Repaso
Responde en voz alta antes de abrir la respuesta.
¿Qué gana: un argumento pasado al Agent o el default del harness?
El argumento explícito. Pasar system_prompt reemplaza el prompt armado desde instructions, y pasar memory_manager reemplaza memory.
¿Cuándo conviene bajar al SDK?
Cuando cambian varias piezas estructurales o quieres tu propio harness desde cero. Si cambia una sola pieza, alcanza el passthrough.
Entregable del módulo
Va al proyecto final: En el README del proyecto final, una sección "Overrides" lista cada parámetro que cambiaste respecto del default y el motivo.
Proyecto final
Un agente de investigación con guardarraíles
Armas un agente que investiga, escribe un informe y lo hace de forma auditable: retoma su conversación, recuerda lo que importa, pide permiso para lo destructivo y conserva su estado cuando el contenedor muere.
Qué entregas
- Un repositorio con compose.yaml, Dockerfile y agent.py, ejecutable con un solo comando docker compose run.
create_harnesscon: modelo explícito, instructions de dominio, un especialista conas_tool(),builtin_toolscomo lista permitida, interventions, session con un id por tarea y memory, ambos con directorios en el volumen.- Un README que reúna lo escrito en cada módulo: defaults conservados y cambiados (1), modelo y variable de entorno (2), especialista y reglas (3), quién escribe cada id (4), quién configura el caching (5), qué va a Cedar o sandbox (6), riesgos aceptados (7) y overrides (8).
Orden de trabajo sugerido
- Esqueleto con el modelo elegido y una tarea de investigación que corra de punta a punta (módulos 1 y 2).
- Instructions de dominio y el especialista (módulo 3).
- Estado en el volumen: sesión por tarea y memoria (módulos 4, 5 y 7).
- Gate de aprobación y lista permitida de herramientas (módulos 6 y 7).
- README con las decisiones y los overrides (módulos 1 y 8).
- Verificación de los seis criterios de abajo desde un clon limpio.
Criterios de aprobación
| Criterio | Cómo se mide | Módulo |
|---|---|---|
Corres una tarea, ejecutas docker compose down, repites con el mismo SESSION_ID y la respuesta usa el contexto anterior. | 4, 7 | |
Un dato dicho en la tarea A aparece en la respuesta de la tarea B, que usa otro SESSION_ID. | 4 | |
| Una acción destructiva que niegas no se ejecuta: el archivo sigue existiendo. | 6 | |
| Una acción destructiva pedida a través del especialista o del generalist también se frena. | 3, 6 | |
builtin_tools es una lista explícita y cada herramienta tiene una línea de justificación en el README. | 1, 7 | |
| Desde un clon limpio, con la key del proveedor, el README alcanza para correr los cinco puntos anteriores. | 8 |
Tu progreso queda guardado en este navegador.
Siguientes pasos
Estas páginas de la doc quedaron fuera del curso. Cada una completa una pieza que ya viste por fuera.
- Load agent skills: El directorio
./.agent/skillsque el harness escanea por defecto. - Connect MCP servers: La opción
mcp_servers: una ruta a un JSON o un mapping. - Run work in the background: La política
background_tasksy cómo el generalist corre en segundo plano. - Shell and file tools: Las herramientas shell, read, write y edit en detalle.
- Web access:
web_fetch,web_searchy el modelo resumidor. - Programmatic tool calling: La herramienta que corre código del modelo en Monty.
- Task tracking and environment: Los plugins integrados todos y environment.
- Compose with the Strands Harness SDK: Cómo se apoya el harness en el SDK y cómo ir más allá.
- Versioning & Support: Política de versiones: léela antes de fijar dependencias.