Guides

Governare un agente dall’inizio alla fine

Registra un agente, gestisci un’approvazione umana, ispeziona la trace ed esporta Evidence sigillata tramite KLA Control Plane.

11 min di lettura2364 parole

Questa guida usa un agente di triage dei reclami che può proporre un rimborso. Il flusso copre la registrazione dell’agente, un’esecuzione governata, l’approvazione umana, l’ispezione della trace e l’esportazione di Evidence.

Gli esempi usano la CLI KLA per le procedure generate del control plane e curl per le route REST Evidence. La CLI legge KLA_ACCESS_TOKEN. Host API e tenant sono espliciti, così gli stessi comandi possono essere eseguiti contro un tenant dev distribuito.

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 l’indicazione di routing x-kla-tenant-id del contesto CLI. Il tenant claim verificato resta autorevole. Una richiesta che seleziona un altro tenant usa x-kla-tenant-external-id; il chiamante deve avere un’appartenenza attiva a quel tenant.

Questo percorso presuppone un tenant con una policy approvata e un workflow distribuito che possa creare un’approvazione in sospeso. Registrazione, binding della policy, approvazione del release, distribuzione ed esecuzione scrivono nel tenant. Usa un tenant sotto il tuo controllo e un secondo principal per l’approvazione 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

Passaggio 1: Registrare l’agente

agents.create è una mutation tRPC. Il comando CLI generato la invia tramite il trasporto tRPC autenticato /v1.

  • Endpoint: POST /v1/agents.create

Crea un manifest di registrazione nel formato accettato dall’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

Registralo:

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

Conserva l’identificatore dell’agente restituito. Un agente appena registrato richiede un binding di policy approvato e distribuito prima che executions.execute possa avviare un’esecuzione governata. Il ciclo di vita CLI è:

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

Il binding sandbox crea o aggiorna la bozza sandbox in sospeso dell’agente e il relativo workflow gestito. Non rende distribuibile un release di produzione. agents.deployVersion richiede che il manifest del release immutabile contenga un binding governedExecution di produzione.

Crea quel manifest esplicito del release di produzione. Un nuovo UUID dà al workflow di produzione gestito una propria identità; la prima versione del workflow è 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")

Il comando release accetta @/tmp/claims-triage.yaml come input strutturato da file. L’API valida UUID, versione del workflow, ID della policy e ambiente di produzione, poi salva il binding del workflow gestito prima di creare il release immutabile.

La credenziale del reviewer deve identificare un principal autenticato diverso. Il server applica il ruolo del tenant e le regole a due persone a ogni comando del ciclo di vita.

Usa lo stesso URL API e lo stesso tenant con un token reviewer di breve durata limitato al comando di approvazione:

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

Dopo l’approvazione del release da parte del reviewer, ripristina la credenziale maker prima di distribuirlo:

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

Passaggio 2: Avviare un’esecuzione governata

Esegui l’agente nella sandbox con un reclamo che dovrebbe raggiungere la policy di approvazione:

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 restituisce un executionId. Un esito di policy require_approval lascia l’esecuzione in attesa di un reviewer. Stato ed eventi dell’esecuzione sono disponibili tramite executions.get e executions.getEvents.

Passaggio 3: Trovare e approvare la richiesta in sospeso

approvals.getPending elenca le Decision Requests in sospeso per il tenant. Il filtro dell’esecuzione restituisce la richiesta di questa esecuzione:

  • 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"

Il reviewer invia approvals.decide con l’identificatore dell’approvazione, una decisione e una motivazione. L’input accetta approve, reject o escalate. Questo esempio usa la seconda credenziale 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"

L’esecuzione riprende secondo il workflow distribuito. Un rifiuto lascia ineseguita l’azione conseguente.

Passaggio 4: Ispezionare e verificare la trace

Il worker può terminare l’esecuzione prima che sia visibile la proiezione del ledger di audit. Leggi la cronologia degli eventi, attendi la voce di audit dell’approvazione e usa gli identificatori restituiti per le chiamate di trace e 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 legge gli span della trace restituita e mantiene i dati sensibili mascherati per impostazione predefinita. traces.verify genera e verifica una proof per l’identificatore di audit restituito. La risposta della proof include un nonce e, quando disponibile, l’identificatore di transazione dell’attestazione del ledger. Entrambe le chiamate richiedono i permessi trace corrispondenti.

Passaggio 5: Esportare Evidence sigillata

L’esportazione Evidence dell’API è una route REST. Esporta tramite l’identificatore dell’esecuzione, poi leggi il manifest e scarica l’archivio verificato:

  • 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 risposta include un exportId, un manifest e un URL di download. L’API espone manifest e archivio tramite queste route:

  • 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 incapsula le stesse due route:

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

kla evidence verify funziona sul bundle scaricato e non effettua richieste di rete. Il report registra la specifica del bundle, il digest del manifest, la radice Merkle e il risultato della verifica.

Il ciclo completo è: registrare con agents.create, avviare executions.execute, risolvere la richiesta in sospeso tramite approvals.getPending e approvals.decide, ispezionare e verificare con traces.*, quindi esportare tramite /v1/evidence/*.

Governare un agente dall’inizio alla fine | Developer Docs | KLA Control Plane