# Ordenar la actividad de los agentes para reconstruir incidentes de forma fiable

El orden de actividad del agente determina si un informe de incidente explica lo ocurrido o solo muestra un montón de marcas de tiempo. Cuando un agente autónomo de programación puede llamar a APIs y abrir sesiones SSH, los investigadores necesitan establecer qué proceso actuó, qué intentó hacer, qué se completó y qué resultado recibió el agente antes de elegir su siguiente acción.

Una lista ordenada por hora no basta. Los relojes se desajustan, las solicitudes se solapan, las respuestas llegan fuera de orden y un tiempo de espera agotado puede ocultar una acción que sí se completó de forma remota. Crea registros que conserven el contexto de la sesión, un orden local duradero y marcas de tiempo distintas para cada etapa del ciclo de vida. De lo contrario, el primer incidente serio convertirá tu rastro de auditoría en una discusión basada en suposiciones.

## El tiempo por sí solo no establece el orden de las acciones

Una marca de tiempo indica cuándo un reloj observó un evento. No demuestra que ese evento ocurriera antes que todos los demás eventos con una marca posterior. La diferencia parece académica hasta que dos llamadas salen del agente con muy poca diferencia, un servicio remoto se queda bloqueado y la segunda llamada devuelve primero el resultado. Ordenar por `completed_at` muestra el orden de las respuestas, no el orden de las decisiones del agente.

Cada llamada tiene varios momentos importantes. El agente decide invocar una herramienta. La puerta de enlace acepta la solicitud. La autoriza. Envía el trabajo a un servicio HTTP o a un asistente SSH. El extremo remoto puede aceptarlo. Llega una respuesta. La puerta de enlace entrega ese resultado al agente. Tratar todo esto como un único evento elimina la evidencia necesaria para explicar un fallo.

Mantén un `call_sequence` local y monotónicamente creciente dentro de cada sesión. Asígnalo cuando la puerta de enlace acepte una llamada, antes de iniciar el trabajo de red. Ese número responde a una pregunta concreta y útil: «¿En qué orden aceptó esta puerta de enlace las llamadas de este proceso del agente?». No pretende describir el orden de ejecución remota. Un alcance honesto es mejor que una afirmación amplia que no puedas defender.

Usa una segunda secuencia duradera para el propio diario de auditoría. Las llamadas de distintas sesiones pueden solaparse, y los eventos de autorización, revocación, bloqueo de la bóveda y verificación deben vivir en el mismo flujo de evidencia. La secuencia del diario indica al investigador el orden de escritura del registrador. La secuencia de llamadas de una sesión indica el orden de intención dentro de una ejecución concreta del agente. Ninguno de los dos campos sustituye al otro.

No uses la precisión de las marcas de tiempo como sustituto de los números de secuencia. Añadir más decimales solo registra una lectura más precisa de un reloj. No resuelve un ajuste del reloj ni establece un orden total entre escritores concurrentes.

RFC 3339 define una representación interoperable útil para las marcas de tiempo del reloj, incluido un desplazamiento UTC explícito. Usa su formato UTC, como `2025-03-08T21:14:03.482Z`, para las exportaciones y la revisión humana. RFC 3339 no promete un orden causal. Esa es la función del registrador, y requiere campos de secuencia y límites de eventos claros.

## Una sesión identifica el proceso que actúa, no una tarea imprecisa

Una sesión debe vincular una ejecución ordenada al proceso concreto del agente que recibió autoridad para actuar. Debe comenzar cuando ese proceso establece una conexión y terminar cuando el proceso sale, pierde su canal o un operador revoca su autoridad. No hagas que una sesión signifique «trabajo en el ticket 184» o «el despliegue de la tarde». Esas etiquetas pueden ayudar en las búsquedas, pero no definen un límite de ejecución.

El registro de sesión necesita suficiente identidad para responder a las preguntas reales de los investigadores: qué ejecutable se conectó, quién lo firmó, qué usuario local lo inició, qué transporte lo conectó y cuándo comenzó y terminó su autoridad. Registra identificadores que el sistema operativo o la conexión puedan acreditar. No permitas que el agente escriba sus propias afirmaciones de identidad en los campos autorizados.

Esta separación importa cuando alguien copia la configuración de una herramienta a otro proceso. La solicitud de acción puede indicar que pretende operar sobre un repositorio concreto. La puerta de enlace debe registrar la identidad del proceso que observó. Durante un incidente, la segunda afirmación tiene más peso.

Un registro de sesión práctico podría contener:

```json
{
  "event_id": "01JNRQ2Q9Y9J0R3E5P8F7K2X4M",
  "journal_sequence": 8124,
  "event_type": "session.opened",
  "occurred_at": "2025-03-08T21:14:02.901Z",
  "session_id": "sess_7f31c4",
  "process": {
    "pid": 48102,
    "code_signing_authority": "observed signing authority",
    "local_user": "developer account"
  }
}
```

El agente debe recibir un identificador opaco de sesión, no autorización para elegir el identificador de sesión ni modificar sus metadatos. Aun así, puede adjuntar su propia etiqueta de ejecución, la ruta del repositorio o la referencia de la tarea en un campo de contexto declarado separado. Marca esos valores como proporcionados por el agente. La etiqueta puede explicar la intención, pero nunca debe sobrescribir los hechos observados sobre el proceso.

La autorización por sesión ofrece una segunda ventaja para la investigación. Registra una decisión humana vinculada a una ejecución de proceso limitada. Si esa ejecución realiza después una llamada dañina, los revisores pueden ver el evento de autorización que la precedió y el evento de cierre o revocación que terminó la sesión. Una aprobación que se aplica silenciosamente a procesos posteriores crea una laguna que ningún volumen de registros de llamadas puede reparar.

## Una llamada necesita un ciclo de vida, no una única línea de finalización

Un registro de llamada útil conserva el ciclo de vida de un intento. No combina una solicitud intentada, un envío de red, un resultado remoto y el resultado visible para el agente en un único campo impreciso de «éxito» o «fallo».

Empieza con un `call_id` inmutable y el siguiente `call_sequence` de la sesión. Registra un evento de aceptación antes de contactar con el exterior. Si una política o una aprobación bloquea la solicitud, el evento de aceptación y el evento de denegación siguen siendo importantes. Muestran la intención y el comportamiento de los controles sin fingir que la acción externa tuvo lugar.

Para una acción HTTP permitida, captura estos límites de evento por separado:

1. `call.accepted` registra la solicitud ordenada en la puerta de enlace.
2. `call.authorized` o `call.denied` registra la decisión de control.
3. `call.dispatched` registra que la puerta de enlace entregó la solicitud a su cliente de red.
4. `call.result_received` registra el resultado del transporte o la respuesta remota.
5. `call.result_returned` registra el resultado entregado al agente.

Los nombres pueden variar, pero la semántica no. Un resultado recibido no siempre es un resultado devuelto. La puerta de enlace puede redactar una respuesta, rechazar datos con formato incorrecto, perder la conexión del agente o sufrir un fallo interno mientras prepara el resultado. Los investigadores necesitan ver esa diferencia.

Conserva `request_started_at`, `dispatched_at`, `result_received_at` y `result_returned_at` cuando se produzcan esos momentos. Usa `null` para un momento que no haya ocurrido. No inventes una hora final cuando un proceso se bloquee. Registra un evento de recuperación posterior que indique que el registrador encontró una llamada sin terminar.

Este ejemplo muestra la estructura de una solicitud completada sin exponer un token bearer ni el cuerpo completo de la respuesta:

```json
{
  "event_id": "01JNRQ3M8W7P0Q4R6S9T1V2X3Y",
  "journal_sequence": 8131,
  "event_type": "call.result_received",
  "occurred_at": "2025-03-08T21:14:05.841Z",
  "session_id": "sess_7f31c4",
  "call_id": "call_00017",
  "call_sequence": 17,
  "channel": "http",
  "target": "api.internal.example/v1/releases",
  "method": "POST",
  "dispatch_event_id": "01JNRQ3G2A...",
  "outcome": {
    "transport": "response",
    "http_status": 201,
    "response_digest": "sha256:..."
  }
}
```

Los resúmenes criptográficos de la solicitud y la respuesta permiten comparar la evidencia conservada sin poner secretos ni grandes cargas sensibles en manos de todos los lectores del diario. Un resumen criptográfico no hace que un secreto sea seguro para registrar. Los valores con poca entropía, los identificadores predecibles y los tokens cortos siguen siendo fáciles de adivinar. Excluye las credenciales al capturar los datos y decide después qué fragmentos de las cargas necesita realmente tu proceso de respuesta ante incidentes.

## Los reintentos y los tiempos de espera agotados crean la ambigüedad más difícil

Un tiempo de espera agotado significa que no sabes si el extremo remoto actuó. No significa que no hiciera nada. Los equipos se equivocan repetidamente en este punto porque los registros de las aplicaciones suelen tratar el tiempo agotado como un error simple y el siguiente reintento como un sustituto del primer intento.

Imagina un agente que crea una versión mediante una solicitud HTTP. La llamada 41 recibe un tiempo de espera agotado después del envío. El agente lee ese fallo y envía la llamada 42, un reintento. Más tarde, el servicio remoto procesa ambas solicitudes. Si el diario sobrescribió la llamada 41 con un estado final de «reintentada», los investigadores verán una solicitud correcta y no detectarán la acción duplicada.

Asigna a cada intento de red su propio `call_id` y su propio `call_sequence`. Añade `retry_of` cuando un intento siga directamente a otro anterior. Conserva la causa visible para el agente que provocó el reintento, como un tiempo de espera agotado, un restablecimiento de conexión o un estado recibido que admite reintento. La relación permite rastrear la cadena sin aplanarla.

Una secuencia completa puede verse así:

```text
sequence 41  accepted       21:14:11.024Z  create release, request r_8d2
sequence 41  dispatched     21:14:11.027Z
sequence 41  result_received 21:14:41.031Z timeout
sequence 41  result_returned 21:14:41.034Z timeout returned to agent
sequence 42  accepted       21:14:42.112Z  retry_of call_00041, request r_8d2
sequence 42  dispatched     21:14:42.115Z
sequence 42  result_received 21:14:42.490Z HTTP 201
sequence 42  result_returned 21:14:42.493Z HTTP 201 returned to agent
```

La referencia repetida de la solicitud solo es útil si la API remota admite un mecanismo de idempotencia u otro identificador estable de operación. Si el servicio acepta una clave de idempotencia, genera y registra una clave no secreta que permanezca constante entre los reintentos de una misma operación prevista. Si no la admite, registra que el riesgo del reintento sigue sin resolverse. No afirmes que una operación es idempotente solo porque la carga parezca similar.

SSH plantea un problema distinto. Un comando puede ejecutarse de forma remota y la conexión puede fallar antes de que el cliente reciba la salida o un código de salida. Registra el envío del comando, la identidad de la conexión, la referencia del host y el estado de terminación observado. Etiqueta un comando SSH interrumpido como «resultado desconocido», no como «fallido». Un comando posterior que compruebe el estado remoto puede reducir la incertidumbre, pero no reescribe el resultado original.

No conviertas todos los fallos en eventos terminales. Una denegación de autorización es terminal para esa llamada porque no se produjo ningún envío externo. Un fallo local de DNS puede ser terminal para el intento. Un tiempo de espera agotado después de que los bytes salgan del equipo deja desconocido el resultado externo. Estas categorías conducen a decisiones diferentes durante un incidente.

## Registra dos tipos de tiempo y explica sus límites

La hora del reloj hace que una línea temporal sea legible entre sistemas. El tiempo monotónico mide el tiempo transcurrido sin los cambios provocados por la sincronización de la hora de red o un ajuste manual del reloj. Recopila ambos cuando el sistema operativo los proporcione y explica qué significa cada uno en tu esquema.

Para cada evento del diario, registra un valor UTC `occurred_at` conforme a RFC 3339. Para los eventos dentro de una sesión activa, registra también `monotonic_ns`, medido desde el origen del reloj monotónico elegido por ese proceso. No compares lecturas monotónicas de máquinas distintas a menos que hayas establecido explícitamente una referencia compartida. Son mediciones locales.

Una corrección del reloj puede producir registros confusos como este:

```text
journal 901  wall 21:19:07.900Z  monotonic 5562019921  call accepted
journal 902  wall 21:18:58.104Z  monotonic 5562026310  call dispatched
```

El reloj retrocedió. La secuencia del diario y el valor monotónico siguen mostrando que el envío ocurrió después de la aceptación. La exportación debe conservar las marcas de tiempo originales en lugar de ordenarlas y reescribirlas silenciosamente. Añade un evento del registrador cuando el sistema operativo informe de un cambio importante de hora, si puedes observarlo. Ese evento ofrece a los revisores una explicación para la discrepancia.

La publicación especial 800-92 del NIST, Guide to Computer Security Log Management, recomienda sincronizar los relojes y definir los requisitos de los datos de registro antes de un incidente. Esa recomendación es correcta, pero los relojes sincronizados por sí solos no proporcionan el orden dentro de una ejecución del agente. La sincronización mejora la correlación con una API remota, un servicio de CI o los registros del host. Tu secuencia local sigue estableciendo el orden del registrador.

Las marcas de tiempo remotas merecen sus propios campos. Un encabezado HTTP `Date`, un ID de solicitud del proveedor y una hora de evento generada por el servidor son afirmaciones externas. Conserva su origen y su valor exacto. No los copies en `occurred_at` ni los uses para renumerar tu diario local. Una marca remota puede ayudar a reconciliar sistemas más adelante, pero podría reflejar una cola, otro reloj o el momento de generación de la respuesta.

Los campos de duración también necesitan una definición precisa. `gateway_duration_ms` puede significar el tiempo desde la aceptación hasta la devolución del resultado. `network_duration_ms` puede significar desde el envío hasta la recepción del resultado. Escribe la definición junto al esquema. De lo contrario, un informe que diga que una llamada tardó 30 segundos no podrá indicar si el retraso ocurrió antes del envío, en el servicio remoto o después de devolver la respuesta.

## El escritor de auditoría debe elegir el orden antes de publicar los resultados

No puedes reconstruir el orden si los trabajadores concurrentes escriben los registros cuando terminan. Proporciona al escritor de auditoría una única ruta de adición que asigne una secuencia del diario, capture la hora del evento, vincule el registro anterior y confirme el registro antes de que el sistema informe al agente de que se ha producido un cambio de estado externo relevante.

Esto no exige un único bloqueo enorme alrededor de toda la actividad de red. Las llamadas pueden ejecutarse de forma concurrente. El registrador solo necesita un punto de confirmación serializado y estrecho. Cuando un trabajador alcanza un límite de evento, envía el evento a ese escritor. El escritor asigna la siguiente secuencia duradera del diario. El orden resultante refleja el orden de confirmación, y debes nombrarlo con precisión en la documentación y las exportaciones.

El patrón de fallo es conocido. El trabajador A acepta la llamada 17 y comienza una solicitud lenta. El trabajador B acepta la llamada 18 y termina rápidamente. Si los trabajadores solo añaden sus registros de finalización, el diario comienza con el éxito de la llamada 18. El investigador no puede saber si la llamada 17 estaba en curso, nunca se envió o se omitió. Los eventos de aceptación y envío de la llamada 17 cierran esa brecha.

El encadenamiento de hashes añade evidencia de manipulación a la secuencia confirmada. Cada entrada contiene el resumen criptográfico de la entrada confirmada anterior y el resumen de su propio contenido canónico. La canonicalización importa. Los mismos datos deben producir los mismos bytes antes de aplicarles el hash. Especifica el orden de los campos, la codificación UTF-8, la representación de las marcas de tiempo, el tratamiento de `null` y los formatos numéricos. «Aplicamos un hash al JSON» no es una especificación, porque el orden habitual de las propiedades de un objeto JSON no es una propiedad de seguridad.

Una entrada conceptual podría usar estos campos:

```json
{
  "journal_sequence": 8131,
  "event_id": "01JNRQ3M8W7P0Q4R6S9T1V2X3Y",
  "previous_hash": "sha256:9c7d...",
  "record_hash": "sha256:04b1...",
  "payload": {"event_type": "call.result_received"}
}
```

Una cadena válida te indica que las entradas conservadas están conectadas sin cambios no detectados, suponiendo que el verificador dispone del ancla esperada de la cadena. No demuestra que la secuencia esté completa si un atacante controla el registrador y puede impedirle escribir un registro. No presentes el encadenamiento de hashes como magia. Hace visibles las alteraciones, pero no puede registrar un evento que el registrador nunca observó.

Sallyport proyecta sus diarios Sessions y Activity desde un único registro de auditoría cifrado y encadenado mediante hashes. Su comando `sp audit verify` verifica la cadena sin conexión sobre texto cifrado, sin necesitar una clave de la bóveda. Este diseño mantiene vinculadas la vista de sesiones y la vista de llamadas individuales a una única fuente ordenada, en lugar de pedir a los investigadores que reconcilien dos registros separados.

## Crea una línea temporal que conserve la incertidumbre

Una línea temporal de incidentes debe mostrar por separado los hechos, las observaciones y los resultados sin resolver. Una narración pulida que convierte lo desconocido en verbos categóricos puede parecer útil durante una revisión tensa, pero crea un registro falso que la evidencia posterior puede contradecir.

Supón que un proceso de agente recibió aprobación a las 09:00:00. Emitió un comando SSH a las 09:03:14. El cliente perdió la conexión a las 09:03:16. A las 09:03:18, el agente usó HTTP para consultar el sistema objetivo y encontró una configuración modificada. Esa evidencia admite varias explicaciones: el comando SSH se completó, otro actor cambió el estado o una tarea puesta en cola anteriormente tuvo efecto. La línea temporal debe indicar qué conclusión respalda la evidencia y cuál no.

Usa este formato en las notas del incidente:

| Orden | Hora | Evidencia | Afirmación respaldada |
|---|---|---|---|
| 444 | 09:03:14.120Z | `call.dispatched` | La puerta de enlace envió el comando SSH al asistente. |
| 445 | 09:03:16.202Z | desconexión del transporte | La puerta de enlace no recibió un estado de salida. |
| 446 | 09:03:18.810Z | respuesta de consulta HTTP | La configuración consultada era distinta en ese momento. |
| 447 | 09:03:19.001Z | `call.result_returned` | El agente recibió el resultado de la consulta. |

Evita escribir «el comando SSH cambió la configuración» a menos que tengas evidencia directa que vincule el comando con el efecto remoto. Los registros de auditoría remotos, un ID único de operación o una respuesta que contenga un ID de solicitud duradero generado por el servidor pueden proporcionar ese vínculo. Una marca de tiempo cercana no lo hace.

Los investigadores también necesitan saber qué vio el agente cuando tomó decisiones posteriores. Por eso `result_returned` merece su propio evento. Si la respuesta remota llegó, pero el agente se desconectó antes de recibirla, una acción posterior del agente no siguió esa respuesta. Si la respuesta llegó al agente, puede explicar una rama peligrosa de su comportamiento.

Muestra tanto una línea de sesión como una línea de llamadas en la vista del incidente. La línea de sesión muestra los eventos de apertura, aprobación, revocación, bloqueo y cierre. La línea de llamadas muestra los eventos de aceptación, autorización, envío y resultado. La lista plana sigue disponible para la verificación, pero las dos vistas responden a preguntas distintas sin mezclarlas.

## La gestión de secretos debe resistir la revisión del incidente

Los rastros de auditoría suelen fallar justo cuando resultan más útiles, porque alguien quiere registrar los encabezados completos, los entornos de shell y los cuerpos de respuesta «solo para esta investigación». Esa decisión puede convertir un incidente contenido del agente en una exposición de credenciales.

Captura la identidad de la acción sin material secreto. Para HTTP, registra el método, el host y la ruta normalizados, la referencia de la credencial o la etiqueta de la clave, los nombres de encabezados seguros, el resumen criptográfico de la solicitud, el estado de la respuesta, el ID de solicitud del proveedor cuando exista y un resumen de respuesta cuidadosamente elegido. Nunca registres un encabezado de autorización, una clave API sin proteger, una clave privada ni un volcado completo del entorno.\n\nPara SSH, registra una referencia del host, la referencia de la cuenta si la política lo permite, una representación normalizada del comando, el resumen criptográfico del comando, el estado de la conexión y el estado de salida cuando se reciba. Los comandos pueden contener secretos. Si tu flujo de trabajo permite texto de shell arbitrario, usa un almacén de evidencia protegido para revisiones autorizadas y limitadas, o registra solo una versión redactada junto con un resumen criptográfico. No supongas que un diario de comandos es inofensivo porque no contiene contraseñas.

Sallyport conserva las credenciales API y SSH en su bóveda cifrada y ejecuta la acción externa sin entregar esas credenciales al agente. Esto elimina una razón habitual por la que la transcripción y los registros de un agente terminan convertidos en un volcado de secretos, pero el destino, el cuerpo de la solicitud, los argumentos del comando y la respuesta aún pueden ser sensibles.

Controla el acceso a los registros sin procesar por separado de la verificación. Un responsable de respuesta puede necesitar ejecutar una verificación de la cadena sin permiso para leer los detalles cifrados de las llamadas. Un revisor de seguridad puede necesitar los metadatos de la sesión y del destino, pero no el contenido de las cargas. Esta separación hace que la respuesta ante incidentes dependa menos de copiar un diario completo en chats, tickets o hojas de cálculo.

Cuando exportes evidencia, incluye la versión del esquema, la hora de exportación, el intervalo de secuencias del diario, el resultado de la verificación y las reglas de redacción utilizadas. Conserva el diario protegido original bajo sus controles habituales. Una exportación es una copia de trabajo, no un sustituto de la evidencia de origen.

## Prueba el registro con una ejecución deliberadamente desordenada

Una demostración del camino correcto no demuestra casi nada sobre la reconstrucción de incidentes. Prueba las condiciones que vuelven ambiguo el orden: llamadas concurrentes, respuestas retrasadas, cambios de reloj, salidas de procesos, denegaciones, revocaciones y un tiempo de espera agotado seguido de un reintento.

Realiza un ejercicio controlado con dos destinos externos permitidos. Haz que la primera llamada espere antes de devolver el resultado. Inicia una segunda llamada después de que se envíe la primera. Interrumpe una tercera llamada después del envío. Después, revoca la sesión y comprueba que las llamadas posteriores reciben una denegación. Exporta el diario y entrégaselo a un compañero que no haya escrito el escenario.

Pide a esa persona que responda cinco preguntas usando únicamente la exportación:

- ¿Qué proceso recibió la autoridad y cuándo terminó esa autoridad?
- ¿En qué orden aceptó la puerta de enlace las llamadas de esa sesión?
- ¿Qué llamadas alcanzaron el límite de envío?
- ¿Qué resultado recibió el agente antes de cada llamada posterior?
- ¿Qué resultados siguen siendo desconocidos en lugar de fallidos o correctos?

Si tiene que preguntarte qué significa un campo, corrige el esquema o la documentación de la exportación. Si deduce un efecto remoto a partir de un tiempo de espera agotado, corrige las etiquetas de resultado. Si no puede distinguir un reintento de una operación nueva, añade la relación y el identificador de operación.

Conserva los artefactos del ejercicio. Se convierten en pruebas de regresión cuando cambias una biblioteca cliente, introduces concurrencia, ajustas la retención o añades un canal nuevo. Los errores de orden suelen llegar mediante refactorizaciones aparentemente inofensivas, porque los desarrolladores se concentran en que las acciones sigan funcionando mientras la ruta de evidencia cambia silenciosamente su momento de confirmación.

El incidente no esperará a que diseñes un sistema de registro más limpio. Asigna una secuencia de sesión cuando aceptes la llamada, confirma los eventos del ciclo de vida mediante un único escritor ordenado, conserva tanto la hora del reloj como el tiempo monotónico y permite que los resultados desconocidos sigan siendo desconocidos. Estas decisiones ofrecen a los investigadores una secuencia que pueden defender, en lugar de una línea temporal que tengan que justificar.
