Scripts y comandos dentro de un skill

Por: Artiko
agent-skillsplugins-de-agentesia-agentesscriptsbashpythonseguridad

Scripts y comandos dentro de un skill

Un skill no está obligado a limitarse a prosa. La especificación reconoce un directorio convencional scripts/ que, en sus propias palabras, “contains executable code that agents can run”. Este capítulo cubre las tres formas de meter ejecución en un skill —comando puntual, script empaquetado y script autocontenido con sus dependencias inline—, cómo diseñar la interfaz para que un agente la use sin equivocarse, y dónde termina la responsabilidad del estándar en materia de seguridad. Si aún no tienes claro el esqueleto de un skill, revisa Anatomía de un skill y La especificación al detalle.

Qué dice la especificación sobre scripts/

Muy poco, y conviene saber exactamente cuánto. El texto normativo completo es:

Contains executable code that agents can run. Scripts should:

  • Be self-contained or clearly document dependencies
  • Include helpful error messages
  • Handle edge cases gracefully

Supported languages depend on the agent implementation. Common options include Python, Bash, and JavaScript.

De ahí salen tres hechos y ni uno más: scripts/ es una convención opcional —un directorio de skill PUEDE (may) contener cualquier archivo más allá del SKILL.md obligatorio, y “the conventions below are recommendations”—; los scripts DEBERÍAN (should, en minúscula en el original: la spec no declara adherencia a RFC 2119) ser autocontenidos o documentar sus dependencias, incluir mensajes de error útiles y manejar casos límite, que son las tres únicas afirmaciones normativas sobre scripts; y los lenguajes soportados dependen de la implementación del agente, con Python, Bash y JavaScript como opciones comunes, no como lista cerrada.

Todo lo demás en este capítulo procede de la guía oficial Using scripts, que es guía de autoría, no norma. Un skill que la ignore sigue siendo válido.

Cuándo un script y cuándo prosa

La prosa le dice al agente qué hacer y lo deja razonar; el script le da un procedimiento congelado y cambia qué parte del trabajo es reproducible.

flowchart TD
    A["Necesito que ocurra algo concreto<br/>durante la ejecución del skill"] --> B{"¿Existe ya un paquete<br/>que lo hace?"}
    B -->|"Sí"| C{"¿Basta invocarlo<br/>con unos pocos flags?"}
    C -->|"Sí"| D["Comando puntual en SKILL.md<br/>con la versión fijada"]
    C -->|"No, el comando crece"| E["Script probado en scripts/"]
    B -->|"No"| F{"¿El agente reinventa<br/>la misma lógica cada vez?"}
    F -->|"Sí"| E
    F -->|"No, varía por caso"| G{"¿La operación es frágil,<br/>destructiva o exige un orden?"}
    G -->|"Sí"| E
    G -->|"No"| H["Instrucciones en prosa<br/>en SKILL.md"]

La guía de buenas prácticas llama a esto bundling reusable scripts: si al comparar trazas ves que el agente reinventa la misma lógica en cada corrida, escribe el script una vez, pruébalo y empaquétalo. Sobre los comandos puntuales dice literalmente: “when a command grows complex enough that it’s hard to get right on the first try, a tested script in scripts/ is more reliable”. El criterio inverso también vale: si la tarea admite varios enfoques legítimos y el agente los resuelve bien, un script solo le quita margen — ese es el eje de Escribir buenas instrucciones.

Comandos puntuales sin scripts/

Cuando un paquete existente ya hace el trabajo, se referencia directo en SKILL.md sin crear ningún directorio; varios ecosistemas resuelven dependencias en tiempo de ejecución:

EjecutorEjemplo documentadoNotas de la guía
uvxuvx [email protected] check .No viene con Python, requiere instalar aparte. Rápido, cachea agresivamente.
pipxpipx run 'black==24.10.0' .Alternativa madura a uvx, con mayor disponibilidad en gestores de paquetes del sistema.
npxnpx eslint@9 --fix .Viene con Node.js, no hace falta instalar nada extra.
bunxbunx eslint@9 --fix .Reemplazo directo de npx; solo apropiado cuando el entorno tiene Bun en vez de Node.js.
deno rundeno run --allow-read npm:eslint@9 -- --fix .Los flags de permiso son obligatorios para acceso a disco o red. Usa -- para separar los flags de Deno de los de la herramienta.
go rungo run golang.org/x/tools/cmd/[email protected] .Integrado en el comando go, sin herramientas adicionales.

Tres consejos que la guía marca explícitamente: fija las versiones (npx [email protected]) para que el comando se comporte igual con el paso del tiempo; declara los prerrequisitos en el SKILL.md —“Requires Node.js 18+”— en vez de asumir que el entorno del agente los tiene, y para requisitos de nivel runtime usa el campo opcional compatibility, que admite hasta 500 caracteres aunque la spec advierta que “most skills do not need the compatibility field”; y mueve a un script los comandos complejos.

Cómo se referencian y ejecutan los scripts

La regla de resolución de rutas es normativa, en la sección File references de la spec: “When referencing other files in your skill, use relative paths from the skill root”. La guía de scripts lo repite: “Use relative paths from the skill directory root to reference bundled files. The agent resolves these paths automatically — no absolute paths needed.”

Nunca escribas rutas absolutas ni ~/.agents/skills/mi-skill/scripts/x.sh dentro de un SKILL.md. El cliente conoce el directorio base del skill (el padre del SKILL.md) y resuelve contra él; la guía de implementadores lo describe desde el otro lado: “resolve them against the skill’s directory (the parent of SKILL.md) and use absolute paths in tool calls”. La spec añade: “Keep file references one level deep from SKILL.md”.

Dos pasos: anunciar y luego ordenar

Anunciar los scripts hace que el agente sepa que existen; ordenar su ejecución hace que los use. La guía separa ambos pasos:

## Available scripts

- **`scripts/validate.sh`** — Validates configuration files

## Workflow

1. Run the validation script:
   ```bash
   bash scripts/validate.sh "$INPUT_FILE"
   ```

Fíjate en un detalle práctico: los ejemplos oficiales invocan a través del intérprete (bash scripts/..., python3 scripts/...), no como ./scripts/.... La especificación no dice nada sobre permisos de archivo ni sobre el bit de ejecución, e invocar por intérprete evita depender de él.

La misma convención funciona en archivos de apoyo: “script execution paths (in code blocks) are relative to the skill directory root, because the agent runs commands from there”. Aunque el bloque de código viva en references/api.md, la ruta se escribe scripts/foo.sh, no ../scripts/foo.sh. Los archivos de apoyo son el tema de Referencias, plantillas y progressive disclosure avanzada.

Scripts autocontenidos: dependencias declaradas inline

La spec pide que los scripts sean “self-contained or clearly document dependencies”; varios lenguajes permiten cumplir la primera opción sin manifiesto externo ni paso de instalación.

Python con PEP 723 — bloque TOML entre marcadores # /// al inicio:

# /// script
# dependencies = ["beautifulsoup4>=4.12,<5"]
# ///

from bs4 import BeautifulSoup

uv run scripts/extract.py crea un entorno aislado, instala las dependencias y ejecuta; pipx run también soporta PEP 723. Fija versiones con especificadores PEP 508, acota el intérprete con requires-python y usa uv lock --script para reproducibilidad total.

Deno — los especificadores npm: y jsr: hacen autocontenido cualquier script: import * as cheerio from "npm:[email protected]";. Las dependencias se cachean globalmente y --reload fuerza la recarga. Aviso literal: “Packages with native addons (node-gyp) may not work”.

Bun — auto-instala paquetes faltantes en tiempo de ejecución y la versión se fija en la ruta de import (import * as cheerio from "[email protected]";), con una trampa documentada: “If a node_modules directory exists anywhere up the directory tree, auto-install is disabled”. El mismo skill puede comportarse distinto dentro de un proyecto Node que en el directorio del usuario.

Rubybundler/inline declara las gemas en el script con un bloque gemfile do ... end. Sin lockfile, hay que fijar versiones explícitamente (gem 'nokogiri', '~> 1.16'); un Gemfile o un BUNDLE_GEMFILE en el directorio de trabajo pueden interferir.

Diseñar la interfaz para un agente, no para una persona

Cuando un agente ejecuta tu script, lee stdout, stderr y el código de salida para decidir el siguiente paso. La interfaz es el canal de comunicación.

flowchart LR
    A["SKILL.md<br/>ordena la ejecución"] --> B["Script"]
    B --> C["stdout<br/>datos estructurados"]
    B --> D["stderr<br/>progreso y avisos"]
    B --> E["código de salida<br/>tipo de fallo"]
    C --> F["El agente decide<br/>el siguiente paso"]
    D --> F
    E --> F

Nada de prompts interactivos. La guía lo marca como “a hard requirement of the agent execution environment”: los agentes operan en shells no interactivos, no pueden responder a prompts TTY, diálogos de contraseña ni menús de confirmación. Un script que se bloquea esperando entrada cuelga indefinidamente. Toda la entrada llega por flags, variables de entorno o stdin. Es una restricción del entorno de ejecución, no una cláusula del formato. En vez de imprimir Target environment: _ y quedarse esperando, el script debe fallar con Error: --env is required. Options: development, staging, production.

--help es la documentación real.--help output is the primary way an agent learns your script’s interface”. Una línea de uso, una descripción breve, la lista de flags con su default y al menos un ejemplo de invocación completa; todo conciso, porque esa salida entra al contexto del agente junto con el resto del trabajo. Los dos ejemplos del final del capítulo muestran el formato.

Mensajes de error que reorientan. Un “Error: invalid input” opaco quema un turno completo. Di qué falló, qué se esperaba y qué probar: Error: --format must be one of: json, csv, table. Received: "xml".

Salida estructurada, datos y diagnósticos separados. JSON, CSV o TSV antes que texto libre: son consumibles por el agente y por jq, cut o awk. Una tabla alineada con espacios —my-service running 2025-01-15— es difícil de parsear programáticamente; {"name": "my-service", "status": "running"} no tiene ambigüedad de campos. Y, literal: “send structured data to stdout and progress messages, warnings, and other diagnostics to stderr”.

Seis consideraciones adicionales

ConsideraciónQué implica
IdempotenciaLos agentes reintentan comandos. “Create if not exists” es más seguro que “create and fail on duplicate”.
Restricciones de entradaRechazar entrada ambigua con un error claro en vez de adivinar. Usar enums y conjuntos cerrados.
Soporte de dry-runPara operaciones destructivas o con estado, --dry-run deja que el agente previsualice qué va a pasar.
Códigos de salida con significadoCódigos distintos por tipo de fallo, documentados en el --help para que el agente sepa qué significa cada uno.
Defaults segurosEvaluar si las operaciones destructivas deben exigir flags explícitos como --confirm o --force.
Tamaño de salida predecible”Many agent harnesses automatically truncate tool output beyond a threshold (e.g., 10-30K characters)”. Por defecto resumen o límite, con flags como --offset; o exigir --output que sea archivo o - para optar por stdout.

La idempotencia se justifica por el reintento: un agente que no ve salida clara vuelve a lanzar el comando, y si el script falla al segundo intento interpreta ese fallo como un problema real y se desvía a arreglarlo.

Ejemplo completo 1: skill con script en Bash

env-audit/
- SKILL.md
- scripts/
  - audit.sh

Contenido de SKILL.md:

---
name: env-audit
description: Compara un archivo .env contra su plantilla .env.example y reporta variables faltantes, vacías y sobrantes. Úsalo cuando el usuario mencione variables de entorno, un .env incompleto, errores de configuración al arrancar la app, o antes de desplegar a un entorno nuevo.
compatibility: Requires bash 4+ and jq
license: Apache-2.0
---

# Auditoría de variables de entorno

## Scripts disponibles

- **`scripts/audit.sh`** — Compara un `.env` con su plantilla y emite JSON por stdout.

## Flujo de trabajo

1. Localiza plantilla y archivo real —por convención `.env.example` y `.env` en
   la raíz del repositorio— y ejecuta
   `bash scripts/audit.sh --template .env.example --actual .env`.
2. Interpreta el JSON: `missing` son variables de la plantilla ausentes, `empty`
   son presentes pero sin valor —causa habitual de fallos al arrancar que no
   dicen qué falta— y `extra` son variables no previstas en la plantilla.
3. Si `missing` o `empty` traen elementos, lista al usuario los nombres exactos.
   **No inventes valores** ni los deduzcas del código: pídelos.

## Gotchas

- Se ignoran líneas en blanco y las que empiezan por `#`.
- `FOO=""` cuenta como vacío y se reporta en `empty`.
- Códigos de salida: `0` completado, `2` argumentos inválidos, `3` no encontrado.

scripts/audit.sh:

#!/usr/bin/env bash
set -euo pipefail

usage() { cat <<'EOF'
Usage: bash scripts/audit.sh --template FILE --actual FILE

Compara un archivo .env contra su plantilla y emite JSON por stdout.

Options:
  --template FILE   Plantilla de referencia, por ejemplo .env.example
  --actual FILE     Archivo real a auditar, por ejemplo .env

Exit codes: 0 ok, 2 argumentos inválidos, 3 archivo no encontrado
EOF
}

TEMPLATE=""; ACTUAL=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --template) TEMPLATE="${2:-}"; shift 2 ;;
    --actual)   ACTUAL="${2:-}";   shift 2 ;;
    -h|--help)  usage; exit 0 ;;
    *) echo "Error: flag desconocido: \"$1\". Usa --help." >&2; exit 2 ;;
  esac
done

if [[ -z "$TEMPLATE" || -z "$ACTUAL" ]]; then
  echo "Error: --template y --actual son obligatorios." >&2; usage >&2; exit 2
fi
for f in "$TEMPLATE" "$ACTUAL"; do
  [[ -f "$f" ]] || { echo "Error: archivo no encontrado: \"$f\"." >&2; exit 3; }
done

claves() { grep -vE '^\s*(#|$)' "$1" | cut -d= -f1 | tr -d '[:space:]' | sort -u; }
lista() { printf '%s' "$1" | jq -R -s 'split("\n") | map(select(length > 0))'; }

MISSING=$(comm -23 <(claves "$TEMPLATE") <(claves "$ACTUAL"))
EXTRA=$(comm -13 <(claves "$TEMPLATE") <(claves "$ACTUAL"))
EMPTY=$(grep -vE '^\s*(#|$)' "$ACTUAL" | grep -E '=\s*("")?\s*$' | cut -d= -f1 | sort -u || true)

jq -n --arg template "$TEMPLATE" --arg actual "$ACTUAL" \
  --argjson missing "$(lista "$MISSING")" \
  --argjson empty "$(lista "$EMPTY")" \
  --argjson extra "$(lista "$EXTRA")" \
  '{template: $template, actual: $actual, missing: $missing, empty: $empty, extra: $extra}'

Qué cumple: no pide entrada interactiva, documenta su interfaz con --help, distingue códigos de salida por tipo de fallo, manda JSON a stdout y los errores a stderr, y es idempotente porque solo lee.

Ejemplo completo 2: skill con script en Python autocontenido

Misma estructura —csv-profile/SKILL.md más csv-profile/scripts/profile.py— con dependencias declaradas inline. SKILL.md:

---
name: csv-profile
description: Genera un perfil estadístico de un archivo CSV o TSV — tipos inferidos, nulos, cardinalidad y rangos por columna. Úsalo cuando el usuario tenga un CSV, TSV o export tabular y quiera entenderlo, detectar datos sucios o decidir cómo limpiarlo, aunque no mencione explícitamente "perfilar" ni "estadísticas".
compatibility: Requires uv and network access on first run to install dependencies
metadata:
  author: siemprelisto
  version: "1.0"
---

# Perfilado de archivos CSV

## Scripts disponibles

- **`scripts/profile.py`** — Perfila un CSV o TSV. Dependencias declaradas
  inline con PEP 723; se ejecuta con `uv run`, sin instalación previa.

## Flujo de trabajo

1. Perfila el archivo con `uv run scripts/profile.py --input datos.csv`.
2. Si supera las 50 columnas la salida se recorta: pide el resto añadiendo
   `--offset 50`. Si el resultado va a un archivo, usa `--output perfil.json`.
3. Reporta al usuario, en este orden: columnas con más de un 20% de nulos,
   columnas con cardinalidad 1 —constantes, candidatas a eliminar— y columnas
   cuyo tipo inferido no coincide con lo que su nombre sugiere.

## Gotchas

- El separador se detecta por extensión: `.tsv` usa tabulador y cualquier otra
  cosa usa coma. Para un CSV con punto y coma, pasa `--sep ";"`.
- El script lee el archivo entero en memoria: avisa si supera los 500 MB.

scripts/profile.py:

#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = ["pandas>=2.2,<3"]
# ///
import argparse, json, sys
from pathlib import Path

import pandas as pd

EXIT_OK, EXIT_BAD_ARGS, EXIT_NOT_FOUND, EXIT_UNPARSEABLE = 0, 2, 3, 4
MAX_COLUMNS = 50

def perfilar(s: pd.Series) -> dict:
    perfil = {"dtype": str(s.dtype), "nulos": int(s.isna().sum()),
              "pct_nulos": round(float(s.isna().mean()) * 100, 2),
              "cardinalidad": int(s.nunique(dropna=True))}
    if pd.api.types.is_numeric_dtype(s) and s.notna().any():
        perfil["min"], perfil["max"] = float(s.min()), float(s.max())
    return perfil

def main() -> int:
    p = argparse.ArgumentParser(
        prog="scripts/profile.py",
        description="Perfila un CSV o TSV: tipos, nulos, cardinalidad y rangos.",
        epilog="Exit codes: 0 ok, 2 args inválidos, 3 no encontrado, 4 no parseable.")
    p.add_argument("--input", required=True, help="Ruta del archivo a perfilar")
    p.add_argument("--sep", default=None, help="Separador explícito, p. ej. ';'")
    p.add_argument("--offset", type=int, default=0, help="Primera columna a reportar")
    p.add_argument("--output", default="-", help="Archivo de salida, o '-' para stdout")
    args = p.parse_args()

    ruta = Path(args.input)
    if not ruta.exists():
        print(f'Error: archivo no encontrado: "{ruta}".', file=sys.stderr)
        return EXIT_NOT_FOUND
    if args.offset < 0:
        print(f"Error: --offset debe ser >= 0. Recibido: {args.offset}", file=sys.stderr)
        return EXIT_BAD_ARGS

    sep = args.sep or ("\t" if ruta.suffix.lower() == ".tsv" else ",")
    try:
        df = pd.read_csv(ruta, sep=sep)
    except Exception as exc:  # noqa: BLE001
        print(f'Error: no se pudo parsear "{ruta}" con separador {sep!r}. Detalle: '
              f"{exc}. Prueba con --sep para fijar el separador.", file=sys.stderr)
        return EXIT_UNPARSEABLE

    cols = list(df.columns)[args.offset : args.offset + MAX_COLUMNS]
    reporte = {"archivo": str(ruta), "filas": int(len(df)),
               "columnas_totales": len(df.columns), "offset": args.offset,
               "truncado": args.offset + len(cols) < len(df.columns),
               "columnas": {c: perfilar(df[c]) for c in cols}}

    salida = json.dumps(reporte, ensure_ascii=False, indent=2)
    if args.output == "-":
        print(salida)
    else:
        Path(args.output).write_text(salida, encoding="utf-8")
        print(f'Perfil escrito en "{args.output}"', file=sys.stderr)
    return EXIT_OK

if __name__ == "__main__":
    sys.exit(main())

Este ejemplo añade tres cosas sobre el de Bash: dependencias inline PEP 723 con versión acotada, control del tamaño de salida con MAX_COLUMNS más --offset y --output con - para optar por stdout, y un código de salida dedicado al caso “el archivo existe pero no se puede parsear”, distinto de “no existe” y que lleva al agente a una acción distinta.

Permisos, herramientas y allowed-tools

Ejecutar un script implica que el cliente permita ejecutar comandos, y ahí hay dos capas que no conviene confundir. La capa del cliente: la guía de implementadores recomienda poner el directorio del skill en la allowlist del sistema de permisos para que leer sus recursos no dispare diálogos de confirmación; es una recomendación para quien construye el cliente y no cubre la ejecución de comandos arbitrarios. Y el campo allowed-tools, que la spec define como “a space-separated string of tools that are pre-approved to run” con un único ejemplo, allowed-tools: Bash(git:*) Bash(jq:*) Read; está marcado como experimental y la propia spec advierte que “support for this field may vary between agent implementations”. No es un sandbox: dice qué está preaprobado, no qué está prohibido, y no hay gramática definida más allá de ese ejemplo. Qué hace cada cliente concreto lo vemos en El ecosistema.

Seguridad: qué garantías NO da el estándar

Un skill con scripts es código de terceros que se ejecuta en tu máquina, con tus credenciales y tus permisos, y frente a eso la especificación ofrece cero mecanismos:

Garantía que no existeEstado real
Firma o verificación de autoríaNo hay ningún proceso de firma definido en la especificación.
Sandbox de ejecuciónEl estándar no define aislamiento de ningún tipo para los scripts.
Modelo de permisosNo hay permisos en el formato. allowed-tools es experimental y preaprueba, no restringe.
Declaración obligatoria de efectoscompatibility es opcional y describe requisitos de entorno, no lo que el script hace.
Registro oficial con revisiónEl repositorio del estándar declara que no mantiene un directorio de skills de la comunidad.
Versionado o dependencias entre skillsNo hay ningún mecanismo documentado.
Escaneo de contenidoskills-ref validate comprueba el frontmatter y las convenciones de nombres, nada más.

Lo único que la documentación aporta en esta dirección son dos consideraciones para implementadores de clientes, ambas redactadas como sugerencia. La primera, literal: “Consider gating project-level skill loading on a trust check” — cargar los skills que vienen dentro de un repositorio solo tras una comprobación de confianza, porque un repo clonado puede traer un SKILL.md con instrucciones y scripts que nadie revisó. La segunda es el allowlisting de permisos ya citado.

Consecuencias prácticas:

  • Leer antes de instalar. Un skill son archivos de texto: un SKILL.md y sus scripts se leen en un minuto.
  • Desconfiar del .agents/skills/ de un repo clonado. Es la ruta que la guía de clientes propone para interoperabilidad, y por eso el vector más directo: clonas, abres el agente y el skill ya está en el catálogo.
  • Fijar versiones en todos los ejecutores, y preferir scripts legibles a comandos crípticos. Como autor, no pidas más de lo necesario: si el script solo lee, que solo lea.

Nada de esto lo impone el formato: es higiene que se ejerce en el cliente y en el proceso de instalación, tema de Distribución. Al empaquetar skills junto con servidores MCP en una unidad distribuible las mismas preguntas se multiplican, y eso lo trata el curso hermano Agent Plugins Spec; para ver cómo resuelve estos detalles un catálogo real de más de cien skills, está Google Skills.

Checklist antes de dar por bueno un script

  • La ruta en SKILL.md es relativa a la raíz del skill, sin ../ ni rutas absolutas.
  • El script está anunciado en una sección de scripts disponibles y su ejecución está ordenada explícitamente en el flujo.
  • Se invoca a través del intérprete: bash scripts/x.sh, uv run scripts/x.py.
  • No hay ninguna ruta de código que espere entrada por TTY.
  • --help describe qué hace, qué flags acepta, qué códigos de salida devuelve y trae al menos un ejemplo.
  • Los errores dicen qué se recibió y qué se esperaba; los datos van a stdout en formato estructurado y el progreso a stderr.
  • Reejecutarlo dos veces seguidas produce el mismo estado final, y la salida tiene un techo previsible o paginación o --output.
  • Las dependencias están declaradas inline con versión acotada, o documentadas en SKILL.md y, si son de entorno, en compatibility.
  • skills-ref validate ./mi-skill pasa sin errores.

Para comprobar que el script además mejora el resultado del agente está Evaluar la calidad de un skill con evals: la corrida con skill contra la corrida sin él.

Resumen

  • La especificación dice de scripts/ solo tres cosas: es un directorio opcional con código ejecutable; los scripts DEBERÍAN (should) ser autocontenidos o documentar dependencias, incluir mensajes de error útiles y manejar casos límite; y los lenguajes soportados dependen del agente.
  • Hay tres niveles de ejecución: comando puntual referenciado en SKILL.md (uvx, pipx, npx, bunx, deno run, go run), script empaquetado en scripts/, y script autocontenido con dependencias inline —PEP 723 en Python, especificadores npm:/jsr: en Deno, versión en el import en Bun, bundler/inline en Ruby. El comando puntual sirve mientras sea un par de flags; en cuanto cuesta acertarlo a la primera, un script probado es más fiable.
  • Las rutas se escriben relativas a la raíz del skill, incluso dentro de archivos de references/, porque el agente ejecuta desde ahí.
  • El agente aprende la interfaz por --help y decide el siguiente paso leyendo stdout, stderr y el código de salida: datos estructurados a stdout, diagnósticos a stderr, códigos distintos por tipo de fallo. Los prompts interactivos son inaceptables, no por el formato sino porque el agente corre en shells no interactivos y el script quedaría colgado.
  • Idempotencia, restricciones de entrada, --dry-run, defaults seguros y tamaño de salida acotado evitan que un reintento del agente rompa algo o que el harness trunque justo lo importante.
  • El estándar no aporta firma, sandbox, permisos, revisión ni registro oficial; allowed-tools es experimental y preaprueba, no restringe. La única defensa documentada es una sugerencia a los clientes: condicionar la carga de skills de proyecto a una comprobación de confianza. Leer el código antes de instalarlo es responsabilidad de quien lo instala.

Siguiente: Referencias, plantillas y progressive disclosure avanzada