Capítulo 1: El Manifiesto de Cero Magia

Si has intentado construir una aplicación de IA en los últimos dos años, probablemente ya has sentido la Framework Fatigue.

Instalas una biblioteca popular. Importas un ReasoningEngine. Llamas a .run(). Funciona como por arte de magia en el ejemplo de “Hello World”. Pero en el momento en que intentas hacer algo real —como editar una línea específica en un archivo Python sin borrar los imports— se rompe.

Y como usaste un framework, no puedes arreglarlo. Te quedas atascado escarbando entre capas de clases abstractas, patrones de factoría y “Chains” tratando de encontrar el único prompt que está causando la alucinación.

No vamos a hacer eso aquí.

Este libro es una rebelión contra la “Magia.” Vamos a adoptar el enfoque de “Cero Magia”: construir un agente de codificación de nivel productivo llamado Nanocode en Python puro. Sin LangChain, sin AutoGPT, sin Pydantic.

¿Por qué? Porque un agente autónomo no es magia. Es simplemente un bucle while.

¿Qué es realmente un Agente?

Quita el marketing del capital de riesgo, y un “Agente” no es más que un termostato.

Un termostato lee la temperatura (entrada), la compara con el objetivo (decisión) y enciende el calentador (acción). Luego espera y repite. Eso es todo. Un agente de IA hace lo mismo, solo que con texto en lugar de temperatura.

El Bucle del Agente: la entrada del usuario fluye a través de un bucle while donde la Entrada (Sensor) alimenta al Cerebro/LLM (Controlador), que activa las Herramientas (Actuador), volviendo a la Entrada hasta que el Cerebro genera una Respuesta.
Figura 1. El Bucle del Agente: la entrada del usuario fluye a través de un bucle while donde la Entrada (Sensor) alimenta al Cerebro/LLM (Controlador), que activa las Herramientas (Actuador), volviendo a la Entrada hasta que el Cerebro genera una Respuesta.

Más en concreto, un agente tiene cuatro partes. El Cerebro es el LLM —una función sin estado donde envías texto y te devuelve texto. Llama a Herramientas —funciones como Leer Archivo y Ejecutar Comando— para interactuar con el mundo exterior. Todo esto vive dentro de un Bucle (un while True) que sigue iterando hasta que la tarea está completa, con la Memoria —simplemente una lista de Python— acumulando el historial de conversación en el proceso. (La lista desaparece cuando el programa termina; añadiremos almacenamiento persistente en el Capítulo 6.)

Si sabes escribir un bucle while, puedes construir un agente.

Al construirlo desde cero, tendrás algo que los usuarios de frameworks no tienen: control. Cuando nuestro agente se quede atascado en un bucle, sabrás exactamente qué línea de código lo causó. Cuando la factura de la API sea demasiado alta, verás exactamente dónde se están filtrando los tokens.

Qué Estamos Construyendo

Nanocode es una herramienta CLI que se ejecuta en tu terminal. Le hablas como a un colega. Lee tus archivos. Ejecuta tus comandos. Edita tu código.

Al final de este libro, lo habrás conectado a Claude Sonnet 4.6 (o DeepSeek, o un modelo local mediante Ollama). Le darás manos —herramientas para leer archivos, escribir archivos y ejecutar comandos de shell— y ojos para buscar en tu base de código. Y construirás un arnés de seguridad para que no pueda hacer accidentalmente rm -rf /.

Configuración del Proyecto

1. Inicializar el Proyecto

1 mkdir nanocode
2 cd nanocode
3 git init

2. Crear un entorno virtual

Nunca instales herramientas de IA de forma global. Entran en conflicto con los paquetes del sistema.

1 # Mac/Linux
2 python3 -m venv venv
3 source venv/bin/activate
4 
5 # Windows
6 python -m venv venv
7 venv\Scripts\activate

3. Instalar dependencias

Solo necesitamos tres bibliotecas:

  • requests — Para comunicarse con las APIs de LLM.
  • python-dotenv — Para cargar las claves de API desde un archivo .env.
  • pytest — Para probar nuestro código sin realizar llamadas a la API.

Crea requirements.txt:

1 requests
2 python-dotenv
3 pytest

Instalar:

1 pip install -r requirements.txt

4. Asegura tus claves

An icon of a warning1

Advertencia: Si subes tu clave de API a GitHub, los bots la extraerán y vaciarán tu cuenta en cuestión de minutos.

Crea .gitignore:

1 .env
2 __pycache__/
3 venv/
4 .DS_Store
5 .nanocode/

La Excepción AgentStop

Antes de escribir el bucle de eventos, necesitamos un mecanismo de salida limpio. Una excepción es mejor que tener sentencias break dispersas por el código.

El contexto: Las excepciones no son solo para errores; también son un mecanismo de flujo de control. Cuando el usuario escribe /q, lanzamos AgentStop. El bucle principal la captura y termina de forma limpia.

El código:

1 # --- Exceptions ---
2 
3 class AgentStop(Exception):
4     """Raised when the agent should stop processing."""
5     pass

Esto va al principio de nanocode.py. Es una excepción marcadora—sin lógica, solo una señal.

La Clase Agent

Ahora la abstracción central: la clase Agent. Almacena estado y lógica en un solo lugar, lo que facilita las pruebas.

El Contexto: Podríamos poner toda la lógica en main(). Pero entonces tendríamos que simular input() y print() para probarla. Al extraer la lógica en Agent.handle_input(), podemos probarla directamente.

El Código:

10 class Agent:
11     """A coding agent that processes user input."""
12 
13     def __init__(self):
14         pass
15 
16     def handle_input(self, user_input):
17         """Handle user input. Returns output string, raises AgentStop to quit."""
18         if user_input.strip() == "/q":
19             raise AgentStop()
20 
21         if not user_input.strip():
22             return ""
23 
24         return f"You said: {user_input}\n(Agent not yet connected)"

El recorrido del código:

  • Líneas 13-14: Constructor vacío por ahora. Añadiremos brain y tools en capítulos posteriores.
  • Líneas 18-19: El comando /q lanza AgentStop en lugar de devolver un valor especial — quien llama decide cómo gestionar el cierre.
  • Línea 24: Repite la entrada tal cual. Es un marcador de posición — más adelante, enviaremos esto al Brain.

Definir el éxito mediante pruebas

Antes de escribir el bucle principal, necesitamos pruebas.

Crea test_nanocode.py:

 1 import pytest
 2 from nanocode import Agent, AgentStop
 3 
 4 
 5 def test_handle_input_returns_string():
 6     """Verify handle_input returns a string for normal input."""
 7     agent = Agent()
 8     result = agent.handle_input("hello")
 9     assert isinstance(result, str)
10     assert "hello" in result
11 
12 
13 def test_empty_input_returns_empty_string():
14     """Verify empty/whitespace input returns empty string."""
15     agent = Agent()
16     assert agent.handle_input("") == ""
17     assert agent.handle_input("   ") == ""
18     assert agent.handle_input("\n") == ""
19 
20 
21 def test_quit_command_raises_agent_stop():
22     """Verify /q raises AgentStop exception."""
23     agent = Agent()
24     with pytest.raises(AgentStop):
25         agent.handle_input("/q")
26 
27 
28 def test_quit_command_with_whitespace():
29     """Verify /q works with surrounding whitespace."""
30     agent = Agent()
31     with pytest.raises(AgentStop):
32         agent.handle_input("  /q  ")

Ejecuta las pruebas:

1 pytest test_nanocode.py -v
1 test_nanocode.py::test_handle_input_returns_string PASSED
2 test_nanocode.py::test_empty_input_returns_empty_string PASSED
3 test_nanocode.py::test_quit_command_raises_agent_stop PASSED
4 test_nanocode.py::test_quit_command_with_whitespace PASSED

Todo en verde. Nuestro agente maneja correctamente los casos básicos.

An icon of a info-circle1

Aparte: ¿Por qué pytest? Descubre funciones que comienzan con test_ y las ejecuta. Sin código repetitivo, sin clases requeridas. El código de prueba en sí es Python puro, sin magia.

El Bucle Principal

Ahora la envoltura de E/S ligera que conecta el agente con la terminal:

29 def main():
30     agent = Agent()
31     print("⚡ Nanocode v0.1 initialized.")
32     print("Type '/q' to quit.")
33 
34     while True:
35         try:
36             user_input = input("\n❯ ")
37             output = agent.handle_input(user_input)
38             if output:
39                 print(output)
40 
41         except (AgentStop, KeyboardInterrupt):
42             print("\nExiting...")
43             break
44 
45 
46 if __name__ == "__main__":
47     main()

El recorrido:

  • Líneas 30-32: Crea el agente e imprime los mensajes de inicio.
  • Línea 36: input() bloquea la ejecución y espera a que el usuario escriba algo.
  • Líneas 37-39: Llama a handle_input() e imprime cualquier salida.
  • Líneas 41-43: Captura AgentStop (desde /q) o KeyboardInterrupt (desde Ctrl+C) y sale del bucle.
An icon of a info-circle1

Aparte: La función input() de Python lee una línea a la vez. Todos los prompts de este libro son de una sola línea. Esto mantiene el código simple — los agentes en producción usan métodos de entrada más ricos, como readline o TUIs completas.

Observa la separación: Agent.handle_input() contiene toda la lógica. main() es simplemente el pegamento de E/S. Esto hace que el agente sea testeable sin simular stdin/stdout.

Ejecútalo

1 python nanocode.py

Deberías ver:

 1 ⚡ Nanocode v0.1 initialized.
 2 Type '/q' to quit.
 3 
 4 ❯ hello
 5 You said: hello
 6 (Agent not yet connected)
 7 
 8 ❯ /q
 9 
10 Exiting...

Este es el chasis. El motor viene a continuación.

Concluyendo

Eso es el chasis: una clase Agent, un método handle_input(), un bucle while True. Todavía no hace nada útil—pero todo lo que construyamos a partir de aquí encaja en este esqueleto. Las pruebas garantizan que no rompamos lo que ya funciona conforme avanzamos.