Guides

Gouverner un agent de bout en bout

Enregistrer un agent, traiter une approbation humaine, inspecter sa trace et exporter des preuves scellées via KLA Control Plane.

11 min de lecture2419 mots

Ce guide utilise un agent de triage des demandes capable de recommander un remboursement. Le parcours couvre l'enregistrement de l'agent, une exécution gouvernée, l'approbation humaine, l'inspection de la trace et l'export Evidence.

Les exemples utilisent la CLI KLA pour les procédures générées du control plane et curl pour les routes REST Evidence. La CLI lit KLA_ACCESS_TOKEN. L'hôte API et le tenant sont explicites afin que les mêmes commandes puissent cibler un tenant dev déployé.

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 configure l'indication de routage x-kla-tenant-id du contexte CLI. Le claim de tenant vérifié reste l'autorité. Une requête qui sélectionne un autre tenant utilise x-kla-tenant-external-id ; l'appelant doit y posséder une adhésion active.

Ce parcours suppose un tenant doté d'une politique approuvée et d'un workflow déployé pouvant créer une approbation en attente. L'enregistrement, la liaison de politique, l'approbation du release, le déploiement et l'exécution écrivent dans le tenant. Utilisez un tenant sous votre contrôle et un second principal pour l'approbation 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

Étape 1 : Enregistrer l'agent

agents.create est une mutation tRPC. La commande CLI générée l'envoie via le transport tRPC authentifié /v1.

  • Point de terminaison: POST /v1/agents.create

Créez un manifeste d'enregistrement au format accepté par l'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

Enregistrez-le :

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

Conservez l'identifiant d'agent renvoyé. Un agent nouvellement enregistré a besoin d'une liaison de politique approuvée et déployée avant qu'executions.execute puisse démarrer une exécution gouvernée. Le cycle de vie CLI est le suivant :

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

La liaison sandbox crée ou met à jour le brouillon sandbox en attente de l'agent et son workflow géré. Elle ne rend pas un release de production déployable. agents.deployVersion exige que le manifeste de release immuable porte une liaison governedExecution de production.

Créez ce manifeste explicite de release de production. Un nouvel UUID donne sa propre identité au workflow de production géré ; la première version du workflow est 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")

La commande de release accepte @/tmp/claims-triage.yaml comme entrée de fichier structurée. L'API valide l'UUID, la version du workflow, l'identifiant de politique et l'environnement de production, puis persiste la liaison du workflow géré avant de créer le release immuable.

La credential du reviewer doit identifier un autre principal authentifié. Le serveur applique le rôle du tenant et les règles à deux personnes à chaque commande du cycle de vie.

Utilisez la même URL API et le même tenant avec un jeton reviewer de courte durée limité à la commande d'approbation :

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

Après l'approbation du release par le reviewer, rétablissez la credential maker avant de le déployer :

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

Étape 2 : Démarrer une exécution gouvernée

Exécutez l'agent dans la sandbox avec une demande qui doit atteindre la politique d'approbation :

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 renvoie un executionId. Le résultat de politique require_approval laisse l'exécution en attente d'un reviewer. Le statut et l'historique des événements sont disponibles via executions.get et executions.getEvents.

Étape 3 : Trouver et approuver la demande en attente

approvals.getPending liste les Decision Requests en attente du tenant. Le filtre d'exécution renvoie la demande de cette exécution :

  • Point de terminaison: 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"

Le reviewer envoie approvals.decide avec l'identifiant d'approbation, une décision et un motif. L'entrée accepte approve, reject ou escalate. Cet exemple utilise la seconde credential reviewer :

  • Point de terminaison: 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"

L'exécution reprend selon le workflow déployé. Un rejet laisse l'action conséquente inexécutée.

Étape 4 : Inspecter et vérifier la trace

Le worker peut terminer l'exécution avant que la projection du registre d'audit soit visible. Lisez l'historique des événements, attendez l'entrée d'audit de l'approbation et utilisez les identifiants renvoyés pour les appels de trace et de preuve.

  • Point de terminaison: GET /v1/traces.getTrace
  • Point de terminaison: GET /v1/traces.verify
  • Point de terminaison: 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 lit les spans de la trace renvoyée et masque les données sensibles par défaut. traces.verify génère et vérifie une preuve pour l'identifiant d'audit renvoyé. La réponse de preuve contient un nonce et, lorsque l'attestation du registre est disponible, son identifiant de transaction. Les deux appels exigent les permissions de trace correspondantes.

Étape 5 : Exporter les preuves scellées

L'export Evidence de l'API est une route REST. Exportez par identifiant d'exécution, puis lisez le manifeste et téléchargez l'archive vérifiée :

  • Point de terminaison: 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 réponse contient un exportId, un manifeste et une URL de téléchargement. L'API expose le manifeste et l'archive via ces routes :

  • Point de terminaison: GET /v1/evidence/exports/$EXPORT_ID
  • Point de terminaison: 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 enveloppe ces deux mêmes routes :

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

kla evidence verify fonctionne sur le bundle téléchargé et n'effectue aucune requête réseau. Son rapport consigne la spécification du bundle, le digest du manifeste, la racine de Merkle et le résultat de la vérification.

Le parcours terminé est le suivant : enregistrer avec agents.create, démarrer executions.execute, résoudre la demande en attente via approvals.getPending et approvals.decide, inspecter et vérifier avec traces.*, puis exporter via /v1/evidence/*.

Gouverner un agent de bout en bout | Developer Docs | KLA Control Plane