Deje de hacer que su agente de programación vuelva a leer la base de código: el patrón LLM Wiki
La idea de Karpathy para una Wiki de LLM y openwiki de LangChain: fuentes originales, una wiki nativa para agentes y un enlace a AGENTS.md, además de tareas de actualización, evaluación y rastreo.
Fuentes: Stop Making Your AI Agent Re-Read Your Entire Codebase Every Session
La mayoría de los agentes de programación comparten un hábito irritante: cada sesión comienza desde cero; vuelven a abrir archivos, descubren de nuevo qué módulo depende de cuál y vuelven a aprender para qué sirve cada parte del código. Paga con tokens, y por lo tanto con dinero, por conocimientos que el agente tenía hace cinco minutos y que pierde cuando se cierra la ventana de contexto.
Andrej Karpathy describió este problema y esbozó una solución a la que llamó el patrón LLM Wiki. LangChain ha lanzado una implementación de código abierto, openwiki. A continuación, se examinan el patrón y la implementación uno al lado del otro: cuál es la idea, por qué es importante y cómo funciona el código detrás de una aplicación de demostración. El resultado es un modelo mental que puede aplicarse a cualquier código que un agente deba comprender.
Agentes con amnesia
Primero, el modo de fallo en términos precisos. La respuesta habitual a “¿cómo entiende un agente un código fuente?” es RAG. Se incrustan los archivos de origen, se colocan en una base de datos vectorial y se recuperan los fragmentos relevantes en el momento de la consulta.
RAG es adecuado para responder preguntas. Sin embargo, para los agentes de programación resulta insuficiente en tres aspectos.
La comprensión se vuelve a calcular cada vez. Supongamos que el agente necesita el enlace entre agent.py y memory.py. Recupera los fragmentos, el modelo deduce la respuesta una vez más, y el resultado se descarta.
La recuperación depende de las incrustaciones. Si quien escribió el código utilizó un vocabulario diferente al de la consulta del agente, los fragmentos adecuados nunca aparecen. Las incrustaciones son aproximadas, mientras que la arquitectura es exacta.
Se vuelve obsoleta sin que se note. El almacén de vectores no tiene idea de que memory.py fue refactorizado el martes pasado, y sirve fragmentos antiguos con total confianza.
La idea de Karpathy era sencilla: hacer que el modelo escribiera la documentación por sí mismo, en un formato adaptado para que los modelos la lean, y mantenerla actualizada automáticamente. Eso significa documentación para agentes, no para humanos.
Tres capas
Cada elemento de openwiki pertenece a una de las tres capas del diseño de Karpathy.
┌─────────────────────────────────────────────────┐
│ Layer 3: The Schema │
│ AGENTS.md / CLAUDE.md — "the wiki exists here" │
├─────────────────────────────────────────────────┤
│ Layer 2: The Wiki │
│ openwiki/*.md — LLM-generated structured docs │
├─────────────────────────────────────────────────┤
│ Layer 1: Raw Sources │
│ Your actual codebase — immutable to the wiki │
└─────────────────────────────────────────────────┘
Cada capa se apoya sobre la que está debajo de ella.
Capa 1: las fuentes originales
Las fuentes originales son simplemente tu base de código. Desde el punto de vista de la wiki, son inmutables: OpenWiki las lee y nunca las modifica.
El proyecto de demostración guarda sus fuentes en un directorio sample_agent/, un agente LangChain ReAct realista que la mayoría de los ingenieros de IA reconocerán.
# sample_agent/agent.py
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from .memory import build_memory
from .prompts import build_prompt
from .tools import TOOLS
def build_agent(
model: str = "openai/gpt-4o-mini",
base_url: str = "https://openrouter.ai/api/v1",
api_key: str | None = None,
temperature: float = 0.0,
) -> AgentExecutor:
llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=temperature)
prompt = build_prompt()
memory = build_memory()
agent = create_react_agent(llm=llm, tools=TOOLS, prompt=prompt)
return AgentExecutor(
agent=agent,
tools=TOOLS,
memory=memory,
verbose=True,
handle_parsing_errors=True,
max_iterations=10,
)
Se conectan entre sí cuatro componentes: un cliente de modelo (ChatOpenAI), un plantilla de prompt, la memoria de conversación y las herramientas. Es el ReAct clásico, el tipo de código para el que está diseñado OpenWiki. Las herramientas se mantienen intencionadamente simples: una búsqueda simulada y una calculadora segura:
# sample_agent/tools.py
@tool
def calculator(expression: str) -> str:
"""Evaluate a safe mathematical expression and return the result."""
allowed_names: dict = {}
try:
result = eval(expression, {"__builtins__": {}}, allowed_names)
return str(result)
except Exception as exc:
return f"Error evaluating '{expression}': {exc}"
Mire {"__builtins__": {}}. Este código elimina todas las funciones integradas de Python del ámbito de eval para que un usuario no pueda pasar algo como __import__('os').system('rm -rf /') y hacer que se ejecute. Un pequeño detalle con consecuencias de seguridad reales.
En cuanto al patrón, lo importante es que OpenWiki leerá cada uno de estos archivos, determinará cómo se relacionan y creará una página de wiki que lo explique todo una sola vez, y no cada vez que se inicie un agente.
Capa 2: la propia wiki
Aquí reside la idea central. La documentación escrita a mano se deteriora, y RAG sobre código en bruto vuelve a generar todo en cada consulta, por lo que el patrón hace que el modelo produzca Markdown estructurado adaptado a la forma en que los modelos procesan el contexto.
Cómo lo genera OpenWiki
En la aplicación Streamlit de la demostración, un botón llamado “Generar wiki” ejecuta lo siguiente:
# app.py
def run_openwiki(
args: list[str],
provider: str,
api_key: str,
model: str,
langsmith: str,
cf_account_id: str = "",
) -> tuple[str, int]:
import sys
env = _build_openwiki_env(provider, api_key, model, langsmith, cf_account_id)
result = subprocess.run(
["openwiki", *args],
cwd=str(SAMPLE_AGENT_DIR),
env=env,
capture_output=True,
text=True,
timeout=300,
shell=sys.platform == "win32", # npm .cmd files need shell=True on Windows
)
return result.stdout + result.stderr, result.returncode
Hay dos detalles importantes.
Primero, cwd=SAMPLE_AGENT_DIR se ha elegido intencionadamente. La CLI de openwiki escanea el directorio desde el que se ejecuta. Al establecer el directorio de trabajo en sample_agent/, el wiki se limita a esa base de código y no a todo el proyecto. Si se omite este parámetro, escanea desde la raíz del proyecto, generando un wiki que incluye pruebas, configuración de CI y node_modules.
En segundo lugar, shell=True en Windows. Allí, los paquetes globales de npm se instalan como scripts envolventes .cmd. Al llamar a subprocess.run(["openwiki", ...]) se busca un binario llamado openwiki, no se encuentra ninguno y se genera FileNotFoundError: [WinError 2]. Al pasar shell=sys.platform == "win32" la llamada se dirige a través de cmd.exe, que procesa correctamente los archivos .cmd. Al principio resulta confuso, pero luego resulta obvio.
Qué significa el formato “agent-native”
La documentación para humanos está escrita para ser leída de arriba hacia abajo. Se abre README.md, se comienza por la parte superior y se va construyendo el contexto a medida que se avanza. Los agentes no leen de esa manera. Ellos escanean los títulos en busca de secciones relevantes, extraen los datos necesarios y pasan a lo siguiente.
La documentación “agent-native” está diseñada en torno a ese comportamiento:
- Títulos detallados en cada nivel, de modo que se pueda escanear la estructura sin leer el texto prosaico.
- Referencias cruzadas explícitas como
Ver: patterns.md#memory-strategy, que el agente sigue de forma mecánica y no por intuición. - Resúmenes de un párrafo por componente: lo suficiente para decidir si seguir leyendo, sin pretender ser la historia completa.
- Sin espacios en blanco. Omitir la introducción tipo “En este documento exploraremos” y cualquier resumen final.
Un openwiki/architecture.md generado para el agente de ejemplo podría verse así:
# Architecture — sample_agent
## Overview
ReAct agent built on LangChain AgentExecutor. Entry point: agent.py.
Four components: LLM (ChatOpenAI via OpenRouter), tools, memory, prompt.
## Components
- agent.py - AgentExecutor wiring. See: components.md#agent
- tools.py - search (simulated) + calculator (sandboxed eval). See: components.md#tools
- memory.py - ConversationBufferWindowMemory, k=5 turns
- prompts.py - ReAct ChatPromptTemplate with agent_scratchpad placeholder
- evaluator.py - LLM-as-judge chain. See: patterns.md#evaluation
## Data Flow
question → AgentExecutor.invoke() → ReAct loop (max 10 iter)
→ tool calls → final answer → evaluator.evaluate() (optional)
Colóquelo junto a un README típico para humanos, con tres párrafos de contexto antes de que aparezca algo concreto. Los agentes no necesitan ese contexto; necesitan los hechos.
Capa 3: el esquema, que cierra el ciclo
Generar un wiki es solo la mitad del trabajo. La otra mitad consiste en asegurarse de que el agente de programación sepa que el wiki existe.
Ese es el papel de AGENTS.md y CLAUDE.md, los archivos de instrucciones que Claude Code, Cursor, GitHub Copilot y la mayoría de los agentes de programación modernos leen al inicio de una sesión. OpenWiki inserta una breve referencia en cualquiera de ellos que utilice su proyecto. Antes:
# sample_agent/AGENTS.md (before OpenWiki)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
- tools.py - available tools (search, calculator)
- memory.py - conversation memory strategy
- prompts.py - system prompt and ReAct template
- evaluator.py - LLM-as-judge evaluation chain
Y después de ejecutar openwiki --init:
# sample_agent/AGENTS.md (after OpenWiki injection)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
...
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
Key files:
- `openwiki/architecture.md` - overall structure and file responsibilities
- `openwiki/patterns.md` - coding patterns and conventions
- `openwiki/components.md` - detailed component documentation
The wiki is auto-updated daily. Always prefer wiki context over re-reading source files.
La inyección es deliberadamente breve. Incluir todo el wiki en el archivo de instrucciones agotaría la ventana de contexto al inicio de cada sesión en una base de código grande. Un puntero es suficiente. El agente lo lee, abre openwiki/architecture.md solo cuando se necesita el contexto de la base de código, y aprende lo necesario sin tocar ni un solo archivo de fuente.
La cuarta pestaña de la demostración muestra esto antes y después, simulando la inyección para que el mecanismo sea visible:
# app.py — Tab 4
agents_md_content = AGENTS_MD.read_text(encoding="utf-8")
openwiki_injection = """
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
...
"""
col_before, col_after = st.columns(2)
with col_before:
st.code(agents_md_content, language="markdown")
with col_after:
st.code(agents_md_content + openwiki_injection, language="markdown")
Elegir un proveedor de modelos
OpenWiki no se preocupa por qué LLM está detrás de él. Utiliza ChatOpenAI de LangChain, el cual lee OPENAI_BASE_URL para obtener un endpoint personalizado, por lo que funciona cualquier proveedor compatible con OpenAI. La demostración admite dos:
# app.py
def _build_openwiki_env(
provider: str,
api_key: str,
model: str,
langsmith: str,
cf_account_id: str = "",
) -> dict:
env = {**os.environ, "OPENWIKI_MODEL_ID": model}
if provider == "Cloudflare Workers AI" and cf_account_id:
cf_base = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/ai/v1"
env["OPENAI_API_KEY"] = api_key
env["OPENAI_BASE_URL"] = cf_base # LangChain reads this automatically
else:
env["OPENROUTER_API_KEY"] = api_key # openwiki's native key name
return env
OpenRouter es la opción más sencilla. Una sola clave permite acceder a la mayoría de los modelos principales a través de un único punto de conexión, y al establecer OPENWIKI_MODEL_ID=openai/gpt-4o-mini se completa la configuración.
Cloudflare Workers AI proporciona modelos desde la periferia. El modelo GLM de ZhipuAI (@cf/zai-org/glm-5.2) se ejecuta en la propia infraestructura de Cloudflare, por lo que la latencia disminuye cuando se pasa a entornos de producción y se desea utilizar un único proveedor de nube. Dado que Cloudflare ofrece un punto de conexión compatible con OpenAI en /ai/v1, no se necesita un cliente personalizado. Basta con dirigir OPENAI_BASE_URL hacia allí, y la integración ya existente de LangChain se encarga del resto.
Mantener la wiki actualizada
Cualquier sistema de documentación termina fallando de la misma manera: la gente olvida actualizarlo cuando cambia el código.
El punto de Karpathy es que esto no es un problema de disciplina sino de economía. El costo de mantenimiento aumenta con el tamaño de la base de código, mientras que la persona encargada no recibe recompensa inmediata. Con el tiempo, la documentación se desvía tanto de la realidad que nadie confía en ella, y el ciclo comienza de nuevo.
La solución en OpenWiki es openwiki --update. En lugar de volver a escanear todo, solicita a git log los commits realizados después de la última ejecución, utiliza git diff para leer solo los archivos modificados y reescribe únicamente las páginas afectadas por esos cambios. Una acción de GitHub incluida con el proyecto la ejecuta según un horario establecido:
# .github/workflows/openwiki-update.yml
name: OpenWiki Update
on:
schedule:
- cron: "0 8 * * *" # 08:00 UTC daily
workflow_dispatch:
jobs:
update-wiki:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full git history so openwiki can read diffs
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm install -g openwiki
- name: Update wiki
working-directory: sample_agent # scope to our codebase
run: openwiki --update --print
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENWIKI_MODEL_ID: openai/gpt-4o-mini
- uses: peter-evans/create-pull-request@v7
with:
commit-message: "docs: update openwiki pages"
title: "OpenWiki: daily documentation update"
branch: openwiki/auto-update
Tenga en cuenta working-directory: sample_agent, la misma opción de alcance que en la llamada a subprocess de Python. Dondequiera que implemente esto, ejecute --update desde la raíz del código, no desde la raíz del proyecto.
Abrir una solicitud de pull request es el enfoque adecuado. El auto-commit en la rama main permitiría que los documentos cambiaran sin revisión, mientras que una PR le brinda un diff para inspeccionar y la oportunidad de rechazar resultados de modelo defectuosos. La quinta pestaña de la demostración simula la actualización localmente:
# app.py — Tab 5
if st.button("🔄 Run openwiki --update", type="primary"):
with st.spinner("Checking for changes and updating wiki…"):
output, returncode = run_openwiki(
["--update", "--print"],
provider,
api_key if provider == "Cloudflare Workers AI" else openrouter_key,
model_id,
langsmith_key,
cf_account_id,
)
st.code(output, language="bash")
Un evaluador con LLM como juez
Junto con el agente existe un evaluador con LLM como juez, una técnica que merece ser analizada por separado.
# sample_agent/evaluator.py
def evaluate(
question: str,
response: str,
model: str = "openai/gpt-4o-mini",
base_url: str = "https://openrouter.ai/api/v1",
api_key: str | None = None,
) -> dict[str, int | str]:
llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=0)
chain = _JUDGE_PROMPT | llm
result = chain.invoke({"question": question, "response": response})
try:
return json.loads(result.content)
except json.JSONDecodeError as exc:
raise ValueError(
f"LLM returned non-JSON response: {result.content!r}"
) from exc
temperature=0 no es opcional aquí. El evaluador debe ser determinista, de modo que la misma pregunta y respuesta siempre obtengan la misma puntuación. Una evaluación aleatoria anula el propósito de contar con puntuaciones.
json.loads está envuelto en try/except JSONDecodeError porque a veces los modelos colocan su JSON dentro de marcos de código markdown, añaden una oración introductoria o interrumpen las respuestas largas. La excepción explícita con result.content!r en el mensaje muestra exactamente lo que devolvió el modelo, lo que facilita la depuración.
La plantilla del evaluador califica por separado la precisión, la utilidad y la claridad, ya que fallan de manera independiente. Una respuesta puede ser correcta pero no abordar la pregunta real, o indicar la dirección adecuada pero confundir al lector. Al separarlas se obtienen puntuaciones con las que se puede actuar.
Explorando el contenido con LangSmith
Al establecer LANGSMITH_API_KEY, se realiza un seguimiento de cada paso que da el agente openwiki: qué archivos lee, el mensaje que envía, lo que devuelve el modelo y cuánto tiempo dura cada llamada.
# app.py
def _build_openwiki_env(...) -> dict:
...
if langsmith:
env["LANGSMITH_API_KEY"] = langsmith
env["LANGCHAIN_TRACING_V2"] = "true"
env["LANGCHAIN_PROJECT"] = "openwiki-demo"
return env
Solo se necesitan tres variables de entorno. LangChain las detecta automáticamente sin necesidad de modificar el código de openwiki ni del agente. Ese es el patrón de observabilidad adecuado para los proyectos de LangChain: utilizar el entorno en lugar del código para la instrumentación, de modo que sea posible activar el seguimiento en producción sin tocar la aplicación.
El seguimiento también revela algo útil. La llamada --init no corresponde a una sola llamada al LLM. Se trata de un bucle del agente que incluye llamadas de planificación, lectura de archivos y escritura en el wiki, y el seguimiento muestra el conteo de tokens y la latencia de cada una. Así se puede responder a la pregunta “¿por qué --init tarda 90 segundos?”: basta con abrir el seguimiento y ver tres llamadas al modelo de 30 segundos cada una, ejecutándose una tras otra.
El flujo completo
Resumiendo todo:
Developer commits code change
↓
GitHub Action runs openwiki --update at 08:00 UTC
↓
CLI reads git diff → only changed files
↓
LLM regenerates affected wiki pages
↓
PR opened with updated openwiki/*.md
↓
Developer merges PR
↓
Agent starts next session → reads AGENTS.md → sees wiki reference
↓
Agent fetches openwiki/architecture.md when it needs codebase context
↓
Agent makes informed change without reading source files
La wiki se enriquece con el tiempo. Seis meses de actualizaciones diarias hacen que openwiki/patterns.md contenga más que la estructura actual: también registra cómo evolucionó la base de código, incluidos los patrones que surgieron durante las refactorizaciones y las razones detrás de las convenciones. Un agente nuevo o un empleado recién contratado obtiene todo eso de inmediato.
La verdadera idea clave
En esencia, el patrón no se trata de documentación. Se trata de quién asume el costo de comprender una base de código.
Bajo el modelo RAG, cada sesión del agente implica un costo: hay que volver a establecer relaciones, volver a aprender las convenciones y volver a leer los archivos que ya se han leído. El costo aumenta según la frecuencia de las sesiones.
En el modelo de wiki, el costo se paga una sola vez por cada cambio en el código, a través de una tarea programada que se ejecuta mientras nadie espera, por una fracción de centavo por página, y lo que se genera sigue ganando valor con el tiempo.
Los modelos no se cansan de mantener las wikis actualizadas. Una fecha límite no desplaza la documentación al final de su lista, y nunca dejan un “TODO: documentar más tarde” para olvidarlo. Ese desequilibrio es precisamente el punto clave.
Para probarlo, instale Node.js 18 o una versión más reciente para la CLI de openwiki y obtenga una clave API de OpenRouter o Cloudflare Workers AI.