Evidence guide

How to log AI agent decisions for SOC 2

SOC 2 auditors evaluate evidence, not the agent label. Each governed action your system took against customer data has a who, a what, a when, an outcome, and a reason. CC6.1 wants the access control evidence. CC7.2 wants the monitoring trail. Both apply to agent tool calls the same way they apply to humans hitting a database. The workflow below creates decision records from the Veto SDK, maps the fields to the two controls auditors lean on, and shows the export commands that produce the record set for your Type II window.

  • Decision records for each governed tool call with 365-day retention.
  • Sensitive field redaction before persistence, with configured fields removed from the stored record.
  • CSV/JSON export into your write-once evidence store.
  • Date-range queries that produce CC6.1 and CC7.2 evidence files in JSONL.

Step 1: Create decision records

The SDK logs governed decisions by default. To turn the retention and redaction policy into something an auditor recognizes, pass an explicit logging block when you construct the client. The retention controls how long records live. The redact_fields list names argument keys that should never be persisted in cleartext.

py
import os
from veto_sdk import Veto

veto = Veto(
    api_key=os.environ["VETO_API_KEY"],
    logging={
        "retention_days": 365,
        "sink": "veto_workspace",
        "redact_fields": ["args.ssn", "args.card_number"],
    },
)

decision = veto.decide(
    tool="export_user_data",
    args={"user_id": "u_42", "format": "csv"},
    agent={"id": "ag_support_001", "role": "support"},
    actor={"user_id": "human_22", "ip": "10.0.1.5"},
    context={
        "request_id": req.id,
        "session_id": req.session,
        "tenant_id": current_tenant_id(),
    },
)

Note the call signature. Each governed decision carries the agent identity, the actor (the human who triggered the agent, when there is one), and a context block with request and tenant identifiers. Those three together answer the auditor question "who did this and on whose behalf."

Step 2: Map the log fields to CC6.1 and CC7.2

CC6.1 is the access-control criterion. It asks whether you can show that only authorized entities touched the protected information. CC7.2 is the monitoring criterion. It asks whether you detected and responded to anomalies. Both want the same five fields per event: actor, action, target, outcome, timestamp. The decision record shape Veto writes has all of them, plus the rule that made the call and the policy version it ran against.

json
{
  "decision_id": "dec_01HXY000",
  "timestamp": "2026-05-12T14:33:11.421Z",
  "tool": "export_user_data",
  "args": { "user_id": "u_42", "format": "csv" },
  "agent": { "id": "ag_support_001", "role": "support" },
  "actor": { "user_id": "human_22", "ip": "10.0.1.5" },
  "context": {
    "request_id": "req_8e2",
    "session_id": "sess_19a",
    "tenant_id": "tn_customer_01"
  },
  "outcome": "allow",
  "rule_matched": "support_can_export_own_tenant",
  "policy_version": "v17",
  "latency_ms": 3,
  "approval_id": null,
  "redacted_fields": []
}

The rule_matched andpolicy_version fields are the bit auditors ask for. They make governed decisions traceable back to the exact policy bundle that was running. Combined with git history on the YAML, you get policy provenance from the same review path.

Step 3: Set up retention and WORM export

Retention without write-once protection is a configuration setting, not a control. To make the logs verifiable, export them as CSV/JSON and copy them to a write-once evidence store such as S3 Object Lock in compliance mode. In that mode, protected object versions cannot be overwritten or deleted by any user, including the AWS account root user, until the retention window expires. Configure the export destination either in the workspace or via the API.

sh
# In workspace: Settings -> Compliance -> Retention
# Or via the API:

curl -X PATCH https://api.veto.so/v1/orgs/current/logging \
  -H "Authorization: Bearer $VETO_API_KEY" \
  -d '{
    "retention_days": 365,
    "export_destination": {
      "type": "s3",
      "bucket": "customer-audit-logs",
      "prefix": "veto/decisions/",
      "kms_key_id": "alias/audit-logs"
    },
    "immutability": "object_lock_compliance"
  }'

Object Lock compliance mode is what auditors want to see. Governance mode lets privileged users shorten the retention; compliance mode does not, even for the root account. Set the KMS key id so records are encrypted at rest with a customer-managed key, which also lands you a clean CC6.7 (transmission integrity) line.

Step 4: Pull evidence for the audit

When the auditor asks for evidence, two date-range queries cover the common requests. The first pulls every denied or approval-gated decision against sensitive tools for the audit window. That is your CC6.1 evidence. The second pulls every approval, who approved it, and how long it took. That is your CC7.2 evidence.

sh
# CC6.1 evidence pull: governed decisions against PHI for the audit window
curl https://api.veto.so/v1/decisions \
  -H "Authorization: Bearer $VETO_API_KEY" \
  -G \
  --data-urlencode "from=2025-11-01T00:00:00Z" \
  --data-urlencode "to=2026-05-01T00:00:00Z" \
  --data-urlencode "tool=export_user_data" \
  --data-urlencode "outcome=deny,require_approval" \
  > evidence_cc61_q2.jsonl

# CC7.2 evidence pull: every approval, who approved, time-to-decision
curl https://api.veto.so/v1/approvals \
  -H "Authorization: Bearer $VETO_API_KEY" \
  -G \
  --data-urlencode "from=2025-11-01T00:00:00Z" \
  --data-urlencode "to=2026-05-01T00:00:00Z" \
  > evidence_cc72_q2.jsonl

Drop both JSONL files into the evidence locker your auditor expects. For a guided run-through of the full control set, see the SOC 2 evidence page.

Failure modes to catch

Logging the raw arguments

If an agent accepts SSNs or card numbers as tool arguments, they will end up in the decision record unless you redact. Use redact_fields with the exact JSONPath of every sensitive argument and check the output before production.

Mutable retention

If an engineer can rotate retention down to seven days from the workspace during an incident, the logs are not write-once. Use Object Lock compliance mode and lock down the workspace setting with an org-admin role.

No policy version on the log

Without policy_version, the auditor cannot trace a decision back to the rule that produced it. The Veto SDK writes it for you. If you build a custom log sink, make sure you preserve the field.

Production checklist

  • retention_days set to the larger of SOC 2 (365) and any other framework in scope.
  • redact_fields covers the sensitive arguments your governed tools can emit.
  • Exported logs land in WORM/Object Lock storage with a customer-managed KMS key.
  • Each governed decision carries rule_matched, policy_version, agent identity, and actor.
  • Evidence pull commands are documented in the runbook and tested before the audit window.

FAQ

Do I need a separate logging system for agent decisions?

Teams keep agent decision records separate from application logs because the retention and access pattern is different. Application logs are operational and rotate on operational schedules. Decision records are evidence review and live for at least a year. Veto keeps governed decision records queryable by retention tier; teams that need WORM-backed retention assurances should export or mirror them into Object Lock or an equivalent write-once evidence store.

What gets redacted before the log is stored?

You name the fields. Common redactions are SSNs, card numbers, passwords, and bearer tokens. The redact_fields option uses a JSONPath-style selector and runs before persistence, so the configured raw fields are redacted before Veto persistence. The decision id remains the same, which keeps traces correlated across the redacted and unredacted views.

How long should I retain decision records?

SOC 2 Type II audits cover a 6- to 12-month window. A 365-day retention covers a Type II in a single bucket. HIPAA's six-year retention rule applies to required policies, procedures, and compliance documentation, not every PHI-touching system log. If decision records are your evidence for HIPAA controls, retain them to that window or the longer period required by state law or internal policy.

Related guides

Export logs auditors can read on the first try.