Un agente de escritorio carga sus herramientas solo cuando las necesita, sin embeddings
El proyecto Ankita evita enviar al modelo los esquemas de todas sus herramientas en cada petición: los carga bajo demanda con listas de palabras clave.

Ankita es un asistente de escritorio open source, con interfaz Electron y CLI de terminal, que ejecuta comandos de shell, edita ficheros con diffs de aprobación, busca en la web en vivo y gestiona rutinas programadas. Funciona con modelos de GitHub Copilot y su autor, que dice tener 16 años, se topó con un problema que aparece en cuanto un agente acumula herramientas: el esquema de parámetros de cada una viaja al modelo en todas y cada una de las peticiones. Su solución ha sido no cargarlas hasta que hagan falta.
Los esquemas se pagan en cada petición
El catálogo cubre búsqueda y extracción web, Git, sistema de ficheros, gestión de procesos, programación de tareas, vigilancia de páginas, notificaciones de GitHub, memoria de proyecto, servidores MCP, generación de imágenes y voz. Cada herramienta es un módulo ESM dentro de tools/ que exporta nombre, descripción, parámetros y una función run(). Si se cargan todas de entrada, el modelo recibe cientos de líneas de esquema JSON antes de que el usuario haya escrito nada. La restricción que ha marcado el diseño del proyecto, cero dependencias npm en tiempo de ejecución para la CLI, es en realidad una restricción de contexto, no de empaquetado.
Una sola herramienta y tres decisiones
La primera: en vez de cargar cincuenta herramientas, el conjunto por defecto es pequeño y el agente cuenta con una única herramienta, find_tools. Cuando necesita algo que no está ahí, la invoca con una consulta en lenguaje natural —"busca en la web", "recuérdamelo cada día"— y recibe los esquemas correspondientes, que pasan a ser invocables en la misma sesión. El resto sigue sin cargarse.
La segunda: el emparejamiento no usa embeddings. En tools/catalog.mjs las herramientas se agrupan por categorías, y cada categoría lleva un sumario corto y una lista de palabras clave. La de procesos, por ejemplo, incluye términos como puerto, proceso, pid, dirección en uso o listener, y apunta a las herramientas de estado de puerto y de terminación de proceso. La función matchCategories() compara la consulta con los identificadores de categoría, las palabras clave y hasta los nombres de las herramientas usando expresiones regulares con límites de palabra en lugar de subcadenas simples: así "port" no salta al encontrar "transport". Todo es síncrono y puro, lo que lo hace trivial de probar con el runner que trae Node. El autor lo plantea como un intercambio consciente: los embeddings manejan mejor las paráfrasis, pero una lista curada es predecible, depurable y no cuesta nada en ejecución.
La tercera: las skills son ficheros markdown. Una skill es un SKILL.md con frontmatter (nombre, descripción y herramientas sugeridas) e instrucciones debajo. La herramienta de skills carga una por nombre y devuelve el cuerpo, con un tope de 8000 caracteres. Las herramientas sugeridas son solo pistas y la skill nunca fuerza una llamada, de modo que el conocimiento procedimental del propio repositorio se queda fuera del prompt de sistema y solo entra cuando la tarea encaja con la descripción de la skill.
El autor reconoce dónde flojea: las listas de palabras clave se mantienen a mano y se desincronizan cada vez que añade una herramienta, porque hay que decidir qué sinónimos escribirá la gente. Baraja generarlas en tiempo de compilación a partir de las descripciones y revisar el diff, curación humana con ayuda de máquina. También quiere auditar cada cierto tiempo las categorías que van siempre activas, porque "siempre cargado" es un coste que conviene vigilar. El código está en el repositorio, con compilaciones portables para Windows en cada versión publicada.


