Guides

Gobernar un agente de principio a fin

Registra un agente, gestiona una aprobación humana, inspecciona su trace y exporta Evidence sellada a través de KLA Control Plane.

11 min de lectura2436 palabras

Esta guía usa un agente de triaje de reclamaciones que puede recomendar un reembolso. El flujo cubre el registro del agente, una ejecución gobernada, la aprobación humana, la inspección del trace y la exportación de Evidence.

Los ejemplos usan la CLI de KLA para los procedimientos generados del control plane y curl para las rutas REST de Evidence. La CLI lee KLA_ACCESS_TOKEN. El host de API y el tenant están explícitos para ejecutar los mismos comandos contra un tenant dev desplegado.

export KLA_API_URL='https://api.dev.kla.digital'
export KLA_ACCESS_TOKEN='<short-lived access token>'
export KLA_TENANT_ID='<tenant external ID>'

# Build the repository CLI while the package is private, then expose it in this shell.
corepack enable
pnpm install --frozen-lockfile
pnpm kla --help
KLA_REPO_ROOT="$(git rev-parse --show-toplevel)"
kla() { KLA_CLI_CWD="$PWD" pnpm --dir "$KLA_REPO_ROOT" --silent kla "$@"; }

kla auth test
kla api doctor

KLA_TENANT_ID configura la indicación de enrutamiento x-kla-tenant-id del contexto de la CLI. El claim de tenant verificado sigue siendo la autoridad. Una solicitud que selecciona otro tenant usa x-kla-tenant-external-id; el llamante debe tener una membresía activa en ese tenant.

Este recorrido supone un tenant con una política aprobada y un workflow desplegado que puede crear una aprobación pendiente. El registro, el vínculo de política, la aprobación del release, el despliegue y la ejecución escriben en el tenant. Usa un tenant bajo tu control y un segundo principal para la aprobación maker-checker.

sequenceDiagram
  participant Dev as Developer
  participant Agent as Claims agent
  participant KLA as KLA Control Plane
  participant Desk as Decision Desk
  participant Room as Evidence Room
  Dev->>KLA: Register with agents.create
  Agent->>KLA: Start executions.execute
  KLA->>Desk: approvals.getPending returns a pending request
  Desk->>KLA: approvals.decide approves the request
  Dev->>KLA: Read traces.getTrace and traces.verify
  Dev->>Room: POST /v1/evidence/export

Paso 1: Registrar el agente

agents.create es una mutation tRPC. El comando generado de la CLI la envía mediante el transporte tRPC autenticado en /v1.

  • Endpoint: POST /v1/agents.create

Crea un manifiesto de registro con el formato aceptado por el Agent Registry:

cat > /tmp/claims-triage-registration.yaml <<'YAML'
name: Claims Triage Agent
flowId: claims_triage
description: Triages inbound claims and prepares refund recommendations for review.
model:
  provider: openai
  name: gpt-4o
pipeline:
  - type: llm_generate
    name: triage_claim
    config:
      template: Review the claim and recommend the next action with supporting facts.
owners:
  - email: [email protected]
tools:
  - lookup_claim
  - process_refund
humanOversightRequired: true
YAML

Regístralo:

AGENT_JSON=$(kla agent create --file /tmp/claims-triage-registration.yaml)
echo "$AGENT_JSON"
AGENT_ID=$(jq -r '.id // .agentId' <<<"$AGENT_JSON")

Conserva el identificador del agente devuelto. Un agente recién registrado necesita un vínculo de política aprobado y desplegado antes de que executions.execute pueda iniciar una ejecución gobernada. El ciclo de vida de la CLI es:

POLICY_ID='<approved policy ID>'
kla agent policy bind "$AGENT_ID" \
  --policy "$POLICY_ID" \
  --environment sandbox \
  --description 'Attach the refund approval policy.'

El vínculo de sandbox crea o actualiza el borrador de sandbox pendiente del agente y su workflow gestionado. No hace desplegable un release de producción. agents.deployVersion exige que el manifiesto de release inmutable lleve un vínculo governedExecution de producción.

Crea ese manifiesto explícito del release de producción. Una UUID nueva da al workflow gestionado de producción su propia identidad; la primera versión del workflow es 1:

WORKFLOW_ID="$(node -e "console.log(require('node:crypto').randomUUID())")"
cp /tmp/claims-triage-registration.yaml /tmp/claims-triage.yaml
cat >> /tmp/claims-triage.yaml <<YAML
governedExecution:
  policyId: ${POLICY_ID}
  workflowId: ${WORKFLOW_ID}
  workflowVersion: 1
  environment: production
YAML

RELEASE_JSON=$(kla agent release create "$AGENT_ID" \
  --manifest @/tmp/claims-triage.yaml \
  --description 'Publish the claims triage release.' \
  --justification 'The release adds the governed refund workflow.' \
  --change-type minor)
echo "$RELEASE_JSON"
VERSION_ID=$(jq -r '.version.id // .versionId // .id' <<<"$RELEASE_JSON")

El comando de release acepta @/tmp/claims-triage.yaml como entrada de archivo estructurada. La API valida la UUID, la versión del workflow, el ID de política y el entorno de producción, y guarda el vínculo del workflow gestionado antes de crear el release inmutable.

La credencial del reviewer debe identificar a otro principal autenticado. El servidor aplica el rol del tenant y las reglas de dos personas a cada comando del ciclo de vida.

Usa la misma URL de API y el mismo tenant con un token de reviewer de corta duración limitado al comando de aprobación:

export KLA_REVIEWER_ACCESS_TOKEN='<reviewer short-lived access token>'
KLA_ACCESS_TOKEN="$KLA_REVIEWER_ACCESS_TOKEN" \
  kla agent release approve "$VERSION_ID" --yes

Después de que el reviewer apruebe el release, restaura la credencial del maker antes de desplegarlo:

export KLA_ACCESS_TOKEN='<maker short-lived access token>'
kla agent release deploy "$VERSION_ID" --yes

Paso 2: Iniciar una ejecución gobernada

Ejecuta el agente en el sandbox con una reclamación que debe llegar a la política de aprobación:

RUN_JSON=$(kla agent run "$AGENT_ID" \
  --environment sandbox \
  --input '{"claim_id":"clm_9921","requested_refund":1250.00}')
echo "$RUN_JSON"
EXECUTION_ID=$(jq -r '.executionId' <<<"$RUN_JSON")

kla api executions get --input "{\"id\":\"$EXECUTION_ID\"}"

executions.execute devuelve un executionId. Un resultado de política require_approval deja la ejecución a la espera de un reviewer. El estado y el historial de eventos están disponibles mediante executions.get y executions.getEvents.

Paso 3: Encontrar y aprobar la solicitud pendiente

approvals.getPending enumera las Decision Requests pendientes del tenant. El filtro de ejecución devuelve la solicitud de esta ejecución:

  • Endpoint: GET /v1/approvals.getPending
# A 202 PENDING result queues approval creation asynchronously. Retry the lookup until the request
# is visible. Rate limits, 5xx responses, and network failures stay retryable for 60 seconds.
APPROVAL_ID=''
PENDING_JSON='{}'
POLL_DEADLINE=$((SECONDS + 60))
while (( SECONDS < POLL_DEADLINE )); do
  PENDING_ATTEMPT_STATUS=0
  PENDING_ATTEMPT_STDERR_FILE="$(mktemp)"
  PENDING_ATTEMPT_OUTPUT=$(kla api approvals getPending \
    --input "{\"executionId\":\"$EXECUTION_ID\",\"limit\":20}" 2>"$PENDING_ATTEMPT_STDERR_FILE") || \
    PENDING_ATTEMPT_STATUS=$?
  PENDING_ATTEMPT_ERROR="$(cat "$PENDING_ATTEMPT_STDERR_FILE")"
  rm -f "$PENDING_ATTEMPT_STDERR_FILE"
  if [[ -n "$PENDING_ATTEMPT_ERROR" ]]; then
    printf '%s\n' "$PENDING_ATTEMPT_ERROR" >&2
  fi
  if (( PENDING_ATTEMPT_STATUS != 0 )); then
    if printf '%s\n' "$PENDING_ATTEMPT_ERROR" | grep -Eqi \
      '(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
      echo 'Transient approvals.getPending failure; retrying until the 60-second deadline.' >&2
      PENDING_JSON="$PENDING_ATTEMPT_OUTPUT"
      sleep 2
      continue
    fi
    echo 'approvals.getPending returned a non-retryable authorization or schema error.' >&2
    if [[ -n "$PENDING_ATTEMPT_OUTPUT" ]]; then
      printf '%s\n' "$PENDING_ATTEMPT_OUTPUT" >&2
    fi
    exit 1
  fi
  PENDING_JSON="$PENDING_ATTEMPT_OUTPUT"
  if ! APPROVAL_ID=$(jq -r '.approvals[0].approvalId // empty' <<<"$PENDING_JSON"); then
    echo 'approvals.getPending returned invalid JSON.' >&2
    exit 1
  fi
  if [[ -n "$APPROVAL_ID" ]]; then
    break
  fi
  sleep 2
done
if [[ -z "$APPROVAL_ID" ]]; then
  echo "No pending approval appeared for $EXECUTION_ID within 60 seconds." >&2
  echo "$PENDING_JSON" >&2
  exit 1
fi
echo "$PENDING_JSON"

El reviewer envía approvals.decide con el identificador de aprobación, una decisión y un motivo. La entrada acepta approve, reject o escalate. Este ejemplo usa la segunda credencial de reviewer:

  • Endpoint: POST /v1/approvals.decide
cat > /tmp/approval-decision.json <<JSON
{
  "approvalId": "${APPROVAL_ID}",
  "decision": "approve",
  "decisionAcknowledged": true,
  "reason": "Claim documentation reviewed and refund authorized."
}
JSON
KLA_ACCESS_TOKEN="$KLA_REVIEWER_ACCESS_TOKEN" \
  kla api approvals decide --input @/tmp/approval-decision.json

# The decision can return before the worker and pending ledger finish. Keep polling the execution
# until it reaches a terminal state. A 202/PENDING response remains expected during this window.
export KLA_ACCESS_TOKEN='<maker short-lived access token>'
EXECUTION_JSON='{}'
EXECUTION_STATUS=''
EXECUTION_STATUS_LOWER=''
EXECUTION_DEADLINE=$((SECONDS + 120))
while (( SECONDS < EXECUTION_DEADLINE )); do
  EXECUTION_ATTEMPT_STATUS=0
  EXECUTION_ATTEMPT_STDERR_FILE="$(mktemp)"
  EXECUTION_ATTEMPT_OUTPUT=$(kla api executions get \
    --input "{\"id\":\"$EXECUTION_ID\"}" 2>"$EXECUTION_ATTEMPT_STDERR_FILE") || \
    EXECUTION_ATTEMPT_STATUS=$?
  EXECUTION_ATTEMPT_ERROR="$(cat "$EXECUTION_ATTEMPT_STDERR_FILE")"
  rm -f "$EXECUTION_ATTEMPT_STDERR_FILE"
  if [[ -n "$EXECUTION_ATTEMPT_ERROR" ]]; then
    printf '%s\n' "$EXECUTION_ATTEMPT_ERROR" >&2
  fi
  if (( EXECUTION_ATTEMPT_STATUS != 0 )); then
    if printf '%s\n' "$EXECUTION_ATTEMPT_ERROR" | grep -Eqi \
      '(^|[^0-9])(202|429|5[0-9][0-9])([^0-9]|$)|PENDING|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
      echo 'Transient execution status response; retrying until the 120-second deadline.' >&2
      sleep 2
      continue
    fi
    echo 'executions.get failed while waiting for the approved run.' >&2
    if [[ -n "$EXECUTION_ATTEMPT_OUTPUT" ]]; then
      printf '%s\n' "$EXECUTION_ATTEMPT_OUTPUT" >&2
    fi
    exit 1
  fi
  EXECUTION_JSON="$EXECUTION_ATTEMPT_OUTPUT"
  if ! EXECUTION_STATUS=$(jq -r '.status // empty' <<<"$EXECUTION_JSON"); then
    echo 'executions.get returned invalid JSON.' >&2
    exit 1
  fi
  EXECUTION_STATUS_LOWER=$(printf '%s' "$EXECUTION_STATUS" | tr '[:upper:]' '[:lower:]')
  case "$EXECUTION_STATUS_LOWER" in
    completed|failed|blocked|cancelled|timeout|stale)
      break
      ;;
    pending|running|gated|waiting_for_input)
      sleep 2
      ;;
    '')
      echo 'executions.get returned no status.' >&2
      echo "$EXECUTION_JSON" >&2
      exit 1
      ;;
    *)
      echo "executions.get returned an unknown status: $EXECUTION_STATUS" >&2
      echo "$EXECUTION_JSON" >&2
      exit 1
      ;;
  esac
done
if [[ ! "$EXECUTION_STATUS_LOWER" =~ ^(completed|failed|blocked|cancelled|timeout|stale)$ ]]; then
  echo "Execution $EXECUTION_ID did not reach a terminal state within 120 seconds." >&2
  echo "$EXECUTION_JSON" >&2
  exit 1
fi
if [[ "$EXECUTION_STATUS_LOWER" != 'completed' ]]; then
  echo "Execution $EXECUTION_ID ended with status $EXECUTION_STATUS; trace and evidence checks require completed status." >&2
  echo "$EXECUTION_JSON" >&2
  exit 1
fi
echo "$EXECUTION_JSON"

La ejecución continúa según el workflow desplegado. Un rechazo deja sin ejecutar la acción consecuente.

Paso 4: Inspeccionar y verificar el trace

El worker puede terminar la ejecución antes de que sea visible la proyección del ledger de auditoría. Lee el historial de eventos, espera la entrada de auditoría de la aprobación y usa los identificadores devueltos para las llamadas de trace y proof.

  • Endpoint: GET /v1/traces.getTrace
  • Endpoint: GET /v1/traces.verify
  • Endpoint: GET /v1/audit.listByResource
EVENTS_OUTPUT=$(kla api executions getEvents \
  --input "{\"id\":\"$EXECUTION_ID\",\"limit\":200}")
if ! jq -e 'type == "object" and (.events | type == "array")' <<<"$EVENTS_OUTPUT" >/dev/null; then
  echo 'executions.getEvents returned invalid JSON or no event list.' >&2
  echo "$EVENTS_OUTPUT" >&2
  exit 1
fi
EVENTS_JSON="$EVENTS_OUTPUT"

TRACE_ID=$(jq -r --argjson events "$EVENTS_JSON" '
  [
    .traceId,
    .trace_id,
    ($events.events[]? | .traceId, .trace_id, .metadata?.traceId, .metadata?.trace_id, .data?.traceId, .data?.trace_id)
  ]
  | map(select(type == "string" and length > 0))
  | first // empty
' <<<"$EXECUTION_JSON")
if [[ -z "$TRACE_ID" ]]; then
  echo "No trace identifier was returned for completed execution $EXECUTION_ID." >&2
  echo "$EVENTS_JSON" >&2
  exit 1
fi

# Approval decisions and requests receive audit IDs asynchronously. Poll both the approval detail
# and audit projection so the proof call uses a materialized ID from this run.
AUDIT_ID=''
APPROVAL_DETAIL_JSON='{}'
AUDIT_JSON='{}'
LEDGER_DEADLINE=$((SECONDS + 60))
while (( SECONDS < LEDGER_DEADLINE )); do
  DETAIL_ATTEMPT_STATUS=0
  DETAIL_ATTEMPT_STDERR_FILE="$(mktemp)"
  DETAIL_ATTEMPT_OUTPUT=$(kla api approvals getDetail \
    --input "{\"approvalId\":\"$APPROVAL_ID\"}" 2>"$DETAIL_ATTEMPT_STDERR_FILE") || \
    DETAIL_ATTEMPT_STATUS=$?
  DETAIL_ATTEMPT_ERROR="$(cat "$DETAIL_ATTEMPT_STDERR_FILE")"
  rm -f "$DETAIL_ATTEMPT_STDERR_FILE"
  if [[ -n "$DETAIL_ATTEMPT_ERROR" ]]; then
    printf '%s\n' "$DETAIL_ATTEMPT_ERROR" >&2
  fi
  if (( DETAIL_ATTEMPT_STATUS != 0 )); then
    if printf '%s\n' "$DETAIL_ATTEMPT_ERROR" | grep -Eqi \
      '(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
      echo 'Transient approval detail response; retrying ledger lookup.' >&2
      sleep 2
      continue
    fi
    echo 'approvals.getDetail failed while waiting for its audit identifier.' >&2
    if [[ -n "$DETAIL_ATTEMPT_OUTPUT" ]]; then
      printf '%s\n' "$DETAIL_ATTEMPT_OUTPUT" >&2
    fi
    exit 1
  fi
  APPROVAL_DETAIL_JSON="$DETAIL_ATTEMPT_OUTPUT"
  if ! jq -e 'type == "object"' <<<"$APPROVAL_DETAIL_JSON" >/dev/null; then
    echo 'approvals.getDetail returned invalid JSON.' >&2
    echo "$APPROVAL_DETAIL_JSON" >&2
    exit 1
  fi
  AUDIT_ID=$(jq -r '
    [
      .decisionReceipt.auditEventId,
      .metadata.decisionReceipt.auditEventId,
      .metadata.requestAuditEventId
    ]
    | map(select(type == "string" and length > 0))
    | first // empty
  ' <<<"$APPROVAL_DETAIL_JSON")
  if [[ -n "$AUDIT_ID" ]]; then
    break
  fi

  AUDIT_ATTEMPT_STATUS=0
  AUDIT_ATTEMPT_STDERR_FILE="$(mktemp)"
  AUDIT_ATTEMPT_OUTPUT=$(kla api audit listByResource \
    --input "{\"resource\":\"approval\",\"resourceId\":\"$APPROVAL_ID\",\"limit\":100,\"includeTotal\":true}" 2>"$AUDIT_ATTEMPT_STDERR_FILE") || \
    AUDIT_ATTEMPT_STATUS=$?
  AUDIT_ATTEMPT_ERROR="$(cat "$AUDIT_ATTEMPT_STDERR_FILE")"
  rm -f "$AUDIT_ATTEMPT_STDERR_FILE"
  if [[ -n "$AUDIT_ATTEMPT_ERROR" ]]; then
    printf '%s\n' "$AUDIT_ATTEMPT_ERROR" >&2
  fi
  if (( AUDIT_ATTEMPT_STATUS != 0 )); then
    if printf '%s\n' "$AUDIT_ATTEMPT_ERROR" | grep -Eqi \
      '(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
      echo 'Transient audit projection response; retrying ledger lookup.' >&2
      sleep 2
      continue
    fi
    echo 'audit.listByResource failed while waiting for its audit identifier.' >&2
    if [[ -n "$AUDIT_ATTEMPT_OUTPUT" ]]; then
      printf '%s\n' "$AUDIT_ATTEMPT_OUTPUT" >&2
    fi
    exit 1
  fi
  AUDIT_JSON="$AUDIT_ATTEMPT_OUTPUT"
  if ! jq -e 'type == "object" and (.entries | type == "array")' <<<"$AUDIT_JSON" >/dev/null; then
    echo 'audit.listByResource returned invalid JSON.' >&2
    echo "$AUDIT_JSON" >&2
    exit 1
  fi
  AUDIT_ID=$(jq -r --arg approval_id "$APPROVAL_ID" '
    [
      .entries[]?
      | select(
          (.resourceId == $approval_id) or
          (.resource_id == $approval_id) or
          (.details?.approvalId == $approval_id) or
          (.details?.approval_id == $approval_id)
        )
      | (.id // .eventId // .event_id)
    ]
    | map(select(type == "string" and length > 0))
    | first // empty
  ' <<<"$AUDIT_JSON")
  if [[ -n "$AUDIT_ID" ]]; then
    break
  fi
  sleep 2
done
if [[ -z "$AUDIT_ID" ]]; then
  echo "No materialized audit identifier appeared for approval $APPROVAL_ID within 60 seconds." >&2
  echo "$APPROVAL_DETAIL_JSON" >&2
  echo "$AUDIT_JSON" >&2
  exit 1
fi

RESULT_JSON='{}'
MERKLE_ROOT=''
RESULT_DEADLINE=$((SECONDS + 60))
while (( SECONDS < RESULT_DEADLINE )); do
  RESULT_ATTEMPT_STATUS=0
  RESULT_ATTEMPT_STDERR_FILE="$(mktemp)"
  RESULT_ATTEMPT_OUTPUT=$(kla api executions result \
    --input "{\"id\":\"$EXECUTION_ID\"}" 2>"$RESULT_ATTEMPT_STDERR_FILE") || \
    RESULT_ATTEMPT_STATUS=$?
  RESULT_ATTEMPT_ERROR="$(cat "$RESULT_ATTEMPT_STDERR_FILE")"
  rm -f "$RESULT_ATTEMPT_STDERR_FILE"
  if [[ -n "$RESULT_ATTEMPT_ERROR" ]]; then
    printf '%s\n' "$RESULT_ATTEMPT_ERROR" >&2
  fi
  if (( RESULT_ATTEMPT_STATUS != 0 )); then
    if printf '%s\n' "$RESULT_ATTEMPT_ERROR" | grep -Eqi \
      '(^|[^0-9])(202|429|5[0-9][0-9])([^0-9]|$)|PENDING|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
      echo 'Transient execution result response; retrying evidence materialization.' >&2
      sleep 2
      continue
    fi
    echo 'executions.result failed while waiting for the evidence anchor.' >&2
    if [[ -n "$RESULT_ATTEMPT_OUTPUT" ]]; then
      printf '%s\n' "$RESULT_ATTEMPT_OUTPUT" >&2
    fi
    exit 1
  fi
  RESULT_JSON="$RESULT_ATTEMPT_OUTPUT"
  if ! MERKLE_ROOT=$(jq -r '.evidence.merkleRoot // empty' <<<"$RESULT_JSON"); then
    echo 'executions.result returned invalid JSON.' >&2
    echo "$RESULT_JSON" >&2
    exit 1
  fi
  if [[ -n "$MERKLE_ROOT" ]]; then
    break
  fi
  sleep 2
done
if [[ -z "$MERKLE_ROOT" ]]; then
  echo "The evidence anchor for completed execution $EXECUTION_ID was not materialized within 60 seconds." >&2
  echo "$RESULT_JSON" >&2
  exit 1
fi

TRACE_INPUT=$(jq -cn \
  --arg trace_id "$TRACE_ID" \
  --arg tenant_id "$KLA_TENANT_ID" \
  '{traceId: $trace_id, tenantId: $tenant_id, includeSensitiveData: false}')
kla api traces getTrace --input "$TRACE_INPUT"

PROOF_INPUT=$(jq -cn --arg tenant "$KLA_TENANT_ID" --arg audit "$AUDIT_ID" \
  '{tenant: $tenant, auditId: $audit}')
PROOF_JSON=$(kla api traces verify --input "$PROOF_INPUT")
PROOF_VALID=$(jq -r '.valid // false' <<<"$PROOF_JSON")
PROOF_NONCE=$(jq -r '.nonce // empty' <<<"$PROOF_JSON")
PROOF_TX_ID=$(jq -r '.ledgerAttestation.txId // empty' <<<"$PROOF_JSON")
if [[ "$PROOF_VALID" != 'true' || -z "$PROOF_NONCE" ]]; then
  echo "traces.verify did not validate audit $AUDIT_ID." >&2
  echo "$PROOF_JSON" >&2
  exit 1
fi
echo "$PROOF_JSON"
echo "Trace: $TRACE_ID"
echo "Audit: $AUDIT_ID"
echo "Evidence Merkle root: $MERKLE_ROOT"
echo "Proof nonce: $PROOF_NONCE"
echo "Proof ledger transaction: ${PROOF_TX_ID:-unavailable}"

traces.getTrace lee los spans del trace devuelto y mantiene los datos sensibles enmascarados por defecto. traces.verify genera y comprueba un proof para el identificador de auditoría devuelto. La respuesta incluye una nonce y, cuando está disponible la attestación del ledger, su identificador de transacción. Ambas llamadas necesitan los permisos de trace correspondientes.

Paso 5: Exportar Evidence sellada

La exportación de Evidence de la API es una ruta REST. Exporta por identificador de ejecución, lee el manifiesto y descarga el archivo verificado:

  • Endpoint: POST /v1/evidence/export
EXPORT_JSON=$(curl -sS -X POST "$KLA_API_URL/v1/evidence/export" \
  -H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
  -H "x-kla-tenant-external-id: $KLA_TENANT_ID" \
  -H 'Content-Type: application/json' \
  -d "{\"runId\":\"$EXECUTION_ID\",\"format\":\"json\",\"includeProofs\":true}")
echo "$EXPORT_JSON"
EXPORT_ID=$(jq -r '.exportId' <<<"$EXPORT_JSON")

La respuesta incluye un exportId, un manifiesto y una URL de descarga. La API expone el manifiesto y el archivo mediante estas rutas:

  • Endpoint: GET /v1/evidence/exports/$EXPORT_ID
  • Endpoint: GET /v1/evidence/exports/$EXPORT_ID/download
curl -sS "$KLA_API_URL/v1/evidence/exports/$EXPORT_ID" \
  -H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
  -H "x-kla-tenant-external-id: $KLA_TENANT_ID" | jq

curl -sS -f "$KLA_API_URL/v1/evidence/exports/$EXPORT_ID/download" \
  -H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
  -H "x-kla-tenant-external-id: $KLA_TENANT_ID" \
  -o "./evidence-$EXPORT_ID.zip"

La CLI envuelve las mismas dos rutas:

kla evidence export --days 7 --out ./evidence
kla evidence verify --bundle "./evidence-$EXPORT_ID.zip" --out ./evidence-report

kla evidence verify funciona con el bundle descargado y no realiza peticiones de red. Su informe registra la especificación del bundle, el digest del manifiesto, la raíz de Merkle y el resultado de la verificación.

El flujo completo es: registrar con agents.create, iniciar executions.execute, resolver la solicitud pendiente mediante approvals.getPending y approvals.decide, inspeccionar y verificar con traces.* y exportar mediante /v1/evidence/*.

Gobernar un agente de principio a fin | Developer Docs | KLA Control Plane