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.
Búsqueda de conocimiento¶
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.
Conversaciones anteriores¶
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.
Búsqueda web¶
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,localen 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_charscomo 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 hastamax_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
statese 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.
Búsqueda de herramientas¶
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;autono 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
knowledgesin 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:
- 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.
- Cuándo usarla, y cuándo usar otra cosa.
create_chartdice que es para números que ya tienes ygenerate_imagepara algo que hay que dibujar;globdice que busca de forma recursiva dondelsno lo hace. - Qué significa cada argumento, incluidos su valor por defecto y su tope.
- 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
grepresponde en tres formas distintas segúnoutput_mode, o queglobse 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.