Saltar a contenido

El catálogo de capabilities

Todo lo que un agent puede hacer viene de uno de dos sitios: una capability registrada en el código de este despliegue, o un servidor MCP que alguien conectó. Esta página es la primera lista.

Una capability es la unidad que merece la pena encender o apagar: una línea en el Builder, una entrada en el spec. Deliberadamente no es «una herramienta»: la búsqueda de conocimiento es una única decisión para quien configura un agent, y si hoy expone una función y el mes que viene tres no es problema suyo. Las capabilities cubren además cosas que no son herramientas en absoluto, y por eso thinking y clock están aquí sin herramientas listadas.

La API es la autoridad; esta página es una instantánea

GET /api/v1/agents/capabilities sirve el registro tal y como está en el despliegue en marcha, incluido todo lo añadido desde que se escribió esta página. El Builder dibuja su selector y sus formularios de configuración a partir de esa respuesta. Si ambos se contradicen, la API tiene razón.

El Builder nombra cada capability en el idioma de la consola y la explica con palabras sencillas: qué le permite hacer al agent, dos o tres ejemplos de uso, qué necesita y qué no hace nunca. La consola no tiene traducción al español, así que allí se ven los nombres en inglés que da la tabla de la versión inglesa. Cinco capabilities que solo ajustan cómo se lleva un run - conversaciones largas, imágenes fuera del historial, recordatorio de instrucciones, límites de resultados de herramientas y búsqueda de herramientas - quedan plegadas bajo Advanced hasta que alguien abre el grupo, busca o activa una de ellas.

En los ajustes de una capability, los límites, la vida de la caché, los prompts y el resto del ajuste fino quedan plegados en Ajustes avanzados, que se abren solos donde alguno se haya establecido.

Las capabilities que tiene el agente inicial - la plantilla AgenticOS, que una organización nueva recibe en su primer arranque - llevan la insignia Recommended, y un agente creado con New agent empieza con exactamente esas activadas, configuradas como las configura la plantilla, junto con sus instrucciones, skills, herramientas de la plataforma y presupuesto. Su autor desactiva lo que el agente no deba hacer.

Acabe con las capabilities que acabe, cada run recibe en sus instrucciones, bajo Your setup, cuáles tiene y cuáles no - así describe solo lo que puede hacer y dice qué habría que activar para el resto.

Cuando una petición necesita una que el agent no tiene, muestra en el chat una tarjeta con ella y su motivo. Quien puede editar el agent la activa desde la tarjeta, vinculada como la vincula el agent inicial y comprobada como comprueba una publicación. Se publica en el acto solo si el borrador no tenía nada sin publicar y esa persona puede publicar; si no, espera en el borrador al Builder. Cualquier otra persona se lo pide desde la tarjeta al propietario del agent, que recibe una notificación que abre el Toolbox del agent.

Dos de ellas dependen de lo que ejecuta el despliegue. El espacio de trabajo es un contenedor en el servicio de sandbox del propio despliegue, que el bootstrap y la primera publicación registran como predeterminado de la organización; sin él solo guarda archivos. El navegador web apunta al servicio de navegador del despliegue (BROWSER_SIGN_IN_CDP_URL) y se omite donde no lo hay.

Qué se entrega

id Nombre Categoría Herramientas Scope Clave Voz
knowledge Búsqueda de conocimiento knowledge search_documents knowledge:read — tal cual
skills Skills knowledge read_skill_resource knowledge:read — adaptada
context Contexto knowledge list_context, read_context — — tal cual
memory_files Memoria knowledge list_memory, read_memory, write_memory, edit_memory, delete_memory — — tal cual
memory_mem0 Memoria (mem0) knowledge remember, recall — obligatoria tal cual
conversation_search Conversaciones anteriores knowledge search_conversations, read_conversation conversations:read — tal cual
web_research Búsqueda web research web_search web:read para servicios de pago tal cual
web_fetch Leer páginas web research web_fetch web:fetch — tal cual
browser_use Navegador web research browse_web, browser_open, browser_read, browser_click, browser_type, browser_select, browser_scroll, browser_back web:browse mediante el extra browser delegada
code_execution Cálculos analysis run_python code:execute — tal cual
sandbox Sandbox analysis ls, read_file, glob, grep, write_file, edit_file, execute sandbox:execute para Daytona delegada
charts Gráficos analysis create_chart — — tal cual
visuals Visuals analysis draw, revise_visual, read_visual — — tal cual
image_generation Generación de imágenes analysis generate_image — obligatoria tal cual
text_to_speech Texto a voz analysis generate_speech — obligatoria tal cual
video_generation Generación de vídeo analysis generate_video — obligatoria delegada
coding_agent Coding agent analysis run_coding_agent sandbox:execute obligatoria delegada
subagents Subagents reasoning task, check_task, wait_tasks, list_active_tasks, answer_subagent, send_message_to_subagent, soft_cancel_task, hard_cancel_task, create_agent, delegate agents:delegate — delegada
planning Planificación reasoning write_plan, read_plan, add_task, update_task_status, update_task_statuses, remove_task, add_subtask, set_dependency, get_available_tasks — — tal cual
thinking Razonamiento reasoning ninguna, a propósito — — mapeada
system_reminders Recordatorio de instrucciones reasoning ninguna, a propósito — — sin efecto
tool_search Búsqueda de herramientas utility ninguna, a propósito — — descartada
clock Fecha y hora utility ninguna, a propósito — — tal cual
guardrails Reglas de protección utility ninguna, a propósito — — en las transcripciones
compaction Conversaciones largas utility ninguna, a propósito — — sin efecto
media Imágenes fuera del historial utility ninguna, a propósito — — sin efecto
tool_output_limits Límites de resultados de herramientas utility read_tool_result — — tal cual
artifacts Aplicaciones utility publish_artifact, read_artifact — — tal cual
channel_tools Información del canal de chat channels get_channel_info, list_channel_members, search_channels, read_channel_history, send_channel_file — — no disponible
ask_user Preguntar al usuario utility ask_user_question — — en pantalla
follow_ups Follow-ups utility suggest_next_steps, propose_setup — — no disponible

Siete de ellas no tienen herramientas a propósito. thinking cambia cómo trabaja el modelo, no qué puede alcanzar, clock pone la fecha en las instrucciones, tool_search aporta su función de búsqueda solo cuando envuelve un toolset con herramientas diferidas — por sí sola no declara nada —, guardrails inspecciona y reescribe el texto que circula por un run, compaction reescribe el historial que lleva una petición, media reescribe cómo se almacena un historial compactado, y system_reminders añade texto de guía al final de la petición.

Ninguna de las siete deja nada que aprobar a una persona, así que ninguna declara una herramienta. Una capability sin herramientas de verdad lo dice con tools=() en lugar de omitir el argumento; consulta Añadir una capability.

Esta columna es lo que una capability declara, que no siempre es lo que se le ofrece a un modelo. La delegación es el único sitio donde ambas difieren: create_agent y delegate aparecen solo bajo allow_dynamic, y answer_subagent no aparece para nadie — las dos cosas se explican en Delegación más abajo.

La columna Voz es lo que hace una capability en una llamada de voz. Tal cual funciona sin cambios. Los skills de una capability adaptada se cargan como instrucciones bajo demanda, sin su herramienta de lectura. La búsqueda de herramientas descartada da a la llamada sus herramientas MCP directamente, porque las herramientas de una llamada quedan fijas cuando se conecta. El razonamiento mapeado se convierte en el propio ajuste de razonamiento del modelo en tiempo real. Delegada se ejecuta en el modelo de texto propio del agent, al que la llamada pasa una tarea (Pasar trabajo); la tarjeta Voice del Builder las enumera para un agent que las tiene, igual que trabajar mientras corren herramientas, que solo manda al fondo una herramienta delegada. En pantalla hace sus preguntas en la tarjeta de la vista de la llamada en /chat. La consulta del canal de chat no está disponible, porque una llamada nunca se hace en un canal, y tampoco Follow-ups, cuyos botones una llamada no tiene dónde mostrar. Sin efecto marca tres que actúan sobre las peticiones al modelo de un run de texto y sobre su historial almacenado, que una llamada no envía.

Los guardrails revisan las transcripciones de la llamada. El resultado de una herramienta se revisa, y se bloquea, antes de que el agent lo lea; la comprobación antes de que una herramienta se ejecute es la aprobación, para las herramientas que la necesitan. Los guardrails no revisan los argumentos de una herramienta. La API del catálogo lleva cómo participa cada capability en una llamada en el campo voice: live en el propio modelo de la llamada, hand_off o never.

Una de ellas no está en el Toolbox en absoluto. channel_tools se elige por bot vinculado en Where this agent is available, y la publicación rechaza un spec que intente llevarla — consulta Consulta del canal de chat.

search_documents — Busca en los documentos de la organización los pasajes relevantes para una pregunta.

Busca en las colecciones que vincula el spec del agent y cita lo que ha usado. El modelo pide qué buscar, nunca dónde: las colecciones se resuelven desde el spec antes del run y se entregan a la capability, así que un agent no puede alcanzar una colección que nadie le conectó.

Cada resultado nombra su documento y dónde está la coincidencia en él. Un PDF, una presentación o un archivo de procesador de textos como .doc, .odt o .rtf da la página. Un archivo Markdown, una hoja de cálculo, una página web o una imagen no tiene una página que un lector pueda abrir. Da solo el nombre del documento, igual que los archivos de texto y .docx. Los chunks no conservan límites de sección fiables, por lo que una línea que empieza por una almohadilla no se interpreta como encabezado: puede ser un comentario de un bloque de código que empezó en el chunk anterior.

Lo decide el tipo del documento al mostrar el resultado, así que una colección ingerida antes no necesita volver a ingerirse.

Configuración Valor por defecto Rango
default_top_k 5 1–50
self_query_enabled false activado / desactivado
query_analysis_mode off off, multi_query, hyde
query_analysis_max_variants 3 1–5
parent_context off off, window, parent

default_top_k se aplica solo cuando el modelo no pide un número por su cuenta.

parent_context activa la recuperación small-to-big. La coincidencia y el ranking siempre operan sobre los fragmentos pequeños y precisos; esta opción solo decide cuánto contexto circundante se devuelve con cada coincidencia, ensamblado en la ruta de retorno:

Modo Lo que recibe el modelo
off Solo el fragmento coincidente — el valor por defecto, sin cambios
window El fragmento coincidente con el fragmento anterior y el siguiente del mismo documento
parent El fragmento coincidente con tanto de su documento alrededor como quepa, el texto más cercano primero

El fragmento coincidente nunca se acorta. Solo el texto añadido a su alrededor cuenta contra dos límites fijos - 8.000 caracteres por resultado y 24.000 por búsqueda -, así que activar el modo nunca le muestra al modelo menos que off. Un pasaje sigue siendo continuo: crece hacia fuera desde la coincidencia y se detiene en el primer fragmento que no cabe o que ya se devolvió con un resultado anterior, de modo que nunca se une texto que no era contiguo en el documento.

Los fragmentos se leen por su posición alrededor de la coincidencia, nunca el documento entero, y la expansión permanece dentro del propio ámbito de inquilino y colección de quien llama. Nunca cambia qué fragmentos coincidieron, sus puntuaciones ni sus citas; una cita dice with surrounding text cuando el pasaje va más allá del fragmento que nombra.

self_query_enabled activa la self-query, desactivada por defecto. Cuando una búsqueda se ejecuta sin ningún filtro que el propio modelo haya indicado, un LLM lee la pregunta — «PDF del mes pasado sobre onboarding» — y deriva los filtros de negocio que implica: un origen, un tipo de documento, una unidad organizativa, un rango de fechas. Los filtros explícitos del propio modelo siempre ganan; la self-query solo rellena el hueco. Cuando se aplican filtros inferidos, el resultado empieza con una línea que los nombra, y el modelo puede repetir la búsqueda sin ellos pasando infer_filters=false.

El objeto inferido es el mismo filtro validado que aporta quien llama, así que no lleva ningún campo de tenant ni de autorización y no puede ampliar el acceso: solo puede acotar dentro del propio tenant y las colecciones del agente. Una unidad organizativa se conserva solo si las colecciones vinculadas la contienen de verdad, leídas en el mismo ámbito que la búsqueda; unas colecciones que suman más de 200 unidades no ofrecen ninguna para la inferencia. Nunca se infiere un id de documento. Una inferencia vacía o fallida busca sin filtro dentro de ese ámbito aún aplicado.

Coste: cada búsqueda que el modelo ejecuta sin filtros propios hace una petición adicional al modelo para la inferencia (dos si su salida necesita corrección). Se ejecuta en el propio modelo del agente, se imputa a la ejecución como cualquier otra petición y se rechaza antes de enviarse cuando el presupuesto ya está agotado. Se traza como las propias peticiones del agente, así que un agente configurado para no registrar contenido también deja aquí la pregunta fuera de sus trazas.

Vinculada sin colecciones, esta capability no aporta nada: no se adjunta en absoluto. Una herramienta de búsqueda que siempre devuelve vacío es peor que no tener ninguna, porque el modelo sigue intentándolo y razona a partir del silencio.

Análisis y expansión de la consulta

Las preguntas cortas, poco especificadas o con vocabulario que no coincide recuperan de menos. Desactivado por defecto, query_analysis_mode expande opcionalmente la consulta antes de la recuperación:

Modo Qué hace Coste
off Busca la consulta tal como está escrita ninguno
multi_query El modelo del run escribe hasta query_analysis_max_variants reformulaciones; el original y las variantes se buscan por separado y sus resultados se fusionan una llamada al modelo, más una recuperación por consulta
hyde El modelo del run escribe una breve respuesta hipotética, y la recuperación se ejecuta contra su embedding una llamada al modelo

multi_query y hyde añaden cada uno una llamada al modelo antes de la búsqueda, así que cambian latencia y un poco de gasto por recuperación en preguntas difusas. multi_query además recupera una vez por consulta, una tras otra, y cada recuperación genera el embedding de su propia consulta —las variantes no se agrupan en una sola llamada de embedding—, así que mantén query_analysis_max_variants bajo para acotar la ramificación.

Ambos modos usan el propio modelo del agente —no hay un modelo aparte que configurar— y su coste se mide contra el presupuesto del run como cualquier otra llamada al modelo. Un presupuesto agotado omite la expansión sin llamar al modelo, y lo mismo ocurre con un modelo que falla o no puede responder a una petición simple: la búsqueda se ejecuta entonces con la consulta tal como está escrita en lugar de fallar.

La expansión amplía la recuperación, nunca el acceso. Cada consulta que produce se busca bajo el mismo ámbito de inquilino y los mismos filtros de negocio que el original, así que una consulta expandida nunca puede alcanzar un documento de otra organización o fuera de ámbito. Se combina con el reordenamiento: la expansión amplía el conjunto de candidatos y los resultados se fusionan, y un reranker reordenaría lo que devolvió la fusión. Con parent_context activado, el texto circundante se añade una sola vez, a los resultados fusionados, así que sus límites cubren toda la búsqueda y no cada consulta por separado.

Skills

read_skill_resource

Conocimiento escrito que el agent carga solo cuando decide que es relevante, un skill cada vez — la alternativa sería un campo de instrucciones que crece hasta que cada run paga por cada procedimiento. Consulta Skills para saber qué es un skill y cómo llega a una organización.

Cada skill vinculado es una capability propia. Su nombre y su descripción están en el catálogo que el modelo lee en cada turno, y el modelo trae el cuerpo con load_capability, la herramienta del propio framework de agents: por eso no está en la lista de arriba y por eso un spec no puede concederla, ni controlarla, ni renombrarla. read_skill_resource es la única herramienta que aporta esta capability, y solo aparece cuando al menos un skill vinculado trae un archivo junto a sus instrucciones.

La herramienta viene de pydantic-ai-skills, así que su nombre y su redacción los cambia otra persona. Un test de deriva compara lo que declara el registro con las herramientas que realmente se le ofrecen al modelo, y eso es lo que avisa el día que ocurra.

run_skill_script está apagada en lugar de expuesta: los archivos de un skill llegan a un run bajo /workspace/skills/, donde los ejecuta el propio execute del sandbox bajo los topes del operador, y una segunda vía de ejecución sería un segundo juego de reglas que equivocar.

Contexto

list_context, read_context

El contexto permanente de una organización, puesto en el run en lugar de pedirlo: un glosario, un tono de marca, una matriz de escalado. Cada archivo vinculado lleva un mode: un archivo inject se inserta literalmente en las instrucciones, y el modelo simplemente lo conoce; un archivo link queda fuera del prompt y se alcanza con read_context, así que un archivo grande o que casi nunca hace falta no cuesta nada hasta que el modelo decide que es relevante. list_context informa de lo que hay disponible sin los cuerpos. Las dos herramientas aparecen solo con un archivo link vinculado; un agent cuyos archivos son todos inject aporta instrucciones y ninguna herramienta.

El contenido inyectado se enmarca como material de referencia: delimitado y precedido de una línea que le dice al modelo que lo trate como información y no como instrucciones, porque el cuerpo lo escribe una persona y llega literalmente al modelo. La delimitación es un mejor esfuerzo contra la fuga accidental: un cuerpo que contenga una etiqueta de cierre </context-file> o </context-files>, o un nombre o formato con una ", se neutraliza para que no derrame texto en las instrucciones de confianza. No es una frontera de seguridad: quien tenga context:edit sigue pudiendo inyectar a propósito. El contenido es texto: un documento que haya que buscar va en una colección de conocimiento, no aquí.

Vinculada sin nada aprovechable — ningún archivo, o solo archivos link con la herramienta de lectura apagada —, esta capability no aporta nada y no se adjunta, igual que knowledge vinculada sin colecciones tampoco se adjunta. Los archivos se gestionan bajo /api/v1/context y se vinculan a un agent por id (AgentSpec.context_ids).

Memoria

list_memory, read_memory, write_memory, edit_memory, delete_memory

Notas que un agent guarda por su cuenta entre conversaciones, indexadas por otra que él mismo mantiene. Donde context es una biblioteca que una persona escribe y vincula a muchos agents, la memoria es del propio agent: escribe con herramientas en mitad del run, y aquí no escribe nadie más. No se vincula por id: activar la capability le da al agent sus notas.

MEMORY.md es el índice, y se le muestra al agent en cada petición. Es una nota corriente que el agent escribe y edita con las mismas herramientas que cualquier otra, y la capability la inserta en las instrucciones igual que se inserta un archivo de contexto vinculado. Así el agent se encuentra con lo que ha guardado antes de decidir nada, y abre una nota listada con read_memory cuando la línea dice que merece la pena leerla, en vez de tener que decidirse a llamar a una herramienta de listado que un modelo más ligero rara vez llama.

De quién son las notas, y quién puede oírlas

Una nota pertenece a una sola persona o a un solo chat de grupo, y un run toca exactamente un almacén: el de la propia conversación. Cuál es se deriva en el servidor a partir de quién va a oír la respuesta, nunca del modelo, así que ninguna herramienta recibe un ámbito y no hay nada que el agent pueda confundir.

  • Uno a uno (chat web, un mensaje directo): las notas son de esa persona y nadie más las lee jamás. La misma persona alcanza un único almacén desde ambas vías: una cuenta de chat vinculada se resuelve a su cuenta y no a la superficie por la que llegó.
  • En un chat de grupo las notas son del chat, y todos los del chat las leen. Las notas propias de quien habla no son alcanzables ahí: algo anotado a solas con alguien no se lee en voz alta donde lo ve un canal entero.
  • En un widget público o en un embed no hay a quién atribuir nada, así que no hay almacén, y las herramientas lo dicen en vez de guardar en algún sitio. Un run iniciado con una clave API tampoco lo tiene: la clave actúa por quien la emitió, pero quien la tiene no es esa persona.
  • En una programación o en un trigger de evento tampoco hay almacén, aunque el run se ejecuta como el creador del trigger. El creador presta al run su autoridad, no su identidad, así que nadie escucha y las herramientas se niegan. Pon un dato que necesite un run programado en el prompt del trigger o en un archivo de contexto vinculado. Consulta Trigger, o deja que el trigger se ejecute como su creador: un horario lee y escribe entonces el almacén del creador, y un evento solo lo lee.

No hay un almacén para toda la organización. Existió uno y se retiró: era un segundo mecanismo para lo que ya hacen los archivos de contexto — conocimiento permanente que una persona escribe y vincula a agents — y un solo trabajo con dos mecanismos es la forma en que los dos acaban contradiciéndose. La memoria es lo que aprendió el agent; lo que escribe una persona va en el contexto.

Un interruptor, Allow personal memory, elimina por completo el almacén por persona, por motivos de cumplimiento o de privacidad; las notas guardadas en chats de grupo se quedan.

Qué se inyecta, y qué solo se consulta

Un resultado de herramienta es algo que el modelo sopesa; las instrucciones son lo que obedece. Por eso el índice llega al prompt solo donde su contenido no habría podido dirigir a nadie más que a quien lo lee: en una conversación uno a uno se inyecta, y en un chat de grupo sigue siendo alcanzable con read_memory y no se inyecta nunca. Las notas de una sala no tienen ámbito propio, así que la frase de un compañero llegaría si no como instrucciones de otro compañero en el mismo canal.

Un índice de más de unos 6.000 caracteres se deja fuera en vez de recortarse. Medio índice — que termina a mitad de línea, a mitad de nombre de archivo — es peor que ninguno.

Leerla, y borrarla

Nadie hojea las notas de otra persona por su rol en la organización. Esa era toda la respuesta anterior —borrado y ninguna lista— y estaba medio bien: una lista es un instrumento de vigilancia sobre el almacén de un colega, y lo contrario sobre el propio. Por eso la respuesta tiene ahora tres partes (#1594).

El tuyo, siempre, en Ajustes → Memoria. Ningún permiso lo protege, porque la respuesta es la misma para un Viewer y para un Owner: lo que los agentes de aquí han escrito sobre ti, a través de todos ellos, con cuál escribió cada nota y cuándo. Tres cosas que puedes hacer con una nota:

Dejar de usarla La nota deja de listarse, leerse y de ser editable por herramienta alguna, así que deja de llegar al modelo — y sigue existiendo, para que la mires y la restaures. La respuesta intermedia para una nota que está mal o es demasiado personal y de la que aún no estás seguro de querer deshacerte.
Volver a usarla La restaura.
Borrarla Desaparece.

El nombre de una nota suspendida sigue ocupado, así que un agente que lo escriba otra vez revive la fila con contenido nuevo. Eso no deshace la suspensión: lo que suspendiste queda sobrescrito, y la fila guarda algo que el agente ha aprendido desde entonces. La alternativa —un nombre permanentemente inservible— es un almacén que se niega a funcionar y nunca dice por qué.

El índice va detrás. MEMORY.md se inserta en las instrucciones de cada petición, así que una nota que detuviste cuya línea del índice sigue describiéndola es una nota que sigue llegando al modelo. Suspender o borrar una nota elimina, por tanto, las líneas del índice que la nombran. Una línea que la describe sin nombrarla sobrevive —el recorte es por líneas y se apoya en el nombre, que es lo que el índice es— y restaurar una nota no vuelve a poner la línea: el índice lo escribe el agente, y este no es el sitio para redactar en su voz.

El de otra persona, solo una administradora del despliegue. GET /memory/person/{id}, nombrando el tenant, y rechazado a todos los demás: ni a un Owner, ni a un Admin, ni a quien tenga un grant de edición sobre el agente que escribió la nota. El razonamiento es el de arriba: un rol de organización no es la parte a la que llega una solicitud de acceso del interesado, y la administradora del propio despliegue, que ya administra cuentas entre tenants, sí lo es. La lectura queda en el rastro de auditoría con el actor, el tenant, el sujeto y un motivo, y sin contenido: una entrada que guardara lo que miró sería una segunda copia de lo que se protege.

El borrado, sin cambios. Una persona limpia desde su propio perfil todo lo que un agente recuerda sobre ella, y una administradora con members:manage puede hacerlo por otra; ambos borran las filas de aquí y los recuerdos correspondientes en mem0 de cada agente que lo vincule. Vaciar del todo la memoria de un agente está en su caja de herramientas, junto a la capability.

Adónde no llega la vista de autoservicio. Un agente vinculado a mem0 guarda sus recuerdos en el servicio de otra empresa, y esta página no los lista: la API de mem0 responde a qué coincide con una pregunta, no a qué contiene un almacén. Esos agentes se nombran en la página en vez de omitirse, porque una lista de notas nativas presentada como un inventario completo sería peor que una que dice lo que le falta. El borrado sí llega a mem0; la lectura no.

El borrado de memoria comprueba los borradores y todas las versiones conservadas del agente, incluidos los endpoints anteriores de mem0. Vaciar un agente también elimina sus espacios de mem0 para todos los propietarios. Si faltan credenciales, falla el proveedor o la lista está incompleta, se devuelve un error. Restablece el acceso y repite la operación para completar el borrado.

Memoria (mem0)

remember, recall

Memoria semántica guardada en un servicio mem0 — en la nube, o autoalojado mediante base_url — en lugar de en este despliegue. remember guarda una frase corta y autocontenida; recall encuentra las que tratan de una pregunta por significado y no por nombre. Necesita una clave de API del vault de la organización.

A qué memorias puede llegar un run obedece exactamente la regla anterior, porque a mem0 se le entrega el ámbito entero como su user_id: {org}:{agent}:{owner}. Una sola cuenta de mem0 no puede, por tanto, mezclar las memorias de dos organizaciones, de dos agents ni de dos personas.

Dos diferencias que conviene conocer antes de elegirla. Aquí no se guarda nada, así que borrar la memoria de una persona llega a mem0 por su propia API en lugar de por una fila que eliminemos. Y mem0 factura su propio embedding por fuera, así que el libro de gasto del despliegue no lo ve y un tope de budget no lo acota.

Un base_url autoalojado tiene que ser https y estar en MEM0_ALLOWED_HOSTS. Una lista de permitidos vacía rechaza de plano el mem0 autoalojado, y es deliberado: la clave viaja en una cabecera Authorization, así que quien construya un agent y pueda vincular una clave compartida sin poder leerla no debe poder apuntarla a un servidor propio.

search_conversations, read_conversation

Encuentra una conversación pasada por lo que se dijo en ella, y la abre entera. La memoria solo puede recordar lo que algún turno anterior consideró digno de anotar; todo lo demás se dijo, se guardó y, hasta que esto existió, era inalcanzable — de modo que «qué decidimos sobre los precios del Q3» se respondía con «no tengo constancia de eso» en un producto que guardaba el intercambio entero.

search_conversations devuelve los hilos que mejor encajan, cada uno con su título, cuándo estuvo activo por última vez, cuántos turnos coincidieron y el pasaje más fuerte con las palabras coincidentes en negrita. read_conversation abre uno como Markdown, dividido en USER: y AI: en el orden en que se escribieron los turnos, nombrando a quien habló cuando una sala tiene varias personas. Un hilo largo llega de ventana en ventana y la respuesta dice cómo pedir la siguiente.

De quién son las conversaciones

Las de una sola persona: aquella a la que el run está respondiendo. Tres vías de entrada, las mismas tres que permite la consola: las conversaciones de su propiedad, las compartidas con ella y los hilos de canal en los que participó y de los que sigue siendo miembro, confirmado contra la plataforma de chat. El registro de runs de un trigger queda deliberadamente fuera: es la transcripción de runs hechos bajo la autoridad de otra persona, y un agent que busca en nombre de alguien no tiene ningún permiso suyo con el que comprobarlo.

Y solo donde esa persona es la única que escucha. En un chat de grupo ambas herramientas se niegan, diciendo por qué: el corpus es personal, así que responder a partir de él en un canal leería las conversaciones privadas de una persona ante todos los de la sala. Es la línea que traza el índice de memoria, una capa más afuera.

Una programación o un trigger de evento no responde a nadie, así que ambas herramientas se niegan también ahí, por la misma razón que los archivos de memoria - y siguen negándose en un trigger que se ejecuta como su creador, que abre la memoria y las conexiones personales, pero nunca las conversaciones de una persona.

Cómo busca

Búsqueda de texto completo de PostgreSQL: un tsvector que la base de datos mantiene sobre cada mensaje, un índice GIN, websearch_to_tsquery para la consulta y ts_rank_cd para el orden. Funcionan las "frases exactas" entre comillas, or y un - inicial para excluir una palabra. No ILIKE, que coincide dentro de las palabras y no sabe ordenar; ni embeddings, que es lo que ya es la búsqueda de conocimiento y responde a otra pregunta.

Las palabras coinciden enteras y sin distinguir mayúsculas, pero sin lematizar: meeting no encuentra meetings. La configuración está fijada en la base de datos y english lematizaría un idioma mientras destroza todos los demás — PostgreSQL no trae diccionario polaco alguno —, así que la uniformidad entre idiomas se paga con las formas de las palabras. La descripción de la herramienta lo indica, para que un modelo que no encuentre nada pruebe otra forma de la palabra en vez de concluir que no se dijo nada.

Un operador que no quiera en absoluto que los agents lean conversaciones retira el scope conversations:read, lo que lo apaga en todo el despliegue. No hay ningún ajuste que amplíe el corpus.

web_search — Busca información actual en la web pública.

Configuración Valor por defecto Valores
method duckduckgo duckduckgo, native, tavily, brave, exa
max_results 5 1–10, ignorado por native

La consola nombra cada método en lugar de imprimir el valor que almacena, y dibuja la marca del propio servicio al lado: el campo lleva x-enum-labels, que es lo que el formulario generado lee como etiqueta. Sin ellas el selector ofrecía duckduckgo y exa con las mayúsculas con las que se almacenan, lo que se lee como una clave de configuración que reconocer y no como un producto que elegir.

  • duckduckgo — gratis, sin cuenta, resultados presentados como fuentes pulsables.
  • native — el provider del modelo busca con su propio índice y devuelve sus propias citas. Solo en modelos que lo admiten.
  • tavily — resultados resumidos para que los lea un modelo.
  • brave — un índice propio.
  • exa — búsqueda por significado en vez de por palabra clave.

Los tres métodos de pago necesitan una clave de API de los secretos de la organización, nombrada por el secret_id de la vinculación. El requisito es condicional y no fijo: uno fijo, o bien encerraría el método gratuito por defecto tras una cuenta, o bien dejaría que un agent con Tavily se publicara sin nada con lo que autenticarse y fallara en su primera búsqueda.

La aprobación y native no combinan, por la razón que da Lectura de páginas web más abajo: la puerta de aprobación envuelve la ejecución de la herramienta, y una búsqueda nativa la ejecuta el provider del modelo, así que una vinculación que exige aprobación para web_search y pone method en native se rechaza al publicar, en lugar de recibir una puerta que nunca se dispara. Elige un método que ejecute este propio despliegue, o retira el requisito de aprobación.

Una búsqueda encuentra una página; no la lee. Leerla es Lectura de páginas web, más abajo, y es una capability aparte con un scope aparte.

Leer páginas web

web_fetch — Lee la página completa de una URL, como Markdown.

Configuración Valor por defecto Valores
method local local, native, auto
max_content_chars 50000 1000–200000, ignorado por native
allowed_domains — nombres de host desnudos que el agent puede leer; null significa cualquiera
blocked_domains — nombres de host desnudos que nunca puede leer
  • local — este despliegue descarga la página. Es el valor por defecto, porque es el único método que se comporta igual en todos los modelos.
  • native — la descarga el provider del modelo, con su propia salida de red y sus propias citas. Solo en modelos que lo admiten; Pydantic AI lanza un error en el resto.
  • auto — nativo donde el modelo lo tiene, local en todo lo demás. Solo se ofrece uno de los dos, así que un run no puede elegir entre ellos por llamada.

La descarga en sí es el web_fetch_tool de Pydantic AI sobre su safe_download protegido contra SSRF, y esa es la razón de que no sea código nuestro.

En qué se convierte una respuesta se decide aquí:

  • Una página (HTML, Markdown, JSON, texto plano) vuelve como Markdown.
  • Una imagen vuelve como imagen, para un modelo que lee imágenes.
  • Un PDF o un documento de oficina vuelve como su texto extraído, leído por el mismo parser por el que pasa un adjunto del chat y recortado en max_content_chars como una página. La biblioteca devolvería los bytes en bruto para que el modelo los leyera de forma nativa. Un modelo servido tras un endpoint compatible con OpenAI que no puede hacerlo rechaza la petición entera (Unsupported chat content part type: 'file'), y el agent vuelve a descargar el mismo documento. Solo se extrae el texto hasta max_content_chars, y un PDF se lee como máximo hasta 2.000 páginas: uno más largo termina con una nota que nombra la página en la que se detuvo, y cuando las páginas leídas no tenían texto, solo vuelve esa nota.
  • Un binario sin texto legible (un PDF escaneado, un archivo comprimido) llega al modelo como un error reintentable que nombra lo que llegó.

La URL viene del modelo y se resuelve desde dentro del contenedor, así que validarla de antemano — como hace app.core.sanitize.validate_webhook_url con un callback que alguien nos entregó — no basta por sí solo. httpx resuelve el nombre de host una segunda vez y sigue las redirecciones sin volver a preguntar, así que un nombre que hace un momento respondía públicamente puede responder ahora 169.254.169.254, y una URL pública puede redirigir a una de esas.

safe_download fija en la petición la dirección que resolvió y revalida cada salto, incluidos los filtros de dominio. Además acota el cuerpo mientras se transmite, y rechaza las codificaciones de compresión que no pueden acotarse así.

Las direcciones privadas, de bucle local, de enlace local y de metadatos de nube se rechazan, y el rechazo llega al modelo como un error reintentable y no como una página vacía: un rechazo con forma de resultado es uno que el modelo esquiva al responder sin decir que tuvo que hacerlo. A la biblioteca se le puede indicar que permita direcciones locales; aquí nada expone eso.

Cualquiera de los dos fallos, como cualquier otro (un 403, un tiempo de espera agotado), es un reintento solo mientras a la herramienta le quede un intento. Pydantic AI cuenta los reintentos por herramienta y no por URL, así que el segundo sitio que rechazara al agente terminaría todo el run; en el último intento el mismo mensaje vuelve en su lugar como resultado de la herramienta, y el modelo pasa a otra fuente o responde sin ella.

Los filtros de dominio no son la frontera de seguridad — safe_download sí

Coinciden con el nombre de host exactamente, sin comodines y sin subdominios implícitos, así que responden a qué sitios puede leer este agent y no a puede este agent alcanzar nuestra red.

Una entrada que nunca podría coincidir se rechaza al publicar: un comodín, un esquema, una ruta, un puerto o una lista de permitidos vacía. Cada una dejaría si no una lista de denegados que calladamente no deniega, o una lista de permitidos que calladamente lo deniega todo.

Una lista de denegados vacía no deniega nada, que es lo que ya significa dejarla sin poner, así que se lee como no puesta en vez de rechazarse: un spec importado que escribe «ningún host denegado» como [] está diciendo algo cierto.

Una entrada que sí puede coincidir se almacena con la única grafía por la que se preguntaría al DNS: en minúsculas, sin la etiqueta raíz final y codificada en IDNA. Un nombre tiene más de una grafía, y una coincidencia exacta contra una sola de ellas es un filtro con un agujero: https://exämple.com/ llega a la comparación tal y como se escribió, así que una lista de denegados con solo xn--exmple-cua.com lo dejaría pasar mientras getaddrinfo resuelve las dos igual. Al filtro se le entregan en tiempo de construcción todas las grafías equivalentes; el spec almacena una.

La aprobación y native no combinan

La puerta de aprobación envuelve la ejecución de la herramienta, que es el único sitio donde una llamada puede retenerse, así que una descarga que el provider del modelo ejecuta por su lado no llega nunca a ella.

Una vinculación que exige aprobación para web_fetch y pone method en native — o en auto, donde cuál de los dos se ejecuta es una propiedad del perfil del modelo y cambia sin republicar — se rechaza al publicar, en lugar de recibir una puerta que calladamente no se dispara nunca.

Pon method en local, o retira el requisito de aprobación. Los dos son agents legítimos, y cuál se quiere no es una decisión que tomar en nombre del autor.

Una versión publicada antes de que existiera ese rechazo se rechaza de nuevo cuando se ensambla, porque nada revalida una versión congelada. Así que un agent así deja de funcionar hasta que se edite, en vez de seguir descargando sin aprobación.

Una página llega como Markdown, truncada en max_content_chars; un PDF o una imagen llegan como contenido binario que el modelo lee de forma nativa. Nada la resume: qué hacer con una página corresponde a las instrucciones del agent.

Navegador web

browse_web — Hace una tarea en un navegador web real, paso a paso.

Una tarea y una URL de inicio. Abre la página en un Chromium que usted opera y repite tres cosas: lee la página en una tabla numerada de los elementos sobre los que una persona podría actuar, pide al modelo del run un paso y lo ejecuta. Un paso juzga si la acción anterior funcionó, guarda una memoria breve para el resto de la tarea, dice para qué es la siguiente acción y nombra una sola acción: hacer clic, escribir, elegir, desplazarse, abrir una URL, volver, esperar, terminar con una respuesta, pedir a una persona o avisar de que la página lo bloquea.

Úselo cuando una página haya que manejarla — un formulario, un filtro, un inicio de sesión con datos que tiene, una búsqueda cuyos resultados piden un clic, un recorrido por varias páginas. Para una página que solo hay que leer, la descarga de páginas es más rápida y no tiene efectos secundarios.

O el agent lo maneja él mismo. browser_open, browser_read, browser_click, browser_type, browser_select, browser_scroll y browser_back son la misma pestaña en manos del propio agent: una acción por llamada, elegida por el modelo del run en su propio turno, y cada una responde con la página después de la acción - su dirección, los elementos numerados y su texto. Sin una segunda petición por paso, y el agent dice en el chat lo que hace entre acciones. Los pasos se dibujan en una única ventana en vivo bajo el mensaje del agent, la que abrió el primer browser_open, y una persona puede tomar la pestaña entre dos pasos cualesquiera. Medido con Claude a través de OpenRouter: un paso de browse_web dura 3,5-5 s, casi todo la petición al modelo del propio paso; una tarea de dos clics que el agent manejó él mismo tardó 13 s, y con browse_web unos 20. Las herramientas convienen para una tarea corta o una que alguien quiere ver, browse_web para una tarea larga entregada entera.

Una acción nombra una fila, nunca un selector. Cada elemento sobre el que el modelo puede actuar es una fila de una tabla construida en el servidor a partir del DOM vivo, así que una página no puede ofrecer una acción describiéndola. Eso no es lo mismo que seguro: «Eliminar cuenta» es una acción que una página ofrece de verdad, por eso la capability es side_effecting y browse_web puede quedar tras una aprobación.

Por defecto no espera aprobación, como toda herramienta. Una aprobación de una navegación llega antes de cargar la primera página, sobre una tarea en lenguaje natural, y pide consentir acciones que nadie puede ver todavía. En su lugar está la observación: la consola dibuja la navegación mientras ocurre, cada paso dice qué pulsó y para qué, y allowed_domains limita adónde puede ir. Quien quiera la puerta pone tool_approval en el binding.

Un límite de pasos es una pausa, no un final. La pestaña sigue abierta el resto del turno. Una navegación que llega a max_steps lo dice junto con lo que aprendió, y el agente continúa llamando de nuevo a browse_web sin start_url — en la misma página, con la sesión iniciada y la misma memoria. Una llamada con start_url empieza una tarea nueva en la misma pestaña. Cuatro resultados, todos normales: terminada, bloqueada por la página, en pausa en el límite de pasos y el navegador no responde. El último es una respuesta, no un run fallido: el agente sabe que el navegador no responde y lo dice, junto con quién puede arreglarlo.

Una persona puede tomar el control del navegador. Cada navegación de un run tiene una vista en directo en la consola, en /browse/{run_id}/{call_id}: la página tal como está ahora y los pasos hasta el momento. La tarjeta del chat enlaza a ella, y en Slack, Telegram y Mattermost el paso publica el enlace. La persona para la que actúa el run, o quien puede editar el agente, puede tomar el control: el agente se detiene tras su paso actual y la página es suya — clic, escribir, desplazarse — hasta que la devuelva, y el agente sigue desde la página tal como la dejó. Los miembros que pueden ver runs pueden mirar, pero no manejar. Lo que alguien escribe llega a la página y no se guarda en ningún sitio; el historial del run solo registra que alguien tomó el control.

La página toma el tamaño de la ventana que la muestra. Mientras alguien que puede manejar la navegación la tiene abierta, el navegador maqueta la página para esa ventana entre pasos - el diseño propio del sitio para ese ancho, con la densidad de esa pantalla -, así que la vista en directo es el sitio y no una pantalla de portátil encogida en una columna. El modelo ve entonces la misma página a ese tamaño.

Una tarea ya hecha va más rápido la segunda vez, para la misma persona. Cuando termina una navegación que actúa por una persona - su propio chat, su mensaje directo, su propia programación - los pasos que la llevaron allí se guardan solo para ella: la dirección y la etiqueta de cada cosa que pulsó, nunca lo que escribió (la grabación se detiene en el primer campo que rellena). La próxima vez que pida la misma tarea desde el mismo start_url, esos pasos se dan sin el modelo mientras la página los siga ofreciendo, y el modelo toma el relevo en la primera diferencia. Una repetición que no termina se descarta. Nadie más en la organización los lee, y Redis los guarda dos semanas desde la última vez que se hizo la tarea.

Cuando solo una persona puede pasar de una página — un inicio de sesión sin datos, un captcha, una decisión que le corresponde a otro —, el modelo pide una persona en lugar de rendirse. La tarjeta y el hilo del canal dicen que el agente necesita a una persona, y para qué. La navegación espera hasta diez minutos a que alguien tome el control y termina bloqueada si nadie llega; una persona tiene el navegador como mucho quince minutos. La vista en directo se guarda en Redis, así que lo necesita, y dura una hora desde la última acción de la navegación.

Configuración Por defecto Valores
cdp_url vacío un endpoint DevTools de Chromium; obligatorio, y su host tiene que estar en BROWSER_CDP_ALLOWED_HOSTS
allowed_domains null hosts en los que puede estar el navegador; se admiten patrones como *.example.com; null es sin restricción en la web. Toda petición que apunte dentro de la red del despliegue (una dirección privada, de loopback, link-local o de metadatos, también tras una redirección) se rechaza salvo que su host esté aquí - añade una herramienta interna para navegarla (y, tras BROWSER_EGRESS_PROXY, también a las excepciones del proxy)
max_steps 50 1–200; pasos por llamada, cada uno una petición al modelo
vision false envía también al modelo la captura de cada paso; el modelo tiene que leer imágenes
candidate_cap 60 2–200; cuántos elementos se pueden ofrecer en un paso
preview true envía la vista del navegador al chat mientras navega
preview_width 1024 320–1920; el ancho de la vista y, por tanto, de los fotogramas
step_model_profile_id vacío un perfil de modelo al que browse_web pregunta en cada paso en lugar del modelo del agent - un modelo más barato abarata un browse largo, y solo lo acelera donde su proveedor responde rápido (Claude Haiku por OpenRouter tardó por paso lo mismo que Sonnet); vacío usa el modelo del agent, que sus propias herramientas de navegador usan siempre
connection built_in built_in, proxy o hosted: cómo sale el navegador a la red
proxy_url vacío con proxy: http://, https:// o socks5:// con host y puerto; el secreto del binding, si lo hay, es su username:password
hosted_url vacío con hosted: la dirección CDP wss:// del servicio; el secreto del binding es su clave y es obligatorio
hosted_key_param apiKey cómo llama el navegador alojado a su clave en la query de la dirección
timezone vacío una zona horaria IANA que declara el navegador - ajústala a la región del proxy
locale vacío una etiqueta de idioma como pl-PL, enviada como Accept-Language y navigator.language

El navegador lo opera usted, y el operador dice cuál. No hay Chromium en la imagen de la API: cdp_url apunta a un servicio de navegador que un operador ejecuta y aísla. Los ficheros de compose traen uno tras el perfil browser — docker compose --profile browser up -d browser, accesible como http://browser:9222. El host tiene que estar en BROWSER_CDP_ALLOWED_HOSTS, y una lista vacía — la de por defecto — rechaza la automatización del navegador al publicar.

El navegador de compose está en una red de Docker propia, a la que solo se unen la API y el worker para llegar a él: una página que abre, o a la que lo redirigen, no alcanza la base de datos, Redis ni MinIO. Sigue alcanzando lo que permita la red del host, como la dirección de metadatos de una nube, así que bloquéala en el host donde importe.

Un agent elige cómo sus browses salen a la red (#2130), en los ajustes del propio navegador, con el mismo edit sobre el agent que el resto. connection es built_in - el navegador que nombra cdp_url, desde la dirección propia del despliegue -, proxy o hosted, y timezone y locale dicen qué zona horaria e idioma declara el navegador, sea cual sea el modo.

Un proxy (proxy_url) envía el tráfico de ese navegador por una dirección http://, https:// o socks5:// - residencial o regional - y se autentica con el secreto del binding como username:password cuando lo tiene.

Un proxy http:// o socks5:// llega al navegador en la dirección que se comprobó, así que un nombre que cambie entre la comprobación y la conexión no lo dirige al interior de la red; un proxy https:// conserva su nombre, para el que se emitió su certificado.

Un navegador alojado (hosted_url) se conecta en cambio por wss:// a un servicio de navegador - Browserbase, Steel o uno propio - con el secreto del binding como clave, añadida a la query de la dirección bajo hosted_key_param (apiKey por defecto).

Un navegador alojado nunca empieza con los inicios de sesión guardados de una persona: su dirección la elige el autor del agent, no un operador. Allí también cada recorrido recibe un contexto de navegador propio, y un servicio que lo rechaza queda rechazado: su perfil predeterminado no se da por privado, y lo que un recorrido escriba ahí - cookies, local storage - le sobreviviría.

Una dirección dentro de la red del despliegue se rechaza al publicar y otra vez al empezar un browse, salvo que el operador haya puesto el host en BROWSER_CDP_ALLOWED_HOSTS. Chromium no puede autenticarse en un proxy SOCKS, así que uno con contraseña tiene que ser HTTP. Un sitio que pide una contraseña nunca recibe la del proxy. El navegador de inicio de sesión siempre usa el del propio despliegue.

El navegador de compose se ve como uno de escritorio. Es un Chromium completo con ventana en una pantalla virtual (docker/browser), con las fuentes de un escritorio y sin los flags que le dicen a una página que está siendo dirigida - el headless shell al que sustituye decía HeadlessChrome en su user agent, y los sitios que rechazan bots lo rechazaban al verlo. La capability también quita lo que una página aún podría leer por CDP: nunca activa el dominio Runtime, y sustituye Headless en el user agent, navigator.webdriver y un renderer WebGL SwiftShader donde un navegador los muestra, así que un headless shell que un operador conserve también parece de escritorio.

Comprobado con la detección de bots de BrowserScan (Normal en cada punto) y la tabla de Sannysoft (todo superado). BROWSER_TZ y BROWSER_LANG en el entorno fijan dónde y en qué idioma dice estar; por defecto UTC y en-US. Una comprobación de bots no son unas condiciones de uso: lo que un sitio permite automatizar lo lee el operador antes de dirigir agentes a él.

Es una lista de permitidos y no la comprobación SSRF por la que pasan otras URL del inquilino, porque cdp_url vive en un spec que escribe cualquiera con edit, y la protección SSRF falla aquí en ambos sentidos: rechaza el servicio aislado de su propia red y acepta un depurador expuesto a internet. Chromium solo responde a DevTools por una dirección IP, así que un endpoint http o ws dado por nombre se resuelve una vez y se alcanza por su dirección; un endpoint TLS conserva su nombre para el certificado.

Cada navegación tiene su propio contexto de navegador, que se descarta al terminar el run. En un navegador compartido por muchos, el contexto por defecto guardaría una cookie que puso el agente de una persona, y el siguiente llegaría con su sesión iniciada.

Cada paso va al propio modelo del agente. La URL de la página, su título, las etiquetas de los elementos y hasta 4.000 caracteres de su texto visible, más una captura con vision activado — al mismo proveedor que la conversación y a ningún otro. Un valor que escribe el agente no se repite: el historial anota que se rellenó un campo, no con qué. Cada paso se comprueba contra el presupuesto del run antes de enviarse, así que un presupuesto agotado detiene la navegación en vez de pagar el paso siguiente.

cdp-use llega con el extra browser, que una instalación por defecto no trae. Quien quiera la capability instala agenticos[browser]; un agente en un despliegue sin él falla esa herramienta de forma visible, con la línea de instalación.

Los specs anteriores a la versión 13 que vinculaban el navegador paso a paso (browser_choice) se cargan como esta capability; la aprobación y el cambio de nombre de browse_page pasan a browse_web, y se descartan los ajustes del modelo de decisión y la clave de TypeSafe.

Cálculos

run_python — Ejecuta un pequeño programa en Python para calcular algo.

Una sandbox restringida, sin red y sin sistema de archivos, que es la razón por la que el tiempo y la memoria son los únicos límites que merece la pena fijar.

Configuración Valor por defecto Rango
timeout_secs 10 > 0, ≤ 120
max_memory_mb 256 16–4096

Por agent, no por despliegue

Un autor que sube un límite para un único agent con mucho tratamiento de datos no debería necesitar a un operador ni un redespliegue, y los topes están acotados en vez de ser abiertos.

Sandbox

ls, read_file, glob, grep — lectura. write_file, edit_file, execute — escritura y ejecución.

Un workspace que sobrevive entre turnos. code_execution calcula y olvida; este recuerda, y en un backend respaldado por contenedores tiene una shell de verdad. Un agent al que se le conceden ambos calcula con uno y guarda su trabajo en el otro, que es el emparejamiento habitual en el backend state, porque ese no tiene shell en absoluto.

Configuración Valor por defecto Valores
backend state state, service
connection_id null una conexión de sandbox registrada; null toma la predeterminada de la organización. Solo service
session_scope conversation run, conversation, channel, user, agent
runtime null un alias que permita el servicio de esa conexión; solo service
include_execute true apagado, quita la shell por completo en lugar de ponerle una puerta

No hay un backend docker ni daytona que elegir. Dónde se ejecuta una sandbox es una propiedad de la conexión que registró un operador — Sandboxes en la aplicación —, así que nombrar la conexión es nombrar el tipo. Elegirlos por separado hacía posible elegir dos cosas que se contradicen.

Un despliegue que ejecuta su propio servicio de sandbox - sandboxd arrancado desde el archivo compose de este proyecto, con el token que generó make sandbox-token - no necesita registrar nada. La primera vez que una organización sin conexión predeterminada lista sus sandboxes o ejecuta un agente que necesita una, la plataforma registra This deployment's sandbox como predeterminada, en nombre del propietario de la organización, con el token en su bóveda y una entrada de auditoría como cualquier otra conexión. Del mismo modo, el Architect recibe archivos y código cuando se instala en un despliegue así.

backend es infraestructura; session_scope es una política de compartición de datos. Equivocarse en lo primero cuesta una funcionalidad. Equivocarse en lo segundo le enseña a una persona los archivos de otra, así que merece la pena leerlo dos veces:

Ámbito Quién comparte el workspace
run Nadie: uno nuevo en cada turno
conversation Todos los de ese chat. En Slack un hilo es un chat, así que los hilos no comparten
channel Todos los hilos de un canal. Un mensaje directo tiene su propio id de chat, así que cada persona sigue teniendo el suyo
user Una persona, en todas las superficies desde las que alcanza este agent
agent Todos los que hablan con este agent, en toda la organización

conversation y channel existen como respuestas separadas porque una plataforma de chat las convierte en cosas distintas. SlackAdapter incorpora thread_ts al id del chat, así que conversation en Slack significa un workspace por hilo: cincuenta hilos en un canal concurrido son cincuenta contenedores y un 429 para la persona número cincuenta y uno que responda.

El ámbito del spec es el valor por defecto. Cada canal en el que se publica el agent puede sobrescribirlo, en la exposición: un agent al que se llega por el chat web y por un bot de Slack es un agent en dos situaciones, y un solo valor para ambas era la forma equivocada. El ámbito user es el que lleva un workspace de una superficie a otra: la misma persona que retoma en Slack una conversación que empezó en el chat web encuentra ahí sus archivos.

agent es el que cruza una frontera entre personas. El Builder avisa en el campo, el panel de archivos etiqueta de quién es el workspace en lugar de llamarlo «los archivos de esta conversación», y fijarlo queda registrado en el log de auditoría, porque un usuario que ve un archivo que no creó debería poder averiguar por qué.

Cambiar el backend o la conexión arranca un workspace nuevo en vez de reengancharse al antiguo. Un documento almacenado, el volumen de un contenedor y una sandbox de Daytona son tres cosas distintas, y dos instalaciones de sandboxd son dos cosas distintas, así que cada una tiene su propio workspace y el anterior se queda donde está, aún listado y aún legible. Mover un agent en marcha no es, por tanto, una manera de llevarse sus archivos; el agent encuentra un workspace vacío en el nuevo host. Dado que connection_id: null significa «la predeterminada de la organización», marcar otra conexión como predeterminada tiene el mismo efecto sin que cambie ningún spec.

Un spec elige una conexión y nunca una imagen, un montaje, un modo de red ni un tope. Eso corresponde a quien opera el despliegue: un spec lo escribe en un navegador cualquiera que tenga edit sobre el agent, y uno que pudiera nombrar una imagen de contenedor podría nombrar una cuyo entrypoint monte el host. runtime es un alias, y el Builder ofrece solo los alias que informa el servicio de esa conexión, leídos en vivo, porque una copia almacenada ofrecería uno que el servicio ya ha dejado de permitir.

Lo que cuesta ejecutar cada backend:

Backend Necesita Shell Dónde viven los archivos
state nada no esta base de datos, con tope en SANDBOX_STATE_MAX_BYTES
service una conexión registrada sí un contenedor en ese host, o la nube de Daytona en la cuenta propia de la organización

Un operador puede ver qué hay en marcha: Sandboxes lista las sandboxes abiertas de esta organización en su host predeterminado con sus runtimes, sus tiempos de inactividad y su memoria, y el registro de actividad por sandbox. Consulta Configuración.

La publicación se rechaza para un workspace service cuando la organización no ha registrado ninguna conexión, cuando la que nombra ya no existe o cuando esa conexión no tiene credencial; cada caso por su nombre, porque los tres son estados a los que un despliegue llega después de que se publicara un agent y el arreglo es de un operador y no del autor.

Solo execute se declara con efectos secundarios, y nada pregunta hasta que un autor lo pone tras una puerta. De las siete, solo ejecutar un comando actúa sobre el mundo: un workspace es espacio de borrador que se borra con la conversación a la que pertenece, así que escribir un archivo en él no es de la misma clase de acto que enviar un correo — y un agent que tiene que preguntar antes de cada write_file no puede hacer trabajo de varios pasos en absoluto, que es como un autor acaba apagando la puerta del todo y perdiendo la que importaba. execute ejecuta comandos arbitrarios en el host de alguien, así que es la que merece una puerta.

Una vinculación que quiera el comportamiento más estricto lo fija por herramienta: tool_approval: {"write_file": "required"}. Consulta Gobernanza para ver cómo se le plantea una aprobación a una persona, y para las dos cosas que una sesión de chat puede decir por encima del spec: eximir toda llamada con puerta en esta conversación, o preguntar por cada herramienta que tenga el agent, incluidas las herramientas MCP que la puerta guiada por el spec deja deliberadamente en paz (agenticos#925).

Algunas rutas se rechazan diga lo que diga la política de aprobación. Las credenciales (**/.env, **/*.pem, **/*.key, **/credentials*, **/.ssh/**, **/.aws/**) y el árbol del sistema (/etc/**, /usr/**, /proc/** y sus hermanos) no pueden leerse, escribirse ni editarse; el agent recibe un rechazo legible y puede seguir. A grep se le filtra en lugar de rechazarlo, ya que un patrón sobre / cubre legítimamente el workspace: las coincidencias dentro de un archivo vedado se descartan, así que una búsqueda no puede devolver una línea de uno. Los nombres no son secretos, así que ls y glob siguen mostrando lo que hay; solo se retiene el contenido.

Un comando que nombre una de esas rutas también se rechaza, así que cat /etc/shadow no se salta la regla preguntándole a otra herramienta. Eso es defensa en profundidad y no una frontera, y la diferencia importa: una shell alcanza un archivo por caminos que la inspección de cadenas no puede ver, así que lo que de verdad hace segura la ejecución es el aislamiento del contenedor y el modo de red del operador. No hay lista de permitidos de cadenas de comandos, porque una así se derrota con sh -c.

Y nada de esto sustituye a la puerta de aprobación: el rechazo de aquí es el no rotundo del código, mientras que execute preguntándole a una persona es la decisión que posee un operador.

Los archivos que alguien adjunta a un mensaje aterrizan en /uploads; consulta Tratamiento de archivos.

Los skills también se vuelven archivos. Un agent que tenga a la vez un workspace y skills recibe cada skill como /workspace/skills/<name>/SKILL.md con sus recursos al lado, que es lo que hace ejecutable el script de un skill: está en disco junto a la shell que puede ejecutarlo. Deliberadamente no hay run_skill_script: execute ya tiene detrás la puerta de aprobación y los topes del operador, y una segunda vía de ejecución sería un segundo conjunto de reglas en el que equivocarse.

Esos archivos son escribibles, y lo que el agent escribe no se convierte en un skill. Un skill son instrucciones que sigue en cada run todo agent vinculado a él, así que un cambio se registra como una propuesta y alguien con skills:edit la acepta o la descarta; consulta Skills.

Gráficos

create_chart — Dibuja un gráfico con números que ya tienes, para que el usuario pueda verlos.

Representa números que el modelo ya tiene. No descarga, ni calcula, ni agrega: para eso, combínalo con code_execution o knowledge. Sin configuración.

Los números llegan como columnas — una lista x_values para el eje y una lista values por serie — porque un argumento data de forma libre no es algo que un JSON Schema pueda describir, y un modelo al que se le dio un array de objetos sin propiedades declaradas devolvió uno solo y vacío.

Un gráfico sin nada dentro ahora es inexpresable en vez de meramente rechazado. Un eje sin puntos, un gráfico sin series o una serie con menos números que puntos tiene el eje: los tres vuelven como un reintento que nombra lo que falta.

Un marco dibujado alrededor de ningún dato se lee como «no hay tendencia» y no como un error, y además se persiste y se vuelve a dibujar en cada repetición de la conversación.

Generación de imágenes

generate_image — Genera una imagen a partir de una descripción escrita.

Dibuja una imagen con un modelo de imagen dedicado — distinto del propio del agent — de modo que funciona sea cual sea el modelo con el que corra el agent. create_chart representa números; esta dibuja ilustraciones.

Configuración Valor por defecto Valores
provider openai Los providers cuya clase de modelo respeta la herramienta de imagen y acepta una clave de API: dos a día de hoy
model gpt-image-2 Los modelos de ese provider, desde app/core/catalog/image_models.json
quality el del provider low, medium, high, auto
size el del provider auto, 1024x1024, 1024x1536, 1536x1024, 512, 1K, 2K, 4K
background el del provider transparent, opaque, auto
output_format el del provider png, webp, jpeg
aspect_ratio el del provider 16:9, 1:1, 9:16, …

Qué providers saben dibujar lo responde el SDK, no una lista. Model.supported_native_tools() es un classmethod de todas las clases de modelo que trae Pydantic AI, así que la plataforma se lo pregunta: OpenAIResponsesModel, GoogleModel y GoogleModel a través de Vertex respetan ImageGenerationTool, nadie más lo hace, y una actualización que enseñe a un cuarto no necesita código aquí. Los modelos de imagen de Together y de Fireworks existen y fallarían en su primera llamada con «not supported by this model», que es por lo que no se ofrecen.

Poder dibujar y poder configurarse son dos preguntas, y Vertex AI es donde se separan. La capability sella una única clave de API y construye con ella todos los providers, mientras que Vertex quiere una cuenta de servicio, así que una entrada para Vertex sería una opción del selector para la que nadie puede aportar una credencial, y se descarta junto a las que no saben dibujar. Ofrecerla significa enseñarle a la capability formas de credencial específicas de cada provider, lo cual es un cambio de la capability y no del catálogo.

Qué modelos ofrece cada provider son datos, en app/core/catalog/image_models.json: un id, un nombre y una frase que dice cuándo recurrir a él. Ningún endpoint de listado responde a esta pregunta — /v1/models devuelve modelos de chat —, así que un modelo publicado esta mañana es una entrada de catálogo y no una release. Una entrada de provider que el SDK no sepa manejar, o cuya credencial esta capability no pueda construir, se descarta al leer el archivo: la salvaguarda contra que el archivo críe algo indibujable o inconfigurable.

Los dos providers nombran el modelo de imagen en sitios distintos, y el catálogo también lo recoge. En Google el modelo elegido es el modelo de imagen. En OpenAI la herramienta la llama un modelo Responses y dibuja con el elegido, así que la entrada nombra a ese llamante y la elección viaja como el model de la propia herramienta. Ninguna de las dos es una pregunta que se le haga al autor.

Un spec publicado antes de que existiera el par sigue funcionando. Esto era antes una única cadena enumerada que llevaba el prefijo del SDK (openai-responses:gpt-5.4), y un spec almacenado todavía la guarda. Leído contra los dos campos eso es un modelo desconocido, así que la configuración normaliza la forma antigua a la entrada: el prefijo nombra al provider, y un nombre que es el llamante en vez de un modelo que dibuja se resuelve al primer modelo de imagen de ese provider. Solo cuando no se almacenó ningún provider: una vinculación que nombre uno está declarando las dos mitades.

model decide además a qué provider pertenece la clave de API. La clave es obligatoria — publicar un agent que vincule esto sin una se rechaza — y viene de los secretos de la organización, nombrada por el secret_id de la vinculación. Todos los demás ajustes son opcionales; sin poner, el provider aplica el suyo por defecto, así que encender la capability basta para generar.

Tiene efectos secundarios. Dibujar una imagen gasta dinero real de una clave de provider y produce contenido que una persona puede publicar, así que cada llamada es candidata a la puerta de aprobación y puede ponerse tras una puerta por vinculación.

Su gasto se contabiliza. El modelo de imagen se ejecuta como un subagente cuyo uso se anota en el libro del run, así que el coste de imagen cuenta contra un budget igual que una petición al modelo. Los modelos de imagen a menudo no tienen precio en la instantánea de precios, en cuyo caso el run registra la llamada a cero y marca su total como parcial (cost_is_partial) en vez de ocultar el gasto.

Adónde va la imagen. Cada imagen generada se guarda por organización y se sirve de vuelta con GET /api/v1/generated/{filename}, acotada a la organización propia de quien llama: una frontera más amplia que la de un archivo subido a un chat, que pertenece a un solo usuario. Se registra con la conversación en la que se hizo y se borra con ella, cuenta contra la cuota de medios generados de la organización y se borra tras el periodo de retención (configuración). Pasada la cuota, la herramienta lo responde antes de gastar nada. Cuando el agent tiene además un workspace (la capability sandbox), la misma imagen se escribe en él bajo /output, para que un paso execute posterior pueda construir con ella: montar un PDF, una diapositiva, una página. Un agent sin workspace sigue generando y mostrando imágenes; simplemente no tiene dónde construir con ellas.

Texto a voz

generate_speech — Lee un texto en voz alta como clip de audio.

Lee un texto en voz alta con el provider de voz que elige el autor y se comporta como la generación de imágenes: el clip se guarda, aparece en la conversación como reproductor y se escribe en output/ cuando hay un workspace abierto (#2259).

Configuración Por defecto Valores
provider openai openai, elevenlabs, google
model gpt-4o-mini-tts Los modelos de ese provider, de app/core/catalog/text_to_speech_models.json
voice la primera del modelo La voz por defecto del agente; puede cambiarla en cada clip dentro de voices. Una voz del catálogo para OpenAI y Gemini; un id de tu propia biblioteca de voces para ElevenLabs
voices ninguna: cualquier voz del modelo Hasta 20 voces a las que el agente puede cambiar en cada clip, cada una con un id y, para una voz de la cuenta, una note de hasta 120 caracteres que dice quién es. Vacía permite todas las voces del modelo; en ElevenLabs, cualquier id de voz de la cuenta
output_format el primero del provider mp3, wav (Gemini solo devuelve wav)
max_characters 5000 1–10 000; el máximo de texto que puede decir una llamada

Providers, modelos, voces y precios son datos, en el fichero del catálogo. Un provider es un propósito de secreto con una clave de API simple - ElevenLabs no ejecuta modelos de chat, así que es un servicio en el vault y no un provider de modelos - y una entrada sin propósito, o cuya forma de API no tiene cliente, se descarta. Los antiguos tts-1 y tts-1-hd de OpenAI hablan con nueve de sus trece voces, por eso las voces se listan por modelo.

Las voces de ElevenLabs pertenecen a la cuenta. Sus voces por defecto compartidas caducan el 31-12-2026, así que el catálogo no lista ninguna y un binding nombra una de las voces propias de la cuenta por su id; publicar sin ella se rechaza.

La voz es un valor por defecto; las voces permitidas son el límite. voice es la voz con la que habla el agente mientras no pida otra, y voices enumera las únicas demás a las que puede cambiar, para un diálogo o para un narrador y un personaje. Vacía, se permite cualquier voz del modelo; en ElevenLabs, cualquier id de voz de la cuenta, clonadas incluidas, y el Builder lo advierte. Las voces llegan al modelo como un enum en el parámetro voice de la herramienta, con la habitual como valor por defecto y la descripción de cada una del catálogo o, para una voz de la cuenta, la nota del autor. Una voz de cuenta abierta es texto libre cuyo formato se comprueba. Una voz de la lista que el modelo no tiene se rechaza al publicar, y una fuera de la lista se devuelve al modelo en la llamada.

El selector de claves muestra las claves del provider elegido. El secreto es obligatorio para todos los providers y se declara obligatorio cuando provider es uno de ellos, lo que le dice al Builder que el provider es el propósito de la clave.

Un texto largo es un solo clip. Un texto que supera el límite por petición del modelo se corta en un párrafo, una frase o una palabra y se dice en orden. Los trozos MP3 se unen uno tras otro; el WAV se pide a cada provider como PCM de 24 kHz y se envuelve en una sola cabecera. instructions - tono, ritmo, acento - llegan a los modelos que las leen (gpt-4o-mini-tts de OpenAI, Gemini) y se omiten en los demás.

Al budget se le pregunta con el precio del clip. La voz se vende por carácter, así que el catálogo guarda un precio fechado por millón de caracteres y el coste se conoce antes de la llamada. Se rechaza un clip que llevaría el run más allá de un límite; después se registra el coste y, si falla a medias, también los trozos ya dichos. Los modelos que facturan por tokens se comprueban con un precio por carácter de habla típica. Gemini, que devuelve su audio como PCM sin procesar, se contabiliza después a partir del audio que devolvió cada trozo - tokens de texto de entrada y 25 tokens de audio por segundo de salida -, así que una locución lenta cuesta lo que el provider factura por ella. Un precio fechado entra en vigor ese día: así queda registrada la duplicación de precios de Gemini el 01-01-2027.

Se registra mientras se dice, un clip cada vez. Cada trozo de un texto largo se registra cuando llega su audio, así que un fallo o un run cancelado a medias sigue registrando los trozos que volvieron. La comprobación del budget no reserva nada, así que la herramienta hace una llamada cada vez. instructions tiene un límite de 1000 caracteres, va al provider con cada trozo y no se tarifica.

Tiene efectos secundarios, por las mismas razones que la generación de imágenes: gasta dinero y produce una grabación que alguien puede publicar.

Los fallos van a quien puede arreglarlos. El texto vacío, el que supera max_characters o el que rechaza el provider vuelve al modelo, igual que un provider que limita el tráfico o no responde. Una clave rechazada, una voz desconocida y un budget agotado se responden como resultado, porque solo el autor o el operador pueden arreglarlos; una voz elegida por el modelo que el provider no conoce vuelve al modelo. Solo se registra el código de error del provider; su texto de error nunca llega a los registros ni al modelo.

Generación de vídeo

generate_video — Crea un vídeo corto a partir de una descripción escrita.

Crea un clip corto con el provider de vídeo que elige el autor y se comporta como la generación de imágenes: el clip se guarda, aparece en la conversación como reproductor y se escribe en output/ cuando hay un workspace abierto (#2260). Puede empezar desde una imagen: el filename que devolvió generate_image en el mismo run o una ruta del workspace, donde una imagen generada también queda en output/, reconocida por sus bytes y no por su nombre. Un nombre de fichero por sí solo nunca llega a los ficheros generados de la organización: el run tiene que haber hecho la imagen.

Configuración Por defecto Valores
provider google google, byteplus, xai, alibaba
model gemini-omni-1.1-flash Los modelos de ese provider, de app/core/catalog/video_models.json
resolution la primera del modelo La de por defecto; el agente puede pedir otra en cada clip, hasta max_resolution
max_resolution ninguna: cualquiera que renderice el modelo La más alta que el agente puede pedir; al menos resolution
aspect_ratio la primera del modelo La de por defecto; el agente puede pedir cualquier proporción que renderice el modelo
seconds 5, o la más cercana del modelo Dentro del rango del modelo; se rechaza para Gemini Omni, que decide su duración
max_seconds el clip más largo del modelo El clip más largo que puede pedir el agente; se rechaza para Gemini Omni
audio la del provider Por defecto activado o no; el agente puede pedir cualquiera de los dos en cada clip. Se rechaza cuando el sonido del modelo es fijo
alibaba_workspace ninguno: el endpoint compartido de DashScope Solo Alibaba: el id del workspace de Model Studio, una etiqueta de host; Wan se llama entonces en https://{workspace}.{region}.maas.aliyuncs.com/api/v1
alibaba_region ap-southeast-1 Solo Alibaba: ap-southeast-1, us-east-1, eu-central-1, ap-northeast-1, cn-beijing, cn-hongkong
timeout_seconds 600 60–1800

Lo que puede hacer cada modelo son datos. El catálogo indica el rango de duración de cada modelo, sus proporciones, sus resoluciones, si sus clips llevan sonido y si puede empezar desde una imagen, y el Builder solo ofrece eso. BytePlus es un propósito de secreto en el vault; Google, xAI y Alibaba Cloud ya son providers de modelos. Falta OpenAI: su API de vídeo se cerró el 24-09-2026.

Los ajustes son valores por defecto; la resolución más alta es el límite. La resolución, la proporción, la duración y el sonido de un binding son los del clip, salvo que el agente pida otros en la llamada. La duración se detiene en max_seconds, la proporción en las que renderiza el modelo y la resolución en max_resolution: vacía, el agente puede pedir cualquier resolución del modelo, y el Builder lo advierte, porque la resolución es lo que cuesta un segundo. El presupuesto se consulta con el precio de la resolución elegida. La proporción y la resolución llegan al modelo como un enum con la habitual por defecto, y el sonido solo se ofrece donde el modelo lo hace opcional.

Un clip es un trabajo que se espera como una imagen. Gemini responde en la propia petición; BytePlus, xAI y Alibaba aceptan un trabajo que se consulta. En ambos casos la llamada espera uno o varios minutos, como mucho timeout_seconds. Un trabajo que lo supera se cancela donde el provider lo permite - en BytePlus, mientras sigue en cola - y la llamada recibe una respuesta en vez de reintentarse, porque xAI y Alibaba no documentan la cancelación y pueden terminar y facturar el trabajo. El clip se descarga antes de que caduque la dirección del provider, y la clave nunca va a un host de almacenamiento.

Al budget se le pregunta con el precio del clip. Todos los providers facturan por segundo con una tarifa que fija la resolución, así que el catálogo guarda un precio fechado por segundo y resolución. Se rechaza un clip que llevaría el run más allá de un límite; después se registra la duración que informa el provider. Gemini Omni decide su duración y no la informa, así que allí el clip se tarifica y se registra con la duración máxima del modelo.

Tiene efectos secundarios, por las mismas razones que la generación de imágenes, y más: un clip es la llamada individual más cara que hace un agente.

Los fallos van a quien puede arreglarlos. Un prompt vacío, una duración o proporción que el modelo no hace y una imagen que no se puede leer vuelven al modelo con una indicación, igual que un provider que limita o está caído. Una clave rechazada, un presupuesto agotado, un prompt que la política de contenido del provider rechazó y un trabajo que pasa el límite de tiempo se responden como resultado: una indicación de reintento ante un rechazo invitaría a sortearlo. Un trabajo que pasa el límite de tiempo, o que se cancela con la ejecución, se contabiliza con su estimación en cuanto su solicitud pudo llegar al provider, porque el provider aún puede terminarlo y cobrarlo. Un clip hecho que no se pudo descargar también se contabiliza, y se responde como resultado en lugar de devolverse al modelo: un segundo intento se cobraría de nuevo.

Coding agent

run_coding_agent — Hand a software task to the coding agent, which works on it in the sandbox until done.

Entrega el trabajo de software a Claude Code o Codex, que se ejecutan en la propia sandbox del agent. Un agent corriente con shell puede editar código; un harness de programación lo hace mejor, y así es como un agent llega a uno. El harness es un agent aparte: el agent le entrega una tarea entera, el harness trabaja hasta terminarla y el chat lo muestra trabajando. Otro agent llega a un coding agent mediante la delegación de siempre, como a cualquier especialista.

Configuración Predeterminado Valores
harness claude_code claude_code (una clave API de Anthropic o un token de suscripción de Claude), codex (una clave API de OpenAI o un inicio de sesión de ChatGPT)
access edit edit cambia archivos y ejecuta comandos; read_only lee el código y responde (solo Claude Code)
model el del harness Claude Opus, Sonnet o Haiku 5.5 para Claude Code; GPT-5.6 Sol, Terra, Luna o GPT-5.3 Codex para Codex
base_url Anthropic directamente Solo Claude Code: una pasarela que habla la API de Anthropic, como https://openrouter.ai/api, cuya clave guarda entonces el vault. Se descarta al elegir Codex
timeout_minutes 15 1–60, por tarea

Una herramienta, y el binding decide lo que puede hacer. Con edit el harness edita cualquier archivo y ejecuta cualquier comando en la sandbox, con sus propias preguntas desactivadas (--dangerously-skip-permissions, --dangerously-bypass-approvals-and-sandbox): nadie está ante su terminal, y el límite es el contenedor para el que ambas opciones están documentadas. Con read_only, Claude Code se ejecuta en modo plan, sin ninguna herramienta que escriba - ni las de edición ni la shell - y lee con Read, Grep y Glob; sabe que no se puede aprobar nada más. Ni siquiera comandos que leen: ninguna lista de prefijos de comandos hace de una shell algo de solo lectura, porque git log --output=<archivo> encaja con git log igual que cualquier lectura. Codex no tiene read_only: su modo de solo lectura es una sandbox propia, que no arranca dentro del contenedor del agent, y la publicación rechaza la combinación. Que una persona diga sí antes de cada tarea es la Aprobación humana de la capability, puesta en Preguntar siempre — como toda compuerta, está desactivada hasta que alguien la pone. No hay pestaña de herramientas: la única herramienta es la capability.

La persona lo ve trabajar. El harness se arranca en segundo plano en la sandbox (claude -p --output-format stream-json o codex exec --json), y su archivo de eventos se lee cada segundo y medio. Cada paso llega al chat mientras ocurre, en un panel propio bajo la marca de Claude Code o Codex, como los de una delegación: el archivo que lee, la edición, el comando que ejecuta, lo que dice entre medias. Al terminar, el resultado de la llamada contiene la respuesta, cada paso y los archivos que cambió. Una tarea que pasa de timeout_minutes, y una que la persona detiene, ve detenido todo su grupo de procesos. En segundo plano significa también que una tarea no se corta en el límite del servicio de sandbox para un comando.

Las tareas de una conversación continúan una sesión. El id de sesión del harness se guarda en la sandbox junto al trabajo, en .agenticos/coding-agent/, uno por conversación, así que la segunda tarea de un chat sabe lo que leyó la primera - y un chat nunca continúa la de otro en una sandbox que comparten varias: la de un proyecto o una asignada a una persona o a un agent. El modelo pasa fresh_session para empezar de cero, y una sesión que la sandbox ya no tiene empieza de cero sola, una vez.

Lo que necesita. La capability sandbox en el backend service con los comandos de shell activados — publicar rechaza cualquier otra cosa — y un runtime con el harness instalado: el runtime workbench fija Claude Code 2.1.288 y Codex 0.160.0. La credencial viene del vault mediante el secret_id del binding y llega al comando por su entorno, nunca en su línea de comandos. En un proyecto que guarda sus archivos sin contenedor no hay shell, así que el coding agent se deja fuera de los runs ahí y el agent sabe por qué.

Una clave o una suscripción. Claude Code toma una clave API de Anthropic, o el token que claude setup-token imprime para una suscripción Claude Pro o Max, guardado como clave — se distingue por su prefijo sk-ant-oat y solo inicia sesión en Anthropic, no en una pasarela. Codex toma una clave API de OpenAI, o un inicio de sesión de ChatGPT hecho con Sign in with ChatGPT, el mismo que usa un perfil de modelo. Para una tarea, el inicio de sesión se escribe en un auth.json en un directorio de Codex propio de esa tarea (CODEX_HOME), fuera del workspace, y se elimina después - dos tareas que se ejecutan a la vez nunca comparten uno, y las sesiones que Codex reanuda se guardan por conversación a su lado. Los tokens que Codex renovó se sellan de nuevo sobre el mismo secreto. Todo lo que Codex ejecuta puede leer ese archivo, así que el inicio de sesión solo se entrega a una persona que puede verlo en el vault - ejecutar el agent nunca es un camino a la suscripción de otro -, y solo en un sandbox que no comparten tareas de otras personas: en el contenedor de un proyecto, en uno a nivel de agent o en un chat compartido con alguien para editar, la tarea de otra persona se ejecuta como el mismo usuario y podría leerlo. Una clave API es distinta: es del agent, como el token de un bot en Slack, así que cada run la usa, sea quien sea quien preguntó - y todo lo que ejecuta una tarea puede leerla. Vincula una clave que puedan compartir todos los que pueden ejecutar el agent. Trabajar con una suscripción personal está sujeto a las condiciones de Anthropic y OpenAI para esos planes, que pueden no permitirlo en un despliegue compartido.

La clave encaja con el harness. El selector ofrece a Claude Code solo claves guardadas para Anthropic, y a Codex solo claves de OpenAI e inicios de sesión de ChatGPT. Una clave guardada como custom encaja con los dos, igual que la clave ya vinculada. Publicar rechaza una clave guardada para otro proveedor y nombra los que acepta el harness: una clave de OpenAI vinculada a Claude Code se publicaría y luego fallaría en cada tarea. Con base_url definido, la clave es de la pasarela - OpenRouter, LiteLLM u otra -, así que Claude Code acepta ahí cualquier clave.

Delegar en un coding agent. Un delegado que enlaza sandbox por sí mismo se ejecuta en un workspace nuevo en memoria, sin shell, así que un coding agent al que se llega por delegación no tiene dónde ejecutar su harness. Da al agent que delega la capability sandbox en una conexión de sandbox y añade sandbox a Capabilities delegates inherit en la delegación: el coding agent trabaja entonces en el contenedor del padre, y los archivos que escribe son los que ve el padre.

Su gasto se mide. Ambos harness informan de sus tokens por modelo, que se anotan en el registro del run como cualquier petición al modelo, y el budget se comprueba antes de cada tarea. Codex no nombra ningún modelo en sus eventos, así que sin model su uso se anota sin precio y el coste del run se marca como parcial. Igual se anota una tarea que termina antes de que su harness diga cuánto gastó: fallida, agotado su tiempo o detenida.

Visuals

draw — Dibuja en la conversación: un diagrama, un mapa, un flujo, una explicación, una escena 3D. revise_visual — Cambia un visual con reemplazos exactos, sin dibujarlo de nuevo. read_visual — Lee un visual de esta conversación tal como está, con las revisiones aplicadas.

Dibuja en el propio chat. kind es svg para diagramas e ilustraciones, mermaid para diagramas de flujo, secuencias y diagramas ER, o html para todo lo interactivo - controles deslizantes, <canvas>, three.js, un mapa. Sin configuración; el skill incluido visual-design lleva el estilo y cinco plantillas, y el Builder lo vincula, junto con Charts, al activar Visuals.

Primero elige la forma. No toda respuesta es un dibujo. El texto de la herramienta y el skill le indican al modelo que responda un dato con texto, valores exactos con una tabla Markdown y cifras que ver con create_chart - así «ahora en gráfico» convierte una tabla en un gráfico nativo - y que dibuje cuando la persona deba entender cómo funciona algo, usar una herramienta hecha para el momento o seguir lugares en un mapa. El skill también dice cómo debe verse un dibujo para que parezca hecho para la respuesta y no generado: un tema grande, espacio en lugar de cajas, solo los colores del tema, sin píldoras, sombras ni degradados.

Un dibujo puede continuar la conversación. agenticos.followUp(text) en un dibujo pide enviar la elección de la persona como su siguiente mensaje - Planificar esta parada, Usar estas cifras. La consola muestra el texto bajo el dibujo y lo envía solo cuando la persona hace clic en Enviar; una petición sin un clic detrás se descarta. Consulte mensajes de seguimiento.

Mapas. lib/agenticos-map-2.js dibuja un itinerario - chinchetas numeradas en orden y la línea entre ellas, mostradas a la vez y que caen una a una cuando se pide, sobre un mapa 16:9 del ancho de la página, y una tarjeta por parada - sobre el servidor de teselas que nombra VISUAL_MAP_TILES, o como un contorno de las paradas en un despliegue que no nombra ninguno. Las instrucciones de la capability le dicen al modelo cuál es el caso, para que no prometa calles que no puede mostrar.

Aparece mientras se escribe. El dibujo es el argumento content de la llamada. La consola lo lee de los argumentos en streaming y lo envía a un marco bajo el mensaje, así que el SVG y el HTML crecen en pantalla y Mermaid se redibuja en cada línea completa. Los scripts se ejecutan una vez, cuando la llamada está completa: hasta entonces el marcado se muestra sin scripts ni atributos on*, así que una escena a medio escribir nunca arranca.

Vive en la conversación. Solo se guarda la llamada a la herramienta, que la transcripción ya conserva, así que una recarga y un hilo compartido lo vuelven a dibujar. Un turno anterior llega al modelo solo como texto, por eso la capability nombra en sus instrucciones los visuales anteriores de la conversación, por id y título, y read_visual recupera uno antes de una revisión.

El modelo sabe cómo se renderizó. Mientras alguien mira en el chat web, la consola informa de los errores de script, las cargas fallidas y las peticiones rechazadas de cada dibujo, y draw espera ese informe hasta diez segundos antes de responder - así el modelo corrige un dibujo roto con revise_visual en el mismo turno. Si nadie mira, responde enseguida y dice que el dibujo no se comprobó.

Las revisiones son reemplazos exactos, la misma regla que las ediciones de publish_artifact, y cada una es un visual propio bajo el mensaje que la hizo. Un dibujo demasiado grande para una aplicación (ARTIFACT_MAX_BYTES), un SVG que no es un único elemento <svg>, o uno que carga algo de otro sitio es un reintento que dice qué cambiar.

Imágenes. images lleva hasta seis imágenes a un dibujo, cada una con un nombre que el dibujo usa como {{image:NAME}}: una imagen adjunta a la conversación, un archivo del workspace o una dirección https://. Una dirección solo sirve a un agente que ya lee la web - con web_fetch o browser_use activados - y solo en un host que uno de ellos leería: sus allowed_domains y blocked_domains limitan también las imágenes, y la descarga no puede redirigirse a otro host. Cada imagen tiene como mucho 2 MiB, y un visual lleva solo las imágenes que su dibujo todavía usa, seis como máximo entre todas sus revisiones.

Tiene el aislamiento de una aplicación. El marco es el sandbox de las aplicaciones - un origen opaco, connect-src 'none', nada remoto salvo el conjunto de bibliotecas del despliegue, que ahora incluye three.js, Mermaid y Leaflet, y la propia ruta de teselas del despliegue. Consulte Visuals.

Aplicaciones

publish_artifact — Publica una página terminada — un informe, un pequeño dashboard, un resumen — bajo un enlace estable. read_artifact — Lee la versión actual de una página que publicó este agent, tal como se escribió.

Publica un único documento HTML o Markdown autocontenido como aplicación: un recurso compartido con propietario, visibilidad y grants, que se abre en el navegador bajo un enlace que no se mueve. Sin configuración.

El nombre es la identidad. (organization, agent, environment, name) elige la aplicación, así que el siguiente run del mismo agent que publique weekly-report — desde un chat, una programación o la API — añade una versión a la misma en lugar de crear un segundo enlace. El entorno sale del run, así que un run en staging publica una página propia. Unos bytes idénticos no añaden versión y responden unchanged.

De dónde sale la página. path lee un archivo del workspace del run a través de su propio backend, así que funciona allí donde funciona la capability sandbox; content recibe la página en línea para un agent sin workspace; edits son reemplazos exactos aplicados a la versión que devolvió read_artifact, y una publicación entre medias los rechaza en lugar de sobrescribirla. Exactamente uno de los tres. Una llamada incorrecta — más de uno, ninguno, un nombre fuera de ^[a-z0-9][a-z0-9-]{0,63}$, una extensión desconocida, una página vacía o demasiado grande, un reemplazo que no coincide con ningún sitio o con varios — es un reintento que nombra qué cambiar. Una lectura que las reglas de permisos del workspace rechazan, y una página que la persona del run no puede abrir, son resultados, no reintentos.

Leer de vuelta. read_artifact devuelve una línea de cabecera (versión, formato, tamaño) y el código fuente, cortado en 100.000 caracteres, cosa que la cabecera dice. Solo abre lo que la persona del run puede abrir en la consola, y nada en un widget público, un embed, una programación o un trigger de eventos, donde no escucha ninguna persona identificada.

Sin efectos secundarios. Una primera publicación es privada para la persona en cuyo nombre se hizo el run, y solo una persona amplía quién la lee, así que la puerta de aprobación solo aparcaría el informe programado para el que existe esto. Un autor que quiera aprobar cada nueva publicación de una página compartida fija tool_approval en publish_artifact.

La página no tiene red. Se sirve en un origen opaco bajo una política sandbox con connect-src 'none', y el texto de la herramienta le dice al modelo que incruste sus propios datos y cargue los gráficos desde el conjunto de bibliotecas del despliegue en lugar de un CDN. Consulta cómo se aísla la página.

Delegación

task — entrega una pieza de trabajo autocontenida a uno de los especialistas de este agent. check_task, wait_tasks, list_active_tasks — seguir una que está en marcha. send_message_to_subagent, soft_cancel_task, hard_cancel_task — dirigir o detener una. Estas seis se ofrecen solo cuando hay una delegación en segundo plano alcanzable: a un agent que solo es sync no se le entrega ninguna. create_agent, delegate — un especialista que el modelo escribe para sí mismo, cuando el autor lo permite. answer_subagent — declarada, y no ofrecida a ningún modelo.

Un agent entregando parte de un trabajo a otro, cada uno con su propio modelo, su propio conocimiento y su propio límite de pasos, y llamados por su nombre. Hay dos formas de delegado y la diferencia decide cómo se revisa, se versiona y se factura; Conceptos es donde se explica. A qué agents publicados puede delegar este no está en esta configuración: está en subagents, en el nivel superior del spec, donde pueden verlo la validación de publicación, la exportación a YAML y el modelo de permisos.

Configuración Valor por defecto Rango
inline ninguno especialistas definidos dentro de este agent
mode sync sync, async, auto
allow_questions false un delegado sync puede preguntar a la persona del padre
allow_dynamic false
max_depth 1 1–3
max_fanout 3 1–10
max_result_chars 2000 200–20000
share_with_delegates ninguna ids de capability a las que este agent está vinculado, salvo subagents

El modo es decisión del autor, no del modelo.

La herramienta task de la biblioteca recibe un argumento mode cuyo valor por defecto es sync, así que «el modelo eligió esperar» y «el modelo no dijo nada» son la misma llamada. No hay forma de respetar a la vez un ajuste y una elección, y el ajuste fue revisado.

Así que el argumento se sustituye al pasar, y auto es la manera en que un autor cede deliberadamente la decisión. auto se resuelve antes de que empiece la delegación, porque si un panel sigue abierto después de que el padre haya respondido depende de la respuesta.

Un delegado fijado o un especialista puede sobrescribir el modo para sí mismo: un investigador lento es el caso que merece la pena ejecutar en segundo plano. Las instrucciones entonces marcan a ese delegado, junto a su nombre: una sola frase que indicara el modo configurado era una promesa que el delegado que lo sobrescribe rompía después, anunciándole al modelo una respuesta y entregándole un id de tarea.

A un agent que solo es sync no se le ofrece ninguna de las seis herramientas del ciclo de vida de tareas. Cada una de check_task, wait_tasks, list_active_tasks, send_message_to_subagent y las dos cancelaciones recibe o informa sobre un id de tarea, y una delegación sync devuelve la respuesta y nada más: no hay id que pasar. Así que se ofrecen solo cuando hay una delegación en segundo plano alcanzable: modo async o auto, un delegado que prefiera cualquiera de los dos, o permiso para inventar especialistas. sync es el valor por defecto, así que esta es la configuración habitual, y seis descripciones de herramienta retenidas son seis que el modelo ya no paga en cada turno. task se queda: un agent sync sigue delegando.

El fan-out y el anidamiento son topes, no errores.

Pasado max_fanout, la siguiente delegación vuelve como un resultado de herramienta sobre el que el modelo puede actuar — esperar, o hacer el trabajo él mismo —, porque un límite de ritmo no debería terminar un run.

max_depth cuenta niveles de delegación incluido el del propio agent configurado: 1 es este agent delegando y sus delegados no; 2 permite un nivel anidado.

En el límite, un delegado se construye sin la capability de delegación, en lugar de con una que solo puede negarse. Una herramienta que siempre responde «no hay delegados disponibles» es una descripción que el modelo paga en cada turno y que intenta igualmente.

Deliberadamente no hay 0. Apagar la delegación es deshabilitar la vinculación, y una segunda grafía del mismo interruptor es una que contradice a la primera.

Y cada agent del árbol se ciñe a su propio max_depth, no al de la raíz. Un delegado recibe el menor entre lo que le queda al árbol y lo que permite su propio spec, así que una raíz configurada para tres niveles que delega en un agent cuyo autor eligió 1 recibe uno: ese delegado delega y sus delegados no, exactamente como lo leyeron sus propios revisores. Un tope que quien llama pudiera ampliar no sería un tope, y la razón para fijar un delegado a una versión es que las decisiones de su autor se mantengan cuando lo llama otro.

Una delegación sync puede detenerse para preguntar a una persona, y se continúa en el sitio. Una herramienta con puerta dentro de una aparca el run entero; aprobarla reanuda ese delegado desde donde se detuvo en lugar de delegar otra vez, que es lo que hace que la aprobación se aplique a la llamada que el revisor vio de verdad. Gobernanza tiene la forma del estado almacenado y por qué volver a ejecutar respondería de otro modo.

Una delegación en segundo plano no puede detenerse para preguntar a una persona.

Una herramienta con puerta dentro de una se rechaza en lugar de aparcarse, y el rechazo le dice al modelo que delegue ese trabajo con mode="sync" en su lugar.

La razón no es de política sino de duración. El canal de aprobación se cierra sobre la sesión de base de datos de la petición, y una delegación en segundo plano sobrevive a la llamada de herramienta que la arrancó, así que para cuando quiso preguntar ya no queda nada con lo que escribir la pregunta.

Una delegación en segundo plano que se suspenda de todos modos — una forma que la biblioteca documenta como no entregable — se registra como failed con ese mismo mensaje. La alternativa es una tarea que informa de «sigue en marcha» mientras viva el proceso: su gasto atribuido a nada, su plaza de fan-out nunca liberada y el panel que abrió una superficie nunca cerrado.

Un delegado sync puede preguntar a la persona del padre, cuando se pone allow_questions.

Apagado por defecto: un especialista trabaja de forma autónoma y lo dice si no pudo.

Encendido, a un delegado cuyo modo sea sync se le da la herramienta ask_parent de la biblioteca, y una pregunta que haga la responde el propio canal ask_user del run — la persona que ya sostiene la llamada de herramienta del padre —, nunca el modelo.

Es decisión del autor porque la pregunta lleva un nombre que el autor publicó. Un especialista que el modelo inventa no pregunta nunca, diga lo que diga esto: unas instrucciones que un modelo escribió hace un momento no son del autor para planteárselas a una persona.

Solo sync. Una delegación en segundo plano ha devuelto un id de tarea sin que quede nadie para responder, y auto puede convertirse en una.

Alcanzar un delegado preconstruido necesitó un cambio aguas arriba. subagents-pydantic-ai#76 respeta can_ask_questions para un agente aportado por quien llama, que es lo que es todo delegado aquí, con lo que aterriza la mitad sync de #184.

answer_subagent no se ofrece a ningún modelo.

Responde a una pregunta en la que aparcó un delegado en segundo plano, y aquí ningún delegado aparca en una: una pregunta sync va a una persona por ask_user y nunca por esta herramienta, y a un delegado async no se le da ask_parent en absoluto. Así que la única respuesta posible de la herramienta es «esa delegación no está esperando una respuesta».

Sigue declarada, porque una herramienta ausente de la declaración no puede ponerse tras una puerta por la política de aprobación ni renombrarse por una vinculación, y esa mitad del fallo es silenciosa.

Se filtra fuera del conjunto ofrecido, porque la otra mitad es una descripción en el contexto de cada turno que describe una acción que no puede ocurrir, y las descripciones de herramienta son el prompt más fuerte de este producto.

La herramienta pasa a ser alcanzable solo cuando se resuelva la mitad de segundo plano de #184: donde responde el propio modelo del padre sin que nada le obligue a mirar, con el delegado bloqueado en una plaza de fan-out que el final del turno cancela.

wait_tasks trunca, y lo dice. El resultado de una tarea completada se corta en max_result_chars con un marcador explícito que apunta a check_task, que siempre devuelve el texto completo. El marcador es la mitad que aguanta el peso: un corte silencioso se lee como una respuesta breve, y un orquestador al que se le da medio informe vuelve a delegar trabajo que ya tiene.

Apagar la delegación es deshabilitar la vinculación, no bajar un número. Una vinculación deshabilitada no es delegación: no se construye nada, así que nada lee los delegados fijados ni los especialistas que lleva — y entonces la publicación se rechaza para un agent que siga nombrando delegados, porque un delegado fijado al que nada llamará jamás es una línea de configuración que se lee como una decisión y no hace nada.

Vinculada sin ningún delegado, esta capability no aporta nada: no se adjunta, igual que knowledge no se adjunta sin colecciones. Diez herramientas que solo pueden negarse son diez herramientas en el contexto de cada turno.

Tres se declaran como que actúan, y un autor las pone tras una puerta con tool_approval: send_message_to_subagent, soft_cancel_task y hard_cancel_task. Dirigir cambia lo que un delegado está haciendo en mitad del run, y cualquiera de las dos cancelaciones destruye trabajo que se pagó y no se entregó. task deliberadamente no tiene efectos secundarios, lo que suena mal por un momento: lo que un delegado hace pasa por la puerta del propio spec del delegado, a través de la misma puerta de aprobación que usa este run, así que poner además una puerta a la delegación pediría que alguien la aprobara antes de que se haya propuesto el trabajo que quizá necesite aprobación. El autor que sí lo quiera tiene una sobrescritura de tool_approval.

A un delegado no se le prestan las capabilities del padre. Se ejecuta con su propio spec más lo que nombre share_with_delegates, id a id: un especialista que ganara calladamente las credenciales del padre sería la vía silenciosa para rodear lo que se le concedió al padre. La publicación rechaza un id compartido al que el padre no está vinculado él mismo, ya que prestar lo que no tienes es una línea de configuración que se lee como una decisión y no hace nada. En la práctica esto existe para sandbox: compartirla es la forma en que un investigador escribe /workspace/notes.md y un redactor lo lee. Un delegado que vincula sandbox sin que se le comparta la del padre recibe el workspace en memoria, porque solo el run abre uno.

subagents no puede compartirse, y es el único id que «¿lo tiene el padre?» nunca podría rechazar: un agent que comparte algo lo tiene por definición.

Compartida, la vinculación del padre aterriza en un delegado que no vincula ninguna, y el runtime lee entonces los especialistas, el allow_dynamic, el max_fanout, el max_depth y la lista de compartición del padre como si los hubiera elegido el autor del delegado.

La publicación lo rechaza, y el runtime además lo quita de la lista de compartición, así que un spec almacenado antes de esa regla tampoco puede ampliar un delegado.

Si un delegado puede delegar siquiera lo responde su propio spec, y lo mismo hasta qué profundidad puede llegar, acotado por lo que le quede al árbol que tiene encima.

Compartir es además la única vía a una conexión MCP para un especialista inline, que no puede vincular ninguna: una conexión es configuración de ámbito de organización, y alcanzar una a través de un especialista que nadie publicó es la puerta equivocada. Vincúlala en el padre y nómbrala aquí.

create_agent y delegate se ofrecen solo bajo allow_dynamic. Una herramienta ausente de la declaración de una capability no puede ponerse tras una puerta por la política de aprobación ni renombrarse por una vinculación, y la mitad peligrosa de eso es silenciosa, así que las diez están declaradas y una configuración por defecto ofrece siete.

Lo que compra el interruptor es un especialista que el modelo escribe él mismo: instrucciones y un modelo, y nada más.

Se construye con el mismo build_agent por el que pasa un especialista inline, sobre el guardián de budget compartido del run y su canal de aprobación, así que sus peticiones se tarifan y se cuentan contra el tope que alguien fijó.

Esa es toda la razón de que esto necesitara una factory y no una bandera. Un especialista que la biblioteca se construyera para sí misma quedaría fuera del catálogo de modelos de este despliegue, de su vault y de su guardián de budget: una petición sin contabilizar, quizá a un provider del que la organización no tiene clave. La factory es lo que la encamina de vuelta por esta plataforma.

Un especialista que no nombre un modelo se rechaza

Antes de subagents-pydantic-ai 0.2.18 la biblioteca llevaba una cadena de modelo por defecto con la que se compilaba un especialista sin modelo. La 0.2.18 eliminó ese recurso, y esta plataforma lo rechaza aún antes, en DelegatingToolset._refuse_dynamic.

El modelo solo puede nombrar un modelo del que la organización tenga un perfil, y el rechazo nombra la lista. No puede adjuntar capabilities: dejar que un modelo conceda una capability a su propio hijo es el fallo del scope no concedido con sombrero nuevo. No recibe conocimiento, ni delegados propios, y no se persiste nada entre runs: conservar un especialista significa publicar un agent, que es la acción de una persona. MAX_DYNAMIC_SPECIALISTS acota cuántos puede conservar un run.

Que un especialista no se persista es un diseño, y tiene una salida en vez de un callejón sin salida: una persona puede promoverlo a un agent en borrador.

Su definición viaja en el frame SubagentStarted de apertura — el único sitio en el que es legible después de que el modelo lo escribiera y antes de que acabe el turno —, de modo que el panel de delegación del chat puede ofrecer conservarlo mientras el run sigue en pantalla, y el Builder ofrece lo mismo sobre un especialista inline.

La promoción crea un borrador propiedad de quien lo promovió, tras la puerta de agents:edit, y ahí se detiene: no publica, no fija el nuevo agent como delegado y no elimina el especialista del que salió.

Consulta Conceptos para ver por qué la regla de persistencia es la razón de que exista la salida, y no una limitación que la salida sortee.

Uno conservado dura todo el run en el que se inventó, incluido un aparcamiento por aprobación: la inscripción vive en un registro que la biblioteca de delegación construye por agente construido, y un run que aparca se vuelve a construir cuando se continúa, así que se perdía a lo largo del aparcamiento hasta que las inscripciones se llevaron en PausedRunState y se volvieron a registrar en la repetición (#175). No sobrevive al siguiente turno de la conversación, que es una construcción nueva sin estado aparcado: un nombre creado en una respuesta es desconocido en la siguiente, y la descripción de create_agent le dice al modelo que lo cree otra vez si task lo indica.

El delegado no especializado propio de la biblioteca de delegación no se ofrece en absoluto, y no hay ajuste para él.

Antes de subagents-pydantic-ai 0.2.18 se habría ejecutado con un modelo que este despliegue no configuró, compilado a partir de la cadena de modelo por defecto de la propia biblioteca, fuera de los perfiles de la organización, de su vault y del guardián de budget del run. Exactamente igual que el especialista en tiempo de ejecución de más arriba, antes de que necesitara una factory.

Un comodín para todo es algo legítimo de querer. Escríbelo como un especialista inline, donde puedes leer lo que hace y se tarifa como todo lo demás.

El propio de la biblioteca quedó arreglado a partir de la 0.2.18 (#174): sin modelo por defecto ni factory, ahora se niega a construir el delegado en vez de elegir un modelo.

Lo que se le cuenta al modelo sobre todo esto se escribe aquí y no en la biblioteca: los delegados por nombre y descripción, el modo que este run usará realmente y el tope de fan-out que si no descubriría al ser rechazado. Dos listas de los mismos delegados en un solo system prompt son contexto pagado dos veces, y solo una de ellas puede decir lo que el despliegue impone.

Agents remotos por A2A

Un agent también puede delegar en el agent de otro proveedor que hable el protocolo Agent2Agent. Se registra una vez en Connections → Remote agents por la dirección de su agent card, que se lee y se valida en ese momento, con una clave API opcional del vault enviada como bearer token. El spec de un agent nombra registros en external_agents, y los ajustes de delegación los muestran junto a los agents propios de la organización. La clave de un registro se presta a cada run, como la de una capability, así que publicar rechaza un registro cuya clave quien publica no puede ver en el vault.

Cada agent que delega en un registro depende de él, así que actualizarlo o eliminarlo le corresponde a quien lo registró, o a quien edita todos los agents; y actualizarlo, que decide adónde va la clave, solo a quien aún puede usar esa clave. Una actualización cuya card lleva el agent a otro host queda en la auditoría, porque ese host recibe desde entonces las peticiones y la clave. La lista muestra la dirección de una card y su endpoint sin query ni userinfo, donde una dirección firmada lleva su credencial.

Borrar la clave que envía un registro borra el registro, en lugar de dejarlo llamando sin ella. Una clave solo se envía a un endpoint HTTPS, así que una card con HTTP sin cifrar se rechaza para un registro que tiene clave. Una dirección con nombre de usuario o contraseña se rechaza de entrada: una credencial va al vault.

Para el modelo que delega, un agent remoto es un delegado más, llamado por su nombre con la misma herramienta task. Cada llamada es un message/send de A2A con la última petición, y el contextId del agent remoto pasa de una llamada a la siguiente dentro de la misma delegación: una pregunta de seguimiento sobre una tarea continúa su conversación remota, y dos tareas enviadas a la vez tienen cada una la suya.

Una respuesta input-required o auth-required vuelve como texto que lo dice, porque un delegado que espera a su propio usuario es una respuesta que el modelo tiene que leer. Una parte de datos o de archivo de una respuesta no se lee, y aparece como una línea que dice que se envió. Un agent remoto al que no se puede llegar responde precisamente eso, y el run sigue.

El budget se comprueba antes de cada llamada. Lo que cuesta una llamada lo fija el proveedor remoto y nadie lo publica, así que cada llamada se anota sin precio y el coste del run dice que es parcial. Cada petición pasa por las comprobaciones de direcciones del despliegue, de modo que ni una card ni un endpoint pueden apuntar la plataforma a su propia red. Publicar rechaza una referencia a un registro que no existe, y un run cuyo registro se eliminó entretanto falla nombrándolo.

Para ver qué cuesta una delegación y qué fila de run la registra, consulta Gobernanza. Para ver quién puede delegar en qué, consulta Permisos.

Planificación

write_plan — plantea o sustituye la lista de comprobación entera. read_plan — ver los pasos y sus ids antes de una edición granular. add_task, update_task_status, update_task_statuses, remove_task — cambiar un paso, o un lote, sin sustituir el plan. add_subtask, set_dependency, get_available_tasks — planificación consciente de las dependencias, ofrecida solo bajo enable_subtasks.

Una lista de comprobación que el modelo mantiene para sí mientras trabaja: qué está hecho, qué está en marcha, qué queda. En trabajo de varios pasos un modelo lo hace mejor cuando primero anota los pasos y los mantiene delante, así que el plan actual se le devuelve cada turno como un recordatorio de cola seguro para la caché: añadido después de un punto de corte de caché, de modo que el prefijo estable del prompt sigue siendo idéntico byte a byte y solo el plan mutable se vuelve a leer cada turno. El plan no aterriza nunca en el system prompt.

Se solapa con la Delegación como un plan se solapa con un equipo: la planificación decide cuáles son los pasos, la delegación decide quién los hace. Son ortogonales — un plan es un toolset más un recordatorio, la delegación es un toolset más un envoltorio del run —, así que un agent puede vincular ambas, una o ninguna.

Configuración Valor por defecto Valores
enable_subtasks false añade las tres herramientas de subtarea/dependencia y el estado blocked
cache_ttl 5m 5m, 1h: cuánto puede cachearse el prefijo anterior al recordatorio

Ninguna de las nueve herramientas actúa sobre el mundo. Cada una muta una lista de comprobación que el modelo mantiene para sí, así que aquí no hay nada que una persona pueda aprobar y la capability declara side_effecting=False. Las tres herramientas de subtarea se declaran incluso cuando una lista plana no las ofrece, porque una herramienta ausente de la declaración no puede ponerse tras una puerta por la política de aprobación ni renombrarse por una vinculación.

El plan pertenece a la conversación, no a un turno de ella.

La lista de comprobación es estado, y si no, cada frontera que tiene un run lo perdería. Un run que aparca en una aprobación a mitad de plan se reanuda como un run nuevo — y aquí un mensaje de chat es un run, así que el siguiente mensaje empezaba desde un almacén vacío.

Un agent escribió tres pasos, se le pidió empezar el primero y respondió que no existía ningún plan y que él nunca había creado uno (agenticos#1077).

Así que el almacén es del runner, no de la capability. Se siembra desde el plan almacenado de la conversación, o desde paused_state en una reanudación, que es la copia más reciente, y se escribe de vuelta en la conversación cuando el run se detiene.

Una superficie sin conversación — una llamada pelada a la API — conserva un plan mientras dura su run, que es todo lo que tiene.

Una lista de comprobación terminada es historia, y un turno nuevo no parte de ella. La fila la conserva — no se borra nada —, pero un plan cuyos pasos están todos completed o cancelled no se siembra en el turno siguiente: el recordatorio de cola llamaría «tu plan actual» a una tarea que nadie está haciendo, y read_plan respondería con ella (agenticos#1221).

Conservar la fila exige una regla más, porque un turno escribe su almacén de vuelta al terminar: el turno cuyo almacén se abrió vacío sobre un plan terminado no escribe nada. No hizo nada a la lista de comprobación, y un volcado vacío borraría la fila en el siguiente mensaje corriente. Un agent que empieza trabajo nuevo vuelca un plan y lo sustituye como siempre.

El filtro está en la siembra, no al marcar el último paso, y en eso consiste toda la decisión. En el turno que termina un plan el almacén todavía lo tiene, así que el agent puede resumir lo que acaba de hacer y nada contradice la transcripción. Es la siguiente pregunta la que empieza limpia, y la lista marcada sigue en los mensajes de encima, donde se lee como lo hecho y no como lo que se está haciendo. Un paso blocked es trabajo pendiente, así que un plan que tenga uno sí se siembra: algo tiene que desbloquearlo. Una reanudación se siembra desde paused_state y queda intacta: está a mitad de plan por construcción.

Un run que termina sin acabar deja su paso. Un run que falló, se detuvo o superó el presupuesto ya no trabaja en nada, así que el plan que escribe de vuelta no tiene ningún paso in_progress: ese paso vuelve a pending, pendiente pero sin nadie haciéndolo. Conservado tal cual, el recordatorio del siguiente turno le decía a un run nuevo que ya lo estaba haciendo (agenticos#2149). Un turno que termina a mitad de plan a propósito —con una pregunta a la persona— y un run aparcado conservan su paso en curso.

Un agent que no vincula la capability no paga nada: ni herramientas, ni recordatorio, ni nada almacenado, porque una lista de comprobación vacía contra una columna que es null no es un cambio que escribir.

No gasta tokens propios. Las herramientas son ediciones locales de una lista de comprobación sin ninguna petición a un modelo o a un embedding detrás, así que, a diferencia del conocimiento o la delegación, no hay uso ambiental que contabilizar. Las idas y vueltas que hace el modelo para llamarlas son suyas, y el guardián de budget ya las cuenta.

Trabajar mientras corren las herramientas

Sin herramientas propias. Con ella activada, una herramienta lenta que otra capability marca - hoy browse_web del navegador web - recibe la opción run_in_background, y el modelo decide en cada llamada: espera una navegación sin cuya respuesta no puede decir nada; deja corriendo otra que puede rodear mientras responde al resto de la petición, y su resultado llega como mensaje cuando está listo. Construida sobre BackgroundTools de pydantic-ai-harness.

  • Revisado igual. Un resultado en segundo plano pasa por las reglas de resultados de herramientas del agente - guardrails, límites de salida de herramientas - igual que uno en primer plano; la biblioteca sola lo entregaría saltándoselas.
  • Primero la aprobación. Una herramienta que espera a una persona se detiene para su aprobación antes de que nada corra en segundo plano.
  • El presupuesto y la vida del run. Lo que gasta una herramienta en segundo plano es del run, y detener el run la detiene. Un run que se pausa - una aprobación, una pregunta - cancela las herramientas en segundo plano que siguen en marcha, y sus resultados no llegan tras reanudar.

Razonamiento

Sin herramientas. Le pide al modelo que razone antes de responder: más lento y más caro, mejor en trabajo que exige tener varios pasos en la cabeza a la vez.

Configuración Valor por defecto Valores
effort sin poner minimal, low, medium, high, xhigh, max

Sin poner significa el esfuerzo por defecto del propio provider. Un nivel que un provider no tenga se asigna al más cercano que tenga, de modo que un spec sigue siendo portable al cambiar de modelo.

Recordatorio de instrucciones

Sin herramientas. Reitera las pautas de guía en mitad del run para que una sesión larga deje de alejarse de sus instrucciones; el fallo que arregla es el desvanecido de las instrucciones, en el que, tras muchos turnos de uso de herramientas, un modelo va ignorando progresivamente las pautas con las que empezó. Es un port de SystemReminders de pydantic-ai-harness.

Hay tres clases de recordatorio, cada una con su propia cadencia:

Clase Coste Texto que inyecta
reminders[] ninguno Una línea fija que escribes tú
goal_reanchor ninguno La primera petición del usuario en el run, reformulada como ancla
llm_reminder una llamada al modelo por disparo Un empujón corto que un modelo escribe a partir de la transcripción reciente

Cada clase recibe interval (dispara cada N peticiones al modelo), first_after (el número de petición del primer disparo) y max_fires (el tope a lo largo de la conversación); cache_ttl en la capability fija la vida del punto de corte de caché. Hay que poner al menos una clase, o la capability no aporta nada y se descarta del run, que es lo que significa una configuración vacía.

La cadencia cuenta a lo largo de toda la conversación, y es duradera. Un recordatorio se dispara en la petición al modelo número N, y ese contador se guarda en la conversación y se vuelve a sembrar en el turno siguiente, así que un recordatorio puesto para dispararse cada diez peticiones sigue contando donde lo dejó el último turno en lugar de reiniciarse a cero, y salir de una conversación y recargarla lo reanuda (#787). Solo se guardan los contadores; el texto del recordatorio se inyecta por petición y no entra nunca en la transcripción.

La inyección es segura para la caché. Un recordatorio disparado se añade a la cola de la petición como un prompt de usuario efímero detrás de un punto de corte de caché, después de que el núcleo haya persistido el historial duradero, así que llega al modelo pero no entra nunca en message_history, no se acumulan recordatorios rancios, y el prefijo cacheado (herramientas, sistema, la conversación real) sigue idéntico byte a byte turno tras turno mientras solo el pequeño recordatorio cae fuera de la caché. Inyectarlo en el system prompt en su lugar reventaría el prefijo cacheado en cada disparo y acumularía recordatorios rancios.

Un recordatorio por LLM se contabiliza y hereda el modelo del run.

Escribe su texto mediante un agente que construye él mismo, al que no envuelve ningún guardián de budget, así que su gasto se anota en el libro del run igual que el de un resumen, y se ejecuta bajo los límites de uso del run menos una petición reservada, de modo que nunca puede empujar al run más allá de su propio límite de pasos.

Usa el modelo propio del run — aquel cuya credencial resolvió el vault — en vez de un nombre de la configuración, la misma decisión que toma la Gestión del contexto sobre su resumidor.

Ante cualquier error, o cuando el budget reservado ya está gastado, recurre a la línea de reanclaje del objetivo. Una generación fallida nunca bloquea el run.

Fecha y hora

Sin herramientas. Pone la fecha y la hora actuales en las instrucciones del agent, para que deje de suponerlas; el fallo que arregla es un agent razonando con seguridad sobre «este trimestre» a partir de su fecha de corte de entrenamiento.

Configuración Valor por defecto
timezone UTC cualquier nombre IANA, p. ej. Europe/Warsaw

Conversaciones largas

Sin herramientas. Recorta el historial de mensajes de un run largo antes de cada petición, para que un run que habría alcanzado el límite del modelo siga funcionando. Las estrategias vienen de pydantic-ai-harness.

Configuración Valor por defecto
strategy summarize summarize, tiered, clear_tool_results, sliding_window
max_fraction 0.9 0,05–0,95 de la ventana, punto en el que empieza la compactación
keep_messages 20 mensajes recientes que sobreviven a un resumen o a una ventana
keep_tool_pairs 3 llamadas de herramienta recientes que conservan su resultado
summary_prompt el propio de la biblioteca qué se le dice al modelo que resume; tiene que contener {messages}
context_window sin poner sobrescribe la ventana: contra qué se dispara esto y entre qué divide el indicador del chat
fallback_context_window 200000 ventana que suponer cuando no se puede resolver la del modelo

summarize es el valor por defecto porque es la única estrategia que conserva lo que dijeron los turnos antiguos. Las que no usan LLM son más baratas porque tiran información — una ventana deslizante descarta sin más los mensajes más antiguos, limpiar el resultado de una herramienta borra una respuesta que el agent quizá aún necesite — y un agent que olvida calladamente lo que le dijeron en mitad del run es un fallo peor que un resumen que nadie pidió. Se dispara al 0,9 de la ventana por la misma razón: la compactación es donde un run empieza a perder detalle, así que se pospone hasta que la ventana está casi llena.

tiered es la opción frugal y está a una vinculación de distancia: primero limpia los resultados de herramienta antiguos y solo paga un resumen si eso no bastó. Resumir convierte tokens de entrada en tokens de salida, que se facturan más caros y se generan en serie, así que a un agent cuyos runs están dominados por resultados de herramienta grandes le suele ir mejor con tiered.

Alcanza un run, no una conversación. Entre turnos el historial se reconstruye desde la transcripción como texto de usuario y de asistente, así que las llamadas de herramienta y sus resultados no están ahí para compactarse y ninguna edición hecha aquí sobrevive a la frontera de un turno. El historial que merece la pena compactar es el largo bucle de herramientas dentro de un solo run, donde un listado de directorio o una búsqueda de conocimiento son decenas de miles de tokens.

El disparador es una fracción porque un número absoluto solo es correcto para un modelo, y el mismo agent se ejecuta con el perfil al que apunte su spec. La ventana viene del perfil del modelo, que la anotó del listado del propio provider cuando alguien añadió el modelo; consulta Qué modelos ofrece un provider.

Donde el perfil no anotó nada, la ventana se resuelve en cambio desde la instantánea de precios incluida, y dos casos se resuelven mal, ambos en la dirección que rompe un run y no en la que desperdicia un resumen: un spec con fallbacks construye un FallbackModel cuyo id compuesto no resuelve a nada, y genai-prices anota 1.000.000 para anthropic:claude-sonnet-4-5 frente a los 200.000 reales, con lo que max_fraction=0.9 pone el disparador en 900.000 y la compactación no se dispara jamás. context_window lo sobrescribe todo y es la respuesta a ambos: un provider publica el máximo que un modelo puede llegar a aceptar, y un despliegue limitado por beta o por nivel recibe menos.

El disparador tiene en cuenta lo que lleva cada petición. Mide las partes del mensaje; una petición lleva además las instrucciones y el esquema de cada herramienta. En un agent real el estimador vio 60 tokens donde el provider cobró por 3.865, así que el sobrecoste se mide contra cada respuesta y la ventana del disparador se baja en esa cantidad, que es lo que mantiene al indicador y al disparador describiendo un solo techo en vez de dos.

Espera a tener una respuesta desde la que medir, así que la primera petición de un run se dispara solo con los mensajes. Y se rinde cuando el sobrecoste por sí solo pasa del disparador: ningún resumen puede bajar de ahí, los esquemas no están en el historial, y una ventana corregida compraría un resumen en cada petición para siempre.

Cuando se rinde, lo dice. Un context_window menor que el propio sobrecoste del agent es ese caso, y no hacer nada al respecto es indistinguible en pantalla de un ajuste que funciona, así que el chat muestra cuál es el sobrecoste y contra qué ventana se midió, que es el par que alguien necesita para elegir un número que funcione. Una vez por run, porque describe una configuración y no un evento, y queda desplazado en cuanto se ejecuta un resumen de verdad.

Un resumen dice que está ocurriendo. Es una petición al modelo entera entre dos de las del propio turno, en la que no se transmite nada más: el chat se quedaba parado en seco durante todo ese rato, lo que se lee como una pantalla rota y hace que se recargue la página, cancelando el turno. Ahora el chat muestra qué se está resumiendo mientras ocurre. Solo la estrategia que resume: las demás editan una lista y devuelven.

Un resumen se contabiliza. La estrategia lo escribe mediante un agente que construye ella misma, al que no envuelve ningún guardián de budget, así que la capability mide el uso del run a lo largo del hook y anota la diferencia en el libro del run. Se registra en vez de impedirse: el guardián se niega en la siguiente petición, así que una compactación que cruza un tope detiene el run después de ella, no durante.

El indicador que hay al lado no forma parte de esta vinculación. Lo llena que estaba la ventana lo informa cada agent, compacte o no; consulta lo lleno que está el context window. El aviso importa sobre todo al agent que no va a compactar, que es el que llega al techo y recibe un rechazo.

Imágenes fuera del historial

Ninguna herramienta. Escribe las partes grandes de una conversación compactada en el almacenamiento y deja en el historial guardado una referencia media+sha256://…. Los almacenes direccionados por contenido y los recorridos vienen de pydantic-ai-harness.

Configuración Por defecto
threshold_bytes 32768 1 KiB–10 MiB; las partes de ese tamaño o mayores se guardan fuera del historial

Hay un sitio donde los medios se acumulan de verdad, y es este. Un adjunto llega al modelo una vez, en el turno en que se adjuntó: el historial ordinario se reconstruye a partir del texto de la transcripción, así que una imagen no se reenvía. La excepción es una conversación compactada — entonces el volcado que hace la propia biblioteca de los mensajes del run se guarda entero y se reproduce exactamente como el modelo lo vio por última vez, base64 incluido, hasta que el siguiente resumen lo sustituye. Ese blob son filas en Postgres y bytes en el cable, en cada turno intermedio.

Dónde van los bytes, y cuánto viven. Al propio almacenamiento de ficheros del despliegue, en media/<organización>/<conversación>/<digest>. La organización es el aislamiento: una URI de medios es un hash de contenido, así que dos inquilinos con la misma imagen calculan la misma URI, y la organización viene del run y no de la URI. La conversación es el ciclo de vida — un hash de contenido no registra quién lo sigue referenciando, así que el prefijo del hilo se va con el hilo y el del inquilino con el inquilino. El almacén no emite ninguna URL pública; una URL que puede descargar un proveedor de modelos la puede descargar cualquiera.

Descargar es opcional; restaurar no. Vincular la capability es la decisión de descargar. La reinserción ocurre para toda conversación, siga vinculada o no: una conversación cuyo agent se desvinculó después sigue teniendo marcadores en su historial, y un marcador que nadie reinserta es una imagen entregada al modelo en un idioma que no lee.

Ambas direcciones fallan con suavidad. Perder un resumen por el que se pagó a un modelo, porque el almacén tuvo un tropiezo, es peor que un historial más grande de lo necesario. El fallo se registra y el historial se usa tal cual.

No reduce lo que se le envía al modelo: las partes se reinsertan antes de que salga la petición, y eso es lo que mantiene correcto el run. Reescribirlas a una URL que el modelo descargue por su cuenta es otra función y necesitaría la URL pública que este almacén deliberadamente no emite.

Límites de resultados de herramientas

Una herramienta, read_tool_result. Donde compaction recorta el historial dentro de la ventana entre peticiones, esta impide de entrada que un retorno de herramienta desmedido llegue ahí. Un ToolReturnPart se persiste, así que un grep sobre un repositorio grande o una respuesta de API verbosa se reenvía entero en cada petición posterior del run; code_execution ya recorta en 8.000 caracteres exactamente por eso, que es el valor por defecto correcto y el techo equivocado: la parte que importaba desaparece de la vista del modelo sin nada sobre lo que actuar. Esta reduce un retorno una vez, cuando se produce, y deja que persista la forma reducida. La reducción en sí es el ToolOutputLimits de pydantic-ai-harness.

Configuración Valor por defecto
action spill spill, truncate, summarize
threshold 60000 tamaño a partir del cual se reduce un retorno; 15000 cuando over_tokens está activado
over_tokens false mide el umbral en tokens estimados, no en caracteres
max_chars 20000 caracteres que se conservan cuando se trunca un retorno, o cuando un volcado recae en un truncado; sin fijar, nunca más que el umbral
truncation_strategy head_tail head, tail, head_tail: qué extremo o extremos conservar
strip_ansi false quita los códigos de color del terminal antes de medir y reducir
summary_prompt el propio de la biblioteca qué se le dice al modelo que resume; tiene que contener {tool_name} y {output}

spill es el valor por defecto y el único sin pérdida: el retorno completo se escribe en el backend del agent y se sustituye por un handle, una vista previa y un esbozo de su forma, y el modelo lee porciones de él bajo demanda mediante read_tool_result(handle, offset, limit, from_end, pattern), el mismo patrón de paginación que le da read_file sobre el workspace. truncate es el recorte barato y con pérdida, con un marcador que dice qué se cortó; summarize sustituye el retorno por un resumen hecho por un LLM y es el caro.

Un volcado va al propio backend del agent.

Un agent que vincula sandbox ya tiene un sistema de archivos — state, un contenedor de Docker, Daytona — que el runner abrió para el run y asoció a la organización. El volcado vive ahí, bajo un prefijo tool_output/, así que comparte la vida de ese workspace y el agent puede incluso alcanzarlo con sus propios read_file y grep.

En el ámbito de sesión run, el que viene por defecto, esa vida es el run, que es lo que pide el requisito de «no debe sobrevivir al run».

Un volcado es un artefacto de dentro del run y tampoco sobrevive nunca al run en un workspace de ámbito más largo (conversation, user, agent):

  • a un workspace state se le quita el prefijo reservado al volcar, así que los volcados acumulados ya no pueden empujarlo hacia su tope de bytes y hacer que rechace las escrituras del propio agent;
  • a un workspace de contenedor se le borran de su sistema de archivos los volcados del run cuando el workspace se cierra, por los handles exactos que el run registró y nunca barriendo el prefijo, de modo que dos runs concurrentes que compartan un workspace no pueden llevarse los volcados del otro en pleno vuelo (#803).

Un agent sin backend recibe uno en memoria construido para el run y descartado con él, así que el volcado no se escribe nunca en disco compartido.

Un volcado que el backend rechace — un workspace state que ya esté en su tope de bytes — recae en un truncado en vez de desaparecer en silencio. Y lo mismo un summarize cuya llamada al modelo falle: summarize → spill → truncate.

Un resumen se factura al run. Igual que el de compaction, la llamada que resume pasa por un Agent que el harness construye él mismo, fuera del guardián de budget, así que sus tokens se anotan en el libro del run por la misma vía de uso ambiental; consulta cómo se cuenta el coste de un run. spill y truncate no llaman a ningún modelo y no cuestan nada.

El harness compone reducciones a partir de una lista ordenada de bandas de tamaño; aquí un autor elige una action en un threshold, porque el formulario del Builder no puede dibujar una lista anidada, la misma razón por la que compaction elige una estrategia en vez de componer niveles.

Sin herramientas propias. Permite al agent encontrar una herramienta dentro de un conjunto grande en lugar de llevar en su contexto el esquema de cada herramienta en cada petición. Esto importa sobre todo para MCP: un agent puede vincular un número arbitrario de servidores, y cada herramienta que expone un servidor es un esquema que el modelo paga en cada turno, la llame alguna vez o no.

Configuración Valor por defecto Valores
strategy auto auto, keywords, bm25, regex
max_results 10 1–50, ignorado por la búsqueda nativa
  • auto — búsqueda nativa de herramientas donde el provider la ofrece (BM25 o regex de Anthropic, del lado del servidor en OpenAI), y el algoritmo local de palabras clave en todo lo demás.
  • keywords — busca siempre en local, con cualquier provider.
  • bm25 / regex — fuerza un algoritmo nativo de Anthropic; un run con un provider sin búsqueda nativa de herramientas da error en lugar de sustituirlo calladamente por otro. El modelo se resuelve aparte del spec, así que este es un coste en tiempo de ejecución que el autor acepta al nombrar uno; auto no falla nunca así.

Activarla es lo que difiere los toolsets MCP. La capability y el diferido son las dos mitades de una sola decisión: el ToolSearch de la biblioteca es inerte si no hay nada diferido, y una herramienta diferida sin una búsqueda que la encuentre es una herramienta que el modelo no puede llamar nunca. Así que vincular tool_search es lo que marca los toolsets de los servidores conectados para carga diferida; las herramientas propias del registro siguen visibles, por ser pocas y elegidas por agent. Un agent que no la vincule no paga nada y ve todas las herramientas como antes.

El Builder avisa cuando merece la pena. En cuanto las vinculaciones MCP de un agent suman más de 30 herramientas - según la lista permitida de cada vinculación o, si no la tiene, según lo que su servidor listó en la última comprobación -, la pestaña MCP muestra un aviso con una estimación aproximada de tokens y un botón que vincula la búsqueda de herramientas. Nunca la vincula por sí solo: el diferido cambia lo que ve el modelo, así que la decisión es de quien edita.

El diferido cambia lo que ve el modelo, nunca la identidad de una herramienta. Una herramienta MCP descubierta llega con su nombre real con prefijo, así que la puerta de aprobación sigue emparejándose con ella y el renombrado de una vinculación sigue alcanzándola; ToolSearch se sitúa en la capa más externa, leyendo los nombres que un renombrado ya aplicó.

No necesita contabilización. Las dos estrategias locales se ejecutan en Python y no gastan tokens; la búsqueda nativa se ejecuta dentro de la propia petición del provider, cuyo uso ya contabiliza el guardián de budget; y las idas y vueltas del descubrimiento son peticiones corrientes al modelo que envuelve ese mismo guardián. La única forma que se le escaparía — un callable de búsqueda a medida que llamara él mismo a un modelo o a un embedding — deliberadamente no se expone.

Reglas de protección

Sin herramientas. Inspecciona el texto que circula por un run en cuatro bordes y o bien censura una coincidencia o bien bloquea el run. Las comprobaciones son detectores ya hechos de pydantic-ai-harness, más un detector de números de teléfono que el harness no incluye; un agent son datos, así que la configuración los selecciona y los parametriza en lugar de llevar una guarda en Python.

Borde Lee Censura Bloqueo
entrada el prompt del usuario redact_secrets_in, redact_pii_in blocked_keywords_in
salida la respuesta del agent redact_secrets_out, redact_pii_out blocked_keywords_out
resultado de herramienta lo que devolvió una herramienta, antes de que lo lea el modelo redact_secrets_tool, redact_pii_tool blocked_keywords_tool
argumentos de herramienta lo que el agent pasa a una herramienta, antes de que se ejecute redact_secrets_args, redact_pii_args blocked_keywords_args
Configuración Valor por defecto
redact_secrets_* false limpia claves de API, tokens, JWT y bloques PEM
redact_pii_* false limpia correos, números de teléfono (válidos en su plan de numeración), IBAN (mod-97), tarjetas (Luhn) y el SSN de EE. UU.
blocked_keywords_* "" términos separados por comas o saltos de línea; una coincidencia termina el run
phone_regions "US, GB, DE, PL" códigos ISO 3166, separados por comas o saltos de línea, cuyos formatos nacionales de teléfono lee la censura de PII, como mucho 16

Todos los campos de borde vienen apagados por defecto, y una capability activada sin ningún borde configurado no adjunta nada: un agent que no la usa no paga nada.

Un número de teléfono solo se censura cuando es un número real. El detector es el de libphonenumber, con su nivel STRICT_GROUPING: un candidato se acepta solo cuando encaja en el plan de numeración de su país y sus separadores caen donde ese país agrupa los dígitos, así que una fecha, un importe o un número de pedido que una regla por cantidad de dígitos tomaría pasan sin cambios. Un número escrito con + indica su propio país y se censura sea cual sea el contenido de phone_regions. Un número nacional, como 415-555-0132, se lee frente a cada país de la lista, y cada país añadido amplía lo que puede ser una simple secuencia de dígitos: 123456789 es un fijo polaco válido, así que con PL en la lista también se censura un número de pedido de nueve dígitos. La agrupación funciona en ambos sentidos: un código postal ZIP+4 de EE. UU. que empieza por 0, como 02134-1234, está agrupado como un prefijo alemán con su número, así que con DE en la lista se censura. Incluye los países a los que atiende el agent. Un código desconocido (lo habitual es UK en lugar de GB), o una lista de más de 16, se rechaza al publicar. Con hasta cuatro países, un texto de más de 200.000 caracteres o con más de 10.000 dígitos no se lee. Cada país por encima de cuatro es otra pasada sobre el texto, así que ambos límites bajan en proporción, a 50.000 caracteres y 2.500 dígitos con dieciséis. Un texto por encima del límite termina el run con guardrail_blocked, porque pasarlo sin leer pasaría también cada número que contiene.

La censura reescribe; un bloqueo es un desenlace del run. Un censor limpia la coincidencia y el run termina: una respuesta que devolvía una clave citada ha hecho el trabajo igualmente. Un bloqueo por palabra clave, en cambio, termina el run con estado guardrail_blocked, un desenlace propio junto a budget_exceeded, porque un rechazo es la plataforma funcionando y un operador que filtre buscando problemas debería poder encontrarlo en lugar de que se lea como cualquier respuesta completada. Consulta Gobernanza.

Una comprobación de salida filtra la respuesta antes de que nadie la vea. Todas las superficies transmiten en streaming: el chat web y el widget incrustado envían la respuesta mientras se escribe, y un bot de canal edita su respuesta según llega el texto. Cuando hay configurada cualquier comprobación de salida, cada fragmento de texto y de razonamiento se retiene hasta que está completo, se comprueba con los mismos detectores y solo entonces se envía. Una clave partida en dos trozos se detecta igualmente. Una partida en dos partes, como el texto antes de una llamada a herramienta y el texto después, o el razonamiento y luego la respuesta, termina el run antes de enviar su segunda mitad, porque la primera ya está en pantalla. También el texto que el modelo escribe antes de llamar a una herramienta, que no forma parte de la respuesta final pero aun así se muestra y se guarda. El coste es que la respuesta de un agent así llega paso a paso en lugar de palabra a palabra. Un agent sin comprobación de salida transmite como antes. Un bloqueo por palabra clave en la respuesta termina el run antes de que se muestre o se guarde nada del texto bloqueado. El razonamiento no es la respuesta, así que una palabra clave bloqueada en él no termina el run: ese paso de razonamiento muestra [reasoning withheld by the output guardrail] en su lugar.

Los argumentos de herramienta son un borde propio. Cada texto que el agent pasa a una herramienta se revisa cuando llega la respuesta del modelo, antes de que la herramienta se ejecute. El valor redactado es lo que recibe la herramienta, lo que guarda la transcripción y lo que muestra el stream, y una palabra bloqueada termina el run antes de llamar a ninguna herramienta. Va aparte de la revisión de salida porque redactar un argumento cambia lo que hace la herramienta: un correo sale con un marcador donde estaba la clave. Mientras está activo, los argumentos de una llamada se retienen hasta que la llamada está completa, como la respuesta.

Un delegado transmite bajo la revisión de salida del run que delega. El texto y el razonamiento que un delegado escribe en el panel de delegación pasan la misma revisión que la respuesta del run, en cada nivel de delegación. Una palabra bloqueada termina esa delegación antes de mostrar nada de ella, y el agent que delegó recibe que ha fallado.

El filtro del stream hereda los límites de tamaño del detector de teléfonos, así que una parte de la respuesta demasiado larga para él termina el run igual que lo haría la respuesta final. Como el filtro se engancha al flujo de eventos del run, las peticiones al modelo de un agent con guardrail se transmiten en streaming incluso a través de la API HTTP, así que su modelo debe admitir streaming.

El borde de entrada cambia lo que lee el modelo, no la transcripción. Un prompt censurado llega al modelo limpio, pero la conversación guarda el mensaje tal como lo escribió la persona, datos personales incluidos. Quien pueda leer la conversación puede leer ese mensaje.

El filtrado de los resultados de herramienta es la razón de que este borde sea el que más importa. Es la única guarda sobre el contenido no confiable que entra en el bucle — una página descargada, un archivo, la respuesta de un servidor MCP — donde si no una carga de inyección de prompt llegaría al modelo sin leerse.

Dos cosas quedan deliberadamente fuera de alcance. Los argumentos de herramienta son un mapeo estructurado sin detector de texto, así que no son un borde. Y el veredicto approve de herramienta del harness no se ha portado: las aprobaciones ya aparcan un run por herramienta para una decisión humana, y una segunda vía, guiada por reglas, al mismo mecanismo es justo lo que evita una única puerta.

Información del canal de chat

get_channel_info — Describe el canal en el que está ocurriendo esta conversación. list_channel_members — Lista las personas de este canal. search_channels — Encuentra otros canales por nombre o por propósito, sin leerlos. read_channel_history — Lee los mensajes más recientes de este canal, el más nuevo al final. send_channel_file — Enviar un fichero solicitado del workspace.

Para conectar la cuenta, envíe al bot un mensaje directo con solo link, sin barra. Abra el enlace privado tras iniciar sesión en AgenticOS y vuelva al canal. Si los ficheros anteriores del hilo requieren conexión, el bot lo explica sin descargarlos y vuelve a intentarlo tras conectar. Las imágenes del canal usan el doble de resolución CSS; los diagramas Mermaid horizontales muy anchos reciben una disposición vertical solo en el PNG.

La única capability que el spec de un agent no puede vincular. Se concede por vinculación, en el Builder bajo Where this agent is available, porque una organización puede vincular un agent a dos servidores de Mattermost y tres workspaces de Slack — y «¿puede leer lo que se dijo en este canal?» tiene una respuesta distinta en el interno y en el del cliente. Un campo del spec tendría una sola respuesta para los cinco, así que la validación de publicación rechaza channel_tools en un spec y el run ensambla la vinculación desde la fila que admitió el mensaje, igual que añade el prompt de esa fila a las instrucciones.

Sigue siendo una capability corriente del registro, que es el motivo de hacerlo así en vez de inyectar un toolset: a sus herramientas se les puede poner una puerta con tool_approval y renombrarlas con tool_overrides, y ambas cosas leen el spec.

Configuración Valor por defecto Rango
tools [] cualquiera de los cuatro ids
default_limit 20 1–200

No se concede nada por defecto, y lo que una plataforma no puede responder no se ofrece: Telegram no le da a un bot ningún directorio de chats en el que buscar ni manera alguna de leer mensajes que no se le enviaron. docs/channels.md tiene la tabla por plataforma y el razonamiento.

Tres propiedades se cumplen en todas las plataformas:

  • La pertenencia del bot es toda la frontera de permisos. Cada llamada usa el token del propio bot, así que el agent ve exactamente lo que ve el bot.
  • El modelo no nombra nunca un canal. Las herramientas se atan en el servidor a aquel en el que llegó el mensaje; en un hilo, al canal que lo contiene.
  • Fuera de un canal no aporta nada. Un run desde el panel, la API o una programación no tiene directorio, así que la capability no se adjunta en absoluto, por la misma razón por la que knowledge sin colecciones tampoco.

Preguntar al usuario

Cuando un agent necesita una decisión que solo la persona puede tomar, pregunta en lugar de adivinar: ask_user_question acepta de una a diez preguntas de opción múltiple, cada una con un encabezado corto, de dos a seis opciones con lo que significa cada una y si se pueden elegir varias. La consola las muestra como una tarjeta, una pregunta a la vez, con espacio para una respuesta escrita y un resumen antes de enviar; el agent recibe lo elegido por encabezado, o se le dice que la persona no quiso responder, y sigue.

La herramienta y su esquema vienen de AskUser de pydantic-ai-harness. Está activa por defecto en cada agent nuevo y en cada plantilla, y el autor puede desactivarla. Donde todavía nadie puede responder — una programación, un webhook, la API — el agent recibe que la persona no quiso responder.

Una pregunta sin respuesta no detiene para siempre el trabajo del agent. Si la persona se va con la tarjeta abierta, el run se aparca en ella — Waiting for an answer en el historial de runs — y la tarjeta vuelve cuando reabre la conversación, en cualquier dispositivo. Responder continúa el run desde donde se detuvo. Solo la persona a quien se preguntó puede responder, mediante POST /runs/{run_id}/answers; GET /runs/{run_id}/questions vuelve a leer la tarjeta. Pasado un día (QUESTION_EXPIRY_HOURS, 24) el run termina como cancelado y el paso registra que la persona no respondió a tiempo.

Follow-ups

Los botones funcionan en el chat de la consola y los canales de mensajería indicados. Los widgets públicos, las páginas alojadas y los sockets embed no procesan tarjetas de sugerencias, por lo que no inician una ejecución de seguimiento de pago. Antes de generar y entregar, los canales vuelven a comprobar la identidad registrada del remitente, la vinculación del bot, la política de acceso actual y la pertenencia a la sala. Un remitente admitido sin cuenta vinculada sigue siendo distinto del publicador usado para contabilizar costes.

Los botones de siguientes pasos los genera exclusivamente un modelo separado, elegido en Modelo para siguientes pasos (model_profile_id). Sin selección, las sugerencias están desactivadas. Después de actualizar, selecciona un perfil en Follow-ups para seguir usando los botones. El modelo principal nunca recibe suggest_next_steps ni espera las sugerencias para terminar su respuesta o aceptar el siguiente mensaje.

Después de confirmar la transacción de la respuesta, una tarea opcional en segundo plano lee la pregunta de ese run y la respuesta guardada. Devuelve hasta tres pasos breves en el idioma de la persona, o ninguno. La consola recibe después un evento que identifica la conversación y la respuesta exactas; un turno posterior queda intacto. Los botones guardados se conservan al volver a abrir la conversación. Slack, Teams, Telegram y Mattermost inician este trabajo después de publicar la respuesta principal y envían los botones por separado.

El modelo auxiliar no tiene herramientas ni instrucciones del agente principal. Su propio run comprueba los presupuestos del agente, departamento y organización, hereda la privacidad del tracing y registra su coste por separado, también tras un error. El coste de la respuesta principal no cambia. Cada proceso permite cuatro tareas simultáneas; las demás se omiten. El trabajo del modelo está limitado a 15 segundos, como máximo dos peticiones y 512 tokens de salida. No hay reintentos automáticos. Las sugerencias pueden perderse al cerrar el proceso o la conexión; nunca sustituyen la respuesta por un error.

Activity identifica el run auxiliar como Siguientes pasos. Conserva los permisos de lectura de la conversación original y cuenta en su coste total, sin marcarla como ocupada ni sustituir su último run principal.

suggest_next_steps conserva la identidad registrada de los botones para el historial y los ajustes existentes del spec; el modelo principal no puede invocarlo.

propose_setup propone hasta tres cosas que configurar, dibujadas como tarjetas bajo la respuesta: un agent propio de la persona, una rutina o un servicio que conectar. Una tarjeta actúa con un clic - una rutina abre el diálogo de rutinas relleno, en el agent que la propuso; un agent es una pregunta al Architect.

Una conexión que nombra su servidor por clave del catálogo es la tarjeta de ese servicio - su logo y su botón Conectar, que lleva directo al inicio de sesión del proveedor y de vuelta a la conversación - y una sin clave abre la página de servidores MCP en su botón de añadir. El agent AgenticOS termina con ellas su entrevista de bienvenida, y ofrece una tarjeta de conexión siempre que una petición necesita un servicio que la persona no ha conectado. Las tarjetas son de la consola; una app de chat no las dibuja.

Está activa en el agent inicial y en el Architect, y un autor puede desactivarla. Una llamada de voz la deja fuera.

Qué puede cambiar una vinculación

El catálogo es la respuesta del despliegue a «qué existe». La entrada capabilities[] de un spec es la respuesta de un agent a «cómo la uso», y puede cambiar cuatro cosas:

Campo Efecto
config Validado contra el esquema de esa capability al publicar, no en tiempo de ejecución
approval default | required | never para cada herramienta que aporte la capability
tool_approval Lo mismo, por herramienta, sobrescribiendo approval
tool_overrides El name y la description que ve el modelo, por herramienta
secret_id Qué secreto de la organización satisface un requisito de clave declarado
enabled Apagada sin perder la configuración

La aprobación es la razón de que una capability declare sus herramientas siquiera. «¿Puede este agent escribir archivos?» y «¿puede leerlos?» son dos decisiones aunque una sola capability responda a ambas, así que activar sigue siendo por capability mientras que aprobar ocurre por herramienta. default no pregunta a nadie; side_effecting solo le dice al Builder qué herramientas actúan sobre el mundo.

La descripción de una herramienta es el prompt con más palanca del producto

Es lo que lee el modelo antes de decidirse a llamar, y su nombre dirige con la misma fuerza: search_refund_policy no es search_documents. Un agent que necesita un comportamiento distinto de la misma herramienta suele necesitar que se reescriban estos textos, no que se escriba una segunda capability.

Referenciado por el id estable de la herramienta, nunca por el nombre que ve el modelo

Eso es lo que mantiene una puerta de aprobación pegada a una herramienta renombrada. Referenciarla por el nombre visible significaría que un renombrado quita la puerta calladamente, y una llamada con efectos secundarios queda entonces desatendida sin que nada lo informe. Un id que ninguna capability así expone

se rechaza al publicar, y también un nombre que ningún modelo podría llamar.

Scopes

Una capability puede declarar scopes que la organización tiene que haber concedido, comprobados cuando se ensambla el agent:

Scope Declarado por
knowledge:read knowledge, skills
conversations:read conversation_search
web:read web_research
web:fetch web_fetch
web:browse browser_use
code:execute code_execution
sandbox:execute sandbox
agents:delegate subagents

Los ocho se conceden hoy por defecto

DEFAULT_GRANTED_SCOPES en app/services/agent_registry.py. La gestión de scopes por organización es trabajo de la hoja de ruta; mientras tanto la comprobación está viva y es honesta, en lugar de estar deshabilitada y olvidada.

agents:delegate no es la puerta sobre a quién se puede delegar

Esa es agents:run, comprobada sobre quien publica contra la fila de cada delegado. Este scope responde a una pregunta que ningún permiso puede responder: si este despliegue permite siquiera que los agents llamen a agents. Quítalo de ese conjunto y la delegación queda apagada en todas partes con una sola edición.

Un operador que no quiera runs anidados ni facturación por fan-out lo quita, y cada spec que delegue lo dice entonces al publicar y no a las tres de la mañana. conversations:read es la misma clase de palanca para la búsqueda de conversaciones: una sola edición impide que cualquier agent lea conversaciones pasadas, para un despliegue que considere una transcripción demasiado sensible como para ser buscable, por muy acotado que esté el corpus.

Qué le dice una herramienta al modelo

La definición de una herramienta es un prompt. El modelo elige una herramienta, y rellena sus argumentos, sin nada más que el texto adjunto a ella, así que cada herramienta de aquí lleva cuatro cosas, y la cuarta es la que suele omitirse:

  1. Qué hace, en una frase. Es además lo que el Builder muestra junto a la casilla de aprobación, así que se escribe una vez y lo leen los dos.
  2. Cuándo usarla, y cuándo usar otra cosa. create_chart dice que es para números que ya tienes y generate_image para algo que hay que dibujar; glob dice que busca de forma recursiva donde ls no lo hace.
  3. Qué significa cada argumento, incluidos su valor por defecto y su tope.
  4. Qué vuelve: la forma de la respuesta, qué aspecto tiene un fallo y dónde el resultado es una porción truncada en vez del conjunto entero. Un modelo que no sabe que grep responde en tres formas distintas según output_mode, o que glob se detiene a las 100 rutas, razona a partir de una porción como si fuera todo.

Las cuatro llegan al modelo con una sola forma, y es la propia de pydantic-ai: la prosa dentro de <summary>, la descripción del retorno dentro de <returns>.

Una herramienta escrita aquí lo obtiene gratis: el framework la construye a partir de la sección Returns: del docstring.

Una herramienta que viene de una biblioteca se registra con una descripción explícita, lo que le quita ese camino. Así que su texto pasa por ToolText, en app/agents/capabilities/_tool_text.py, que dibuja lo que habría dibujado el framework.

Dos convenciones en una misma lista de herramientas es una cosa más que el modelo tiene que reconciliar, y tests/test_tool_text_shape.py es lo que mantiene que haya una: fija ToolText contra una herramienta que pydantic-ai construye por su cuenta, y comprueba que las herramientas de cada capability llevan una forma de retorno.

Eso cubre también las herramientas que este despliegue no escribió: a planning y a la delegación se les entrega el texto de este repositorio, web_fetch y search_tools se vuelven a describir donde se construyen, y read_tool_result y read_skill_resource se vuelven a describir en el sitio, sobre el toolset de la biblioteca. Una mereció la molestia más allá de la coherencia: la frase de la biblioteca para read_tool_result no decía nada de con qué responde un handle, lo único que necesita un modelo que sostiene uno.

Una herramienta de una biblioteca para la que este repositorio no tiene texto conserva el de la biblioteca, que es el valor por defecto correcto: run_skill_script se excluye en vez de describirse, y si algún día llega, llega diciendo lo que escribiera su autor.

Un error, un resultado y un rechazo

Cómo informa una herramienta de un problema decide qué hace el modelo a continuación, y los tres no son intercambiables.

El fallo Qué hace la herramienta Por qué
Los propios argumentos del modelo: una serie de un gráfico con el número equivocado de valores, un archivo de contexto que no existe, un NameError en el Python que escribió Petición de reintento Volver a llamar de otra manera es un arreglo plausible, y el mensaje dice qué aspecto tiene una llamada correcta
Un fallo transitorio de lo que hay detrás de la herramienta: un provider de búsqueda caído, una base de conocimiento que agota el tiempo Petición de reintento Un error con forma de resultado se lee como «no se encontró nada», y el modelo responde entonces de memoria, con seguridad, sin decir que tuvo que hacerlo
Un resultado que simplemente son malas noticias: un comando que salió con código distinto de cero, una búsqueda sin aciertos, un canal que este bot no puede ver Se devuelve como texto Es la respuesta. El modelo razona sobre ella y sigue
Un rechazo: una regla de permisos, una capability que el despliegue no ofrece Se devuelve como texto Una petición de reintento invita al modelo a buscar la manera de rodearlo

Los reintentos están presupuestados: una llamada de herramienta tiene un intento para corregirse, y un reintento lanzado más allá de ese presupuesto termina el run entero en vez de la llamada. Así que el último intento devuelve su mensaje en lugar de lanzar: dirigido mientras hay presupuesto para ello, y nunca peor que la respuesta que habría dado de todas formas. El único helper que decide esto es app/agents/capabilities/_failures.py; pydantic-ai-backend mantiene la misma regla para las herramientas del workspace.

Añadir a esta lista

Las capabilities son código: nada de lo que teclee un operador trae una nueva a la existencia, que es lo que hace revisable el conjunto de cosas que un agent puede hacer. Consulta Añadir una capability para una nueva, o Añadir una herramienta a una capability cuando la capability ya existe.

Para herramientas que nadie de aquí tiene que escribir, consulta MCP.