Bash es una de las interfaces más flexibles y poderosas que se le exponen hoy a los agentes de IA. En el sistema correcto, un modelo que emite grep, curl, tar o un pipeline de shell está produciendo una acción ejecutable que puede leer archivos, mutar un workspace, abrir conexiones de red y encadenar herramientas. Para el AI Red Team de NVIDIA, eso convierte la generación de comandos en un objetivo de investigación útil: si los modelos pequeños pueden guiarse hacia estructuras de comando válidas y compatibles con políticas, se vuelven componentes más confiables para flujos agentic desplegados en entornos diversos.
La técnica que aplicaron es constrained decoding: una modificación al sampling autoregresivo donde, en cada paso de generación, se calcula el logit como siempre, pero antes de elegir un token se aplica una gramática para alterar la distribución (típicamente bloqueando ciertos tokens). PICARD usó esta técnica para mejorar la generación de SQL. NVIDIA aplicó el mismo concepto a Bash.
¿Cuáles fueron los resultados del experimento?
El equipo corrió 13 small language models sobre 299 tareas y subió el pass rate promedio de 62,5% a 75,2%. El resultado más fuerte fue sobre Qwen3-0.6B, donde el pass rate saltó de 16,7% a 59,2%. Los autores publicaron el código en GitHub bajo grammargen.
¿Por qué Bash en particular?
Los sistemas agentic usan cada vez más LLMs para generar código y comandos que se ejecutan en shells, notebooks, sistemas de build y jobs de CI. El desafío de seguridad no es solo si el modelo "entiende" la tarea: es si puede generar una acción sintácticamente válida, acotada al entorno previsto y constrained lejos de formas inseguras.
Bash es un ejemplo compacto de ese problema:
- Los errores de sintaxis no perdonan, y el riesgo escala con la complejidad de la tarea.
- Un comando válido puede ser operacionalmente peligroso (un comando de red sin timeout, un comando destructivo con un path demasiado amplio).
- La composición de shell multiplica el espacio de estados: pipes, redirects, command substitution, heredocs, loops y condicionales cambian lo que el modelo debe emitir.
- Los modelos pequeños suelen acertar el binario raíz pero fallan en la sintaxis exacta, el orden de argumentos, las comillas, los operadores de control o la terminación.
La pregunta central de la investigación: ¿puede el constrained decoding mejorar lo suficiente la confiabilidad del Bash generado por modelos chicos como para hacerlos útiles en flujos agentic?
Generación de gramáticas
Escribir a mano una gramática para cada comando es frágil. Los comandos Bash tienen muchos flags, alias, valores opcionales, argumentos posicionales y variaciones de sintaxis. En vez de eso, grammargen convierte evidencia estructurada de comandos en gramáticas Lark.
La representación intermedia captura las piezas necesarias para constrained decoding: nombres de comandos y alias, flags cortas booleanas y largas, flags con valor (como -A 3 o --max-count=10), argumentos posicionales (paths, patrones, palabras, enteros) y repeticiones acotadas para mantener finito el estado de decodificación.
Por ejemplo, una gramática generada para grep incluye una regla start a nivel comando, repetición acotada de opciones, flags cortos combinados, alternativas de flags largos, valores tipados y terminales compartidos:
start: "grep" (WS grep_opt){0,8} WS WORD (WS PATH){0,5}
grep_opt: "-" /[EFGHILPRTUVZabchilnoqrsvwxz]+/
| "-e" WS WORD
| "-f" WS PATH
| "-m" WS /[0-9]+/
| "--ignore-case"
| "--recursive"
| "--regexp" ("=" | WS) WORD
| "--file" ("=" | WS) PATH
| "--max-count" ("=" | WS) /[0-9]+/
WORD: /[^\s|><&;()]{1,200}/Esta gramática no pretende probar que cada comando aceptado sea seguro: define un borde de decodificación que restringe al modelo a tokens compatibles con la gramática. La política puede codificarse luego como restricciones adicionales o aplicarse como control separado. grammargen puede generar gramáticas a partir de la documentación --help o de schemas JSON de herramientas.
Aplicación durante la decodificación
Las gramáticas se aplican a la inferencia con llama.cpp usando llguidance. La evaluación comparó el desempeño nativo del modelo contra un modo de "constrained retry" que usaba decodificación restringida y luego validaba el output con tree-sitter-bash antes de ejecutar.
Si tree-sitter lanzaba un error, el sistema pasaba el error como contexto de vuelta al modo nativo, garantizando al menos performance nativa. Así se podía elevar el rendimiento del modelo ejecutando un solo comando en el entorno de prueba.
Por ejemplo, ante el prompt "Base64 encode the contents of /workspace/plain.txt using openssl", se espera que el modelo siga openssl con base64. Pero el token de mayor logit para SmolLM2-360M-Instruct es 2, lo que generaría un comando inválido. Con una gramática openssl aplicada, el siguiente token resulta base (y autoregresivamente termina en openssl base64 con tarea completada).
¿Cuánto sube el pass rate por tier?
Cada modelo se evaluó sobre las mismas 299 tareas, divididas en cuatro tiers:
- Tier 1: 57 tareas de primitivas I/O
- Tier 2: 65 tareas de filtrado y transformación
- Tier 3: 139 tareas de reconocimiento y acción
- Tier 4: 38 tareas de constructos de shell
A través de los 13 modelos, constrained retry mejoró el pass rate promedio de 62,5% a 75,2%. Cada modelo mejoró en general, pero las ganancias fueron mayores para los baselines más chicos y débiles. El Tier 4 incluyó tareas como chaining, backgrounding y loops que requerían combinaciones de gramáticas; ahí la generación restringida resultó demasiado restrictiva o demasiado permisiva.

A través de 3.887 resultados pareados modelo-tarea, constrained retry preservó 2.248 passes nativos, arregló 676 fallas nativas, regresó 181 passes nativos y dejó 782 fallas sin resolver. En neto: +495 tareas pasando sobre el run completo, aunque hubo regresiones donde la gramática chocaba con el bias del modelo o donde la gramática era incompleta.

La gramática recupera muchas fallas de surface-form en tiers 1-3. El Tier 4 es más difícil con constructos de Bash más ricos (scripts multilínea, heredocs, loops, condicionales, command substitution, process substitution), que necesitan o gramáticas más ricas o una estrategia que pueda caer selectivamente al modo nativo.
Implicancias de seguridad
Constrained decoding cambia la distribución de probabilidad del output del modelo antes de la ejecución. Eso lo hace útil, pero solo como una capa más en un control stack agentic. Las propiedades de seguridad interesantes son:
- Confiabilidad como propiedad de seguridad. Restringir la superficie de acción puede disminuir la incertidumbre en el espacio de acción.
- Política codificada como sintaxis. Las gramáticas pueden prohibir o requerir formas y argumentos, por ejemplo excluyendo flags inseguras o requiriendo timeouts.
El experimento completo, junto con código y gramáticas generadas, está disponible en el repositorio grammargen en GitHub.




