Un flujo de trabajo local para agentes LLM creado a partir de carpetas y archivos Markdown
Cree un pipeline local de agentes con múltiples etapas y control de acceso, donde cada etapa es una carpeta, los prompts están en formato Markdown editable y un humano revisa el resultado antes de pasar a la etapa costosa.
Fuentes: Build Your Own Local LLM Agent Workflow in 400 Lines of Python
Un agente local puede contar con un flujo de trabajo reproducible e inspeccionable sin necesidad de ningún framework. La configuración aquí ejecuta un trabajo en varias etapas, donde cada etapa corresponde a una carpeta, la instrucción de cada etapa es un archivo Markdown sencillo que cualquier persona puede leer y editar, y una persona revisa los resultados entre etapas. El agente no cambia; solo cambia la forma en que se organiza su contexto, y ese es el punto clave.
Por qué un agente local necesita esto
El agente ya maneja búsquedas en la web, lectura de páginas y acciones a través de una pestaña del navegador persistente. Lo que faltaba era alguna forma de ejecutar un trabajo repetidamente con la intervención humana a mitad del proceso.
La causa era que todo lo que lo dirigía estaba en código. Un prompt del sistema se generaba al momento de la importación, y se creaba una única lista de herramientas al iniciar el programa, la cual se entregaba al modelo en cada turno. Para que el agente desempeñara un rol diferente en una etapa distinta, era necesario editar el código en Python. Para detenerse y examinar los resultados intermedios antes de una etapa costosa, no existía forma de hacerlo. Eso es aceptable para un bucle de chat, pero resulta incómodo en tareas con varias etapas.
Lo que se obtiene es un pipeline compacto de investigación y creación de cursos. Cuatro etapas llevan una sola oración que describe un curso hasta llegar a un borrador de minicurso, con una fase de revisión después de cada etapa. Carpetas numeradas con formato Markdown simple albergan ahora el contexto que dirige al agente, de modo que se puede leer y corregir el esquema antes de comenzar a redactar, sin tocar ningún código en todo el proceso.
La estructura sigue un método llamado Interpretable Context Methodology, o ICM, que sustituye la orquestación a nivel de framework por una estructura de sistema de archivos.
El diagrama muestra el flujo de trabajo como una carpeta llamada workspace. Cada carpeta numerada dentro de pipeline/ representa una etapa; el archivo CONTEXT.md de cada etapa contiene su instrucción, y proposed/ es un área de preparación donde una persona puede aprobar los cambios manualmente.
Las carpetas numeradas funcionan como etapas, los archivos Markdown albergan el contexto de cada etapa, y los scripts locales se encargan del trabajo mecánico que no requiere modelos. El diseño toma prestado de los pipelines de Unix y los compiladores de múltiples pasos, donde la salida de un paso se utiliza como entrada para el siguiente, y cada resultado intermedio es un archivo común que se puede abrir.
Qué necesita tener en ejecución
Para el modelo, qwen3.5:9b funciona con Ollama teniendo desactivado el pensamiento y una temperatura de 0.1. La navegación y la búsqueda reutilizan trabajos anteriores: camofox-browser escucha en el puerto 9500 y SearXNG en el 8090, ambos dentro de Docker. Es necesario ejecutar esos dos contenedores, junto con Ollama y un modelo que soporte llamadas a herramientas, antes de comenzar.
La construcción se divide en cinco etapas:
- En la Etapa 1 se inician camofox-browser y SearXNG en Docker, servicios a los que recurrirán las etapas posteriores.
- En la Etapa 2 se genera el prompt del sistema del agente a partir de una carpeta con archivos en formato markdown, reemplazando una cadena predefinida.
- En la Etapa 3 se proporciona el controlador, aplicándose en cada etapa controles de revisión y límites para las herramientas.
- La Etapa 4 es la etapa de planificación: en ella se utilizan los resultados de las dos etapas anteriores y una persona aprueba el plan antes de pasar a la fase costosa.
proposed/ que deja espacio para extensiones futuras.Instalación
Cada etapa se encuentra en su propio subdirectorio y está disponible como script de consola a través de pyproject.toml; por lo tanto, después de una instalación se puede ejecutar cualquier etapa por su nombre. El primer paso es clonar el repositorio e instalarlo en modo editable, utilizando un entorno virtual:
git clone https://github.com/jfjensen/local-LLM-agent-icm-workflow.git
cd local-LLM-agent-icm-workflow
python -m venv .venv
# Windows PowerShell:
.\.venv\Scripts\Activate.ps1
pip install -e .
La instalación incluye mcp, ollama y httpx, además de registrar un conjunto de scripts de consola. Las etapas del 2 al 5 cuentan con un ejecutor de flujos de trabajo (mcp-workflow-stage2 hasta mcp-workflow-stage5) y un agente (mcp-agent-stage2 hasta mcp-agent-stage5) respectivamente. Se copian dos servidores MCP, mcp-search-part3 y mcp-browser-stage5, de iteraciones anteriores, lo que mantiene el repositorio autónomo.
La configuración se encuentra en config.toml, en la raíz del repositorio, donde se agrega una nueva sección [workflow]. Dado que cada etapa lee sus archivos en workspace/ y escribe sus registros en history/ con respecto al directorio actual, lo más adecuado es ejecutar una etapa desde su propia carpeta:
cd stage5
mcp-workflow-stage5
Etapa 1: los servicios locales
Aquí no se utiliza Python. Esta etapa inicia la pareja de servicios que el agente utiliza durante la investigación: camofox-browser, el navegador furtivo en el que se encuentran las herramientas de lectura, y SearXNG, el motor de búsqueda bajo search. Quienes ya tengan la configuración anterior pueden seguir utilizando el mismo docker-compose.yml.
cd stage1
docker compose up -d
docker compose logs -f
Algunos detalles que vale la pena conocer:
- Camofox escucha en el puerto 9500 y SearXNG en el 8090, que son los puertos esperados por
config.toml, por lo que el agente los encuentra ambos sin necesidad de configuración adicional. - El archivo compose realiza un montaje vinculado a
searxng/settings.yml, lo que habilita la API JSON y desactiva el limitador de velocidad, permitiendo así realizar búsquedas desde la primera ejecución. - La imagen de camofox se genera una sola vez a partir de su repositorio original. La primera generación es lenta debido al gran tamaño del binario Camoufox, y
stage1/README.mddetalla los pasos a seguir.
Para verificar el estado de funcionamiento, solicite http://localhost:9500/health y http://localhost:8090/search?q=ollama&format=json con curl. Ambos deberían responder en formato JSON.
La Etapa 1 incluye únicamente esos dos servicios. Una vez que camofox responda en el 9500 y SearXNG en el 8090, ya tendremos algo con lo que realizar búsquedas.
Etapa 2: contexto de archivos
Este es el único cambio real en el código. Antes, el agente utilizaba una instrucción del sistema, una cadena a nivel de módulo que se generaba al iniciar y se enviaba sin modificaciones en cada turno, independientemente de la tarea. Al permitirle asumir un rol diferente en cada etapa, es necesario que la instrucción provenga de un lugar editable, y leerla desde archivos es la opción más sencilla.
Por lo tanto, el primer cambio permite sobrescribir la instrucción del sistema:
def build_messages_for_model(self) -> list[dict[str, Any]]:
system = self.system_text if self.system_text is not None else SYSTEM_PROMPT
return [{"role": "system", "content": system}] + self.messages
El comportamiento es sencillo:
- Cuando no se establece
system_text, el agente recurre al mismoSYSTEM_PROMPTpredefinido de antes, por lo que el agente interactivomcp-agent-stage2funciona exactamente como lo hacía anteriormente. - Cuando se establece
system_text, este pasa a ser el prompt del sistema para la ejecución. El ejecutor del flujo de trabajo lo configura según cada etapa a partir de archivos, y la clase del agente no se modifica en ningún otro aspecto.
En cuanto a los archivos en sí, un espacio de trabajo de ICM no es más que una carpeta. stage2/workspace/ contiene un CLAUDE.md que describe el espacio de trabajo y su estructura, un CONTEXT.md que enumera las etapas en secuencia, un directorio _config/ con material de referencia que permanece estable entre ejecuciones, y un directorio pipeline/ con las etapas numeradas. Cada directorio de etapa tiene su propio archivo CONTEXT.md además de un directorio output/.
Los dos archivos de nivel superior proporcionan la identidad y la ruta de acceso. CLAUDE.md corresponde a la Capa 0, lo primero que lee el agente:
# Workspace: course-research
You are a single agent operating inside an ICM workspace. This file tells
you where you are and how the workspace is laid out. Read it first.
This workspace turns a one-line course idea into a drafted mini-course,
through four stages that run one at a time with a human review between each.
Layout:
- `CONTEXT.md` (next to this file): the workspace routing. It lists the
stages in order and the shared resources they draw on.
- `_config/`: shared reference material that stays the same across runs
(the audience profile, the writing voice). This is the factory.
- `pipeline/`: the numbered stages. Each stage folder holds a `CONTEXT.md`
contract, an `output/` folder for what that stage produces, and
sometimes a `references/` folder for stage-local reference material.
- `pipeline/proposed/`: a staging area, not a stage. A human moves
improvements from here into `_config/` once they are happy with them.
You only ever work on one stage at a time. The stage's own `CONTEXT.md`
tells you exactly what to read, what to do, and what to write. Do not read
files the stage contract does not list.
Junto a él, CONTEXT.md corresponde a la Capa 1, el enrutamiento que enumera las etapas:
# Routing
The course-research pipeline runs in four stages, in this order:
1. `01-scope`: turn the one-line course idea and the audience profile into
a short list of scoped learning objectives.
2. `02-research`: for each objective, search the web and read sources, and
write structured notes with links. This is the only stage that uses
tools.
3. `03-outline`: turn the objectives and the notes into a module-by-module
course outline. This is the gate before the expensive stage, so the
outline is worth a careful human read.
4. `04-draft`: draft each module from the approved outline and the notes.
Each stage reads from the stage before it (its `output/` folder) and
writes to its own `output/` folder. A human reviews each output before the
next stage runs.
Shared resources (in `_config/`, the same every run):
- `audience.md`: who the course is for. Every stage should respect it.
- `voice.md`: how the course should read. The draft stage follows it.
El material de referencia compartido se encuentra en _config/; se configura una vez y se utiliza en cada ejecución. A continuación se muestra audience.md, un archivo de nivel 3 que son leídos tanto las fases de alcance como las de investigación y esquema:
# Audience
The course is for working software developers who are comfortable with
Python and the command line, but who have not run a language model on
their own machine before.
They want practical, hands-on knowledge they can apply the same day, not
a survey of the field. Assume they can install software, edit a config
file, and read a stack trace. Do not assume they know any machine-learning
theory, model formats, or GPU terminology.
Keep examples concrete and runnable. Prefer one worked example over three
abstract options. When a trade-off matters, state it plainly and give a
default.
El contrato 01-scope/CONTEXT.md es el núcleo del diseño. Este es el documento completo para la primera etapa de la pipeline:
# Stage: 01-scope
## Inputs
- Layer 4 (working): input.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## Process
Read the one-line course idea and the audience profile. Produce a short
list of scoped learning objectives for the course, four to six of them.
Each objective should be one sentence, start with a verb, and describe
something the learner will be able to do by the end. Keep the scope tight:
this is a short course, so leave out anything that does not fit a learner
going from zero to a working local model. Do not pad the list to hit a
number. Fewer, sharper objectives are better.
Write the objectives as a numbered markdown list, nothing else.
## Outputs
- objectives.md -> output/
La única entrada de Capa 4, 01-scope/input.md, es el material de trabajo para la ejecución, la idea en una sola línea que se convierte en un curso:
# Course idea
A short, practical course that takes a developer from zero to running a
local large language model on their own machine with Ollama, and calling
it from a small Python script.
Algunas notas sobre cómo se lee el contrato:
- La tabla de Entradas enumera los archivos exactos que lee una etapa y los etiqueta con un nivel: Nivel 3 para el material de referencia que permanece estable en todas las ejecuciones (el perfil del público), Nivel 4 para el material de trabajo de esa ejecución concreta (la idea en una sola línea). Nada más se carga en el espacio de trabajo.
- La línea de Herramientas indica
ninguna, ya que convertir una idea en objetivos no requiere herramientas. La etapa 3 vuelve a este punto. - La sección de Proceso contiene las instrucciones que sigue el agente, y estas pasan a ser el turno del usuario.
- La línea de Salidas indica el nombre del archivo que la etapa escribe en su propia carpeta
output/.
Según el contrato, el cargador convierte la tabla de entradas en el contenido real de los archivos y construye el contexto a partir de ellos. Los materiales de referencia y los materiales de trabajo permanecen separados, ya que desempeñan funciones diferentes: los primeros son un conjunto de reglas que el modelo debe seguir, mientras que los segundos son las entradas en bruto que debe transformar.
# System text: identity + routing + the reference material (Layer 3).
parts = []
if layer0:
parts.append(layer0.strip())
if layer1:
parts.append(layer1.strip())
refs3 = [r for r in inputs if r.layer == 3 and r.exists]
if refs3:
block = ["# Reference material (follow these as constraints)"]
for r in refs3:
block.append(f"\n## {r.rel_path}\n\n{r.text.strip()}")
parts.append("\n".join(block))
system_text = "\n\n".join(parts).strip()
El comportamiento del cargador se desglosa de la siguiente manera:
- Primero vienen la identidad y el enrutamiento (
CLAUDE.md,CONTEXT.md), que indican al agente dónde se encuentra y qué etapas existen. - Luego siguen los archivos de referencia de la capa 3 bajo un encabezado que los presenta como restricciones. Los archivos de trabajo de la capa 4 se agrupan por separado, debajo del texto del proceso de la etapa, como lo que el agente debe actuar.
- El resultado es la instrucción del sistema para esa etapa, creada a partir de archivos simples sin incluir nada que la etapa no haya solicitado.
Antes de ejecutar cualquier cosa contra el modelo, conviene verificar qué produce el cargador, ya que esa parte es determinista. El repositorio incluye una pequeña herramienta de prueba, probe_loader.py, que imprime el contexto compilado para una etapa junto con un conteo aproximado de tokens y que nunca llama al modelo. Úsela desde la carpeta stage2 contra la primera etapa:
cd .\stage2\
python ..\probe_loader.py 01-scope
La captura de pantalla muestra las primeras líneas del informe de la sonda para la etapa de alcance, junto con el tamaño de la ventana de contexto. Esa etapa termina con unos 900 tokens, todos ellos relevantes, lo que hace tangible la idea del ICM: una pequeña ventana de alcance por etapa, y sin un único prompt gigantesco que contenga todo.
Para ejecutar realmente esa etapa, utilice mcp-workflow-stage2. Este analiza el contrato, ejecuta al agente una vez y escribe output/objectives.md. Para la idea de ejemplo, un breve curso práctico sobre cómo ejecutar un LLM local con Ollama, se obtuvieron cinco objetivos claros, del tipo que una persona revisa y luego decide conservar o eliminar.
El banner en la captura de pantalla muestra el contexto del alcance que predijo la sonda, cerca de 900 tokens, y el resultado de la etapa es objectives.md. Los objetivos de una ejecución aparecen a continuación; una segunda ejecución los redactaría ligeramente de forma diferente:
1. Install the necessary software stack (Ollama) and verify it is successfully pulling and serving a local LLM like `llama3`.
2. Configure environment variables to securely expose the local model's API endpoint for external access.
3. Write a Python script using the `requests` library to send prompts to Ollama and parse JSON responses into readable text.
4. Implement error handling in your script to gracefully manage connection timeouts or out-of-memory errors common with local inference.
5. Build an interactive loop that allows you to chat with the model directly from a terminal-based Python application without using a GUI framework.
Ahora que el contexto proviene de los archivos, lo que falta es ejecutar las etapas en orden y detenerse entre ellas.
Etapa 3: el controlador, las puertas de acceso y delimitación de herramientas
Un flujo de trabajo implica etapas ordenadas con un punto de control humano entre ellas. Por lo tanto, el controlador escanea las carpetas numeradas, ejecuta la primera etapa con un resultado vacío y se detiene.
async def run_pipeline(workspace_dir: Path, run_all: bool = False) -> None:
stages = stage_dirs(workspace_dir)
for sd in stages:
if stage_is_done(sd):
print(f"[skip] {sd.name}: output already present")
continue
await run_stage(workspace_dir, sd.name)
if run_all:
continue
# The review gate. Stop here so the human can read and edit the
# output before the next stage reads it.
print("\n--- review gate ---")
print(f"Review and edit if needed:\n {sd / 'output'}")
return
Funcionamiento:
- El estado se almacena en el disco. Una etapa se considera completada cuando su carpeta
output/contiene un archivo real, por lo que no existe un almacén de estado separado. El sistema de archivos es el estado. - La compuerta funciona simplemente como una salida anticipada. Una vez que una etapa finaliza, el controlador indica dónde buscar y se detiene. Se lee el resultado, se modifica si es necesario y se ejecuta nuevamente la misma orden para continuar.
--alles la opción que permite ejecutar todo directamente sin detenerse, para cuando se confía en el resultado. El valor predeterminado es con compuertas, ya que éstas son la finalidad principal.
Para ejecutar la tubería de procesamiento, utilice mcp-workflow-stage3, lea lo que escribió y luego ejecútelo nuevamente para avanzar. La primera ejecución abarca 01-scope y se detiene; la segunda abarca la fase de investigación y también se detiene, y así sucesivamente.
Cada invocación maneja una etapa, indica dónde buscar y se detiene en la puerta de revisión; ejecutar el mismo comando la reanuda.
Ambito de las herramientas
La investigación es la única etapa que utiliza herramientas, y ahí aparecieron los primeros problemas reales. El agente se conecta a los servidores de búsqueda y navegación, y la versión anterior combinaba todas las herramientas de cada servidor en una lista única y mostraba toda esa lista en cada turno. Eso suma unas quince herramientas, de las cuales la investigación necesita aproximadamente cinco. El resto son herramientas de pestaña y acción para trabajar en una página, algo que la investigación nunca hace.
Al ofrecer los quince en total, el modelo 9B pasó a utilizar las herramientas de interacción. Elegió open_tab y read_tab en lugar de los lectores, y read_tab solo muestra la parte superior de una página. La documentación de Ollama se ejecuta en Mintlify, por lo que esa parte superior es idéntica en todas partes: el mismo texto de navegación. El modelo siguió leyendo ese menú, abriendo una pestaña tras otra, hasta que el límite de llamadas puso fin a la ejecución con una disculpa de 143 caracteres como salida.
La solución consiste en limitar las herramientas a cada etapa. Un contrato enumera las herramientas que se necesitan, y en tiempo de ejecución solo esas se exponen. Aquí está el contrato completo de investigación:
# Stage: 02-research
## Inputs
- Layer 4 (working): ../01-scope/output/objectives.md
- Layer 3 (reference): ../../_config/audience.md
- Layer 3 (reference): references/tool-use.md
## Tools
search-server: search
browser-server: summarize, extract, fetch_snippet, fetch_urls
## Process
For each learning objective, find and read one or two good current sources,
then write short, factual notes the outline and draft stages can rely on.
Work objective by objective. For each one: run a web search, then read the
most promising result with `summarize` or `extract`. Use `summarize(url,
question=...)` for an open question about a page, and `extract(url, schema)`
when you can name the fields you want (a version number, a default port).
These read the whole page; do not try to page through a site by hand. Read
each page at most once and do not re-read a page you have already read. Once
you have one or two sources per objective, stop calling tools and write the
notes. Pull out the concrete facts that matter for teaching this objective
(commands, version notes, default ports, common pitfalls). Prefer official
documentation and project sources over blog aggregators.
Write the notes as markdown, grouped under a heading per objective. Under
each heading, a few bullet points of facts, then a "Sources:" line with
the URLs you actually read. Keep it factual. Do not draft course prose here;
that happens later.
## Outputs
- notes.md -> output/
Los nuevos nombres de la sección Tools son search en el servidor de búsqueda y solo cuatro herramientas del servidor del navegador, todas lectoras. El tiempo de ejecución aplica ese filtro a la lista de herramientas antes de que el modelo la reciba:
def rebuild_ollama_tools(self, allowed: dict[str, set[str] | None] | None = None):
out = []
self._tool_to_server = {}
for server_name, tools in self.mcp_tools_by_server.items():
allow_set = allowed.get(server_name) if allowed else None
for tool in tools:
if allow_set is not None and tool.name not in allow_set:
continue
prefixed = f"{server_name}_{tool.name}"
self._tool_to_server[prefixed] = server_name
out.append({
"type": "function",
"function": {
"name": prefixed,
"description": tool.description or "",
"parameters": tool.inputSchema,
},
})
self.ollama_tools = out
El filtro funciona de la siguiente manera:
- La lista de permisos es específica para cada servidor. El valor
Noneindica que se permiten todas las herramientas del servidor, mientras que un conjunto de nombres solo permite esas herramientas específicas. - Cualquier herramienta que no esté en el conjunto de permisos es descartada, por lo que el modelo nunca la ve. Actualmente, la función de investigación reconoce cinco herramientas, mientras que scope, outline y draft no reconocen ninguna.
- El mapa se reconstruye paso a paso, por lo que tampoco es posible llamar a herramientas ocultas; una llamada
open_tabgenerada artificialmente simplemente se consideraría como una herramienta desconocida.
El contrato de investigación también incluye su propia referencia de nivel 3, references/tool-use.md, limitada a esta etapa, que explica cómo utilizar las herramientas restantes:
# Tool use (research stage only)
You have two MCP servers for this stage. Use them like this.
- `search-server_search(query, max_results)`: a web search. Start here for
any lookup. It returns URLs with titles and snippets. After a search,
your next action must be a browser-server call on the most promising URL.
Do not stop after a search and do not answer from the snippets alone.
The browser-server reader tools each return a small slice of a page, not
the raw page:
- `browser-server_fetch_snippet(url)`: the head of a page, for a quick look.
- `browser-server_fetch_urls(url)`: the page's links, to decide what to read next.
- `browser-server_fetch_structure(url)`: the page's heading outline.
- `browser-server_extract(url, schema)`: pull named fields you can list in
advance (a version number, a default port) using a JSON Schema.
- `browser-server_summarize(url, question="")`: a prose summary of a whole
page; pass `question` to focus it on what this objective needs.
Prefer `extract` when you know the field names, `summarize` for an open
question about a long page, and `fetch_snippet` for a quick confirmation.
Prefer official documentation and project sources over blog aggregators.
Una vez más, el sistema de archivos desempeña el papel del marco de trabajo. En un marco de trabajo, el conjunto de herramientas del agente es código; aquí se trata de una línea en Markdown que cualquiera puede editar. Con los lectores activos y las herramientas de pestañas desactivadas, los modelos summarize y extract, que leen páginas completas mediante el lector por bloques, junto con las notas, contenían los hallazgos reales: la orden de instalación, el puerto predeterminado 11434 y cómo utilizar la biblioteca de Python, cada uno con un enlace de fuente.
Cuando el modelo no puede detenerse
Otro problema fue más sutil. Equipado con las herramientas adecuadas, el modelo leía páginas buenas pero nunca reconocía cuándo debía terminar. En una ejecución, la misma página se resumió con la misma pregunta más de una docena de veces sin ningún progreso adicional hasta que se alcanzó el límite de llamadas. Los hallazgos útiles estaban en el historial de mensajes, pero al no haberse escrito una respuesta final, la salida volvió a ser una disculpa.
Para hacer frente a eso, el ejecutor ahora realiza un paso adicional cada vez que una ronda se acorta: una última solicitud sin herramientas, en la que se instruye al modelo a escribir su respuesta con base en lo que ha recopilado.
async def _finalize(self, stop_reason: str) -> dict:
if not self.synthesize_on_stop:
self.messages.append({"role": "assistant", "content": stop_reason})
return {"role": "assistant", "content": stop_reason}
self.messages.append({
"role": "user",
"content": ("Stop using tools now. Using only the information you have "
"already gathered above, write the final output the task "
"asked for. Do not call any tools."),
})
resp = ollama.chat(model=MODEL_NAME, messages=self.build_messages_for_model(),
options={"temperature": MODEL_TEMPERATURE}, think=MODEL_THINKING)
msg = resp["message"]
content = msg.get("content", "") if isinstance(msg, dict) else getattr(msg, "content", "")
final = {"role": "assistant", "content": content or stop_reason}
self.messages.append(final)
return final
Tres detalles son importantes:
- Es opcional. El agente interactivo deja desactivado y simplemente informa del cese, como antes, mientras que el flujo de trabajo lo activa para que una etapa siempre genere un resultado real.
- Salva el trabajo. Los resultados de la herramienta ya se encuentran en el historial, por lo que la llamada sin herramientas puede extraer las notas de ellos, convirtiendo una interacción que se descontrola en un
notes.mdutilizable en lugar de una disculpa. - Una protección contra bucles infinitos ayuda. La misma llamada repetida tres veces seguidas ahora finaliza la interacción antes de tiempo, con error o sin él, de modo que el modelo procesa menos datos antes de que la síntesis asuma el control.
Una ejecución limpia de la investigación duró alrededor de siete minutos y generó poco más de 10,000 caracteres de notas extraídas para los cinco objetivos.
La captura de pantalla muestra que la segunda ejecución de la herramienta está finalizando y pidiendo una revisión del resultado. Con los controles, el delimitamiento del alcance y las acciones correctivas en su lugar, la investigación es lo suficientemente fiable como para seguir trabajando en ella, y el siguiente control es el más importante.
Etapa 4: el control del esquema
De todas las etapas, la elaboración del esquema merece la mayor atención, ya que a continuación viene la redacción, que es el paso más costoso. Un error detectado en la etapa del esquema cuesta menos corregir. Esta etapa combina dos resultados anteriores: los objetivos de 01-scope y las notas de investigación de 02-research, para crear un esquema organizado por módulos. El contrato establece lo siguiente:
# Stage: 03-outline
## Inputs
- Layer 4 (working): ../01-scope/output/objectives.md
- Layer 4 (working): ../02-research/output/notes.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## Process
Turn the objectives and the research notes into a module-by-module course
outline. One module per objective is a reasonable default, but merge or
split where the notes suggest it.
For each module, give a short title and three to five bullet points naming
what it covers, drawn from the notes. Order the modules so each one builds
on the one before it. Note any module that depends on something from an
earlier module.
This outline is the last checkpoint before the course is drafted, so make
it easy for a human to read and correct. Write it as markdown headings with
bullet points. Do not draft the module text yet.
## Outputs
- outline.md -> output/
Esas dos líneas de capa 4 son lo nuevo. Hacen concreto el proceso de transferencia en múltiples pasos descrito en el artículo de ICM, ya que la salida de una etapa se alimenta a la siguiente, la cual combina dos resultados anteriores. El cargador incorpora ambos como material de trabajo y el modelo genera el esquema.
Échelo a correr con mcp-workflow-stage4. Dado que el alcance y la investigación ya están completos, la ejecución lleva a cabo 03-outline, escribe outline.md y se detiene en la puerta de control.
Al ejecutar mcp-workflow-stage4 se genera el esquema y se detiene en la puerta de control, que es el último momento económico para corregir el rumbo antes de redactar.
La puerta de enlace demostró su utilidad de inmediato. Durante una ejecución, la investigación recopiló demasiada información. El curso está dirigido a quienes pasan de no saber nada a convertirse en un modelo de entrenamiento, pero las notas incluyeron información sobre claves API en la nube y cuantización de caché KV, temas que el modelo había encontrado en la documentación y registró fielmente. El esquema heredó ese contenido y añadió un módulo completo llamado “Proteger su conexión con variables de entorno”, repleto de información que los principiantes no necesitan.
Nada en el flujo de trabajo falló. El exceso de información apareció como un encabezado legible en un archivo plano, antes incluso de que se escribiera cualquier texto del curso. Basta con eliminar el módulo, reorganizar sus lecciones o redirigirlo a otra ubicación, y la fase de redacción nunca perderá tiempo en él. La corrección consiste en una sola edición en un archivo Markdown, mientras que después de la redacción sería necesario volver a leer cinco módulos y numerosas lecciones para encontrar aquel que no debería estar allí.
Un esquema de ejemplo está abierto para edición en VS Code en la fase inicial, donde los módulos o lecciones fuera del alcance se eliminan antes de que la etapa de borrador los vea.
En esa fase inicial, reduce el esquema a los módulos y lecciones que desees y vuelve a ejecutar el proceso. La cantidad de módulos corresponde simplemente al número de encabezados ## Module que contiene el archivo, por lo que al ajustar el esquema también se determina la duración del curso.
Etapa 5: redacción por módulo
En la etapa de borrador, se escribe cada módulo a partir del esquema aprobado, tomando los datos de las notas. El primer intento consistió en redactar todo el curso de una sola vez, pero no fue suficiente: algunas ejecuciones generaron cuatro módulos, mientras que otras produjeron solo uno y declararon victoria. Un modelo simplificado considera “escribir todo el curso” como una tarea enorme y tiende a detenerse temprano.
Por eso, en la etapa de borrador se recorre cada módulo por separado. Su estructura completa incluye una directiva For each que declara el bucle:
# Stage: 04-draft
## Inputs
- Layer 4 (working): ../02-research/output/notes.md
- Layer 3 (reference): ../../_config/voice.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## For each
module in ../03-outline/output/outline.md
## Process
Draft the one module shown below, and only that module. Use the research
notes for the facts (commands, ports, pitfalls) rather than inventing them,
and follow the voice and audience guides. Write a few short paragraphs of
teaching prose plus any commands the learner should run. Do not write the
other modules. Start your output with the module's heading, and output only
the drafted module, nothing else.
This is a first draft for a human to revise, so aim for a clear, correct
scaffold rather than a finished, polished module.
## Outputs
- draft.md -> output/
La fase de redacción es la única en la que se lee voice.md, el archivo final de Capa 3 del espacio de trabajo, el cual define el tono del curso:
# Voice
Write plainly, as one developer explaining something to another at the
next desk. Short instructional sentences, followed by a longer one that
explains why.
- Lead with what to do, then explain the reasoning.
- Use concrete names: real tools, real commands, real file paths.
- Hedge honestly when something is a judgement call or can fail.
- No marketing language. No hype. No "unlock", "seamless", "robust".
- One idea per paragraph. Bullets only for listing the parts of a thing.
La línea For each es la que convierte una secuencia abierta en un bucle, y el tiempo de ejecución ejecuta el proceso una vez por módulo:
fe = ctx.for_each
print(f" for each: {len(fe.modules)} {fe.item}(s) from {fe.rel_path}")
pieces = []
for i, (heading, body) in enumerate(fe.modules, 1):
print(f" [{i}/{len(fe.modules)}] {heading}")
agent.messages = []
item_block = (f"# The {fe.item} to write now\n\n"
f"## {heading}\n\n{body}")
agent.messages.append({"role": "user", "content": ctx.user_text + "\n\n" + item_block})
final = await agent.run_turn()
pieces.append((final.get("content", "") or "").strip())
content = "\n\n".join(p for p in pieces if p)
Qué hace el bucle:
- El esquema se divide en módulos en los encabezados
##, y el bucle se ejecuta una vez por cada uno. La cantidad no está fijada en cinco; cuatro módulos significan cuatro pasadas y seis, seis, según cuáles sobrevivieron la selección inicial. - Cada llamada comienza desde cero.
agent.messagesse vacía cada vez, por lo que se elabora un módulo con las notas disponibles sin recordar las de los demás, lo que hace que cada turno sea breve y autónomo. - Los resultados se concatenan en un único archivo
draft.md. Una llamada por módulo, en lugar de una sola solicitud de “escribir el curso”, es lo que permite al modelo 9B completarlos todos.
La directiva es general y no tiene un caso especial para la redacción. Cualquier etapa puede declarar For each, aunque las otras tres no tienen directiva alguna y se ejecutan en una sola vuelta. Al ejecutar mcp-workflow-stage5 se muestra el progreso de [1/5] hasta [5/5], y la ejecución de ejemplo generó poco más de 15,000 caracteres en cinco módulos.
La etapa de borrador recorre el esquema realizando una llamada al modelo por cada módulo. La cantidad de ejecuciones corresponde a cuántos elementos queden en el esquema; por lo tanto, cuatro o seis módulos significan cuatro o seis ejecuciones.
El espacio de trabajo también contiene una carpeta proposed/ que el pipeline nunca ejecuta. Es un área de almacenamiento temporal: cuando una ejecución sugiere una mejora que debería perdurar más allá de ella, como un objetivo más preciso o una regla de voz que sigue fallando al redactarse, la sugerencia se guarda allí como un archivo, y una persona la traslada a _config/ si está de acuerdo. La intervención humana es crucial aquí. Nada modifica el espacio de trabajo hasta que alguien promueve manualmente un cambio, lo cual mantiene el proceso de revisión tal como lo pretende ICM y proporciona un punto de conexión para futuras automatizaciones.
Qué funciona y qué no
Un modelo de 9B que redacta los módulos genera estructuras útiles, pero no un contenido educativo completo. La ventaja radica en el flujo de trabajo y su capacidad de interpretación, mientras que un modelo local que escribe un curso sin supervisión no cumple con ese objetivo.
Los problemas reales se observan durante las ejecuciones:
- El borrador generó de forma imprudente una unidad systemd defectuosa en la que las líneas
[Unit]y[Service]se fusionaron en una sola, y las variables de entorno se incluyeron dentro deExecStart. - Apareció una cadena de versión que no se menciona en ninguna parte de las notas, y los nombres de los modelos variaban entre
gemma2,gemma3ygemma3:2bsegún los módulos. - Dado que cada módulo se desarrolló de forma independiente sin tener en cuenta los demás, los Módulos 1 y 2 se superponían en gran medida, ya que cada uno abarcaba tanto las variables de entorno como systemd.
- La búsqueda tarda aproximadamente siete minutos, ya que cada página pasa por el resumidor en bloques; solo las Preguntas Frecuentes de Ollama ocuparon 41,000 caracteres distribuidos en ocho bloques.
- Una de las fuentes era un sistema de detección de bots de Cloudflare. El lector informó correctamente de que no contenía contenido real, por lo que el modelo procedió sin inventar nada.
Nada de esto convierte al agente en un proceso determinista, y quien lo necesite debe escribir un script. La ventaja es que las directrices se encuentran en archivos que una persona puede leer y corregir entre etapas, de modo que un borrador preliminar se corrige en una fase específica y no se descubre posteriormente por sorpresa. La línea sobre “cuatro a seis objetivos” en el contrato de alcance solo sirve como indicio para el modelo hasta que se edita el archivo, y luego se convierte en un hecho concreto. La distancia entre lo que tiende a hacer el modelo y lo que establece una persona es precisamente la razón de la existencia de estas fases.
Todo junto
Al final, hay una carpeta de espacio de trabajo por pipeline, junto al agente, que contiene un archivo CONTEXT.md para cada etapa y una carpeta output/ que almacena los resultados. El agente es el mismo agente multi-servidor de antes, y el entorno de ejecución que lo impulsa, es decir, el cargador, el controlador con filtros, la definición de herramientas y el bucle por módulo, cabe en un único archivo de unas 400 líneas.
En resumen, los componentes son:
- Un espacio de trabajo donde el contexto de cada etapa se encuentra en carpetas numeradas en formato markdown puro, editables sin tocar el código.
- Un cargador que construye el prompt del sistema de cada etapa a partir de archivos, manteniendo separados los materiales de referencia de los de trabajo, dentro de un rango de aproximadamente 900 a 1,500 tokens por etapa.
- Un controlador con filtros que ejecuta una etapa, hace una pausa para que intervenga una persona y continúa, guardando su estado en el disco.
El hecho de que un espacio de trabajo sea simplemente una carpeta significa que un trabajo diferente solo necesita otra carpeta distinta. El repositorio incluye una segunda, workspace-rust, para un curso de Rust destinado a principiantes. Esta copia la primera y solo difiere en dos archivos: el perfil del público objetivo y la idea en una sola línea; mientras que todos los contratos de etapa, el archivo de enrutamiento y la referencia de uso de herramientas permanecen idénticos a nivel de bytes. Al ejecutar mcp-workflow-stage5 --workspace workspace-rust se obtiene un curso de Rust a partir del mismo proceso. Esa es la concepción de ICM sobre configurar la fábrica en lugar del producto.
Los dos espacios de trabajo difieren únicamente en audience.md y en la idea de una sola línea: el mismo proceso, curso diferente.
Hay espacio para ampliar esto. Una sección Verify en un contrato de etapa podría hacer que la etapa vuelva a leer una salida anterior y señale desviaciones antes de que lo revise un humano, algo que el artículo del ICM describe como verificación entre etapas. Un resumen de una sola línea de los módulos anteriores podría suministrarse a cada llamada de borrador, reduciendo la superposición entre los Módulos 1 y 2. Y la carpeta proposed/ podría convertirse en algo que se llene automáticamente.
¿Por qué crear esta versión ligera a mano cuando existen CrewAI y LangGraph? Son herramientas útiles, y un flujo de trabajo que requiere agentes concurrentes, ramificaciones dinámicas o despliegue real justifica su complejidad. Sin embargo, muchos trabajos son secuenciales, revisables y repetibles, y no necesitan esas características. Para ellos, la solución más sencilla ya está disponible en cualquier equipo: una carpeta con archivos. Construir primero esta versión también muestra en qué punto las herramientas más complejas comienzan a ser ventajosas.