Un recorrido práctico por Strands Agents Harness, la forma de crear agentes óptimos y listos para producción en una sola llamada. Según sus benchmarks, logra rendimiento de vanguardia con un coste de token un 28 % menor. Pero ¿qué pasa cuando lo sacás del ejemplo de la doc y lo ponés a trabajar en un caso más real? 🧐
Introducción
Hoy, por todos lados, nos enseñan a crear un agente en dos líneas. Y es cierto: con Strands harness, create_harness() te devuelve un agente que ya trae shell, herramientas de archivo, acceso web, memoria y sesiones. Con una pequeña tarea funciona e impresiona.
Pero cuando dejás el ejemplo de demo y te ponés a construir algo real, aparece todo lo que esas dos líneas tapan. Un agente no es una llamada y una respuesta: es un proceso que corre por turnos, que decide por su cuenta cuántas veces usar sus herramientas antes de contestar, que recuerda conversaciones previas si le das una sesión, que arrastra un contexto que crece y hay que administrar, y que puede tener memoria de largo plazo que a veces ayuda y a veces estorba. Entender cómo encajan turnos, sesiones, memoria, contexto es la diferencia entre un demo y un prototipo que se sostiene.
Descubrí que el harness empareja las condiciones, pero se comporta diferente según el modelo. Algunos de estos hallazgos los plasmo en estas notas.
Conceptos clave de Strands harness
Strands harness es un harness de agente ya ensamblado: con una sola llamada (create_harness() en Python, createHarness() en TypeScript) obtenés un agente listo para trabajar, sin tener que cablear las piezas. Está construido sobre el Strands Harness SDK, y lo que te devuelve es un strands.Agent estándar optimizado: no hay wrapper ni abstracción oculta, así que cualquier default se puede sobreescribir, extender o reemplazar.
La idea de fondo: en vez de armar un agente pieza por pieza (modelo, herramientas, memoria, sesiones, gestión de contexto, system prompt), arrancás desde un agente ya optimizado y solo ajustás lo que tu caso de uso necesita.
Es diferente a Strands Harness SDK que es la capa de mas bajo nivel. El SDK te da las piezas sueltas (herramientas, proveedores de modelos, memoria, sesiones, hooks) y armás el agente desde cero: elegís cada componente, cómo se relacionan y controlás el ciclo de vida completo. El harness, en cambio, ya tiene ensambladas todas esas piezas.
Tres formas de comenzar a crear tu Agente.
Configurar el harness mediante la CLI
Esta opción te permite generar el agente y testearlo → prototipar rápido. Aún le falta madurar, a la fecha de la prueba algunas opciones me las deja como no elegidas en el harness cuando intentaba exportar el agente, pero seguramente lo vayan puliendo pronto. Te invito a experimentar.
Básicamente instalás la CLI (en mi caso para Python)
npm install -g @strands-agents/cli
- En tu terminal iniciás la CLI con la palabra
strands, te abrirá una interfaz similar. Aquí podés customizar el harness, activando o desactivando lo que desees, indicarle nombre-system prompt, etc. Si tenés modelos locales vía Ollama te los detecta (los podés setear), si tenés en tu máquina configurada credenciales de AWS te permite elegir cualquier modelo soportado por Bedrock y por supuesto configurar el modelo vía API key:
- Luego podés probar el agente en el chat de la CLI o acceder a cualquier settings con
/(notar que aquí tengo seleccionado "Nova 2 Lite"):
Podés ver este video que hace un tour por la CLI.
Configurar el harness mediante tu agente de Código (kiro, claudecode, codex...)
Esta opción básicamente es un prompt predefinido que copiás/pegás. Lo probé con Kiro y te guía para crear el harness sin importar el caso de uso. Conviene en la primera interacción indicarle tu listado de requerimientos para el agent harness y subagentes que quieras crear + el prompt predefinido.
Siempre es útil, si vas a empezar a crear agentes con Strands Agents Harness, que configures el MCP server de Strands en tu agente de código. Le da acceso en vivo a la documentación oficial, así genera código con las últimas versiones y tu asistente no lo saca de su conocimiento. Necesita uv instalado y se ejecuta con uvx strands-agents-mcp-server. En Kiro, se agrega en ~/.kiro/settings/mcp.json:
{
"mcpServers": {
"strands-agents": {
"command": "uvx",
"args": ["strands-agents-mcp-server@latest"],
"disabled": false
}
}
}
Para otros agentes de código (Cursor, Claude Code, Codex, VS Code) la idea es la misma; cambia la ubicación del archivo de configuración.
Usalo como una librería (library)
pip install strands-harness
Y comenzás a escribir tu harness:
from strands_harness import create_harness
agent = create_harness()
agent("Research the three most common strategies for versioning a REST API, compare their tradeoffs, and write a recommendation to api-versioning.md")
Qué trae el agente por defecto
Sin pasar ningún argumento, create_harness() ya te entrega un agente que:
- Corre sobre el modelo que elijas, con proveedores: Amazon Bedrock (default), Anthropic, OpenAI, Google y Ollama (local). Con razonamiento activado por defecto.
- Sigue un system prompt afinado: explorar antes de actuar, confirmar antes de algo irreversible, y verificar antes de dar una tarea por terminada.
-
Trae herramientas listas:
- Un shell (shell virtual pensado para agentes). Aquí hay que tener cuidado: ¡es peligroso dejarlo activado si no lo vas a usar!
-
Herramientas de archivo:
read,write,edit. -
Acceso web:
web_fetch(siempre activo) yweb_search(según el proveedor). -
programmatic_tool_caller: un sandbox donde el agente escribe código que encadena, repite y paraleliza sus otras herramientas para orquestaciones más complejas.
- Gestiona su propia ventana de contexto: resume turnos viejos y mueve resultados voluminosos a almacenamiento, dejando una referencia corta para recuperarlos. Cachea las partes reutilizadas de cada request donde el proveedor lo soporta.
-
Recuerda: cada conversación se guarda en disco y es reanudable por
session id; además tiene memoria de largo plazo que destila hechos entre conversaciones. - Delega subtareas a un subagente integrado (lo explico más adelante).
- Carga Agent Skills cuando están presentes: podés reutilizar las existentes y también crear nuevas.
El modelo por defecto es un modelo de frontera (Claude Opus en Bedrock). Esto es importante: el harness está afinado para modelos frontera, y eso explica varias de las limitaciones que aparecen con modelos más chicos.
Cómo se ejecuta un agente: el agent-loop y los turnos
Un detalle que conviene tener claro desde el principio: cuando le das una tarea al agente, no hace una sola llamada al modelo y responde. Entra en un bucle (el agent-loop): el modelo piensa, decide usar una herramienta, recibe el resultado, vuelve a pensar, usa otra herramienta, y así hasta que considera que terminó. Una sola petición tuya puede disparar varias idas y vueltas al modelo. Esto es lo normal y lo deseable, es lo que le permite leer un archivo, razonar sobre él y después escribir. Pero también significa que un "turno" puede costar varias llamadas (y en modelos con cuota limitada, eso se nota).
Cada agente corre su propio agent-loop, incluidos los subagentes. Un subagente sin herramientas suele resolver en una sola iteración; el agente principal, que lee, delega y escribe, hace el bucle largo.
Sesiones, memoria y contexto: lo que el agente arrastra entre turnos
Tres piezas distintas que es fácil confundir:
-
Sesión: el historial de la conversación. El harness lo persiste en disco y lo puede reanudar si le das un
session idestable. Es la "memoria de corto plazo" de esta historia. - Memoria de largo plazo: hechos que el agente destila entre conversaciones (preferencias, datos del proyecto), guardados aparte. Útil para que recuerde cosas entre sesiones distintas; prescindible y a veces contraproducente, si tu estado ya vive en archivos propios.
- Contexto (la ventana del modelo): el texto que el modelo lee de una sola vez. Crece con cada turno, así que el harness lo administra: resume lo viejo y mueve resultados grandes a almacenamiento, dejando una referencia para recuperarlos.
Entender estas tres capas es lo que separa un demo de un prototipo que se sostiene entre ejecuciones.
La interfaz create_harness(...) y cómo podemos acceder
Todos los argumentos son opcionales. Los más relevantes para empezar:
| Argumento | Para qué sirve |
|---|---|
model |
"proveedor/nombre", un id de Bedrock a secas, o una instancia Model. |
effort |
Nivel de razonamiento: auto, off, minimal, low, medium, high, xhigh, max. |
instructions |
Texto de dominio que se añade al system prompt (identidad, reglas, alcance). |
tools |
Tus herramientas propias, junto a las integradas. |
builtin_tools |
Edita el set de herramientas integradas (una lista fija los nombres; un mapa agrega/quita/configura). |
session |
True \ |
memory |
Memoria de largo plazo: True, un dict de config, o False. |
caching |
Cachea system prompt, tools e historial donde se soporta. |
mcp_servers |
Conecta servidores MCP (ruta a un JSON o el mapping inline). |
interventions |
Puertas de aprobación sobre las llamadas a herramientas. |
Ejemplo de cómo elegir modelo:
create_harness(model="anthropic/claude-opus-5") # Anthropic directo
create_harness(model="openai/gpt-5.6-sol") # OpenAI
create_harness(model="google/gemini-2.5-flash") # Google
create_harness(model="bedrock/global.anthropic.claude-opus-5") # el default, explícito
Cada proveedor necesita su extra: pip install "strands-harness[gemini]" (o [anthropic], [openai], [ollama], [litellm]). Bedrock viene de base.
Observar logs del modelo y Evaluar el agente (evals)
- Si usás algún modelo vía Amazon Bedrock, podés activar los logs en la consola de AWS y revisar cómo se está invocando tu modelo y cómo resuelve las instrucciones incrustadas por el harness:
- Un agente que "parece que anda" no es un agente que anda. Antes de confiar en él conviene evaluarlo: ¿elige bien las herramientas?, ¿sigue las instrucciones?, ¿comunica los fallos en vez de inventar que todo salió bien? Strands incluye un SDK de evaluaciones (
/evals) justamente para esto —validar el comportamiento del agente antes de darlo por bueno— con evaluadores de calidad, de seguimiento de instrucciones, de selección de herramientas y más.
Primeras pruebas: lo que la documentación no te cuenta
Esta es la parte más valiosa del artículo: lo que la doc no te cuenta y aparece al ejecutarlo de verdad en Windows con modelos chicos. La tarea de prueba era sencilla: investigar estrategias de versionado de una REST API y escribir una recomendación en un .md.
Antes de entrar en los tropiezos, conviene recordar algo que la propia doc deja claro: el harness está optimizado para modelos grandes o de frontera (su default es Claude Opus en Bedrock). Su system prompt afinado (el que está en el harness, no el que le incrustamos nosotros), el agent-loop, la gestión de contexto y el uso de herramientas asumen un modelo con buena capacidad de razonamiento y de seguimiento de instrucciones. Con un modelo chico el harness igual arranca, pero empiezan las fricciones: el modelo se enreda con las herramientas, inventa rutas, no sabe cerrar la tarea. No son bugs del harness en sí; es el desajuste entre un andamiaje pensado para modelos capaces y un modelo que no llega. Es importante que lo tengas presente.
Nova-2-lite (Amazon Bedrock)
-
Bucle de
web_fetch:global.amazon.nova-2-lite-v1:0no tiene web_search nativo. Sin búsqueda real, el agente intentó "investigar" trayendo página tras página conweb_fetch: llegó a 229 llamadas seguidas sin cerrar la tarea. Se arregla dándole búsqueda de verdad conbuiltin_tools={"web_search": "exa"}y/o acotando la tarea. -
Rutas de Windows "unixificadas": la herramienta
writeexige ruta absoluta. Nova-2-lite convertía la rutaC:\Users\...a formato Unix/C/Users/..., así que escribía al vacío. El archivo nunca aparecía donde debía. El modelo razona como si estuviera en Linux. - No reporta bien los errores: afirmaba "escribí el archivo" cuando no era cierto. Lección: verificar en disco, no confiar en lo que el agente dice.
Gemini (Google)
Cambiar a Gemini resolvió lo de las rutas, pero trajo límites propios del proveedor:
-
Cuota de la capa gratuita (429
RESOURCE_EXHAUSTED): el harness hace muchas llamadas por minuto (razonar, buscar, procesar, escribir) y supera el límite RPM gratuito enseguida. Una llamada simple de prueba sí funcionaba; la ráfaga del harness no. -
No se puede combinar búsqueda nativa + function calling (400
INVALID_ARGUMENT): Gemini rechaza usargoogle_searchjunto con el resto de herramientas en la misma petición. Solución:builtin_tools={"web_search": False}. -
"Thinking level is not supported" (400): el harness activa razonamiento por defecto (
effort="auto"), ygemini-2.5-flashrechazó el nivel que le llegaba. Solución:effort="off". Probé tambiénlowymediumy los rechazó igual: con este modelo, apagar el razonamiento fue la única salida. -
A veces no llama a
write: con el razonamiento apagado, el modelo pequeño respondía el texto en el chat en lugar de invocar la herramienta de escritura.
Algunas soluciones que podés adoptar
Después de varias iteraciones, la conclusión fue: confiar en que un modelo pequeño invoque write con la ruta correcta es frágil. La herramienta write funciona; lo que falla es que el modelo chico la use bien.
Patrón robusto: dejar que el agente genere el contenido y que Python escriba el archivo con str(result). Así se saca de la ecuación la parte que falla (que el modelo construya bien la ruta de Windows) y se garantiza el resultado. Ejemplo:
result = agent("...devuelve solo el documento markdown...")
with open(ruta_absoluta, "w", encoding="utf-8") as f:
f.write(str(result))
Inconvenientes con Unicode en consola (Windows)
Comenté algunos tropiezos que tuve pero vale la pena mencionar por si recién estás empezando en este mundo. En Windows la consola usa cp1252 y crashea al imprimir emojis (p. ej. el ✅ que el agente pone al confirmar). Eso tiraba el proceso con UnicodeEncodeError antes de terminar. Se arregla forzando UTF-8 al inicio del script:
import sys
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
Sesiones: mantener una conversación entre ejecuciones
Por defecto el harness persiste cada conversación en disco bajo ./.agent/sessions. Pero no reanuda solo: sin id, genera uno nuevo en cada corrida (por eso aparecen carpetas con nombres aleatorios). Para continuar una conversación, hay que pasar un id estable:
agent = create_harness(session={"id": "api-design"})
agent("¿Dónde quedamos?")
Un ejemplo en dos ejecuciones separadas (dos corridas del programa, una después de la otra, cerrando la primera antes de abrir la segunda):
-
Primera corrida: se construye el agente con
session={"id": "api-design"}y se le pide, por ejemplo, que compare tres estrategias de versionado de una API. Se cierra el programa. -
Segunda corrida: se vuelve a construir el agente con el mismo id (
"api-design") y se le pregunta "¿cuál de esas elegirías...?". Aunque es un proceso nuevo, el agente responde correctamente sin que le repitas cuáles eran las opciones: recuerda la conversación anterior.
Eso funciona porque el estado quedó guardado en ./.agent/sessions/session/api-design/ como snapshot_latest.json, y el harness lo rehidrata al construir un agente con ese mismo id. Si no pasás un id, cada corrida genera uno nuevo y arranca de cero.
-
session={"dir": ...}cambia dónde vive el estado. -
session=Falsedesactiva la persistencia (corrida desechable). - Para multi-host, se pasa un
SessionManagerpropio (por ejemploS3SessionManager).
Esto último merece una aclaración, es clave si pensás llevar el agente a producción. Por defecto el harness guarda las sesiones en el disco local de la máquina donde ejecuta. Eso alcanza mientras ejecutes siempre en el mismo lugar. Pero cuando el agente vive en varias máquinas a la vez o en funciones serverless (que se levantan y apagan), el disco local deja de servir. La solución es guardar las sesiones en un lugar compartido por todas las máquinas, y para eso se usa S3SessionManager, que guarda el estado en Amazon S3.
from strands.session.s3_session_manager import S3SessionManager
create_harness(session=S3SessionManager(session_id="api-design", bucket="mi-bucket"))
Dato para tener en cuenta: si desplegás en Amazon Bedrock AgentCore Runtime, las sesiones guardadas en disco se pierden cuando la microVM se reinicia. Ahí es donde podemos usar S3 para no perder el estado.
Un caso de uso completo: Narrador de Historias
Armé un narrador de historias (aún en construcción), inspirado en Sherpa de Pocket FM pero reducido a lo esencial, solo texto, en español, sin audio ni imágenes. Invocás al agente y te entrevista qué clase de historia querés escribir (formato, género, tono, personajes, premisa) y se pone a trabajar.
No es un solo agente respondiendo, sino un conjunto de agentes trabajando como un pequeño taller literario. Un orquestador (el narrador) conduce la conversación y escribe la prosa, y delega el trabajo de revisión en tres subagentes especialistas, uno que chequea la continuidad contra la "biblia" de la historia, otro que audita la estructura y el ritmo, y un bibliotecario que mantiene esa biblia actualizada. Cada uno hace su parte sin ensuciar el contexto principal.
📦 GitHub: github.com/reinalau/strands-harness-narrador
La arquitectura: un orquestador y tres subagentes
Subagentes trabajando para un orquestador
Un subagente es un agente al que el agente principal puede llamar como si fuera una herramienta. Lo importante es que la subtarea corre en su propia conversación fresca y solo vuelve su respuesta final — el trabajo intermedio no inunda el contexto principal. Hay dos formas de usarlos, y conviven.
El generalista integrado (built-in). El harness activa por defecto un subagente llamado generalist, expuesto al agente como herramienta. Es un agente harness completo que hereda la configuración del padre (modelo, razonamiento, caching, gestión de contexto, herramientas integradas, plugins, subagentes, interventions y sandbox), pero corre con un prompt de rol genérico en lugar de tus instructions (es el prompt que hace a la identidad del agente, no le decimos system prompt porque el harness lo compone: contrato del harness + tus instructions). Arranca de una conversación en blanco, así que el modelo debe meter en la llamada todo lo que la subtarea necesita, y devuelve una única respuesta autocontenida. No hay que crearlo, viene activado y corre en segundo plano. Se usa cuando una subtarea inundaría el contexto principal (buscar entre muchos archivos, un cambio multi-paso, exploración abierta donde solo necesitás la conclusión). Para desactivarlo:
agent = create_harness(builtin_tools={"subagent": False}) # sin generalista
En el narrador el generalista no se usa: al configurar
builtin_tools={"*": False, "read": True, "write": True, "edit": True}partimos de cero y solo habilitamos las herramientas que nombramos, así que elsubagentintegrado queda apagado. La delegación la hacen los tres especialistas de abajo.
Agentes especialistas (Agents as Tools). En este caso de uso creé agentes especialistas: se pasan instancias de Agent envueltas con as_tool() (Python) / asTool() (TS). Cada uno se convierte en una herramienta nombrada según el name del agente, así que conviene darle un name y description claros para que el modelo sepa cuándo usarla.
Ejemplo:
from strands import Agent
from strands_harness import create_harness
researcher = Agent(
name="researcher",
description="Investiga un tema y devuelve un resumen conciso y con fuentes.",
system_prompt="Investiga el tema dado y devuelve un resumen conciso y con fuentes.",
)
agent = create_harness(tools=[researcher.as_tool()])
Cada llamada corre desde el estado base del especialista (conversación fresca), así que no acumula estado entre llamadas. El name del subagente debe ser único en el set de herramientas, igual que cualquier tool.
En el narrador, los tres revisores (continuidad, estructura/ritmo, bibliotecario) son especialistas de este tipo: trabajan sobre el capítulo sin ensuciar la conversación principal, y solo devuelven su reporte o su JSON.
Ojo con el modelo: Agent crudo necesita una instancia de modelo
Acá me comí un error que vale la pena contar, porque es fácil de cometer. Al orquestador lo creás con create_harness(model="google/gemini-3.5-flash"), y ese string con prefijo —"google/...", "bedrock/..."— funciona porque lo resuelve el harness: traduce el atajo a una instancia de modelo del proveedor.
Pero los subagentes especialistas son Agent crudos del SDK, no harness. Y si a un Agent crudo le pasás ese mismo string, falla:
ValidationException ... The provided model identifier is invalid.
El Agent del SDK no entiende el prefijo "google/...": lo interpreta como un id de Bedrock a secas y revienta. La solución es pasarle una instancia de modelo del SDK, no el string:
from strands import Agent
from strands.models.bedrock import BedrockModel
continuidad = Agent(
name="continuidad",
description="Revisa la continuidad de un capítulo contra la biblia.",
system_prompt="Sos un revisor de continuidad...",
model=BedrockModel(model_id="us.amazon.nova-lite-v1:0", region_name="us-east-1"),
)
Regla para recordar: el string "proveedor/nombre" es syntactic sugar del harness (un atajo de sintaxis que resuelve solo create_harness()); fuera de él (en un Agent del SDK) tenés que instanciar el modelo vos mismo con GeminiModel(...), BedrockModel(...), etc.
La biblia con structured output
La "biblia" de la historia (personajes, relaciones, qué sabe cada uno, hilos abiertos, objetos, línea de tiempo) vive en bible.json, y la mantiene el subagente bibliotecario. El primer impulso es pedirle al modelo "devolveme el JSON actualizado, sin
``" y después parsearlo. Funciona a veces: un modelo chico mete un backtick de más, un comentario, una coma colgada, y eljson.loads` explota.
El harness (vía el SDK) tiene una forma mejor: structured output. Definís el esquema con un modelo Pydantic y se lo pasás a la invocación; el SDK se encarga de que la respuesta salga validada contra ese esquema. En lugar de esperar con incertidumbre, obtenés un objeto que ya cumple la forma esperada.
En el narrador, la biblia es un modelo Pydantic (Biblia, con submodelos para personajes, relaciones, etc.), y el bibliotecario se invoca pidiéndole esa forma:
python
from pydantic import BaseModel
class Biblia(BaseModel):
meta: Meta
personajes: list[Personaje]
relaciones: list[Relacion]
# ... hilos_abiertos, objetos, linea_de_tiempo
# dentro de la tool del bibliotecario:
result = _bibliotecario_agent(prompt, structured_output_model=Biblia)
biblia: Biblia = result.structured_output # objeto validado, no texto suelto
return biblia.model_dump_json(indent=2) # el orquestador lo escribe en disco
El valor validado se lee en result.structured_output. Después lo serializo con model_dump_json() y es el orquestador quien lo escribe en bible.json, el bibliotecario solo genera y valida, nunca toca disco. Así la biblia siempre tiene la forma correcta, sin importar lo prolijo que sea el modelo que la generó.
Human-in-the-loop: un capítulo por turno
Cuando pensé en que intervenga una persona con el agente, mi primer reflejo fue buscar un mecanismo del harness: interventions, que gatea las llamadas a herramientas y pide aprobación antes de ejecutarlas. Pero eso resuelve otra cosa (aprobar o no una acción riesgosa), no lo que yo quería. Lo que buscaba era más simple: que el narrador escriba un capítulo, lo guarde, y pare, que no siga de largo con el capítulo 2, para que yo pueda leerlo, editarlo, y recién entonces decirle cómo seguir.
Y esa pausa no necesita ningún mecanismo especial: es el chat por turnos. El agente termina su turno después de guardar el capítulo, y el control vuelve a mí. La regla la fijé en el system prompt: escribí un capítulo por turno; después de guardarlo, pará y esperá a que te pidan "revisar" o "continuar"; no empieces el siguiente por tu cuenta.
La pieza que hace que esto sea sólido es que los archivos son la fuente de verdad. Si yo edito a mano story/capitulo-01.md o bible.json entre turnos, el agente no se entera por arte de magia: tiene que releer desde disco antes de actuar. Así que el prompt también le ordena que, al pedirle "revisar" o "continuar", vuelva a leer los archivos en vez de confiar en lo que tiene en su contexto (que puede estar desactualizado respecto a mis ediciones). El flujo es así:
- Le pido un capítulo → lo escribe, lo guarda, y para.
- Lo leo. Si quiero, edito el
.mdo la biblia a mano. - Le digo "revisá" → relee desde disco, delega en continuidad y estructura, y me reporta.
- Le digo "continuá" → relee la biblia y el último capítulo, y escribe el siguiente.
Dos modelos, un mismo harness: cada uno con su personalidad
El diseño terminó con dos modelos: el orquestador (que escribe la prosa y en disco) y los subagentes de análisis (que no tocan disco, así que pueden correr en un modelo más barato). Pero llegar a esa repartición no fue elegir en un papel, fue probar modelo tras modelo.
El recorrido para el orquestador fue así:
-
Nova 2 Lite (Bedrock): barato y disponible, pero en Windows inventa rutas estilo Linux (
/C/...,/workspace/...). La herramientawriteexige ruta absoluta y el modelo construye una que no existe, así que el archivo nunca se graba. Encima afirmaba haber guardado cuando no era cierto. -
Gemini 2.5 Flash (Google): resolvió las rutas, pero trajo lo suyo. Rechazó el nivel de razonamiento que el harness activa por defecto (
Thinking level is not supported, hay que forzareffort="off"), no deja combinar su búsqueda nativa con el resto de herramientas. -
Nova Pro (Bedrock): más capaz, probé si manejaba mejor la escritura. Dos sorpresas: primero un
ValidationException: User messages cannot contain reasoning content(el harness le mandaba bloques de reasoning que el modelo rechaza); y cuando logró escribir, también unixificó la ruta (/workspace/prueba2.md), confirmado leyendo el log de invocación en CloudWatch. Mismo problema de rutas que su hermano chico. - Gemini 3.5 Flash (Google): el que mejor se portó —construye rutas correctas y escribe donde debe.
Por eso terminó teniendo sentido la repartición en dos modelos: dejar la escritura y la prosa en manos de un modelo que se porta bien con el sistema de archivos, y mandar el análisis (que solo lee y devuelve texto, sin tocar rutas) a uno más barato y chico.
Qué queda por explorar
Para no estirar el artículo, dejé afuera varias piezas del harness que quedan para una próxima. Las menciono por si querés seguir el camino por tu cuenta:
-
Agent Skills: habilidades que el agente carga desde
./.agent/skills, con sus instrucciones y archivos de apoyo. Útil para empaquetar "cómo hacer X" sin inflar el system prompt. -
Interventions: las puertas de aprobación del harness (
"ask","smart", políticas en lenguaje natural, Cedar). Las nombré al hablar del human-in-the-loop, pero no las usé en el narrador. - Gestión de contexto y caching en detalle: el harness los administra por defecto; queda pendiente mirar de cerca cómo resume, qué cachea y cómo se afina.
- Export desde la CLI y despliegue en AgentCore: llevar el prototipo de la laptop a un entorno gestionado (y ahí sí, resolver la persistencia durable de sesiones).
Conclusiones
Volvamos a la promesa del principio: crear un agente en dos líneas. Es verdad, y no es poco: create_harness() te ahorra ensamblar a mano el modelo, las herramientas, la memoria, las sesiones y la gestión de contexto. Para empezar, es un salto enorme.
Pero construir algo que se sostenga pide entender lo que esas dos líneas tapan. Que un turno no es una llamada sino un bucle que puede disparar varias. Que las sesiones hacen reanudable la conversación, pero el id lo gestiona tu código, no el modelo. Que la memoria de largo plazo a veces ayuda y a veces estorba, en el narrador la apagué, porque el estado real vivía mejor en archivos. Que el contexto crece y el harness lo administra por nosotros. Y que delegar en subagentes mantiene limpio el hilo principal porque cada uno trabaja aparte y solo devuelve su conclusión.
Otro punto importante es la elección de modelos. Si tenemos la posibilidad de usar los modelos de Claude, a priori sabemos que la mayoría de las tareas las van a resolver con eficacia, pero creo que el aprendizaje pasa por la experimentación. Si estás dando tus primeros pasos, te invito a que uses todos los modelos que posean free tier. También experimentar con Docker + Ollama, bajando a tu laptop modelos pequeños y entender en qué tareas de subagentes son más óptimos.
Y una línea de diseño para construir agentes es dejá que los agentes hagan lo que requiere criterio, y que el código haga lo determinista. La prosa, la revisión de continuidad, el juicio sobre el ritmo: eso es trabajo de agentes, y es donde no se puede resolver vía código (simulando ser personas de un pequeño taller literario escribiendo y corrigiendo). El id de la sesión, la ruta del archivo, el contador de capítulos: eso es trabajo del código, determinista y predecible. Mezclarlos es justo donde aparecen las alucinaciones y bugs.



Top comments (0)