Módulo 6: Estado compartido y el patrón blackboard
El trade-off medido: visibilidad contra aislamiento
Descripción
Las lecciones 02 a 05 construyeron el Blackboard completo, y lo usaron de las dos formas posibles:
escribiendo (03), leyendo (04), auditando (05). En ningún momento, hasta ahora, se detuvieron a
contar algo que la lección 01 adelantó como el eje del módulo: cuánto ve cada agente cuando lee
el Blackboard, comparado con lo que su tarea necesita. Esta lección cuenta eso, con números
reales, y lo compara con la alternativa: un paquete de handoff (Módulo 5) diseñado a la medida
exacta del receptor.
El resultado no es una opinión — es una cuenta de campos. policy_agent, para responder "¿puedo
cancelar mi reserva?", necesita exactamente un campo (booking_id). Leyendo el Blackboard
completo, tiene acceso a seis. Esos cinco de más —member, room, tier, hours,
price_cents— son ruido: información que el agente puede ver, sin que su tarea la requiera.
Conexión con el módulo
Esta lección reusa Blackboard sin ningún cambio, y agrega la medición: RELEVANT_FIELDS, un
mapeo de qué necesita cada agente, y la comparación con un paquete de handoff construido a mano,
siguiendo la forma genérica que describió el Módulo 1 (quién envía, a quién, la tarea, el payload
mínimo). La lección 07 corre la corrida completa sobre la que se apoya esta medición; el Módulo 8
del agent-security-and-sandboxing-guide retoma la superficie que esta lección solo nombra.
Analogía: la pizarra que cualquiera puede leer entera, contra el sobre cerrado con un solo dato
Vuelve a la pizarra de la sala de guardia. Un enfermero que solo necesita saber en qué cama está un paciente, para llevarle una bandeja, lee la pizarra entera —incluidos el diagnóstico, la medicación, las notas del médico de guardia— para encontrar ese único dato que le importa. No hay forma de que la pizarra le muestre solo la cama y le oculte el resto; está toda a la vista, para quien sea que la mire. Un sobre cerrado, en cambio, dirigido a ese enfermero específico, contendría un solo papel: "cama 4". Nada más. Esta lección cuenta, literalmente, cuántos "papeles de más" trae la pizarra frente al sobre — para cada tarea concreta de Reservo.
Ejemplo trabajado: contando el ruido, campo por campo
import itertools
from dataclasses import dataclass, field, fields as dc_fields
WRITE_SEQ = itertools.count(1)
@dataclass
class WriteLogEntry:
seq: int
writer: str
field: str
value: object
@dataclass
class Blackboard:
member: str | None = None
room: str | None = None
tier: str | None = None
hours: int | None = None
price_cents: int | None = None
booking_id: int | None = None
log: list = field(default_factory=list)
def write(self, writer, **fields):
for key, value in fields.items():
setattr(self, key, value)
self.log.append(
WriteLogEntry(seq=next(WRITE_SEQ), writer=writer, field=key, value=value)
)
bb = Blackboard()
bb.write("supervisor", member="Ana")
bb.write("booking_agent", room="Focus", tier="pro", hours=3, price_cents=6000)
bb.write("booking_agent", booking_id=1)
ALL_FIELDS = {f.name for f in dc_fields(Blackboard) if f.name != "log"}
print("--- campos del Blackboard, todos visibles para CUALQUIER agente con una referencia a bb ---")
print(sorted(ALL_FIELDS))
# Lo que cada tarea REALMENTE necesita -- no lo que la estructura expone.
RELEVANT_FIELDS = {
"policy_agent": {"booking_id"},
"pricing_agent": {"tier", "hours"},
}
print()
print("--- ruido: campos visibles menos campos relevantes, por agente ---")
for agent, relevant in RELEVANT_FIELDS.items():
noise = sorted(ALL_FIELDS - relevant)
print(f"{agent}:")
print(f" necesita: {sorted(relevant)}")
print(f" ve además: {noise} ({len(noise)} campos de ruido)")
print()
print("--- comparación con un paquete de mensaje punto a punto (M5), del mismo tamaño mínimo ---")
def build_handoff_packet(sender, receiver, task, **payload):
"""La forma genérica del paquete de M5: quién envía, a quién, la
tarea, y SOLO el payload que el receptor necesita -- nunca la
estructura completa de datos del emisor."""
return {"from": sender, "to": receiver, "task": task, "payload": payload}
packet = build_handoff_packet(
sender="booking_agent", receiver="policy_agent",
task="verificar política de cancelación", booking_id=bb.booking_id,
)
print("paquete armado a mano para policy_agent:", packet)
print(f"campos en el payload del paquete: {sorted(packet['payload'])} ({len(packet['payload'])} campo)")
print(f"campos visibles leyendo el Blackboard completo: {sorted(ALL_FIELDS)} ({len(ALL_FIELDS)} campos)")
print()
print("--- nada impide que un agente sin relación con la tarea lea un campo que no necesita ---")
print("pricing_agent nunca necesita saber quién es el socio, pero nada en Blackboard.write ni en")
print("los campos públicos se lo impide:")
print("bb.member leído desde 'pricing_agent':", bb.member)
Qué esperar:
--- campos del Blackboard, todos visibles para CUALQUIER agente con una referencia a bb ---
['booking_id', 'hours', 'member', 'price_cents', 'room', 'tier']
--- ruido: campos visibles menos campos relevantes, por agente ---
policy_agent:
necesita: ['booking_id']
ve además: ['hours', 'member', 'price_cents', 'room', 'tier'] (5 campos de ruido)
pricing_agent:
necesita: ['hours', 'tier']
ve además: ['booking_id', 'member', 'price_cents', 'room'] (4 campos de ruido)
--- comparación con un paquete de mensaje punto a punto (M5), del mismo tamaño mínimo ---
paquete armado a mano para policy_agent: {'from': 'booking_agent', 'to': 'policy_agent', 'task': 'verificar política de cancelación', 'payload': {'booking_id': 1}}
campos en el payload del paquete: ['booking_id'] (1 campo)
campos visibles leyendo el Blackboard completo: ['booking_id', 'hours', 'member', 'price_cents', 'room', 'tier'] (6 campos)
--- nada impide que un agente sin relación con la tarea lea un campo que no necesita ---
pricing_agent nunca necesita saber quién es el socio, pero nada en Blackboard.write ni en
los campos públicos se lo impide:
bb.member leído desde 'pricing_agent': Ana
El número central de esta lección: policy_agent necesita 1 campo y ve 6 — un factor de
6x de ruido para su tarea concreta. Un paquete de handoff diseñado a medida, en cambio, expone
exactamente 1 campo, ni uno más. Esa diferencia —1 contra 6— es el costo real de la simplicidad
de coordinación que ofrece un Blackboard: nadie tuvo que decidir, al escribir, qué le correspondía
a quién; pero, a cambio, cualquiera que lea ve todo.
Por qué esto es exactamente "context bloat"
El nombre técnico de este ruido, en el ecosistema de esta guía, es context bloat: cuánto entra en
la ventana de contexto de un agente —su system/messages en la próxima llamada— que no aporta
nada a la tarea que tiene que resolver. Con seis campos en el Blackboard, el "bloat" de este
ejemplo es pequeño (cinco campos de más, en el peor caso) — pero el mecanismo escala mal: un
Blackboard real, con más agentes y más hechos compartidos a lo largo de una corrida más larga,
puede terminar con decenas de campos, mientras que cada tarea individual sigue necesitando solo
unos pocos. Presupuestar y comprimir ese contenido —decidir qué SÍ entra en la ventana de un
agente, qué se resume, qué se descarta— es terreno de context-engineering-guide; esta lección solo
mide la superficie del problema sobre el caso concreto de Reservo, sin construir ningún mecanismo de
compresión.
La otra cara: nada impide una lectura que no debería pasar
La última sección del ejemplo trabajado —pricing_agent leyendo bb.member— no es un error de
código: es una demostración de que el Blackboard, tal como está construido en este módulo, no
tiene ningún control de acceso por campo. Cualquier función que reciba una referencia a bb puede
leer cualquier atributo público, sin que exista una lista de "campos permitidos por agente" que se
haga cumplir en tiempo de ejecución. RELEVANT_FIELDS de esta lección es solo documentación —un
diccionario que describe la intención de diseño— no una restricción que el Blackboard imponga.
Esta ausencia de control de acceso es exactamente la puerta que deja abierta la superficie que la
lección 01 nombró: un agente que, por un bug o por una instrucción maliciosa incrustada en algo que
procesó, decide leer (o peor, escribir) un campo fuera de lo que su rol debería tocar. Construir esa
defensa —permisos por campo, sandboxing, validación de qué puede hacer cada agente— es terreno de
agent-security-and-sandboxing-guide; esta lección solo deja visible, con código ejecutado, que el
problema es real y no hipotético.
Errores comunes
-
Pensar que "ruido" significa que el Blackboard está mal diseñado. No — el ruido es el costo estructural del patrón, no un error de esta implementación en particular. Cualquier
Blackboardcon más de un campo tiene, para cualquier tarea que solo necesite un subconjunto, algo de ruido. La pregunta de diseño no es "¿cómo elimino el ruido?" sino "¿el ahorro de coordinación que da el Blackboard justifica el ruido, para este caso?". -
Confundir
RELEVANT_FIELDScon una restricción real. Es solo un diccionario de documentación en esta lección — no impide que el código depricing_agentleabb.membersi alguien lo escribiera así, como demuestra el ejemplo trabajado. -
Medir el ruido en bytes o caracteres, en vez de en campos. Esta lección cuenta campos, no tamaño de texto — es una medida más simple y suficiente para comparar diseños, aunque
context-engineering-guidesí trabaja con medidas más finas (tokens) cuando hace falta precisión real de presupuesto. -
Pensar que un paquete de handoff (M5) nunca tiene ruido. Sí puede tenerlo, si se diseña mal — por ejemplo, un paquete que incluye el
Blackboardcompleto como payload, "por si acaso". La ventaja del handoff no es automática: depende de que quien arma el paquete elija bien qué incluir. UnBlackboardnunca da esa opción; un paquete mal diseñado, sí puede desperdiciarla. -
Sacar la conclusión de que "blackboard siempre pierde" contra un mensaje dirigido. Esta lección mide un solo eje (ruido de lectura) — no mide el costo de diseñar un paquete distinto para cada combinación de emisor y receptor, que un
Blackboardevita por completo. La lección 01 ya lo dejó claro: son dos herramientas para preguntas de diseño distintas, no una mejor que la otra en abstracto.
Ejercicios
Ejercicio 1: Confirma tu propia ejecución (Fácil)
Ejecuta el ejemplo trabajado tú mismo y confirma que el ruido de policy_agent (5 campos) es mayor
que el de pricing_agent (4 campos), y explica en una frase por qué son distintos aunque los dos
lean el mismo Blackboard.
Ver solución
No hay una única "solución de código" para este ejercicio — es una verificación, más la
explicación: el ruido de cada agente depende de cuántos campos necesita (RELEVANT_FIELDS), no
solo de cuántos campos tiene el Blackboard. policy_agent necesita 1 de 6 (ruido de 5);
pricing_agent necesita 2 de 6 (ruido de 4) — mismo Blackboard, distinta cantidad de campos
relevantes, distinto ruido.
Ejercicio 2: El ruido de booking_agent mismo (Medio)
booking_agent necesita room, tier, hours y member para hacer su trabajo (cotizar,
reservar), pero no necesita booking_id de una reserva anterior en la misma corrida (si el
socio ya reservó algo antes y ahora pide algo nuevo). Agrega "booking_agent": {"room", "tier", "hours", "member"} a RELEVANT_FIELDS y calcula su ruido.
Ver solución
RELEVANT_FIELDS["booking_agent"] = {"room", "tier", "hours", "member"}
noise_booking = sorted(ALL_FIELDS - RELEVANT_FIELDS["booking_agent"])
print(f"booking_agent necesita: {sorted(RELEVANT_FIELDS['booking_agent'])}")
print(f"booking_agent ve además: {noise_booking} ({len(noise_booking)} campo de ruido)")
Salida esperada:
booking_agent necesita: ['hours', 'member', 'room', 'tier']
booking_agent ve además: ['booking_id', 'price_cents'] (2 campos de ruido)
Explicación: booking_agent es el agente con menos ruido de los tres — necesita 4 de los 6
campos, así que solo le sobran booking_id y price_cents (datos que él mismo produce en la
mayoría de las corridas, pero que no necesita leer para decidir qué cotizar o reservar). Esto
confirma un patrón: el ruido de un agente depende de cuán cerca está su expertise del centro de
gravedad de los datos del Blackboard — booking_agent, que escribe la mayoría de los campos,
naturalmente necesita leer la mayoría también.
Ejercicio 3: Un Blackboard con el doble de campos, el mismo ruido relativo (Difícil)
Imagina que el Blackboard de Reservo creciera a 12 campos (agregando, por ejemplo, check_in_time,
check_out_time, payment_method, discount_code, member_tier, notes — sin construirlos, solo
para el cálculo). Si policy_agent siguiera necesitando solo booking_id, ¿cuál sería su ruido
absoluto, y cómo cambiaría el porcentaje de campos que necesita, comparado con el Blackboard de
6 campos de esta lección?
Ver solución
fields_6 = 6
fields_12 = 12
needed = 1 # policy_agent, en ambos casos
noise_6 = fields_6 - needed
noise_12 = fields_12 - needed
pct_6 = needed / fields_6 * 100
pct_12 = needed / fields_12 * 100
print(f"Blackboard de {fields_6} campos: ruido={noise_6}, necesita el {pct_6:.1f}% de los campos")
print(f"Blackboard de {fields_12} campos: ruido={noise_12}, necesita el {pct_12:.1f}% de los campos")
Salida esperada:
Blackboard de 6 campos: ruido=5, necesita el 16.7% de los campos
Blackboard de 12 campos: ruido=11, necesita el 8.3% de los campos
Explicación: el ruido absoluto casi se duplica (de 5 a 11 campos), y el porcentaje de campos que
policy_agent realmente necesita cae a la mitad (de 16.7% a 8.3%) — porque el numerador (lo que
necesita) se mantiene fijo en 1, mientras el denominador (lo que el Blackboard expone) crece. Esto
confirma que el ruido de un Blackboard no es un costo fijo: crece con la cantidad de hechos
compartidos, mientras que cada tarea individual sigue necesitando, casi siempre, un subconjunto
pequeño y estable. Es exactamente la razón por la que context-engineering-guide existe como
disciplina propia: a medida que un sistema crece, decidir qué SÍ entra en la ventana de cada agente
deja de ser opcional.
Resumen y siguiente paso
policy_agentnecesita 1 de los 6 campos delBlackboard;pricing_agentnecesita 2 — el ruido medido (5 y 4 campos respectivamente) es el costo real de la simplicidad de coordinación que ofrece el patrón.- Un paquete de handoff (Módulo 5), diseñado a medida, expone exactamente lo que el receptor
necesita — 1 campo para
policy_agent, contra los 6 visibles en elBlackboardcompleto. - Este ruido es, con otro nombre, context bloat — cuánto entra en la ventana de un agente sin
aportar a su tarea. Presupuestarlo y comprimirlo es terreno de
context-engineering-guide, nombrado aquí, no construido. - El
Blackboardno tiene ningún control de acceso por campo — cualquier agente con una referencia al objeto puede leer (o escribir) cualquier cosa. Esa ausencia es la superficie queagent-security-and-sandboxing-guideendurece; esta lección solo la deja visible. - El ruido crece con la cantidad de campos del
Blackboard, mientras que lo que cada tarea necesita se mantiene estable — el trade-off empeora, no mejora, a medida que el sistema crece.
Siguiente lección: 07 — La corrida completa: supervisor y tres especialistas comparten un Blackboard. La pieza central del módulo: los cuatro roles, un solo objeto, de punta a punta.
Recursos adicionales
- Anthropic — Effective context engineering for AI agents — El principio de tratar el contexto de un agente como un recurso finito y valioso — la base conceptual del "ruido" que mide esta lección.
- Anthropic — Multi-agent research system — Un caso real donde limitar qué ve cada sub-agente, en vez de compartirlo todo, fue una decisión de diseño explícita para mantener la calidad de las respuestas.
- Python —
dataclasses.fields— La función que enumera los campos declarados deBlackboardpara construirALL_FIELDS, sin hardcodear la lista a mano. - Python — Operaciones de conjuntos — La resta de conjuntos (
ALL_FIELDS - relevant) detrás del cálculo de ruido de esta lección.