Chasqui/1 — Correo y Libro para agentes

Estado: borrador ejecutable v0.3 (septiembre 2026) Implementación de referencia: este repositorio (Node 20+, sin dependencias)

0. Qué es

Un solo sistema con dos componentes que comparten identidad, transporte y almacenamiento:

No son dos protocolos compatibles. El Libro no tiene login ni API propia: se opera escribiéndole sobres a libro@<casa>, y la cadena de confianza del Correo es su autenticación. Sus respuestas son recibos firmados por la casa que llegan al buzón como cualquier carta.

El email logró algo que ningún protocolo de agentes tiene hoy: una dirección universal, un buzón, y una red donde cualquier servidor le escribe a cualquier otro sin pedir permiso. MCP conecta un agente con sus herramientas; A2A conecta agentes que ya se conocen y están en línea. Ninguno da identidad por persona, buzón, confianza verificable entre desconocidos, ni una forma de que un acuerdo tenga peso.

Chasqui/1 cierra esos huecos así:

HuecoCómo lo cierra Chasqui
Identidad por persona, no solo por dominioDirección agente@dominio. El dominio certifica la clave pública de cada agente. La persona es dueña de su clave; el dominio solo la avala.
Buzón (store-and-forward)Cada dominio tiene una estafeta que acepta, guarda y reintenta. El agente puede estar apagado días; nada se pierde.
Confianza y anti-spamTodo sobre viene firmado por el agente y avalado por su dominio (ancla en DNS). Sin firma verificable no hay entrega. El receptor decide su política: abierto, lista blanca, o estampilla (proof-of-work / pago).
FragmentaciónChasqui no reemplaza a MCP ni a A2A: es el sobre universal. El contenido puede ser texto, JSON, una tarea A2A o una llamada MCP; la tarjeta del agente publica sus endpoints MCP/A2A.
Palabras gratisUn acuerdo es un asiento en el Libro, no prosa. Escrow retiene hasta que la prueba pasa; la fianza le pone precio a afirmar; el mandato acota cuánto puede gastar cada agente y quién paga al final.
Recibo negableTodo recibo lleva el hash del sobre que lo causó y la firma de quien lo emite.

Y cifrado extremo a extremo por defecto. Las estafetas ven de, para y el tamaño; nunca el contenido.

1. Términos

2. Descubrimiento y ancla de confianza

Dada asistente@sigo.uk, el resolver localiza la estafeta de sigo.uk en este orden:

  1. Override local (hosts.json): pruebas y redes privadas. Puede fijar la clave esperada (sig).
  2. DNS: registro TXT en _chasqui.sigo.uk:
v=chasqui1; url=https://mail.sigo.uk; sig=<clave pública Ed25519 del dominio, base64url>

sig es el ancla: la tarjeta del dominio debe estar firmada por esa clave. Con DNSSEC, la cadena queda completa.

  1. Well-known sin DNS: https://sigo.uk/.well-known/chasqui.json. Si no hay ancla, el resolver aplica TOFU (confía en el primer uso y pinea la clave; un cambio posterior se rechaza hasta que el operador lo confirme).

Luego descarga la tarjeta del dominio y la del agente, y verifica la cadena: DNS → clave del dominio → tarjeta del agente → firma del sobre.

Las tarjetas se cachean (5 min por defecto). Si un sobre llega firmado con una clave que la tarjeta cacheada no reconoce, el receptor refresca la tarjeta una vez antes de rechazar (esto es lo que hace funcionar la rotación de claves sin coordinación).

3. Tarjeta de dominio

{
  "chasqui": "1",
  "domain": "sigo.uk",
  "estafeta": "https://mail.sigo.uk",
  "keys": [ { "sig": "<Ed25519 pub>", "created": "2026-09-04T00:00:00Z" } ],
  "policy": { "inbound": "verified", "max_bytes": 1048576 },
  "extensions": ["urn:chasqui:ext:mcp", "urn:chasqui:ext:a2a"],
  "issued": "2026-09-04T19:00:00Z",
  "signature": { "alg": "Ed25519", "kid": "<Ed25519 pub>", "value": "<base64url>" }
}

Reglas:

4. Tarjeta de agente

GET https://<estafeta>/agents/<local>

{
  "chasqui": "1",
  "address": "asistente@sigo.uk",
  "sig": "<Ed25519 pub del agente>",
  "enc": "<X25519 pub del agente>",
  "capabilities": {
    "accepts": ["text/plain", "application/json", "application/a2a-task+json"],
    "mcp": "https://agents.sigo.uk/asistente/mcp",
    "a2a": "https://agents.sigo.uk/asistente/.well-known/agent-card.json"
  },
  "inbox": { "policy": "open" },
  "valid_from": "2026-09-04T19:00:00Z",
  "valid_until": null,
  "previous": [ { "sig": "<clave anterior>", "until": "2026-09-11T19:00:00Z" } ],
  "certification": { "alg": "Ed25519", "kid": "<clave del dominio>", "value": "<base64url>" }
}

Tarjeta delegada (un subagente que actúa por otro agente): el nombre es <nombre>.<padre>, y la tarjeta trae además

"delegation": {
  "by": "constructor@sigo.uk", "address": "tester.constructor@sigo.uk", "sig": "<clave del subagente>",
  "scope": { "types": ["message", "result"], "to_domains": ["sigo.uk"], "cap": 100 },
  "valid_until": null, "issued": "...", "signature": { "alg": "Ed25519", "kid": "<clave del padre>", "value": "..." }
}

Reglas:

5. El sobre

{
  "chasqui": "1",
  "id": "uuid",
  "from": "nicolas@sigo.uk",
  "to": ["asistente@beta.example"],
  "created": "ISO-8601",
  "expires": null,
  "deliver_after": null,
  "thread": "uuid o null",
  "in_reply_to": "id o null",
  "type": "message | task | result | receipt | intro",

  "content":   { "media": "application/json", "body": { "...": "..." } },
  "encrypted": { "alg": "X25519+HKDF-SHA256+A256GCM", "epk": "...", "iv": "...", "ct": "...", "tag": "...", "keys": { "asistente@beta.example": { "iv": "...", "ct": "...", "tag": "..." } } },

  "attachments": [ { "name": "informe.pdf", "media": "application/pdf", "sha256": "...", "url": "https://...", "bytes": 12345 } ],
  "pow": { "bits": 16, "nonce": "12345" },
  "receipt": "delivered",
  "extensions": { "urn:chasqui:ext:a2a": { "task_id": "..." } },
  "signature": { "alg": "Ed25519", "kid": "<sig del agente>", "value": "<base64url>" }
}

Reglas:

Tamaño máximo por defecto: 1 MB. Lo declara cada dominio en su tarjeta.

6. Transporte entre estafetas

POST https://<estafeta destino>/inbound con el sobre como cuerpo JSON y este header:

X-Chasqui-Relay: chasqui1 domain=<dominio emisor>; kid=<clave del dominio>; sig=<firma de "relay:<id>:<dominio destino>">

La firma del agente autentica al remitente (como DKIM). La firma de relay autentica a la estafeta emisora (como SPF). Un dominio puede exigir ambas (require_relay).

Respuesta:

{ "ok": true, "code": 202, "accepted": ["asistente@beta.example"], "rejected": [ { "to": "...", "code": 403, "reason": "..." } ] }

Semántica de códigos (por sobre o por destinatario):

CódigoSignificadoEmisor
200duplicado ya recibido (idempotente)marca entregado
202aceptado en buzónmarca entregado
400sobre malformadorebote inmediato
402falta estampilla (proof-of-work)rebote; el cliente puede reintentar con pow
403firma inválida, remitente no verificable, políticarebote
404destinatario inexistenterebote
410vencidorebote
413demasiado granderebote
421, 429, 5xx, red caídatemporalreintento con backoff exponencial

7. Buzón y entrega (store-and-forward)

  1. El agente entrega su sobre firmado a su propia estafeta (POST /outbound).
  2. La estafeta lo encola por dominio destino y responde 202 de inmediato. Con deliver_after, el primer intento se agenda para esa fecha (el mismo next_attempt de la cola): el sobre espera ahí, sin aparecer en ningún buzón, hasta que llegue el momento.
  3. Un trabajador intenta la entrega. Si falla temporalmente, reintenta con backoff exponencial (1 s, 2 s, 4 s… hasta 60 s) durante hasta 3 días. Luego rebota. Si un sobre vence (expires) mientras espera en la cola —por diferimiento o por reintentos a un destino caído—, rebota al remitente con la razón; no desaparece mudo.
  4. La estafeta receptora verifica, aplica política y guarda el sobre en el buzón del destinatario.
  5. El destinatario lee por poll (GET /mailbox/<local>) o recibe push (webhook firmado por el dominio). El sobre permanece hasta que el agente confirma (POST /mailbox/<local>/ack). Un agente apagado una semana recibe todo al volver.
  6. Rebotes y acuses son sobres normales de postmaster@<dominio>, firmados con la clave del dominio, con type: receipt, in_reply_to al sobre original y sha256 del sobre original. Acuse de entrega solo si el sobre pide "receipt": "delivered". Los recibos que emite un agente (processed, etc.) también llevan el sha256 del sobre: son no repudiables sin ningún registro central.
  7. Idempotencia por id: una segunda entrega del mismo sobre devuelve 200 y no duplica.

8. API agente ↔ estafeta

Autenticación: Authorization: Chasqui <token>.<firma> donde token = base64url del canónico de {address, ts, nonce, method, path, host} y firma = Ed25519 con la clave del agente. Ventana de 5 minutos, nonce de un solo uso, atado a método, ruta y casa destino (host): un token capturado no sirve contra otra estafeta.

MétodoRutaQuiénPara
GET/.well-known/chasqui.jsonpúblicotarjeta del dominio
GET/agentspúblicodirectorio de la casa (?capability=mcp&accepts=<media>&q=<texto>&limit&offset)
GET/agents/:localpúblicotarjeta del agente
POST/agentsver sección 8bregistrar/actualizar agente
POST/invitationsadminemitir código de invitación { uses, expires, note, welcome }
GET/invitationsadminlistar invitaciones y su uso
POST/outboundagenteenviar
POST/inboundestafetasrecibir
GET/mailbox/:localagenteleer pendientes
POST/mailbox/:local/ackagenteconfirmar procesados
GET/outbox/:localagenteestado de envíos
GET/healthpúblicosalud

8b. Servicio de registro

Cómo entra un agente a una casa lo decide la tarjeta del dominio (policy.registration):

ModoQuién inscribeCómo
admin (por defecto)solo la casaPOST /agents con Authorization: Bearer <token de la casa>
invitequien tenga un códigola casa emite códigos con usos y vencimiento; el agente lo presenta en invite
opencualquieraprimer llegado, primer servido; límite de altas por minuto

En invite y open el cuerpo va firmado con la misma clave que se inscribe (signature.kid == sig, con ts dentro de 5 minutos): prueba de posesión. Nadie puede registrar una clave que no controla. Un nombre ya tomado solo lo actualiza su dueño (autenticación firmada, incluso al rotar claves: el cuerpo lleva las nuevas, la autenticación se firma con las viejas) o la casa. Nombres reservados: postmaster, libro, casa, admin, root, abuse, security, hostmaster, noreply, support, estafeta, chasqui. Los subagentes se inscriben con la firma del padre (sección 4).

El directorio (GET /agents) es la lista pública de las tarjetas de la casa que pidieron figurar (capabilities.listed: true): claves, capacidades, política de buzón, si es delegado y por quién. Sin webhooks ni datos privados. El default es no aparecer: un agente no figura en el directorio ni en ningún índice sin haberlo pedido. El lookup directo por dirección (GET /agents/<local>) resuelve a cualquier agente que ya conoces, listado o no. Sirve para encontrar quién ofrece qué dentro de una casa; entre casas, el descubrimiento sigue siendo por dirección (sección 2): no hay un registro global, y ese hueco está declarado en la sección 21.

Cada alta es un evento registrado (registered_via: admin, self, delegation, open, invite:<código>) y, si la casa da regalo de bienvenida, un asiento en el Libro.

9. Políticas de entrada

La estafeta receptora rechaza sin excepción sobres sin firma verificable. Sobre eso, cada agente elige:

10. Extensiones

Una extensión es una URI. El dominio y el agente declaran las que soportan; un sobre puede llevar datos bajo extensions[uri]. Implementaciones que no la conocen ignoran esos datos sin fallar.

11. Versionado

12. Modelo de amenazas

AmenazaMitigación
Suplantar a un agenteFirma Ed25519 verificada contra la tarjeta certificada por su dominio.
Suplantar a un dominioAncla en DNS (con DNSSEC) o pin TOFU; cambio de clave sin anuncio se rechaza.
Leer el contenido en tránsito o en la estafetaCifrado extremo a extremo; las estafetas solo ven metadatos.
Re-dirigir o re-firmar un sobre ajenoAAD del cifrado incluye id/from/to.
Repetir un sobreDeduplicación por id; expires.
Repetir un token de authNonce único, ventana de 5 min, atado a método y ruta.
Spam masivoFirma obligatoria (cuesta un dominio), límite de tasa por dominio, allowlist/intro, proof-of-work o estampilla.
Estafeta emisora falsa usando sobres robadosFirma de relay del dominio emisor; require_relay.
Pérdida por caída del destinoCola persistente con reintentos y rebote final al remitente.
Clave de agente comprometidaRotación con período de gracia; valid_until; blocklist inmediata en el dominio.

13. El índice federado (extensión urn:chasqui:ext:indice)

El directorio (§8b) es por casa. Para "encuentra un agente que haga X en cualquier casa" existe el índice federado: una casa cualquiera que decide operar un buscador. No es infraestructura del protocolo: es un servicio que cualquiera monta, como un buscador sobre la web.

tarjeta del dominio por la cadena normal (§2) y solo lista lo que firma como casa Chasqui.

rastree— solo si su tarjeta declara capabilities.listed: true. El default es no figurar: nadie se lista sin pedirlo. No listar no es esconderse: el lookup directo por dirección (GET /agents/<local>) sigue resolviendo a cualquier agente que ya conoces; lo opt-in es la *enumeración*, no el alcance.

los agentes con listed: true), re-verifica la tarjeta del dominio en cada pasada, y descarta toda tarjeta cuya certificación no firme el dominio de origen. Lo que el dominio no certificó no entra al índice.

firmada por la casa del índice, con cada tarjeta acompañada de su casa de origen (_house).

tarjeta por la cadena normal (DNS -> dominio -> agente) antes de actuar. Un índice malicioso puede omitir o desordenar, pero no puede falsificar una tarjeta ni un sobre.

firmadas lo permiten); ningún índice es el índice.

14. El Libro: kernel

Cada casa (dominio) lleva un ledger de doble entrada. Cuentas:

Un asiento es { id, n, at, house, concept, lines: [{ account, delta }], meta, refs, signature }. Las líneas suman cero. Lo firma la casa. refs apunta a los sobres que lo causaron (op, op_sha256, quote, quote_sha256, contract). La suma de todos los saldos de una casa es siempre 0.

Siete primitivas, y nada más:

PrimitivaAsientoFee
cotizarninguno: es un documento firmado por el vendedor
cobrarcomprador − X · vendedor + (X − fee) · casa + fee
retenerpagador − X · escrow + Xno
liberarescrow − X · beneficiario + (X − fee) · casa + fee
devolverescrow − X · pagador + Xno
repartirN líneas que suman 0 (el fee es un reparto)
afianzarretener con salida distinta: liberar (vuelve) o ejecutar (va al beneficiario)no

Transversales: idempotencia por id de sobre (una operación reentregada devuelve el mismo resultado sin repetir el asiento) y meta (contexto legible por máquina en cada asiento). Los montos son enteros (tokens).

15. Cotizaciones

Una cotización es un documento firmado por el vendedor, independiente del sobre que la transporta:

{ "tipo": "cotizacion", "id": "uuid", "house": "sigo.uk", "seller": "verifica@sigo.uk", "buyer": "nicolas@sigo.uk",
  "contract": "spot | escrow | metered", "price": 40, "currency": "tok", "concept": "verificación de despliegue",
  "terms": { "acceptance": "lighthouse >= 90", "deadline": "2026-09-15" }, "arbiter": null,
  "referrer": { "address": "socio@otra.casa", "share": 1500 },
  "issued": "...", "expires": null, "signature": { "alg": "Ed25519", "kid": "<sig del vendedor>", "value": "..." } }

Viaja al comprador dentro de un sobre con media: application/chasqui.cotizacion+json, cifrado. La casa la ve recién cuando el comprador la acepta. El Libro verifica: firma del vendedor (vía resolver), buyer igual al que acepta, house igual a la propia, vigencia, y que no haya sido aceptada antes (409).

Comisión de referido (referrer, opcional): el vendedor firma en la cotización que le paga share (en basis points) a quien trajo el trato. La comisión sale de lo que recibe el vendedor, no se suma al precio: el comprador paga igual y la casa cobra igual. Al liquidar (el transfer del spot o el release del escrow), el asiento pasa a cuatro líneas —comprador, vendedor, casa, referidor— y sigue sumando cero. El Libro exige share entero y > 0, que fee + share ≤ 10000 bps (el vendedor nunca queda en negativo), y que el referidor no sea el propio vendedor. La distribución se paga sola: nadie la factura aparte, se asienta en el mismo movimiento.

16. Operaciones

Se envían como sobre a libro@<casa> con type: task, media: application/chasqui.libro+json, sin cifrar (la casa debe leerlo), body: { op, ... }. La respuesta llega al buzón de cada parte como type: receipt de libro@<casa> con media: application/chasqui.recibo+json. Si la operación falla, el remitente recibe un rebote del postmaster con el código y la razón.

opquiénefecto
accept { quote }compradorcrea el contrato; spot: cobra; escrow: retiene; metered: crea mandato
deliver { contract, evidence_sha256, note }vendedor (escrow)held → delivered, registra el hash de la evidencia
release { contract }comprador o árbitro (escrow); verificador o árbitro (fianza); afianzado solo si vencióescrow → vendedor con fee; fianza → vuelve al afianzado
refund { contract, note }vendedor o árbitro; comprador solo si aún no hay entregaescrow → comprador sin fee
bond { amount, claim, verifier, beneficiary?, arbiter?, evidence_sha256?, expires? }el que afirmaretiene el monto junto a la afirmación
forfeit { contract, reason }verificador o árbitrofianza → beneficiario (por defecto la casa)
mandate { grantee, cap, scope?, expires?, parent? }mandanteautoridad de gasto; con parent, sub-mandato acotado
charge { mandate, amount, concept }mandatariopaga el mandante raíz; toda la cadena descuenta
revoke { mandate }mandante o superiorrevoca en cascada
balance, statement { limit }, contract { contract }el propiolectura, respuesta por recibo

Lecturas directas sin correo: GET /libro/cuenta/:address y GET /libro/contrato/:id con la misma autenticación firmada (también para foráneos). Administración: POST /libro/topup y GET /libro/diario con token de la casa.

17. Contratos

Un contrato es una máquina de estados sobre las primitivas. El kernel no sabe qué contrato sirve.

ContratoEstadosMecánica
spotsettledcotizar → aceptar = cobrar
escrow`held → delivered → released \refunded`retener al aceptar; liberar si la prueba pasa; devolver si falla; árbitro pactado en la cotización
bond (fianza)`posted → released \forfeited`el que afirma deposita; el verificador libera o ejecuta; vencida, el afianzado la recupera
meteredactive + mandatoaceptar crea un mandato con tope = precio; el vendedor cobra bajo él

Registro del contrato: { id, kind, house, seller, buyer, verifier?, arbiter?, amount, concept, terms, state, quote_id, quote_sha256, accept_sha256, evidence_sha256?, history: [{ at, op, by, asiento, ... }] }. La reputación no se construye: es una consulta sobre estos registros (escrows liberados vs devueltos, fianzas intactas vs ejecutadas), y cada punto costó tokens.

Bounties, suscripciones, subastas, referidos y disputas son composiciones de las mismas primitivas; se agregan a contratos.js cuando una transacción real las pida.

18. Mandatos en cadena

Un mandato es { id, grantor, grantee, cap, spent, scope: { concepts? }, expires, parent, root, chain, state }. El mandatario puede sub-delegar un mandato con cap ≤ cap − spent del padre y expires ≤ el del padre. Un cobro bajo cualquier eslabón lo paga el mandante raíz, descuenta spent en toda la cadena, y el recibo llega a todos los que están en ella. Revocar un mandato revoca todo lo que cuelga. Es un poder notarial anidado y auditable: cada token que se mueve tiene su cadena de autoridad completa en el asiento (meta.chain).

Dos delegaciones distintas, ambas encadenadas: la tarjeta delegada (sección 4) dice quién es un subagente y qué puede enviar; el mandato dice cuánto puede gastar y quién paga. Un subagente con scope.cap no puede aceptar, afianzar, mandar ni cobrar por encima de ese tope, tenga el mandato que tenga.

19. Estampillas

Un buzón con inbox: { policy: "stamp", price, house? } cobra por recibir. El sobre lleva stamp: { house, amount } dentro de lo firmado; la estafeta receptora ejecuta cobrar(remitente → destinatario) en su Libro al aceptar el sobre y guarda el id del asiento junto al sobre. Sin saldo en esa casa, 402 y rebote. Es el anti-spam con precio real: escribirle a un desconocido cuesta, y lo cobra el desconocido.

20. Recibos

Todo recibo del Libro contiene { of, op, op_sha256, from, contract? | mandate? | asiento?, cotizacion_sha256?, chain? }, va firmado por la casa y se entrega a todas las partes. Junto con el sobre original (firmado por quien operó) y la cotización (firmada por el vendedor), forma una prueba de tres firmas que ninguna parte puede fabricar ni negar. Ese es el instrumento: el chat entre agentes es barato; el recibo es caro y verificable.

21. Lo que Chasqui/1 no resuelve todavía (y no finge resolver)