docs
Límites

Alcance

Sobre cada petición se hacen dos preguntas, y el error es responderlas juntas. Lo que un agente puede alcanzar y de quién es la credencial que va con ella tienen radios de explosión muy distintos.

El camino está abierto

Porque un registro nunca es un solo host

deploy/config.yaml
defaults:
  grants:
    - id: web
      host: "*"
      injection:
        kind: none

A un agente al que se le pide una página hello-world le hace falta npm install antes que ninguna otra cosa, y un registro nunca es un solo host — npm es un registro y una CDN, PyPI es un índice y un servidor de archivos, un git clone son tres nombres antes de ser un checkout. Una lista de ellos es una lista equivocada por uno, y equivocada por uno es peor que cualquiera de los dos extremos: parece que funciona hasta la tarde en que no, y lo que el agente hace entonces no es levantar la mano. Lee la denegación como que internet se ha caído y escribe la página que le pidieron como un párrafo sobre no poder escribirla.

Toda petición sigue cruzando el proxy, sigue siendo cotejada, y sigue cayendo en el registro de auditoría con el host y la ruta a la que fue. Borrá la concesión web y el plano vuelve a denegar por defecto, host por host, exactamente como estaba.

Las claves no lo están

kind: none es toda la razón por la que esa línea es segura de escribir

Nada tuyo se adjunta a nada que se alcance por la concesión abierta. Una concesión sobre * que llevara una credencial pondría ese secreto en todos los servidores que el agente alcanza, así que se rechaza donde se lee la configuración en vez de descubrirse más tarde:

Invalid configuration:
  - defaults.grants[0] is host "*" with a bearer credential, which would put that secret on
    every server the agent reaches. Name the host, or use injection: { kind: none }

Un host con nombre siempre gana la petición frente al abierto, así que la clave del modelo va al modelo y a ningún otro sitio. El token se escribe sobre la petición una vez que ha coincidido, de salida — el agente nunca lo tiene, y a un agente al que convenzan de publicar su entorno en algún sitio no le queda nada que publicar.

deploy/config.yaml
agents:
  - id: scout
    grants:
      - id: github-issues
        host: api.github.com
        pathPrefix: /repos/acme/website/issues
        methods: [GET, POST]
        injection:
          kind: bearer
          token: { ref: GITHUB_TOKEN }

ref nombra una variable del entorno del propio plano de control, nunca un valor. El bloque propio de un agente se suma a los defaults en lugar de reemplazarlos, y una concesión que declare con el mismo id gana — que es como se estrecha un agente sin estrechar el resto.

Añadir un host

El rechazo llega en un turno, y la respuesta no debería ser un despliegue

│ config                                                         │
│                                                                │
│ An agent has no route out of its own: the sandbox sits on a    │
│ network with nowhere to go, and every request it makes is one  │
│ the proxy was told beforehand to allow. A host that is not on  │
│ this list is a connection refused.                             │
│                                                                │
│ A host opened here carries nothing. Keys are attached by name, │
│ in deploy/config.yaml, and that is the half of a grant this    │
│ screen has no box for — so what is added here widens where an  │
│ agent may go and not one thing about what it may spend.        │
│                                                                │
│ ● api.anthropic.com                       with a model         │
│ ● api.openai.com     /v1/responses  POST  for searching        │
│ ● api.github.com                          from the file        │
│ ● api.chess.com                           opened here          │
│ + a host                                                       │
│ ╭────────────────────────────────────────────────────────────╮ │
│ │ carries nothing   opened here   ⌫ closes it                │ │
│ ╰────────────────────────────────────────────────────────────╯ │

Host por host es una buena manera de llevar esto, y deja de serlo en cuanto añadir un host exige editar un archivo en el servidor y volver a levantar el plano. Lo que eso cuesta no es el minuto: es que el rechazo llega en el turno de un agente, horas después de la última vez que se pensó en el archivo.

Una casilla y una palabra — api.chess.com, o la URL entera que estabas mirando cuando ocurrió el rechazo, ya que el host se lee de ella. Lo que una persona tiene a mano en ese momento es la dirección del error y no el host que hay en ella. No hay campo para una ruta, un método, un id o una clave. Lo último es el punto: esta pantalla escribe injection: { kind: none } y no tiene dónde expresar otra cosa, así que la consola puede ampliar adónde va un agente y nunca puede decidir lo que gasta. Esa mitad se queda en el archivo, y por eso las filas que vinieron de él rechazan ⌫ y dicen en qué lista cambiarlas.

Una concesión que dedujo el plano está marcada con lo que la dedujo — with a model, for searching — porque un host del que no podés dar cuenta es uno que nadie se atreve a cerrar. Modelos y búsqueda web son de donde vienen esos dos.

Lo que no pretende

El límite que sostiene el peso es el que rodea los secretos

Un agente que puede ejecutar código en un sandbox y alcanzar internet puede mandar lo que leyó a algún sitio que no elegiste. Eso ya era cierto de cualquier concesión lo bastante amplia para ser útil, y es la razón por la que lo que se mantiene lejos de él es la credencial y no la dirección. Un agente robado obtiene el alcance que tenía; no obtiene tu cuenta. Confianza es el resto de eso.

Hay una clase de concesión que lleva un alcance que el método no puede llevar: un repositorio git, donde clonar y pushear son los dos un GET y un POST bajo la misma ruta. Repositorios es cómo se nombran las ramas, y cómo se rechaza un push a la equivocada.