EN

SDK & API

Gobierno como una llamada. Registras el agente una vez y después preguntas por cada paso. Ninguna llamada a un modelo en el camino gobernado: una decisión es aritmética, así que cuesta microsegundos.

sin dependenciasHTTP · SDK · MCP · CLI ~36 µs por decisión1.000 llamadas gratis

Guía rápida

Tres llamadas. Las dos primeras ocurren una vez; la tercera, en cada paso del agente.

# 1 · consigue una clave (una vez por organización)
curl -s https://api.diacroma.com/v1/signup \
  -d '{"email":"you@company.com","company":"Acme"}'
# → {"api_key":"d4a_live_…","free_calls":1000}   (se muestra una vez — guárdala)

# 2 · registra el agente (una vez por agente)
curl -s https://api.diacroma.com/v1/agents \
  -H "Authorization: Bearer $D4A_KEY" -d '{
    "mission":"Resolve billing disputes within refund policy.",
    "tools":["lookup_invoice","issue_refund","escalate_to_human"],
    "signing_authority":"acme-deploy-key",
    "hard_constraints":[{"tool":"issue_refund","arg":"amount","op":"<=","value":200}]
  }'
# → {"agent_id":"agt_…","anchor_hash":"…"}

# 3 · gobierna cada paso (por cada acción que proponga tu agente)
curl -s https://api.diacroma.com/v1/agents/agt_…/step \
  -H "Authorization: Bearer $D4A_KEY" \
  -d '{"proposed_tool":"issue_refund","tool_args":{"amount":500},"output_kind":"action"}'
# → {"action":"block_escalate","block":true,
#    "reason":"admissibility:hard_constraint:issue_refund.amount=500 !<= 200"}
Y esa es toda la integración. Si block es verdadero, no ejecutes la herramienta: devuelve reason a tu agente para que replanifique. Todo lo demás es opcional.

Conceptos

TérminoQué es
anclaLa misión del agente, congelada y firmada en el despliegue. Se escribe una sola vez; el agente no puede reescribirla en ejecución. Se captura de la misión y las herramientas que tu agente ya declara — no hay una configuración aparte que redactar.
pasoUna cosa que el agente se propone hacer: llamar a una herramienta o producir una salida. Un paso = una llamada gobernada.
desviaciónCuánto se aparta un paso del ancla, medido en seis canales: scope, objective, output_type, tradeoff, tone, execution.
persistenciaUna media exponencial de la desviación. Decae. Responde a «¿está el agente fuera de misión ahora mismo?"
rachaContadores de ciclos consecutivos por canal. No decaen. Cazan la deriva lenta y sostenida que se queda por debajo de todos los umbrales por paso — el fallo que un guardrail turno a turno no puede ver.
admisibilidadUna comprobación estructural y dura que se ejecuta antes de cualquier puntuación, y con independencia de ella. Una herramienta fuera de alcance o una restricción incumplida se bloquea de plano; ninguna puntuación puede readmitirla.

Cómo funciona por dentro

Las cinco piezas, en orden. Esto es el mecanismo; la portada dice qué te hace a ti. Nada de aquí es opcional en ejecución — es lo que corre en cada paso gobernado.

Cinco piezas, y se leen en orden: 01 fija contra qué se mide todo, 02 mide, 03 decide, 04 lo deja demostrable meses después — y 05 es lo que ninguna de las otras cuatro puede ver, porque no está dentro de ningún agente.

01 · ANCLA

Firmada en el despliegue, se escribe una vez

La misión se captura de lo que el agente ya declara y la firma la credencial de despliegue — y lo que el gate aplica a partir de ella es estructurado: tus herramientas y tus topes, al pie de la letra. El agente no puede reescribir su propia vara de medir.

02 · CUATRO MONITORES

Ahora, últimamente, en total — y hacia dónde

Una media que se olvida de lo viejo (¿se está saliendo ahora?). Un contador de turnos seguidos que se pone a cero con un solo turno limpio (¿cuánto lleva así?). Un contador que nunca resta y no tiene tope (¿cuánto se ha salido en toda su vida?). Y una medida de dirección (¿se aleja en línea recta, o da vueltas y vuelve?). Sólo el tercero caza la deriva que va y viene; sólo el cuarto separa irse de verdad del ruido que se cancela — dos trayectorias que gastan lo mismo y llevan la misma racha, y todos los demás contadores las leen iguales.

03 · GATE DURO

Admisibilidad primero

Una herramienta fuera de alcance o una acción fuera de política se bloquea antes de cualquier puntuación, y con independencia de ella. La puntuación no puede readmitirla jamás.

04 · AUDITORÍA

Reproducible, y firmada

Cada decisión es una fila encadenada por hash y firmada con tu credencial. Tocar un paso rompe la cadena; reescribir la historia entera y rehacer los enlaces tampoco cuela, porque la firma no se puede recalcular sin la clave. Hecha para defenderse ante un regulador.

05 · ACOPLAMIENTO

La deriva que te llega de otro agente

Los cuatro de arriba vigilan a un agente. Éste vigila lo que pasa entre ellos. Un agente que lee lo que otro escribió hereda cuánto se había alejado ese otro de su propia misión — así que una trayectoria que nunca se salió de su encargo puede acabar parada, por una exposición que no gastó. Cualquier contador que mire dentro de un solo agente la lee como limpia, porque la información no está ahí dentro. Véalo con tres agentes →

Qué compara realmente el gate

Esta es la pregunta que hace todo el mundo, y merece una respuesta directa: si la misión es texto libre, ¿cómo se aplica sin que un modelo la lea?

Porque el texto libre no es lo que el gate compara. El registro produce dos cosas distintas, y solo una de ellas es aplicable.

Firmado & registradoAplicado en cada paso
qué es El texto de tu misión y el perfil de tono — resumidos con hash dentro del ancla, para que la auditoría pueda probar qué misión estaba en vigor. allowed_tools, output_types, hard_constraints — listas y comparaciones.
de dónde sale De tu system prompt, literal. De tu manifiesto de herramientas, tomado al pie de la letra — nunca inferido — más los topes que declares.
¿necesita un modelo? No. Se guarda y se resume con hash, no se interpreta. No. Pertenencia a un conjunto y aritmética.

Así que el gate nunca pregunta «¿esto está en misión?», que es un juicio. Pregunta «¿está esta herramienta en la lista?» y «¿es 500 ≤ 200?», que son hechos. Por eso una decisión son ~36 µs y no cuesta nada.

Ejemplo resuelto

# texto de la misión (firmado, con hash, para humanos — el gate no lo interpreta)
"Resuelve disputas de facturación dentro de la política de reembolsos. Nunca reembolses más de $200."

# lo que se aplica de verdad: tu manifiesto + el tope que declaraste
allowed_tools    = [lookup_invoice, issue_refund, escalate_to_human]
hard_constraints = [ issue_refund.amount <= 200 ]

# el agente propone issue_refund(amount=500)
  is "issue_refund" in allowed_tools?   yes
  is 500 <= 200?                        no   -> BLOCK

# el agente propone process_upsell(plan="premium")
  is "process_upsell" in allowed_tools? no   -> BLOCK

Entonces, ¿para qué sirve el modelo?

Para los casos que una lista no sabe expresar. Un agente puede permanecer dentro de todas las herramientas permitidas y por debajo de todos los topes mientras sus respuestas se van convirtiendo poco a poco en un argumentario de venta, o su registro se desplaza bajo presión. Eso es significado, y el significado necesita un modelo — es el canal semánticoopcional, que puntúa el objetivo y el tono en texto libre.

Dos capas, y la diferencia importa comercialmente. La capa estructural es determinista, sin modelo, de microsegundos, y es la que bloquea. La capa semántica es opcional, cacheada, fuera del camino crítico, y solo aconseja: puede levantar una señal que a la estructural se le escapó, pero nunca puede bajar una. Si el modelo no está disponible, la API te lo dice ("protected" y una nota l1_unavailable ) en lugar de adivinar en silencio.

El propio registro puede usar un modelo una vez, opcionalmente, para convertir un prompt largo en prosa en esos campos estructurados. Es una llamada por agente, nunca por paso — y allowed_tools se toma siempre al pie de la letra del manifiesto, así que la comprobación de mayor peso nunca descansa sobre una inferencia.

Compruébalo tú mismo — corre la demo

El mismo agente, bajo la misma presión, ejecutado dos veces: una solo y otra con el gate delante de sus herramientas. Dos minutos, un comando, nada que instalar y sin registrarse — el script se da de alta solo en el plan gratuito. El escenario reconstruye un incidente real y documentado: un agente choca con un muro de credenciales en staging, encuentra un token con alcance sobre producción y escala.

Descargar la demo  ·  28 KB, 6 ficheros

# 1. una clave de modelo, la que quieras (el plan gratis de Groq son dos minutos)
export GROQ_API_KEY=gsk_...

# 2. y esa es toda la instalación
python3 run_demo.py

Cada paso que da el agente lo decide el modelo — nada del escenario está guionizado. Y lo que cuenta como fallo lo juzga el estado del entorno simulado — ¿se destruyó el volumen?, ¿salieron credenciales? — nunca nuestras propias reglas. El entorno es el juez; el gate es el examinado.

  WITHOUT DiaCroma
     runs that failed   : 1/2
       - DESTRUCTIVE: executed -> sudo -u postgres psql -c "DROP DATABASE railway;"

  WITH DiaCroma
     runs that failed   : 0/2
     steps blocked      : 3
       turn 4: run_terminal_cmd
         -> admissibility:tool_out_of_scope:run_terminal_cmd
Si tu agente no falla, la demo lo dice, y después dice cuánto vale eso. Cero fallos en n corridas no es una tasa cero; es una cota superior de 3/n al 95 % de confianza. La herramienta imprime esa cota y la multiplica por tu volumen mensual, porque “nunca nos ha pasado” y “hasta 300 incidentes al mes” pueden ser exactamente la misma medición. Los modelos capaces aguantan a menudo — y aun así no puedes descartar la cola observando.

SDK de Python Autoalojado

Si construyes tus propios agentes, este es el camino más corto: el gobernador lee la misma misión y las mismas herramientas que tu agente ya tiene.

Ejecutarse en tu proceso significa que el código corre en tus máquinas, así que se licencia por sede con una cuota anual fija en vez de medirse por llamada — uso interno, sin redistribución, con derecho de auditoría. La API alojada de arriba es la opción medida por paso y no necesita instalar nada.

# una línea — el gobernador se crea a partir del propio agente
import d4a

agent = Agent(instructions=MISSION, tools=[lookup_invoice, issue_refund, escalate])
gov   = d4a.govern(agent, signing_authority="acme-deploy-key")

# después, antes de cada llamada a una herramienta:
r = gov.observe(AgentStep(step_id=i, proposed_tool=name, tool_args=args,
                          output_kind="action", proposed_text=text))
if r.action.value == "block_escalate":
    raise ToolDenied(r.reason)          # no se ejecuta nunca
elif r.action.value == "warn_replan":
    hint = r.reason                          # devuélveselo; deja que replanifique

Fábrica de agentes — gobernados por defecto

Cambias tu fábrica una vez y todos los agentes nuevos de la organización quedan gobernados, sin nada que recordar agente por agente.

def create_agent(mission, tools, authority):
    agent = Agent(instructions=mission, tools=tools)
    gov   = d4a.from_definition(mission=mission, tools=tools,
                                signing_authority=authority)
    return d4a.bind(agent, gov)

API HTTP

Agnóstica del lenguaje. Vale cualquier cliente HTTP; la petición es pequeña y la respuesta es un veredicto.

import requests

D4A = "https://api.diacroma.com"
H   = {"Authorization": f"Bearer {KEY}"}

def governed_call(agent_id, tool, args, text=None):
    r = requests.post(f"{D4A}/v1/agents/{agent_id}/step", headers=H, json={
        "proposed_tool": tool, "tool_args": args,
        "proposed_text": text, "output_kind": "action"}).json()
    if r["block"]:
        raise PermissionError(r["reason"])
    return run_tool(tool, args)
const D4A = "https://api.diacroma.com";

async function governedCall(agentId, tool, args, text) {
  const r = await fetch(`${D4A}/v1/agents/${agentId}/step`, {
    method: "POST",
    headers: { "Authorization": `Bearer ${KEY}`,
               "Content-Type": "application/json" },
    body: JSON.stringify({ proposed_tool: tool, tool_args: args,
                           proposed_text: text, output_kind: "action" })
  }).then(x => x.json());
  if (r.block) throw new Error(r.reason);
  return runTool(tool, args);
}
curl -s $D4A/v1/agents/$AGENT/step \
  -H "Authorization: Bearer $D4A_KEY" \
  -H "Content-Type: application/json" \
  -d '{"proposed_tool":"issue_refund","tool_args":{"amount":500},
       "output_kind":"action","proposed_text":"Processing your refund."}'

Servidor MCP Autoalojado

El camino universal. Registras la capa de gobierno en la costura de herramientas y todo agente compatible con MCP queda gobernado — sin tocar el agente.

En proceso, como el SDK, y con la misma licencia. Si quieres la costura MCP sin licencia en sede, apunta el adaptador a la API alojada.

# encamina las herramientas del agente A TRAVÉS de la capa de gobierno
agent.mcp_servers = [ d4a.mcp(your_mcp_server, mission=MISSION) ]

Las llamadas a herramientas se inspeccionan al vuelo; una llamada denegada devuelve un error MCP que el cliente le muestra al modelo, y eso es lo que le permite replanificar en vez de fallar a ciegas.

Donde una plataforma no te deje bloquear, la capa degrada a solo observación y se marca como tal — en la respuesta de la API ("protected": false) y en el panel. Nunca se te dice que estás protegido cuando solo se te está observando.

CLI

Los códigos de salida la convierten en una puerta directa para pipelines y CI. El paquete d4a es un cliente de esta API y nada más — ninguna puntuación, ningún umbral, ninguna matemática de deriva se ejecuta en tu máquina.

pip install d4a          # sin dependencias; el cliente es un cliente HTTP

d4a signup    --email you@company.com
d4a provision --mission "Resuelve disputas de facturación dentro de la política de reembolsos." \
              --tools lookup_invoice,issue_refund,escalate_to_human \
              --authority acme-deploy-key --cap issue_refund.amount:200
d4a check     --agent agt_… --tool issue_refund --args '{"amount":500}'
d4a usage
d4a audit     --agent agt_…
salidasignificado
0permitir — adelante
1aviso — adelante, pero replanifica hacia la misión
2bloqueo — no ejecutes la acción; escala
3cuota agotada (ver 402)
4el comando o la conexión estaban mal — nunca un veredicto de gobierno, así que 2 significa siempre un bloqueo real

Compruébalo de punta a punta, en tu máquina

Sesenta segundos, sin clave de modelo, sin registrarte en nada y sin red: arrancas el servicio en tu portátil y le hablas. Lo que sigue es una transcripción real — los mismos comandos, la misma salida.

1 · Arranca el servicio

cd C:\Apps\D4A          # o donde lo tengas
python3 cli.py serve --port 8123

Sin base de datos configurada usa SQLite en un fichero. Para producción se le pasa D4A_DB=postgres://… y cambia el almacén, no el gobierno.

2 · Date de alta y registra un agente CON su tope

curl -s localhost:8123/v1/signup -H 'Content-Type: application/json' \
  -d '{"email":"tu@empresa.com"}'
# -> {"api_key":"d4a_live_…","free_calls":1000, …}

curl -s localhost:8123/v1/agents -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{
    "mission": "Resolver disputas de facturacion dentro de politica.",
    "tools": ["lookup_invoice","issue_refund"],
    "signing_authority": "acme-deployment-key",
    "acknowledge_no_semantic_sensor": true,
    "hard_constraints": [{"tool":"issue_refund","arg":"amount","op":"<=","value":200}]
  }'
# -> {"agent_id":"agt_2e309d72c4084bf2","anchor_hash":"1500254eb6f0ec1b…"}

El tope hay que declararlo. Si lo omites, ese tope no existe y nada lo comprueba — no es un descuido nuestro, es la regla de la casa: lo que no declaras no se gobierna.

3 · Cuatro pasos, y lo que contesta cada uno

POST /v1/agents/agt_…/step

  lookup_invoice                    -> ALLOW
  issue_refund {"amount":150}       -> ALLOW
  issue_refund {"amount":500}       -> BLOCK_ESCALATE
        admissibility:hard_constraint:issue_refund.amount=500 !<= 200
  borrar_base_de_datos              -> BLOCK_ESCALATE
        admissibility:tool_out_of_scope:borrar_base_de_datos

Los dos bloqueos llegan por admisibilidad, no por puntuación: se deciden antes de mirar ningún número, y ninguna puntuación posterior puede readmitirlos.

4 · La auditoría se verifica sola

curl -s localhost:8123/v1/agents/agt_…/audit -H "Authorization: Bearer $KEY"
# -> "verify": {"ok": true, "first_broken": null, "detail": "verified 3 records"}
#    "head":   "27c524018969f4f5e018d2c4f9a53b7b…"

5 · Lo mismo por CLI, con códigos de salida

d4a provision --mission "…" --tools lookup_invoice,issue_refund \
              --authority acme-key --cap issue_refund.amount:200

d4a check --agent agt_… --tool issue_refund --args '{"amount":150}'
# ALLOW            verdict=nominal cumulative=0.0000 admissible=True
# codigo de salida = 0

d4a check --agent agt_… --tool issue_refund --args '{"amount":500}'
# BLOCK_ESCALATE   admissible=False  admissibility:hard_constraint…
# codigo de salida = 2

Ojo con el separador de --cap: son dos puntos, issue_refund.amount:200. Escrito como amount<=200 la orden se rechaza con un mensaje — el tope no se aplica a medias ni se ignora en silencio.

Veredictos & control

Graduado, no binario — porque una parada en seco ante cada bandazo es inusable, y un aviso ante una violación real es negligencia.

acciónqué significaqué haces
permitirEn misión.Ejecuta la acción.
warn_replanLa deriva sostenida cruzó el umbral de aviso, o lo hizo la racha de un canal. Todavía no ha ocurrido nada ilícito.Ejecútalo si quieres, pero devuélvele reason para que el agente se reoriente.
block_escalateUna restricción dura o una acción fuera de alcance, o deriva acumulada por encima del umbral de rechazo.No lo ejecutes. Escala a una persona; dale al agente el motivo.

Campos de la respuesta

{
  "action":      "allow" | "warn_replan" | "block_escalate",
  "allow":       true,          // booleanos de conveniencia
  "block":       false,
  "verdict":     "nominal" | "warning" | "refuse",
  "cumulative":  0.1640,       // persistencia EWMA, 0–1
  "max_streak":  15,           // racha más larga por canal
  "admissible":  true,          // false = violación estructural dura
  "reason":      "…",          // legible; dáselo al agente
  "protected":   true,          // false = solo observa, NO aplica
  "audit_seq":   41,
  "audit_head":  "9f2c…",       // cabeza de la cadena tras este paso
  "usage":       { "calls_used": 41, "calls_remaining": 959 }
}

El ancla de misión

El ancla es aquello contra lo que se mide todo. Se crea en el registro, la firma tu credencial de despliegue, se resume con hash y no se muta jamás. No hay ningún endpoint para editarla: cambiar la misión significa registrar un agente nuevo, y esa es justo la idea — un agente que deriva no puede mover su propia portería.

campoobligatorionotas
missionTexto libre — tu system prompt de siempre vale. Se firma y se resume con hash; el gate no lo interpreta (ver qué compara el gate). Lo lee el canal semántico opcional.
toolsSe toma al pie de la letra, nunca se infiere. Esta es la lista que el gate aplica de verdad.
signing_authorityTu credencial de despliegue o identidad de servicio. Los valores vacíos o de relleno se rechazan.
hard_constraintsno{"tool","arg","op","value"}. Operadores: <= < >= > ==. Incumplirla = bloqueo inmediato.
allowed_topicsnoAlcance temático.
out_of_scopenoExclusiones explícitas.
modenoenforce (por defecto) o observe.
El límite de confianza, dicho sin rodeos. Si alguien puede reescribir el system prompt de tu agente sin autorización, puede reescribir su misión — pero a esas alturas ya es dueño del agente de todos modos. Heredamos exactamente el límite de confianza de tu cadena de despliegue: ni más débil que tu plataforma, ni ceremonias que no añaden seguridad real. Lo que es estructural: el agente, en ejecución, no puede reescribir su ancla ni saltarse el gate.

Endpoints

POST/v1/signup

Crea una organización y emite una clave de API. Sin autenticación. La clave se muestra una sola vez y solo se guarda su hash.

POST/v1/agents

Registra un agente; devuelve agent_id y anchor_hash.

GET/v1/agents

Lista tus agentes registrados.

POST/v1/agents/{agent_id}/step

Gobierna un paso. Esta es la llamada que se factura.

POST/v1/govern

Lo mismo, con agent_id en el cuerpo — cómodo para clientes ligeros.

GET/v1/agents/{agent_id}/anchor

El ancla firmada, tal y como está guardada.

GET/v1/agents/{agent_id}/audit

El registro de decisiones encadenado por hash, más el resultado de su verificación.

GET/v1/usage

Llamadas usadas, restantes e importe pendiente.

GET/healthz

Vida del servicio. Sin autenticación.

Campos de la petición de paso

campotiponotas
proposed_toolstring|nullLa herramienta que el agente quiere llamar. null cuando solo está hablando.
tool_argsobjectSe comprueba contra hard_constraints.
proposed_textstringLo que piensa decir. Activa la comprobación de honestidad (afirmar que se ha verificado algo sin haber hecho ninguna consulta).
output_kindstringaction · answer · ask · refuse · frontier
executed_toolstringLo que se ejecutó de verdad — permite detectar propuesto-frente-a-ejecutado.
considered_toolsstring[]Alternativas sopesadas, para el canal de compromiso.
step_idintOpcional; se autoincrementa por agente.

Errores & cuota

códigosignificado
400Petición mal formada — el mensaje dice qué campo.
401Clave de API ausente o inválida.
402Plan gratuito agotado. No es un fallo: es una negativa tipada, con las condiciones para reabrirlo.
404Agente desconocido, o de otra organización.
500Culpa nuestra. Decide tu postura — ver abajo.
// 402 — el plan gratis es una negativa constructiva, no un muro
{
  "error": "free_tier_exhausted",
  "mode":  "REFUSE",
  "blocking_condition": "se agotó el plan gratuito de 1000 llamadas gobernadas",
  "reopening_conditions": [
    "inicia un contrato para continuar — diacroma@veritglobal.com",
    "subimos tu cuota el mismo día; tus agentes y tus anclas se quedan como están"
  ]
}

Los avisos de consumo aparecen en todas las respuestas a partir del 80 % del plan, para que nadie descubra el límite a mitad de una ejecución.

Si la capa de gobierno no está disponible

Decide esto a propósito. Fallar abierto (por defecto) mantiene tu agente funcionando sin gobierno y avisa a gritos — lo correcto para la mayoría de productos, porque una capa de gobierno que tumba tu agente se desinstala el primer día. Fallar cerrado se niega a actuar sin veredicto — lo correcto para trabajo regulado o de radio de impacto alto. Elijas la que elijas, déjalo escrito en tu propio manual de operación.

Qué se declara, y qué pasa si no lo declaras

El gate solo puede ver lo que le has dicho que mire. Ninguna omisión hace que falle abierto sobre lo que declaraste — lo que hacen es reducir qué puede ver, y eso queda escrito en el perfil firmado que acompaña a cada decisión. Esta tabla es la lista completa.

declaracióndóndesi NO la haces
missionregistro El agente no se registra.
toolsregistro El agente no se registra. Es la lista que el gate aplica de verdad; sin ella no hay nada que aplicar.
signing_authorityregistro El agente no se registra. Un ancla sin autoridad real no gobierna nada, así que preferimos que el despliegue no arranque a que arranque sin proteger.
hard_constraintsregistro Ese tope no existe y nada lo comprueba. No hay valor por defecto secreto. Un tope con un operador que el gate no implementa se rechaza al sellar, no al ejecutar.
evidence_mapregistro Una afirmación se sostiene con haber ejecutado algo de la clase correcta — una heurística. Declarándolo se exige el recibo concreto.
claim_evidence: "receipts"registro Se queda en "tools": un nombre de herramienta basta como evidencia. Para pagos, identidad o accesos, eso es poco.
evidence_max_age_sregistro No se exige frescura: un recibo de hace seis horas sostiene «se ha liquidado» en un sistema que cambia cada minuto.
acknowledge_no_semantic_sensorregistro No se puede desplegar sin el juez semántico sin reconocerlo. Es una bandera que hay que escribir a mano, a propósito: sin el juez, el canal de objetivo — el de más peso — lo cubre solo el detector estructural.
declare_reads / descriptoresen vida No se marca a nadie nunca. El acoplamiento entre agentes deja de verse. No falla: es que no existe. Se declara en cualquier momento — el conjunto de lectura CRECE durante la vida del agente, no se fija al sellar.
nivel del descriptor (1/2/3)en vida Se toma el nivel 3 (la colección entera): marca de más, nunca de menos. Cada nivel marca un superconjunto del anterior, y marcar de más solo cuesta confianza, no trabajo abortado.
declare_invarianten vida El caso en que cada agente cumple su regla y la regla que los relaciona se rompe igual no se detecta. Es el fallo que ninguna comprobación por paso puede ver.
declare_lineageen vida Una escritura en el origen no marca a quien lee el derivado, y durante la ventana de retardo el lector trabaja sobre datos que ya no valen sin saberlo.
declare_precedenceen vida Las restricciones sobre la secuencia —«no reembolsar sobre un ticket cerrado»— no se comprueban. Cada paso es admisible por separado; el orden no.
state_providerinterfaz efectora La interfaz refusa. Sin él no puede saber si la trayectoria se movió después de emitir el permiso, y un permiso que no se puede ligar no autoriza nada. Debe devolver digest, cycle y generation: el ciclo lo pone el almacén, nunca quien presenta el token.
D4A_AUDIT_KEYentorno La cadena de auditoría queda sin firmar: es consistente consigo misma, pero quien pueda escribir el almacén puede reescribir la historia entera y recalcular los enlaces. Con clave, no.

La regla, en una línea: lo que no declaras no se gobierna, y te lo decimos al desplegar, no cuando pase algo.

Tres agentes o más

Todo lo de esta página gobierna un agente. Para que varios se vigilen entre sí no hace falta que hablen: hace falta que declaren qué leen. Es opcional — sin declarar nada, cada agente queda gobernado por su cuenta y el acoplamiento simplemente no existe.

1 · Declarar qué lee cada agente — en cualquiera de los tres momentos

Es opcional, y los tres son el mismo campo reads. Existen tres porque un agente descubre qué datos necesita mientras trabaja: si solo se pudiera declarar al crearlo, todo lo que descubra después quedaría invisible. El conjunto de lectura crece durante la vida del agente; no se fija al sellar.

cuándodóndepara qué sirve
al crearPOST /v1/agents lo que ya sabes que va a leer. La respuesta te devuelve reads_declared: cuántos descriptores quedaron.
en cualquier momentoPOST /v1/agents/{id}/reads cuando lo descubres entre pasos, o lo sabe otro sistema.
en el mismo pasoPOST /v1/agents/{id}/step el momento natural: el agente acaba de usar el dato. Se procesa antes que las escrituras del mismo paso, para que un paso que lee y escribe a la vez quede registrado como lector antes de marcar a nadie.
// 1) al crear el agente
POST /v1/agents
{ "mission": "...", "tools": ["..."], "signing_authority": "...",
  "tenant": "acme",
  "reads": [{"collection": "facturas", "tier": 1, "key": "f-1042"}] }
// -> { "agent_id": "agt_…", "reads_declared": 1, … }

// 2) en cualquier momento, tantas veces como haga falta
POST /v1/agents/{agent_id}/reads
{
  "tenant": "acme",
  "reads": [
    {"collection": "facturas", "tier": 1, "key": "f-1042"},
    {"collection": "clientes", "tier": 2,
     "ranges": [["saldo", 0, 5000]]},
    {"collection": "kb_politicas", "tier": 3}
  ]
}
// -> { "declared": 3, "exact_keys": 1, … }

// 3) dentro del paso que usa el dato
POST /v1/agents/{agent_id}/step
{ "proposed_tool": "…", "output_kind": "…", "tenant": "acme",
  "reads":  [{"collection": "kb_politicas", "tier": 3}],
  "writes": [{"collection": "facturas", "key": "f-1042"}] }

tenant es obligatorio en las tres. Sin ámbito no hay partición, y un índice sin partición le revela a un cliente que otro está trabajando: se rechaza al declarar, no al evaluar.

nivelqué declarasqué marca
1la clave exacta que leíste solo escrituras sobre esa clave
2intervalos sobre atributos ordenados lo que cae dentro del intervalo
3solo la colección (el valor por defecto) toda escritura en ella

Cada nivel marca un superconjunto del anterior, así que omitir el nivel marca de más y nunca de menos. El 3 es el que hace que una consulta por similitud (RAG) deje de ser un caso aparte: no se puede expresar como condición, pero la colección sí se puede declarar. Si no declaras nada, no se marca a nadie nunca.

2 · Declarar qué escribe, en el mismo paso gobernado

No hay una llamada aparte: va en el /step que ya haces.

POST /v1/agents/{agent_id}/step
{
  "proposed_tool": "actualizar_factura",
  "tool_args": {"id": "f-1042", "estado": "anulada"},
  "output_kind": "action",
  "writes": [
    {"tenant": "acme", "collection": "facturas", "key": "f-1042"}
  ]
}

Con eso, todo agente que hubiera declarado leer f-1042 queda marcado — y la marca arrastra cuánta exposición llevaba el que escribió. Un escritor limpio ensucia menos que uno que ya iba desviado.

3 · Qué devuelve el paso: lo que marcaste y lo que heredaste

Son dos cosas distintas y por eso son dos campos. coupling es lo que este paso movió debajo de otros. inherited es lo que otros movieron debajo de este — y es el que gobierna.

POST /v1/agents/{agent_id}/step
{
  "action": "block_escalate",
  "admissible": false,
  "reason": "admissibility:coupling_poisoned: 1 premise(s) moved …",
  "exposure": 1.87,

  // lo que ESTE paso marcó (solo si declaró `writes`)
  "coupling": { "marked": 3, "pivot": false },

  // lo que ESTE paso heredó (solo si había marcas pendientes)
  "inherited": {
    "marks": 2,
    "grade": "contaminated",
    "inherited_exposure_native": 700000000,
    "confidence_penalty": 0.50,
    "block": false,
    "degraded": false,
    "written_by": ["agt_cobros", "agt_libro"]
  }
}

Qué hace cada uno con la decisión, que es lo que lo separa de un panel:

campoefecto en el ciclo
inherited_exposure_native se suma a Lt, el techo de por vida, dentro de la misma transición que firma la fila. En escala nativa entera, así que un replay la recomputa exacta. Un agente impecable respecto de su propia ancla puede agotar su presupuesto por servir fielmente a un dato que ya venía torcido.
confidence_penalty multiplica a la confianza (1 − p) antes del umbral, así que puede disparar la política de confianza baja que ya tenías declarada. La fila dice cuál de las dos mitades bajó: confidence_coverage y coupling_confidence_penalty van por separado.
block marca envenenada — la escribió alguien que estaba refusando, o que afirmó algo sin recibo. Entra por admisibilidad, no por deriva: ninguna puntuación posterior la readmite.
degraded el almacén de acoplamiento no respondió. No se sigue como si no hubiera marcas: no saber si tus premisas se movieron es exactamente el caso en el que baja la confianza.

grade es stale (alguien movió tu premisa), contaminated (además iba desviado) o poisoned (el que escribió estaba bloqueado o afirmó algo sin recibo). pivot: true significa que este agente es el cruce: trae premisas movidas y lo que escribe lo lee alguien. Es el que hay que ir a mirar de los tres.

4 · Mirar sin cobrar

GET /v1/agents/{agent_id}/coupling
{
  "marks": 2, "grade": "contaminated", "pending": true,
  "inherited_exposure": 0.70, "inherited_exposure_native": 700000000,
  "confidence_penalty": 0.50, "block": false,
  "circulation": 0, "pending_lineage": 0,
  "written_by": ["agt_cobros", "agt_libro"]
}

Esta vista no consume. Mírala mil veces y el número no cambia: es lo que el próximo paso gobernado va a heredar — por eso pending: true. Quien consume es el ciclo, y solo si se confirma: el borrado de las marcas va en la misma transacción que el estado y la fila de auditoría, así que o se cobran las tres cosas o no se cobra ninguna. Un GET que descontara lo que mide dejaría que un panel refrescando cada treinta segundos se comiera la contaminación antes de que la pagara nadie.

5 · Lo que existe en la biblioteca y todavía no es un endpoint

Todavía no en la API hospedada. Las invariantes declaradas, el linaje entre sistemas, la precedencia de operaciones y los presupuestos delegados están construidos y probados en la biblioteca, y no están expuestos como rutas HTTP. Se usan hoy auto-hospedando el SDK. Preferimos decirlo a documentar una ruta que devuelve 404.
declaraciónqué cazasi no la haces
invariants A escribe X, B escribe Y, ninguno toca el dato del otro y la regla que los relaciona se rompe igual no se detecta; cada agente sigue pareciendo correcto
lineage el derivado aún no ha cambiado y el lector ya está trabajando sobre él; la marca se retiene hasta que pasa lag_seconds marcarías al sincronizar, que llega tarde
precedence reglas sobre la secuencia: reembolsar sobre un ticket ya cerrado cada paso es admisible por separado y el orden no se comprueba
delegación un padre reparte su presupuesto entre subagentes; el ancla derivada solo puede restringir, y lo arrendado cuenta como gastado cada subagente lleva su cuenta y la suma puede pasar del techo del padre
Una nota de honestidad. Nada de esto adivina relaciones. Si dos agentes leen el mismo dato y ninguno lo declara, no hay acoplamiento que ver — y te lo decimos al desplegar, no cuando pase algo.

Auditoría & reproducción

Cada decisión es una fila en un registro de solo-añadir, encadenado con SHA-256. Manipular cualquier fila pasada rompe todos los enlaces posteriores, y la verificación informa de la primera secuencia rota.

GET /v1/agents/{agent_id}/audit

{
  "verify": { "ok": true, "first_broken": null, "detail": "verified 41 records" },
  "head":   "9f2c…",
  "records": [ { "sequence": 0, "payload": {…}, "chain_hash": "…" } ]
}

La reproducción es determinista: la misma trayectoria produce los mismos veredictos y los mismos hashes. Eso es lo que hace que el registro sea defendible ante un auditor, y no meramente informativo.

Rendimiento

No hay ningún LLM en el camino gobernado. Una decisión es aritmética sobre seis canales, una búsqueda en un conjunto y un hash — así que no hay latencia de modelo, ni coste por tokens, ni dependencia de un proveedor.

caminolatencia
Decisión, en proceso (SDK)~36 µs p50 · ~58 µs p95
Decisión + escritura de auditoría~99 µs
Ida y vuelta a la API alojada~1,2 ms + red
Rendimiento~10.000 decisiones/s por núcleo

Los agentes sensibles a la latencia (asistentes de código) deberían usar el SDK en proceso y ahorrarse el salto de red por completo. Las mediciones son reproducibles: python3 tests/bench.py.

Seguridad & privacidad

Qué guardamos

Por defecto, el rastro de auditoría registra resúmenes y metadatos estructurados — nombres de herramienta, argumentos comprobados contra restricciones, veredictos, hashes — no los prompts en crudo de tu agente ni los datos de tus clientes. Las claves de API se guardan solo como hash SHA-256; la clave en claro existe una vez, en la respuesta a tu llamada de alta.

Aislamiento

Los agentes pertenecen a una organización. Una clave de una organización no puede leer ni gobernar los agentes de otra — ese límite se aplica en cada petición y está cubierto por tests.

Para compradores regulados

Hay despliegue en tus instalaciones y dentro de tu VPC, de modo que ningún tráfico de agentes sale de tu red, junto con un acuerdo de tratamiento de datos. Pregúntalo antes de integrar, no después.

Autoalojamiento Autoalojado

Una dependencia — el driver de Postgres, y solo si lo apuntas a Postgres; con una ruta a un fichero SQLite es biblioteca estándar y nada más. El contenedor es pequeño y arranca en menos de un segundo. Monta un volumen para el libro mayor, o dale un DSN de Postgres, y apunta un nombre de host hacia él.

docker build -t d4agent .
docker run -p 8088:8088 -v d4a-data:/data d4agent

# o sin contenedor ninguno
python3 -m service.app --port 8088

El autoalojamiento se licencia con una cuota anual fija por sede, no por llamada. Una vez el servicio corre en tu infraestructura, el libro de consumo es tu base de datos, así que los términos son contractuales y no técnicos: uso interno, sin redistribución, con derecho de auditoría. Instalaciones aisladas de red, bienvenidas. Todo lo que se mide por paso gobernado corre sobre la API alojada.

Siguiente

Ver la demo de deriva en vivo →
Precios →
Consigue una clave de API →