Harness Lab · Strands

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.

Compartir WhatsAppLinkedInX

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)TypeScriptDefaultQué hace
modelmodelbedrock/global.anthropic.claude-opus-5Un string provider/name, un id de Bedrock o una instancia de Model.
efforteffort"auto"Esfuerzo de razonamiento: auto, low, medium, high u off. Se ignora si pasas una instancia de Model.
instructionsinstructionsningunoBloque de dominio que se agrega después del contrato del harness. Se ignora si pasas un system prompt completo.
toolstoolsningunoTus herramientas, sumadas a las integradas.
pluginspluginsningunoPlugins del SDK, sumados a los plugins integrados.
mcp_serversmcpServersningunoServidores MCP a conectar: ruta a un JSON o un mapping.
builtin_toolsbuiltinToolsshell, read, write, edit, web_fetch, web_search, programmatic_tool_caller, subagentHerramientas integradas a habilitar, o [] para ninguna.
builtin_tools={"web_fetch": {"model": ...}}builtinTools: { web_fetch: { model } }el del proveedorModelo resumidor sobre el que corre web_fetch.
cachingcaching"auto" (activo)Prompt caching donde el proveedor lo soporta. Apagarlo desactiva lo que el harness configura.
context_managercontextManager"auto"auto, agentic u off. Activa el manejo de contexto y el offloading.
session={"id": ...}session: { id }ningunoPersiste y retoma esta conversación por id.
session={"dir": ...}session: { dir }./.agent/sessionsDónde viven el estado de sesión y los artefactos descargados del contexto.
skillsskills./.agent/skillsDirectorio (o lista) que se escanea en busca de Agent Skills. Off lo desactiva.
builtin_pluginsbuiltinPlugins["todos", "environment"]Plugins integrados a habilitar, o [] para ninguno.
memorymemoryactivaMemoria de largo plazo basada en archivos. Off la desactiva.
memory={"dir": ...}memory: { dir }./.agent/memoryDónde viven los archivos del store de memoria por defecto.
memory={"stores": [...]}memory: { stores }ningunoCambia el backend de memoria y conserva la política del harness.
interventionsinterventionsningunoPone las llamadas a herramientas detrás de aprobación o de una política.
background_tasksbackgroundTasks{ agentic: ['*'] }Política de tareas en segundo plano.
Insight

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.

Antipatrón

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.

Ejercicio 1

Primer run sin configurar nada

  1. Crea un entorno con Python 3.10 o superior e instala strands-harness.
  2. Dale credenciales de Bedrock (AWS_BEARER_TOKEN_BEDROCK o credenciales AWS) y habilita el modelo en la consola de Bedrock.
  3. Corre el código y espera a que termine.
  4. 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")

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.

Ejercicio 2

Mapa de defaults contra la referencia

  1. Abre la tabla de referencia de configuración de este módulo.
  2. 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.
  3. Comprueba tu tabla armando un agente con todo lo apagable apagado.
  4. 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.")

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/harness en 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.

Insight

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.

Antipatrón

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.

Ejercicio 1

Mismo agente, tres proveedores

  1. Pon la key de cada proveedor en el entorno y corre Ollama con ollama pull llama3.1.
  2. Corre el script: usa la misma tarea con tres modelos y guarda un archivo por modelo.
  3. 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}")

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.

Ejercicio 2

CLI, export y sesión retomable

  1. Instala la CLI y ejecuta strands. Elige Quickstart, un proveedor y un modelo, y Save and Launch.
  2. Sal y entra de nuevo con un id elegido por ti. Pídele una tarea corta.
  3. Cierra, reabre con el mismo id y pregunta "¿qué te pedí antes?".
  4. Dentro del chat ejecuta /export y elige Python. Abre el proyecto y busca create_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 con as_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.

Insight

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.

Antipatrón

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.

Ejercicio 1

Un especialista con nombre y descripción

  1. Define un Agent researcher con name, description y system_prompt.
  2. Pásalo con as_tool() y suma un bloque instructions que diga cuándo delegar.
  3. Pide una tarea que requiera investigar y redactar y mira la llamada a researcher en la salida.
  4. 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.")

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.

Ejercicio 2

Generalist: delegar para no inundar el contexto

  1. Pon una carpeta ./repos con varios README.md (pueden ser repos tuyos o copias).
  2. 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.
  3. 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) e InMemoryStorage (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 comunitario strands-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.
Insight

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.

Antipatrón

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.

Ejercicio 1

Retomar tras un reinicio

  1. Corre el primer bloque con el id m4-api y deja que termine.
  2. Detén el proceso por completo. Ese es el reinicio.
  3. En un proceso nuevo corre el segundo bloque con el mismo id.
  4. Mira ./.agent/sessions para 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?")

Cómo saber que salió bien: La segunda respuesta habla de las tres estrategias sin que se las repitas.

Ejercicio 2

Memoria entre conversaciones no relacionadas

  1. 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.
  2. En la conversación B (otro id de sesión) pregunta por ese dato.
  3. En la conversación C, con memory=False, haz la misma pregunta.
  4. 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.
Insight

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.

Antipatrón

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.

Ejercicio 1

Qué ve el modelo y qué queda guardado

  1. 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.
  2. Busca en cada directorio un subdirectorio de contexto: la doc indica que el stash del context manager vive bajo context/.
  3. 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.

Ejercicio 2

Caching: qué configura el harness y qué no

  1. 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).
  2. Corre la misma conversación de varios turnos con caching por defecto y con caching=False y compara el tiempo total.
  3. 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-wasm en TypeScript). El texto Cedar inline no se autodetecta: pasa una instancia de CedarAuthorization.

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.

Insight

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.

Antipatrón

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.

Ejercicio 1

Presets y reanudación

  1. Crea un agente con interventions="ask" y pídele crear y borrar un archivo.
  2. Cuando se interrumpa, aprueba la primera llamada y niega la de borrado.
  3. 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.

Ejercicio 2

Regla natural y gate heredado

  1. Crea un agente con la regla "Ask before deleting files or making any network request."
  2. Pídele algo que requiera la web y verifica que result.stop_reason sea "interrupt".
  3. Pídele una tarea que delegue al generalist (leer muchos archivos y luego borrar uno) y verifica que el borrado también se frene.
  4. 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 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_caller ejecuta 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.
Insight

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.

Antipatrón

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ó.

Ejercicio 1

Inventario de superficie de riesgo

  1. 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.
  2. Decide cuáles se quedan y arma el agente con esa lista en builtin_tools.
  3. 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.

Ejercicio 2

Contenedor con estado durable

  1. Crea los tres archivos: agent.py, Dockerfile y compose.yaml. El estado vive en el volumen /data.
  2. Corre una tarea con un SESSION_ID fijo.
  3. Destruye el contenedor con docker compose down. El volumen se conserva.
  4. Corre otra tarea con el mismo SESSION_ID y 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])

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_manager la 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.
Insight

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.

Antipatrón

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.

Ejercicio 1

instructions contra system_prompt

  1. Crea un archivo notes.txt en la carpeta de trabajo.
  2. Arma dos agentes con la misma regla: uno con instructions y otro con system_prompt.
  3. 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.

Ejercicio 2

Leer el contrato y cambiar un store

  1. Importa HARNESS_CONTRACT e imprímelo. Subraya las reglas que reconoces de tu defaults.md.
  2. Cambia solo el directorio de memoria con memory={"dir": ...}.
  3. Después de algunos turnos, verifica que los archivos de memoria aparecen en el directorio nuevo y no en ./.agent/memory.
  4. 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_harness con: modelo explícito, instructions de dominio, un especialista con as_tool(), builtin_tools como 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

  1. Esqueleto con el modelo elegido y una tarea de investigación que corra de punta a punta (módulos 1 y 2).
  2. Instructions de dominio y el especialista (módulo 3).
  3. Estado en el volumen: sesión por tarea y memoria (módulos 4, 5 y 7).
  4. Gate de aprobación y lista permitida de herramientas (módulos 6 y 7).
  5. README con las decisiones y los overrides (módulos 1 y 8).
  6. Verificación de los seis criterios de abajo desde un clon limpio.

Criterios de aprobación

CriterioCómo se mideMó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.