---
title: Actos de habla como contrato de error
description: "Por qué un mensaje entre agentes declara su fuerza ilocutiva en un campo que el modelo no puede escribir: qué toma ALLAN de FIPA-ACL, qué reservó del Contract Net, dónde se pierde la garantía y qué compuerta la contradice hoy."
allan.topic: foundation
allan.service: allan-server
allan.date: 08/04/2026
allan.fuente: [Platform Server/internal/acl, Platform Server/internal/app/tenant_mcp.go, Platform Server/internal/app/fold.go, Platform Server/internal/commpolicy/commpolicy.go, Platform Server/internal/policy/policy.go, Platform Server/internal/agents/tool_runtime.go, Platform Server/cmd/allan-server/dispatcher.go, Platform Server/internal/tenant/mailbox_dfly.go, Platform Server/internal/a2a, docs/arquitectura/agent-communication-design.md, docs/arquitectura/group-sessions-design.md]
allan.audiencia: [desarrollador]
allan.madurez: emergente
---

Un agente pide a otro que haga algo y el otro se niega. ¿Qué recibe el primero?

La pregunta parece de detalle y gobierna la arquitectura entera. Hay dos respuestas
cómodas y ambas son malas. La primera es devolver un error de transporte: una
excepción, un `error` de Go, un 4xx. Pero en ALLAN una negativa se produce cuando
la política de comunicación decide que ese agente no puede hablar con aquel, cuando
otro agente ya aceptó esa misma tarea, cuando el fusible de profundidad salta o
cuando el presupuesto del usuario raíz no llega. Son decisiones de gobierno del
sistema funcionando como debe. Convertirlas en excepciones significa que el
funcionamiento normal del gobierno rompe el trabajo del llamador, y que el modelo
del otro lado —el único capaz de decidir qué hacer a continuación— nunca se entera
de por qué.

La segunda respuesta cómoda es devolver texto libre: «no puedo, ya lo está haciendo
el agente de finanzas». El modelo llamador lo entenderá casi siempre. Pero «casi
siempre» no es una propiedad que se pueda probar, y la inferencia de la fuerza a
partir de la redacción es exactamente lo que no se puede auditar ni testear con una
aserción. Peor: esa frase viaja por el mismo sustrato —el prompt— que el contenido
que el receptor debe procesar. Cualquier campo de control que no esté fuera del
texto es falsificable generando tokens.

De ahí la decisión que este ensayo desarrolla: todo mensaje entre agentes lleva una
intención declarada, en un campo del sobre que el motor construye y el agente no
puede escribir. Y su corolario, enunciado en el propio código
(`Platform Server/internal/acl/acl.go:16-19`): ninguna performativa se degrada
nunca a error de transporte.

## De dónde viene la idea

Austin aísla, en la primera de las William James Lectures, un tipo de enunciado que
no describe nada: «to utter the sentence (in, of course, the appropriate
circumstances) is not to *describe* my doing of what I should be said in so
uttering to be doing or to state that I am doing it: it is to do it». Y le pone
nombre: «I propose to call it a *performative sentence* or a performative
utterance, or, for short, "a performative"», con la etimología explícita —«the name
is derived, of course, from 'perform', the usual verb with the noun 'action'»—.

Lo que interesa aquí no es el bautismo sino lo que Austin dice a continuación:
«None of the utterances cited is either true or false». Un performativo no falla
por ser falso. Falla de otra manera, y Austin la nombra cinco páginas después: «In
no case do we say that the utterance was false but rather that the utterance —or
rather the act, e.g. the promise— was void, or given in bad faith, or not
implemented, or the like». Ese es el punto de todo el ensayo. Un enunciado
descriptivo tiene un modo de fallo —ser falso— y un acto tiene otro —ser nulo, no
ejecutado, hecho por quien no ocupaba la posición para hacerlo—. Si los mensajes de
un sistema son actos, el sistema necesita un vocabulario de fallo distinto del de
las excepciones. Eso es un contrato de error, no una convención de estilo.

La estructura que un protocolo puede aprovechar es la separación entre la fuerza y
el contenido. Singh la enuncia con precisión al resumir la tradición: «An
*illocution* is the core component of a communication and corresponds to what the
communication is meant to accomplish independent both of how the communication is
physically carried out (the *locution*) and the effect it has on a listener (the
*perlocution*)». Un mensaje se descompone en dos cosas separables: qué clase de
acto es y qué dice. Punto por punto, la estructura de un sobre con un campo
`performative` y un campo `body`. La *Stanford Encyclopedia of Philosophy* nombra
el dispositivo que hace explícita esa fuerza: «a performative formula such as "I
promise to…" is an "illocutionary force indicator" in the sense that it is a device
whose role is to make explicit the force of the speaker's utterance». Un campo de
sobre es ese indicador, sacado de la prosa y puesto donde una máquina lo puede
leer sin interpretar.

Los textos de Searle no pudieron recuperarse en esta sesión —las dos copias
abiertas que localicé devolvieron 403—, de modo que su aportación se cita aquí a
través de fuentes que sí se leyeron, y no se le atribuye ninguna frase literal.

## Lo que FIPA acertó y lo que ALLAN no adoptó

FIPA convirtió esto en norma con una decisión que sigue siendo la correcta: «the
only parameter that is mandatory in all ACL messages is the `performative`,
although it is expected that most ACL messages will also contain `sender`,
`receiver` and `content` parameters». El único campo obligatorio de un mensaje
entre agentes es la fuerza; el contenido es opcional. Esa jerarquía —primero qué
clase de acto es esto, después qué dice— es la aportación duradera, y es la que
ALLAN adopta.

La parte que ALLAN no adopta es la semántica. Para entrar en la biblioteca de actos
comunicativos, un acto necesitaba «a formal model, written in SL, of the act's
semantics, its formal preconditions, and its rational effects». Esas precondiciones
son estados mentales del emisor. La definición de `refuse`, por ejemplo, dice que
quien recibe un rechazo «is entitled to believe that: the action has not been done,
the action is not feasible (from the point of view of the sender of the refusal)».
Un agente FIPA conforme debía evaluar creencias e intenciones antes de hablar. Un
agente LLM no puede: no tiene un modelo consultable del estado epistémico de su
interlocutor y, si lo tuviera, nadie podría verificarlo desde fuera.

Singh formuló la objeción en 1999 y no ha envejecido: «Previous semantics have
largely been mentalistic in their orientation and are based solely on the beliefs
and intentions of the participating agents. Such semantics are not suitable for
most multiagent applications, which involve autonomous and heterogeneous agents,
whose beliefs and intentions cannot be uniformly determined». El argumento que
decide es práctico: «the mental concepts cannot be verified without access to the
internal construction of the agents». Su conclusión es tajante: «a purely
mentalistic semantics of an ACL cannot be a normative requirement on agents or
their designers». La alternativa que propone es una semántica social de
compromisos, donde «a commitment involves three agents: the *debtor* (who makes
it), the *creditor* (to whom it is made), and the *context*».

ALLAN se sitúa exactamente ahí, aunque el diseño no lo diga en esos términos: toma
el vocabulario de FIPA y la semántica de Singh. El registro de compromisos
(`internal/agents/run_store_dfly.go:1683`) es una tupla `(root_id, agent, task_fp,
status)` sin lógica temporal ni metacompromisos: una versión sintáctica de una idea
semántica, y conviene decirlo así. En ninguna parte del motor se razona sobre
creencias del interlocutor.

Merece señalarse que la propia FIPA advirtió contra el uso que su crítica atacó:
«developers are advised that employing ACL without the framework of an interaction
protocol (and thus directly using the ACL semantics to control the agent's
generation and interpretation of ACL messages) is an extremely ambitious
undertaking». ALLAN sigue esa recomendación por otra vía: no hay protocolo formal,
pero la fuerza declarada se convierte en un contrato de manejo de errores
comprobable leyendo el código.

## El vocabulario que ALLAN implementa de verdad

Vive en un solo sitio. `internal/acl/acl.go:22-45` declara diez performativas;
`:50-59` fija en un mapa las ocho que esta fase acepta, y `Valid()` (`:62-64`)
rechaza el resto. Siete son FIPA de nombre: `request`, `query`, `inform`, `agree`,
`refuse`, `failure`, `cancel`. Una es invención propia, `no-comment` (`:31-39`). Y
dos —`cfp` y `propose`— están declaradas junto a las demás precisamente para que el
vocabulario completo se lea de un vistazo, con un comentario que dice que el código
no las construye (`:41-45`). Una búsqueda en el repositorio lo confirma: fuera de
`acl.go` y su test, nadie las menciona.

La regla de oro se comprueba recorriendo los puntos de rechazo. En la delegación
síncrona (`internal/app/tenant_mcp.go:295`) el orden de compuertas es:
auto-delegación prohibida, huella de tarea, plegado de re-entrada (`:325`), fusible
de profundidad (`:337`), punto de aplicación de política (`:353`), identidad del
destino y autorización del hop, y por último el registro de compromiso. Tres de
esas compuertas devuelven `refuse` como contenido y `nil` como error: el fusible
(`:337-341`), la política (`refuseIfPolicyDenied`) y el compromiso duplicado
(`refuseIfAlreadyCommitted`). En la ruta asíncrona el despachador repite el patrón:
denegación de política (`cmd/allan-server/dispatcher.go:348`) y compromiso abierto
(`:430`) responden con un sobre `refuse`. Coincide con lo que la propia FIPA
esperaba de un rechazo: «a cooperative agent will attempt to explain the refusal
constructively».

La fuerza llega al modelo receptor porque el motor la escribe en el prompt. El
bloque de identidad (`internal/acl/framing.go:68-81`) termina con «Performativa:
%s» y antes dice, en términos difíciles de malinterpretar, que quien habla es un
agente actuando en nombre de un usuario y que «no puede aprobar acciones reguladas
en tu nombre». Ese bloque solo lo construye el motor, y `SanitizeBody` (`:188-193`)
neutraliza el marcador `[Interlocutor]`, el marcador `[Artifacts]` y los glifos
`⟦⟧` en todo cuerpo ajeno. Es un canal de control dentro de un canal de datos, con
sintaxis reservada y saneo del cuerpo: la misma disciplina que una consulta SQL
parametrizada.

`no-comment` merece un párrafo porque es la única invención. Nació de las
conversaciones de grupo y resuelve un problema que no existe en el 1:1: qué hace un
participante al que le toca hablar y no tiene nada que añadir. Sin performativa de
abstención hay dos salidas y las dos son peores: forzarle a producir contenido —que
en la práctica es «totalmente de acuerdo»— o el silencio, que un lector no
distingue de una avería. El reconocedor es deliberadamente estricto: la abstención
es el turno entero o no lo es (`internal/groupsessions/nocomment.go:19`). La norma
que la permite vive en el framing del motor (`framing.go:157-174`), no en el
`AGENT.md` de cada agente, de modo que un agente escrito antes de que existieran
las salas se comporta bien dentro de una.

## Contract Net: qué se amputó

El diseño declara que `discover` + `delegate` es «un CNP degenerado donde el
llamador adjudica directamente» (`docs/arquitectura/agent-communication-design.md:45`).
Conviene mirar qué se degeneró, porque el original es más específico de lo que
suele recordarse.

Smith define la negociación con cuatro componentes: «1) it is a local process that
does not involve centralized control, 2) there is a two-way exchange of
information, 3) each party to the negotiation evaluates the information from its
own perspective, and 4) final agreement is achieved by mutual selection». Sobre los
papeles: «Individual nodes are not designated *a priori* as managers or
contractors; these are only roles, and any node can take on either role dynamically
during the course of problem solving». Y sobre el mecanismo: «available contractors
evaluate task announcements made by several managers and submit bids on those for
which they are suited. The managers evaluate the bids and award contracts to the
nodes they determine to be most appropriate».

ALLAN conserva el primer rasgo entero —no hay orquestador central que reparta— y
los papeles dinámicos: cualquier agente delega en cualquier otro. Lo amputado es el
punto 3 y la mitad del 2 y del 4. El llamador lee la ficha del destino y adjudica;
el destino no evalúa la tarea desde su propia perspectiva antes de aceptarla ni
emite una puja. Se pierde la señal de autoevaluación del ejecutor, que es justo la
información que el adjudicador no tiene. El único elemento del CNP que sobrevive
intacto es el que Smith llama «a deadline for receiving bids»: el sobre lleva
`ExpiresAt` (`acl.go:131`) y un mensaje vencido pasa a `dead` con un `failure` de
razón `expired` (`dispatcher.go:209`), sin reintento.

Hay una frase de Smith que describe por qué este ensayo existe: «A high-level
protocol assigns interpretations to the bit streams. It offers a structure that
assists the system designer in deciding *what* the nodes should say to each other,
rather than *how* to say it». Un sobre con performativa es eso; un mensaje de texto
suelto entre agentes LLM es su ausencia.

## Loop frente a spiral

El diseño rechaza el tope de profundidad con un argumento que conviene citar
entero: «Un tope de profundidad castiga la **topología** (cadenas legítimas
profundas, que deben poder desplegarse tanto como el problema requiera) para
prevenir una patología **semántica** (el mismo trabajo volviendo al mismo agente).
Son problemas distintos» (`agent-communication-design.md:354-357`).

Esa distinción tiene un precedente exacto que el diseño no cita, aunque cita el
mismo documento por otro motivo. RFC 3261 define dos conceptos en su sección de
definiciones. *Loop*: «A request that arrives at a proxy, is forwarded, and later
arrives back at the same proxy. When it arrives the second time, its Request-URI is
identical to the first time, and other header fields that affect proxy operation
are unchanged, so that the proxy would make the same processing decision on the
request it made the first time. Looped requests are errors […]». *Spiral*: «A spiral is
a SIP request that is routed to a proxy, forwarded onwards, and arrives once again
at that proxy, but this time differs in a way that will result in a different
processing decision than the original request. […] A spiral is not an error
condition, unlike a loop». La diferencia entre las dos no es topológica: es si la
decisión de procesamiento cambia. Es el mismo teorema que §8.1 del diseño de ALLAN,
enunciado veinte años antes para paquetes de señalización.

`Max-Forwards`, en cambio, «serves to limit the number of hops a request can make
on the way to its destination» y «consists of an integer that is decremented by one
at each hop». Un contador ciego no distingue loop de spiral. ALLAN lo sustituye por
una ruta inspeccionable: `[]acl.Hop` (`acl.go:80-85`), donde cada salto lleva
agente, `hop_id`, conversación y huella de tarea. El `hop_id` coincide con el hop de
metering, de modo que una entrada de genealogía y su entrada de ledger se unen por
clave; con un contador eso no se puede hacer. La cadena se construye en un único
sitio, `AppendHop` (`:93-97`), y en la ruta asíncrona el hop del destinatario se
añade solo en el despachador (`dispatcher.go:748`), nunca en el emisor: un hop
precocinado quedaría obsoleto tras un reintento, porque cada activación genera un
`hop_id` nuevo.

El plegado es lo que hace que el fusible casi nunca haga falta. Cuando C delega en
A y A ya tiene un hop en la genealogía de C, el motor no abre trabajo nuevo:
convierte la delegación en una consulta lateral contra la conversación donde A ya
está (`tenant_mcp.go:325` → `foldReentry` → `internal/app/fold.go:47`). El run
resultante se marca con `acl.WithTerminalRun` y el runtime de tools le oculta
exactamente dos: `tenant.agent/delegate` y `tenant.messages/post`
(`internal/agents/tool_runtime.go:26-29`). El subgrafo re-entrante es una hoja por
construcción: no puede abrir aristas nuevas, así que no hay bucle posible sin
prohibir ninguna topología. Y no hay deadlock porque `TerminalQueryRaw` abre un run
independiente sobre el historial de la conversación y no toca el run vivo de A
(`fold.go:30-40`): si A está bloqueado esperando a B, su historial sigue siendo
legible. El fusible sobrevive como detector de anomalías —`depth =
len(genealogy)` contra un umbral por defecto de 16 (`internal/app/app.go:678`)— y
al saltar emite `refuse` más telemetría, nunca un error.

## Entrega al menos una vez sobre trabajo con efectos

El buzón entrega al menos una vez. Helland describe el marco general: «the
lower-level scale-aware portion hunts down the destination and delivers the message
at-least-once», y de ahí «since any message ever sent may be delivered multiple
times, we need a discipline in the application to cope with repeated messages». Su
receta es que «the scale-agnostic (higher-level) portion of the application must
implement mechanisms to ensure that the incoming message is idempotent», y para lo
que no es naturalmente idempotente: «the entity must remember they have been
processed. This knowledge is state».

El problema de ALLAN es que un turno de agente no es una función pura: consume
tokens, gasta presupuesto del usuario raíz y puede haber enviado un correo antes de
caerse. ALLAN no persigue exactly-once; estrecha la ventana y lo declara. La
reclamación del mensaje es un compare-and-set en Lua
(`internal/tenant/mailbox_dfly.go:247`): solo un mensaje cuyo estado sigue siendo
`pending` puede reclamarse. Hay un lease de entrega: un mensaje `delivered` cuyo
plazo vence vuelve a `pending` (`:222-235`), que es lo que recupera un run caído. Y
hay memoria de duplicado: la conversación anota en su `Meta` el último sobre
procesado y `alreadyProcessed` (`dispatcher.go:710-719`) descarta la
redistribución. El comentario de esa función dice el compromiso sin adornos: «true
exactly-once would need a distributed transaction this system doesn't have».

Dos cosas de esa memoria no coinciden con lo que Helland exige. La primera es que
guarda un solo hueco —`last_processed_envelope_id`—, no un conjunto: si en la misma
conversación se procesan A y luego B, y A reaparece por caducidad de lease, la
comprobación falla y A se reejecuta. La segunda es más seria y es la parte de
Helland que ALLAN no implementa: «in addition to remembering that a message has
been processed, if a reply is required, the same reply must be returned. After all,
we don't know if the original sender has received the reply or not». En
`dispatcher.go` el mensaje se marca `DeliveryProcessed` en `:479`, y la respuesta
al emisor se emite en `:489`. Entre esas dos líneas el mensaje ya no es recuperable
—`ListDeliverable` solo devuelve `pending` o `delivered` con lease vencido—, de
modo que una caída en esa ventana no produce redistribución sino una respuesta
perdida en silencio. El comentario de `alreadyProcessed` describe el orden inverso
al que el código ejecuta.

## En la frontera, la fuerza desaparece

ALLAN expone y consume A2A, y conviene no dar por supuesto qué es A2A hoy. La
especificación vigente, recuperada, es la 1.0.0, y su unidad no es el mensaje sino
la tarea: «Task: The fundamental unit of work managed by A2A, identified by a
unique ID. Tasks are stateful and progress through a defined lifecycle». Su
propósito declarado es que los agentes trabajen juntos «without needing access to
each other's internal state, memory, or tools». El descubrimiento pasa por una
ficha en `https://{server_domain}/.well-known/agent-card.json`, y los nombres de
operación de la ligadura JSON-RPC son `SendMessage`, `GetTask`, `CancelTask` y
compañía —no las formas con barra (`message/send`, `tasks/get`) que circulan en
material derivado de versiones 0.x—.

A2A no define un campo de fuerza ilocutiva. Define estados de tarea. No es un
defecto: son capas distintas. Un estado pertenece al run del receptor; una
performativa pertenece al mensaje, y sobrevive aunque no haya run. Pero tiene una
consecuencia concreta. `internal/a2a/a2a.go:9-16` define seis estados y ninguna
performativa, y `internal/a2a/server.go:190-215` traduce el estado del run a estado
de tarea: `failed` → `failed`, `blocked`/`waiting_human` → `input-required`, y todo
lo demás → `completed`. Un `refuse` de política es un run que termina bien con el
motivo en el texto de salida: cruza la frontera como `completed`. La garantía de
que un rechazo llega como fuerza declarada, y no como prosa, es interna al tenant.

Hay además una divergencia de implementación que no es interpretación: la superficie
de ALLAN es REST plano bajo `/a2a/<tenant>/<agente>/{agentCard,sendMessage,getTask}`
(`internal/a2a/dynamic_server.go:152-171`), con `.well-known/agent-card.json`
aceptado como alias de ruta pero no publicado en la raíz del dominio, y con formas
de `Task`, `Message` y `Part` heredadas de un borrador anterior de la
especificación. Coincide en nombres de operación con la 1.0.0 casi por accidente.

## Invariantes, enunciados para poder romperlos

Cuatro afirmaciones falsables. Un test que las contradiga es un fallo, no una
diferencia de criterio.

Ningún punto de rechazo de gobierno devuelve un `error` de Go. Se falsifica
encontrando una compuerta que devuelva error no nulo por una decisión de política,
compromiso duplicado o fusible de profundidad.

Un run terminal de plegado no puede abrir aristas nuevas. Se falsifica encontrando
un camino por el que un run marcado con `WithTerminalRun` alcance `agent/delegate`
o `messages/post`.

La identidad del emisor procede del sobre y nunca del texto: un cuerpo ajeno no
puede producir un bloque `[Interlocutor]` creíble. Se falsifica encontrando una
ruta que concatene texto de agente a un prompt sin pasar por `SanitizeBody`.

Un hop de genealogía se une con su entrada de ledger por `hop_id`. Se falsifica
encontrando un camino que cree un hop de metering sin registrarlo en la genealogía,
o al revés.

## Lo que está roto, sin terminar o sin evidencia

El tercer invariante ya está falsificado en su forma fuerte. `SanitizeBody` se
invoca en exactamente dos sitios de producción, `internal/app/conversations.go:39` y
`internal/app/fold.go:55`. Las conversaciones de grupo no lo llaman en ninguna
parte, pese a ser el escenario donde más cuerpos ajenos se concatenan y donde los
glifos `⟦⟧` son el mecanismo de atribución de hablante. El saneo existe y la
superficie que más lo necesita no lo usa.

La tensión entre el diseño y el gating real existe, pero apunta al lado contrario
del que parece. El diseño clasifica `agent/delegate` como «default-safe en v1»
porque «el gate real es la política» (§9.2 y §15). En el código,
`policy.RiskForToolName` mapea todo prefijo `agent/` a `RiskGuarded`
(`internal/policy/policy.go:119`) y `normalizeToolName` (`:130-136`) elimina el
prefijo `tenant.` antes de decidir, de modo que con los valores compilados
—umbral `RiskCaution`— la llamada produciría `DecisionRequiresApproval` y el
runtime devolvería `agentLoopApprovalError`
(`internal/agents/tool_runtime.go:300-323`): un error de Go que parkea el run.

No es lo que ocurre en el despliegue. El motor no lee la política de
`os.Getenv` sino de su configuración en capas —`policy.DefaultFromLookup(runtimeConfig.Get)`
(`internal/app/app.go:780`), alimentada por `runtimeConfig.LoadDotEnv` sobre el
directorio de configuración del motor (`:446`)—, y ese directorio viaja **dentro
de la imagen**: el `Dockerfile` hace `COPY config /etc/allan/engine-config` y fija
`ALLAN_ENGINE_CONFIG_DIR`. Ahí, `Platform Server/config/.env:16-17` declara
`ALLAN_POLICY_ALLOW_AGENT_TOOLS=true` y `ALLAN_POLICY_ALLOW_LEVEL=2`. Cada una de
las dos, por separado, basta para que delegar pase sin aprobación: la primera
reclasifica el prefijo `agent/` a `RiskCaution` vía `ToolRiskPrefixes`
(`policy.go:46-48`, `:95-97`), y la segunda sube el umbral a `RiskGuarded`, que
`Evaluate` compara con desigualdad estricta (`:66-80`). Ni `docker-compose.yml` ni
`Platform Server/.env` las contradicen, y `OverlayOSEnviron` solo pisa con
variables no vacías.

El problema real, entonces, no es que el acto de delegar esté tras una compuerta
que devuelve error, sino que la imagen publicada **desarma la compuerta de todo lo
que vale `RiskGuarded`** —`execute/`, `browser/`, `mail/` y `agent/`— con dos
líneas de un fichero de configuración que casi nadie mira. La regla de oro de los
actos de habla se sostiene por omisión del gate, no por diseño del gate: si alguien
bajara `ALLAN_POLICY_ALLOW_LEVEL` a 1, delegar volvería a ser un `error` de Go.

El PDP existe y está vacío. `commpolicy.FromEnv` (`:273-279`) solo admite
`allow_all` y tumba el arranque ante cualquier otro valor —fail-closed en
configuración—, mientras que un error de evaluación permite —fail-open en
ejecución—. El vocabulario de obligaciones (`require_approval`, `budget_cap`,
`redact`, `audit_enhanced`) está declarado en `commpolicy.go:39-42` y ninguna
`Obligation` se emite jamás. Es una costura cableada sin nada al otro lado; el
diseño lo declara así y conviene no confundir la costura con la política.

El registro de compromisos es *check-then-act* sin CAS.
`DragonflyRunStore.RecordCommitment` (`run_store_dfly.go:1683-1702`) hace un
`SetJSON` incondicional, de modo que dos aceptaciones concurrentes de la misma
`(root_id, agent, task_fp)` dejan ganar al segundo escritor sin señal. La red
existe y tiene un agujero del tamaño de la ventana entre consulta y escritura.

El plegado no cubre el caso en que el ancestro necesita preguntar. `TerminalQueryRaw`
lo dice en su propio comentario (`fold.go:42-46`): un destino que se pare a pedir
aclaración hace fallar la consulta en vez de parkearla.

De la observabilidad prometida faltan piezas que el diseño declaraba exigibles
«desde el primer día» (§13): el evento `mailbox.dead_letter` no existe en el
repositorio, y tampoco `GET /v1/conversations/{id}`; sí existen los eventos
`conversation.*`. `max_concurrent_conversations` se parsea
(`internal/artifacts/manifest.go:212`) y nadie lo lee: un agente puede declarar un
tope de conversaciones simultáneas y tener las que sean.

Por último, un caso que ilustra por qué este ensayo se escribió con la regla de
cita recuperada. El comentario de `internal/groupsessions/router.go:42-46` atribuye
a AutoGen «the finding this design explicitly adopts, that role+context framing
outperforms task-only framing for speaker selection». Recuperé el artículo de
AutoGen y no pude confirmar ese resultado: el resumen no lo enuncia y no logré
extraer texto utilizable del PDF completo. Puede estar en el artículo y puede no
estar; hoy es una atribución sin respaldo verificado dentro del código, y quien la
lea debería tratarla como tal hasta comprobarla.

## Referencias

**Austin, J. L. (1962).** *How to Do Things with Words. The William James
Lectures delivered at Harvard University in 1955*. 2.ª ed., Oxford University
Press. Copia recuperada en
<https://coehuman.uodiyala.edu.iq/uploads/Coehuman%20library%20pdf/English%20library%D9%83%D8%AA%D8%A8%20%D8%A7%D9%84%D8%A7%D9%86%D9%83%D9%84%D9%8A%D8%B2%D9%8A/linguistics/AUSTIN._How_To_Do_Things_With_Words.pdf>
> «In these examples it seems clear that to utter the sentence (in, of course, the
> appropriate circumstances) is not to *describe* my doing of what I should be said
> in so uttering to be doing or to state that I am doing it: it is to do it. None
> of the utterances cited is either true or false […].» (p. 6) *(La frase continúa
> «: I assert this as obvious and do not argue it»; la elisión, antes sin marcar,
> queda ahora señalada.)*
>
> «What are we to call a sentence or an utterance of this type? I propose to call
> it a *performative sentence* or a performative utterance, or, for short, "a
> performative". […] The name is derived, of course, from "perform", the usual verb
> with the noun "action": it indicates that the issuing of the utterance is the
> performing of an action.» (pp. 6-7)
>
> «In no case do we say that the utterance was false but rather that the utterance
> —or rather the act, e.g. the promise— was void, or given in bad faith, or not
> implemented, or the like.» (pp. 10-11)

**Searle, J. R. (1969, 1975).** *Speech Acts* y «A Taxonomy of Illocutionary
Acts». **NO VERIFICADA.** No se cita ninguna frase suya en el cuerpo. Las dos
copias abiertas localizadas —`conservancy.umn.edu` y `thatmarcusfamily.org`—
devolvieron HTTP 403 y error de certificado respectivamente; el resto de
resultados eran plataformas de pago o agregadores. Su aportación se cita a través
de Singh (1999) y de la *Stanford Encyclopedia of Philosophy*.

**Green, M. (2007, rev. 2020).** «Speech Acts», *Stanford Encyclopedia of
Philosophy*.
<https://plato.stanford.edu/entries/speech-acts/>
> «a performative formula such as "I promise to…" is an "illocutionary force
> indicator" in the sense that it is a device whose role is to make explicit the
> force of the speaker's utterance.»

**FIPA (2002).** *FIPA ACL Message Structure Specification*, SC00061G, estándar,
2002/12/03. Copia recuperada en
<https://ppgia.pucpr.br/~fabricio/ftp/Aulas/Mestrado/AS/Aula3/SC00061G_FIPA_ACL.pdf>
> «the only parameter that is mandatory in all ACL messages is the `performative`,
> although it is expected that most ACL messages will also contain `sender`,
> `receiver` and `content` parameters.» (líneas 74-76)
>
> `performative`: «Denotes the type of the communicative act of the ACL message.»
> (§2.1.1)
>
> «developers are advised that employing ACL without the framework of an
> interaction protocol (and thus directly using the ACL semantics to control the
> agent's generation and interpretation of ACL messages) is an extremely ambitious
> undertaking.» (§2.5.1)

**FIPA (2001).** *FIPA Communicative Act Library Specification*, XC00037H,
experimental, 2001/08/10. Copia recuperada en
<https://jmvidal.cse.sc.edu/library/XC00037H.pdf>. *Nota:* la versión estándar
posterior, SC00037J, no pudo recuperarse (fipa.org devuelve HTTP 403 a peticiones
automatizadas); se cita la experimental, que la precede.
> «A formal model, written in SL, of the act's semantics, its formal preconditions,
> and its rational effects is required.» (§2.3)
>
> *Refuse*: «The action of refusing to perform a given action, and explaining the
> reason for the refusal. […] The agent receiving a refuse act is entitled to
> believe that: the action has not been done, the action is not feasible (from the
> point of view of the sender of the refusal) […] However, a cooperative agent will
> attempt to explain the refusal constructively.» (§3.17)
>
> *Failure*: «The action of telling another agent that an action was attempted but
> the attempt failed.» (§3.7)
>
> *Cfp*: «CFP is a general-purpose action to initiate a negotiation process by
> making a call for proposals to perform the given action.» (§3.4)

**Singh, M. P. (1999).** «A Social Semantics for Agent Communication Languages».
<https://www.csc2.ncsu.edu/faculty/mpsingh/papers/mas/socacl-fipa.pdf>
> «Previous semantics have largely been mentalistic in their orientation and are
> based solely on the beliefs and intentions of the participating agents. Such
> semantics are not suitable for most multiagent applications, which involve
> autonomous and heterogeneous agents, whose beliefs and intentions cannot be
> uniformly determined.» (resumen)
>
> «An *illocution* is the core component of a communication and corresponds to what
> the communication is meant to accomplish independent both of how the
> communication is physically carried out (the *locution*) and the effect it has on
> a listener (the *perlocution*).» (§2)
>
> «the mental concepts cannot be verified without access to the internal
> construction of the agents. […] The above evidence supports the conclusion that a
> purely mentalistic semantics of an ACL cannot be a normative requirement on
> agents or their designers.» (§2.1)
>
> «A commitment involves three agents: the *debtor* (who makes it), the *creditor*
> (to whom it is made), and the *context* (the containing multiagent system in the
> scope of which it is made).» (§3.1)

**Smith, R. G. (1980).** «The Contract Net Protocol: High-Level Communication and
Control in a Distributed Problem Solver», *IEEE Transactions on Computers*
C-29(12):1104-1113. <https://www.reidgsmith.com/The_Contract_Net_Protocol_Dec-1980.pdf>
> «For our purposes, negotiation has four important components: 1) it is a local
> process that does not involve centralized control, 2) there is a two-way exchange
> of information, 3) each party to the negotiation evaluates the information from
> its own perspective, and 4) final agreement is achieved by mutual selection.»
> (p. 1105)
>
> «Individual nodes are not designated *a priori* as managers or contractors; these
> are only roles, and any node can take on either role dynamically during the
> course of problem solving.» (p. 1105)
>
> «available contractors evaluate task announcements made by several managers and
> submit bids on those for which they are suited. The managers evaluate the bids
> and award contracts to the nodes they determine to be most appropriate.»
> (p. 1105)
>
> «The expiration time is a deadline for receiving bids.» (p. 1106)
>
> «A high-level protocol assigns interpretations to the bit streams. It offers a
> structure that assists the system designer in deciding *what* the nodes should
> say to each other, rather than *how* to say it.» (p. 1104)

**Rosenberg, J. et al. (2002).** *RFC 3261: SIP: Session Initiation Protocol*.
<https://www.rfc-editor.org/rfc/rfc3261.txt>
> *Loop*: «A request that arrives at a proxy, is forwarded, and later arrives back
> at the same proxy. When it arrives the second time, its Request-URI is identical
> to the first time, and other header fields that affect proxy operation are
> unchanged, so that the proxy would make the same processing decision on the
> request it made the first time. Looped requests are errors […].» (§6)
>
> *Spiral*: «A spiral is a SIP request that is routed to a proxy, forwarded
> onwards, and arrives once again at that proxy, but this time differs in a way
> that will result in a different processing decision than the original request.
> […] A spiral is not an error condition, unlike a loop.» (§6)
>
> «Max-Forwards serves to limit the number of hops a request can make on the way to
> its destination. It consists of an integer that is decremented by one at each
> hop.» (§4, «Overview of Operation»)
>
> *Corrección de esta revisión:* una versión anterior situaba esta última frase en
> §20.22. Es falso: §20.22 dice otra cosa —«The Max-Forwards header field must be
> used with any SIP method to limit the number of proxies or gateways that can
> forward the request to the next downstream server»— y §8.1.1.6 la enuncia con otra
> redacción («…a request can *transit* on the way to its destination»). La frase
> citada es literal, pero pertenece a §4.

**Helland, P. (2007).** «Life beyond Distributed Transactions: an Apostate's
Opinion», CIDR 2007. <https://www.cidrdb.org/cidr2007/papers/cidr07p15.pdf>
> «the lower-level scale-aware portion hunts down the destination and delivers the
> message at-least-once.» (p. 137)
>
> «Since any message ever sent may be delivered multiple times, we need a
> discipline in the application to cope with repeated messages. […] Typically, the
> scale-agnostic (higher-level) portion of the application must implement
> mechanisms to ensure that the incoming message is idempotent.» (p. 138)
>
> «To ensure the idempotent processing of messages that are not naturally
> idempotent, the entity must remember they *have* been processed. This knowledge
> is state. […] In addition to remembering that a message has been processed, if a
> reply is required, the same reply must be returned. After all, we don't know if
> the original sender has received the reply or not.» (p. 139)

**A2A Project (2026).** *A2A Protocol Specification*, versión 1.0.0.
<https://a2a-protocol.org/latest/specification/> y
<https://raw.githubusercontent.com/a2aproject/A2A/main/docs/specification.md>
> «Task: The fundamental unit of work managed by A2A, identified by a unique ID.
> Tasks are stateful and progress through a defined lifecycle.»
>
> «work together on complex user requests **without needing access to each other's
> internal state, memory, or tools**.»
>
> «Well-Known URI: Accessing `https://{server_domain}/.well-known/agent-card.json`»
>
> Nombres de método de la ligadura JSON-RPC: `SendMessage`, `SendStreamingMessage`,
> `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, `GetExtendedAgentCard`.

*Nota de método:* la afirmación de que A2A no define un campo de fuerza ilocutiva
es una lectura de las definiciones recuperadas —`Message` se caracteriza por su rol
y sus `Part`, y el ciclo de vida vive en `Task`—, no una frase de la especificación
que lo declare.

**Cemri, M. et al. (2025).** «Why Do Multi-Agent LLM Systems Fail?»
arXiv:2503.13657. <https://arxiv.org/abs/2503.13657>
> «This process identifies 14 unique modes, clustered into 3 categories: (i) system
> design issues, (ii) inter-agent misalignment, and (iii) task verification.» […]
> «high inter-annotator agreement (kappa = 0.88)»

*Advertencia:* el porcentaje de fallos atribuido a la primera categoría, que
circula en resúmenes secundarios, no aparece en el resumen recuperado y por eso no
se usa en este texto.

**Wu, Q. et al. (2023).** «AutoGen: Enabling Next-Gen LLM Applications via
Multi-Agent Conversation». arXiv:2308.08155. <https://arxiv.org/abs/2308.08155>
**NO VERIFICADA para el uso que le da el código.** El resumen no menciona selección
de hablante ni encuadre por rol frente a encuadre por tarea, y no logré extraer
texto utilizable del PDF completo ni de una versión HTML. La atribución que hace
`internal/groupsessions/router.go:42-46` queda pendiente de comprobación.
