Este es el tercer Artículo de Azurebrains y esta vez el tema no nace solo de una noticia externa, sino de una pequeña contribución personal: microharness-python, un repositorio que he creado para aprender y enseñar Microsoft Agent Framework construyendo un harness de agentes desde código real.
La palabra importante aquí es aprender. Cuando aparece una librería nueva alrededor de agentes, la tentación es leer el anuncio, mirar dos snippets y pensar que ya hemos entendido el modelo. A mí me ocurre justo lo contrario: hasta que no construyo una versión pequeña, la ejecuto, miro dónde vive el estado, veo cómo se invocan las herramientas y compruebo qué parte pertenece al framework y qué parte pertenece a mi aplicación, no siento que haya entendido de verdad la tecnología.
Por eso este repositorio no intenta ser una plataforma de agentes completa ni competir con Microsoft Agent Framework. Es un laboratorio didáctico. Su objetivo es poner las piezas encima de la mesa: Agent, tools, contexto, memoria, sesiones, aprobaciones, middleware, FastAPI, AG-UI y una variante MAF-native que usa primitivas del propio framework. Pequeño, revisable y suficientemente real como para que sirva de punto de partida.
Por qué he creado este repositorio
La publicación de Microsoft sobre Agent Framework Harness me parece relevante porque apunta a una necesidad que muchos equipos ya estaban resolviendo por su cuenta: convertir un modelo conversacional en un sistema operativo. Un chatbot responde. Un agente, si está bien diseñado, mantiene una tarea, decide pasos, invoca herramientas, incorpora resultados, solicita aprobaciones y deja trazas.
En el artículo sobre Microsoft Agent Framework Harness ya expliqué esa idea desde el punto de vista de la novedad del framework. Pero había una parte que necesitaba bajar a tierra: cómo se ve eso en un proyecto Python concreto, con ficheros, endpoints, tools y artefactos locales que cualquiera pueda abrir.
microharness-python nace de esa necesidad. Quería un repositorio que respondiera a preguntas muy prácticas:
- ¿Cuál es el mínimo código razonable para construir un agente con Microsoft Agent Framework?
- ¿Cómo expongo tools Python sin esconderlo todo en prompts?
- ¿Dónde coloco el contexto y la memoria para que sean inspeccionables?
- ¿Cómo represento acciones sensibles que necesitan aprobación humana?
- ¿Cómo publico el agente por HTTP y streaming sin convertirlo en una demo cerrada?
- ¿Qué cambia cuando paso de una implementación didáctica a primitivas nativas del framework?
La respuesta no podía ser otro diagrama. Tenía que ser código.
Dos recorridos: didáctico y MAF-native
El repositorio tiene dos recorridos complementarios.
El primero es el microharness didáctico. Implementa contexto, memoria local, hooks de ciclo de vida y subagentes deterministas con código pequeño. No porque esa sea siempre la forma final de hacerlo, sino porque ayuda a entender el problema. Si ves cómo se compone un bloque de contexto, cómo se persiste un fact o cómo se escribe una traza JSONL, después entiendes mejor qué valor aporta el framework cuando ofrece extensiones nativas.
El segundo recorrido es la variante MAF-native. Ahí el proyecto usa primitivas de Microsoft Agent Framework como AgentSession, ContextProvider, FileHistoryProvider, MemoryContextProvider, TodoProvider, ToolApprovalMiddleware y middleware de agente y funciones. Esta parte es la que más me interesaba para validar que el repositorio no era una simulación paralela, sino una forma de aprender el framework desde sus propios puntos de extensión.
Browser / CLI / Notebook
↓
FastAPI + AG-UI
↓
Microsoft Agent Framework Agent
↓
MicroHarness runtime
├─ configuration
├─ context manager
├─ skills / tools
├─ deterministic sub-agents
├─ file-backed memory
└─ lifecycle hooks
↓
Azure OpenAI / compatible model endpoint
Esta separación me parece importante para aprender. La ruta didáctica te deja tocar cada pieza. La ruta MAF-native te enseña cómo esas piezas encajan en la arquitectura real del framework.
El agente empieza en Agent, no en una clase inventada
Una de las lecciones que me llevo de este ejercicio es que, si estamos hablando de Microsoft Agent Framework, los ejemplos deben usar Microsoft Agent Framework. Parece obvio, pero no siempre ocurre. Por eso el punto de entrada del repositorio es un Agent real, configurado con un cliente de chat y un conjunto de tools.
from agent_framework import Agent
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity import AzureCliCredential
from microharness.config import Settings, load_settings
from microharness.tools import HARNESS_TOOLS
def build_chat_client(settings: Settings | None = None) -> OpenAIChatCompletionClient:
settings = settings or load_settings(require_model=True)
if not settings.model:
raise RuntimeError("El modelo no está configurado.")
kwargs: dict[str, object] = {"model": settings.model}
if settings.base_url:
kwargs["base_url"] = settings.base_url
if settings.api_key:
kwargs["api_key"] = settings.api_key
else:
kwargs["azure_endpoint"] = settings.azure_endpoint
if settings.api_version:
kwargs["api_version"] = settings.api_version
if settings.api_key:
kwargs["api_key"] = settings.api_key
else:
kwargs["credential"] = AzureCliCredential()
return OpenAIChatCompletionClient(**kwargs)
def build_agent(settings: Settings | None = None) -> Agent:
return Agent(
name="MicroHarness",
instructions=INSTRUCTIONS,
client=build_chat_client(settings),
tools=HARNESS_TOOLS,
)
El código no intenta ocultar la configuración del modelo. El repositorio soporta Azure OpenAI o endpoints compatibles, permite cargar configuración desde .env, variables de entorno o Key Vault, y evita guardar secretos. Esa parte puede parecer menos llamativa que las tools, pero para mí es parte del aprendizaje: un agente no empieza cuando llama a una función; empieza cuando somos capaces de configurarlo sin convertir el repositorio en un riesgo.
Tools como contrato, no como texto libre
La segunda pieza clave son las tools. En microharness-python, las capacidades del agente se exponen como funciones Python decoradas con @tool, usando tipos y descripciones. Eso evita una mala práctica habitual: dejar que el modelo interactúe con la aplicación mediante texto libre y después intentar parsear intenciones.
from typing import Annotated, Any
from agent_framework import tool
from pydantic import Field
from microharness.context import ContextManager
@tool
def build_context_snapshot(
session_id: Annotated[str, Field(description="Session identifier for the context snapshot.")],
) -> dict[str, Any]:
context = ContextManager().build(session_id)
return {
"session_id": context.session_id,
"source": context.source,
"facts": context.facts,
"run_count": context.run_count,
"prompt_block": context.as_prompt_block(),
}
Este ejemplo resume bien la filosofía del repositorio. El agente puede pedir contexto, pero no decide por arte de magia cómo se construye. Hay una función tipada, con un contrato claro, que empaqueta la información local y los facts de sesión. El modelo recibe una observación estructurada y el harness puede registrar qué ha ocurrido.
Para aprender agentes, esta diferencia es fundamental. Una tool no debería ser solo “algo que el modelo puede llamar”. Debería ser una frontera de software: entrada validable, salida comprensible, permisos definidos y trazabilidad.
Contexto y memoria sin esconder la caja negra
En conversaciones cortas, es fácil confundir contexto con memoria. Se reinyecta el historial, se añade algún resumen y ya parece que el agente recuerda. Pero cuando queremos construir agentes operativos, conviene separar conceptos.
En MicroHarness, el contexto es el paquete que se entrega al modelo en un turno concreto. La memoria es estado persistido que puede sobrevivir entre turnos: facts, contador de ejecuciones, trazas de tools y artefactos Markdown. Todo eso se escribe bajo working/output/, precisamente para que sea fácil inspeccionarlo durante el aprendizaje.
working/output/
agent_summary.md
session_state.json
lifecycle_trace.jsonl
No es una solución enterprise de persistencia, ni pretende serlo. Es una forma sencilla de poder abrir los artefactos después de una ejecución y preguntar: qué contexto entró, qué tool se llamó, qué fact se guardó, qué respuesta se generó. Para aprender, esa visibilidad vale oro.
La variante MAF-native
La parte más interesante del proyecto, si ya tienes claro el patrón básico, es src/microharness/maf_native.py. Ahí el repositorio empieza a mover responsabilidades al framework.
from agent_framework import (
ContextProvider,
FileHistoryProvider,
MemoryContextProvider,
MemoryFileStore,
TodoFileStore,
TodoProvider,
)
def build_native_context_providers() -> list[ContextProvider]:
return [
FileHistoryProvider(MAF_NATIVE_DIR / "history"),
HarnessKnowledgeProvider(),
TodoProvider(
store=TodoFileStore(MAF_NATIVE_DIR / "todos", owner_state_key="microharness")
),
MemoryContextProvider(
store=MemoryFileStore(MAF_NATIVE_DIR / "memory", owner_state_key="microharness"),
recent_turns=2,
),
]
Este fragmento me gusta porque enseña una transición natural. Primero entiendes a mano qué significa historial, contexto, memoria y planificación. Después ves cómo el framework ofrece proveedores para integrar esas responsabilidades en el ciclo del agente.
La variante MAF-native no elimina el código propio. Lo coloca en el sitio correcto. HarnessKnowledgeProvider inyecta conocimiento controlado; FileHistoryProvider aporta continuidad conversacional; TodoProvider introduce estado de planificación; MemoryContextProvider conecta memoria local. El harness deja de ser una caja opaca y se convierte en una composición de extensiones.
Aprobaciones humanas desde el diseño
Otra razón para construir este repositorio era enseñar que no todas las tools son iguales. Algunas pueden leer contexto o generar un artefacto. Otras representan acciones sensibles. Y un agente operativo no debería ejecutar una acción sensible solo porque el modelo la propone.
Por eso incluí una tool marcada con aprobación obligatoria:
from agent_framework import tool
@tool(approval_mode="always_require")
def propose_foundry_deployment(environment: str, reason: str) -> dict[str, str]:
return {
"environment": environment,
"status": "deployment-proposal-recorded",
"reason": reason,
"note": "This safe tool records intent only; it does not deploy anything.",
}
Fíjate en el detalle: la tool no despliega nada. Registra una intención. Es un ejemplo seguro para aprender el patrón sin introducir una acción destructiva. En la variante MAF-native, ToolApprovalMiddleware se añade al pipeline para que las aprobaciones formen parte del runtime y puedan exponerse por AG-UI.
Esta es una de las ideas que más me interesa transmitir cuando hablamos de agentes: la seguridad no debería aparecer al final, como un filtro añadido a una demo. Debe formar parte del diseño de las tools y del ciclo de ejecución.
FastAPI y AG-UI para salir del notebook
Me gustan los notebooks para aprender, y de hecho el repositorio incluye un recorrido incremental. Pero un agente no debería quedarse encerrado en un notebook. En algún momento necesita una superficie de producto: un endpoint, una interfaz, un flujo streaming, una forma de integrarse con otras aplicaciones.
Por eso MicroHarness publica endpoints con FastAPI:
| Ruta | Propósito |
|---|---|
/api/chat |
Endpoint JSON de la ruta didáctica |
/agent |
Stream AG-UI de la ruta didáctica |
/api/chat/maf-native |
Endpoint JSON de la variante MAF-native |
/agent/maf-native |
Stream AG-UI de la variante MAF-native |
/health y /healthz |
Comprobaciones de salud |
AG-UI es especialmente interesante porque permite pensar en experiencias agentivas más ricas: streaming, eventos, aprobaciones y una interfaz donde el agente no es simplemente una caja de texto que devuelve una respuesta final.
Qué quiero que se aprenda con MicroHarness
Si alguien clona este repositorio, no quiero que piense que está ante “mi framework”. No lo es. Quiero que lo use como una libreta de laboratorio para entender Microsoft Agent Framework desde dentro.
El recorrido que propongo es muy concreto:
- Configurar el modelo sin guardar secretos.
- Ejecutar el agente base.
- Inspeccionar las tools.
- Ver cómo se compone el contexto.
- Guardar y leer facts de sesión.
- Revisar las trazas JSONL.
- Probar el endpoint FastAPI.
- Comparar la ruta didáctica con la variante MAF-native.
- Pensar qué habría que cambiar para llevarlo a producción.
Ese último punto es importante. MicroHarness no es una plantilla productiva cerrada. Para producción habría que añadir autenticación en endpoints, almacenamiento gestionado, políticas por tool, redacción de secretos en trazas, evaluación automatizada, despliegue gobernado y observabilidad real. Pero antes de hacer todo eso conviene entender la mecánica básica. Y para eso está el repositorio.
Una contribución pequeña, pero útil
En Azurebrains siempre he defendido que aprender tecnología no consiste solo en repetir documentación. Consiste en convertir una idea en algo ejecutable, romperlo un poco, mirarlo por dentro y después explicarlo con honestidad. microharness-python va justo de eso.
No pretende ser grande. De hecho, su valor está en ser pequeño. Hay repositorios que enseñan porque abstraen mucho; este intenta enseñar porque abstrae lo justo. Si quieres entender qué hace un harness, puedes seguir el código desde la configuración hasta el endpoint. Si quieres ver cómo encaja con Microsoft Agent Framework, puedes abrir la variante MAF-native. Si quieres experimentar, puedes añadir una tool, cambiar el contexto o probar otro backend de memoria.
Para mí, esa es una forma muy sana de aproximarse al ecosistema agentivo actual. Hay mucho ruido, muchas demos y muchas palabras que se usan de forma intercambiable: agent, harness, workflow, memory, context, tool, skill, middleware. La única forma de aclararlas es construir algo pequeño donde cada palabra tenga un fichero, una función y un efecto observable.
Conclusión
MicroHarness Python es mi forma de aterrizar Microsoft Agent Framework en un repositorio que se pueda leer, ejecutar y discutir. Nace como laboratorio personal, pero lo publico porque creo que puede ayudar a otros a aprender las nuevas librerías de Agent Framework sin empezar desde una abstracción demasiado grande ni desde pseudocódigo.
El objetivo no es demostrar que tengo “otro framework”. El objetivo es enseñar cómo se ve un harness cuando lo construyes con piezas reales: Agent, tools, contexto, memoria, aprobaciones, middleware, AG-UI y endpoints HTTP.
Si estás aprendiendo agentes en Python, mi recomendación es sencilla: no te quedes solo en el anuncio. Clona un repositorio pequeño, ejecuta el flujo, mira los artefactos, cambia una tool y observa qué ocurre. Ahí es donde una librería nueva deja de ser una promesa y empieza a convertirse en criterio técnico.