Compliance

AI Agent Decision Records: SOC 2 and GDPR

Map agent decision records to SOC 2, GDPR, retention, reviewer workflows, and audit-ready evidence.

Kyrie KirkJanuary 24, 202617 min
  • AI agent decision records need tool-call decision records, not just API access logs or model transcripts.
  • SOC 2, GDPR, and enterprise security reviews care about actor, tenant, tool, arguments, policy version, outcome, and approver.
  • Runtime authorization turns each sensitive tool call into evidence that controls operated before execution.

Your SOC 2 auditor asks: "Show me each governed action your AI agent took on customer data last quarter." Your GDPR data subject asks: "Explain the automated decision that denied my loan application." Your CISO asks: "Which agent accessed production credentials at 2 AM on Tuesday?" If you cannot answer these questions from structured, queryable records, you have an audit gap. And audit gaps become regulatory findings, fines, and front-page incidents.

What SOC 2 asks you to show

SOC 2 is built on five Trust Services Criteria: Security, Availability, Processing Integrity, Confidentiality, and Privacy. For AI agent decision records, five specific Common Criteria controls are relevant:

  • CC6.1: Logical and Physical Access Controls : Requires deploying identity and access management controls including unique user IDs, multi-factor authentication for administrative functions, and role-based access control. For AI agents, this means each governed tool call must be traceable to a specific user session, agent identity, and authorization policy.
  • CC6.2: User Account Management : Requires secure creation, management, and deletion of accounts before, during, and after data access. For agents, this maps to lifecycle tracking: when an agent was created, what credentials it holds, and what happens when those credentials are revoked.
  • CC6.3: Access Authorization and Revocation : Requires managing data access based on users' responsibilities with least privilege and segmentation. This directly applies to per-tool, per-tenant agent policies. Your auditor wants to see: what was the agent authorized to do, and was that authorization appropriate for the task?
  • CC7.2: Monitoring System Components : Requires implementing infrastructure monitoring for all in-scope systems. For agents, this means continuous decision records for governed tool calls, outcomes, and execution results. Not only errors: governed decisions and outcomes.
  • CC7.3: Security Event Analysis : Requires analyzing identified security events to determine and respond to compromises. Your AI agent's denied tool calls, anomalous access patterns, and approval timeout events are security-relevant events that should be aggregated, analyzed, and retained.

What GDPR Requires

GDPR Article 22 grants EU data subjects the right not to be subject to decisions "based solely on automated processing" that produce legal effects. Articles 13-15 require providing "meaningful information about the logic involved, as well as the significance and the envisaged consequences of such processing."

For AI agents, this means:

  • Decision explainability: Every automated decision must be traceable to the policy that governed it, the data that informed it, and the logic that produced it.
  • Human intervention pathway: Data subjects must be able to request human review of automated decisions. Your system must record when human review was requested, who reviewed it, and what the outcome was.
  • Data subject access: When a data subject requests their data under Article 15, you must be able to produce governed decisions your agent made about them, with decision context.

The shape of a decision record

A decision record is not just "tool X was called at time Y." It must capture the decision record: who triggered the action, what the agent was trying to do, what policy governed the decision, what the outcome was, and (if applicable) who reviewed it.

complete_decision_record.json
{
  "record_id": "aud_7f8g9h0i1j2k",
  "timestamp": "2026-04-04T09:15:33.721Z",
  "event_type": "tool_call_decision",

  "identity": {
    "agent_id": "agent_customer_ops_v3",
    "session_id": "sess_abc123def456",
    "triggered_by": {
      "user_id": "user_789",
      "email": "j.smith@approved.example",
      "role": "support_l2",
      "ip_address": "198.51.100.42"
    },
    "tenant_id": "tenant_regulated_01",
    "organization_id": "org_tenant_customer"
  },

  "tool_call": {
    "tool": "query_customer_data",
    "arguments": {
      "customer_id": "cust_456",
      "fields": ["name", "email", "order_history"],
      "date_range": "last_90_days"
    },
    "argument_hash": "sha256:a1b2c3d40000"
  },

  "policy_evaluation": {
    "policy_name": "customer-support-agent-v2.1",
    "policy_version": "2.1",
    "rule_matched": "rule_3_customer_data_access",
    "conditions_evaluated": [
      {"condition": "role == support_l2", "result": true},
      {"condition": "fields subset of allowed_fields", "result": true},
      {"condition": "customer belongs to tenant", "result": true}
    ],
    "decision": "allow",
    "reason": "All conditions satisfied for L2 support data access"
  },

  "execution": {
    "started_at": "2026-04-04T09:15:33.891Z",
    "completed_at": "2026-04-04T09:15:34.203Z",
    "duration_ms": 312,
    "success": true,
    "result_hash": "sha256:e5f6g7h80000"
  },

  "compliance_metadata": {
    "gdpr_relevant": true,
    "data_subject_id": "cust_456",
    "legal_basis": "legitimate_interest",
    "retention_policy": "3years",
    "exportable": true
  }
}

Mapping Veto to SOC 2 Controls

Here is how Veto decision records map to each relevant SOC 2 control, with the specific evidence your auditor expects:

soc2_control_mapping.txt
┌──────────┬─────────────────────────────┬──────────────────────────────────────┐
│ Control  │ Requirement  │ Veto Evidence  │
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC6.1  │ Identity + access mgmt  │ Each governed decision record includes:  │
│  │  │ user_id, agent_id, role, tenant_id,  │
│  │  │ session_id, IP address.  │
│  │  │ Export: /api/v1/logs?filter=identity │
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC6.2  │ Account lifecycle  │ Agent creation/revocation events  │
│  │  │ logged. API key rotation tracked.  │
│  │  │ Export: /api/v1/logs?filter=lifecycle│
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC6.3  │ Least privilege and RBAC  │ Policy definitions (YAML) show  │
│  │  │ per-role, per-tool authorization.  │
│  │  │ Denial records show enforcement.  │
│  │  │ Export: /api/v1/policies + /logs  │
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC7.2  │ Continuous monitoring  │ Configured protect() calls can be logged.  │
│  │  │ Decision view shows volume,  │
│  │  │ denial rate, approval queue depth.  │
│  │  │ Export: /api/v1/logs (full stream)  │
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC7.3  │ Security event analysis  │ Denied calls, anomalies, timeout  │
│  │  │ events flagged and aggregated.  │
│  │  │ Alert rules configurable per tool.  │
│  │  │ Export: /api/v1/logs?filter=security │
├──────────┼─────────────────────────────┼──────────────────────────────────────┤
│ CC8.1  │ Change authorization  │ Policy version history tracked.  │
│  │  │ Every policy change recorded with  │
│  │  │ author, diff, and effective date.  │
│  │  │ Export: /api/v1/policies/history  │
└──────────┴─────────────────────────────┴──────────────────────────────────────┘

Mapping Veto to GDPR Requirements

gdpr_mapping.txt
┌─────────────────┬──────────────────────────────┬──────────────────────────────┐
│ GDPR Article  │ Requirement  │ Veto Evidence  │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 13-15  │ Meaningful info about logic  │ policy_evaluation block in  │
│ (Right to info) │ involved in automated  │ each log entry shows: rule  │
│  │ decision-making  │ matched, conditions, reason. │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 22(1)  │ Right not to be subject to  │ require_approval action  │
│ (Automated  │ solely automated decisions  │ ensures human involvement.  │
│  decisions)  │ with legal effects  │ Approval logs document it.  │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 22(3)  │ Right to human intervention,  │ Approval workflow with  │
│ (Safeguards)  │ express point of view,  │ reviewer identity, decision, │
│  │ contest decision  │ and reasoning recorded.  │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 15(1)(h)  │ Data subject access to  │ Per-subject log export:  │
│ (Access right)  │ automated decision info  │ /api/v1/logs?subject_id=X  │
│  │  │ returns all governed decisions about  │
│  │  │ that individual.  │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 17  │ Right to erasure  │ Audit logs are retained for  │
│ (Right to be  │  │ compliance but subject data  │
│  forgotten)  │  │ can be anonymized on request. │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Art. 30  │ Records of processing  │ Full log export supports    │
│ (Processing  │ activities  │ processing activity records  │
│  records)  │  │ for agent-based processing.  │
└─────────────────┴──────────────────────────────┴──────────────────────────────┘

Implementation: Structured Logging with Veto

Veto records governed decisions on each protect() call. You do not need to build a separate logging pipeline. But you can also forward Veto's logs to your existing SIEM for centralized analysis:

audit_pipeline.py
from veto import Veto
import structlog
from datetime import datetime, timedelta

veto = Veto(api_key=os.environ["VETO_API_KEY"], project="production-agent")
logger = structlog.get_logger()

# Forward governed decisions to your logging infrastructure
@veto.on_decision
def forward_to_siem(decision):
    logger.info(
        "agent_decision",
        record_id=decision.id,
        tool=decision.tool,
        action=decision.action,
        reason=decision.reason,
        user_id=decision.context.get("user_id"),
        tenant_id=decision.context.get("tenant_id"),
        agent_id=decision.context.get("agent_id"),
        policy_name=decision.policy.name,
        policy_version=decision.policy.version,
        rule_matched=decision.policy.rule_index,
        timestamp=decision.timestamp.isoformat(),
        duration_ms=decision.duration_ms,
    )

# Query logs for SOC 2 auditor: all denied actions in the review window
def generate_soc2_denial_report(quarter_start: datetime):
    quarter_end = quarter_start + timedelta(days=90)
    denied = veto.logs.query(
        action="deny",
        since=quarter_start,
        until=quarter_end,
        format="csv",
    )
    return denied

# Query logs for GDPR data subject access request
def handle_dsar(subject_id: str):
    """Article 15: produce all governed decisions made about this individual."""
    records = veto.logs.query(
        subject_id=subject_id,
        include_policy_details=True,
        include_execution_results=True,
        format="json",
    )
    return records

# Query logs for incident investigation
def investigate_incident(start: datetime, end: datetime, agent_id: str):
    """CC7.3: analyze security events for a specific agent."""
    events = veto.logs.query(
        agent_id=agent_id,
        since=start,
        until=end,
        include_session_context=True,
    )
    anomalies = [e for e in events if e.get("anomaly_score", 0) > 0.8]
    denials = [e for e in events if e["action"] == "deny"]
    return {
        "total_events": len(events),
        "anomalies": anomalies,
        "denials": denials,
    }

Retention Policies

Different frameworks require different retention periods. Veto lets you configure retention per log category:

retention_config.yaml
retention:
  # SOC 2: retain for audit window + buffer
  decisions:
    duration: 3years
    reason: "SOC 2 CC7.2: monitoring records"

  # GDPR: retain until no longer necessary for processing purpose
  # but keep anonymized records for evidence review
  gdpr_relevant:
    duration: 6years
    anonymize_after: 3years
    reason: "GDPR Art. 5(1)(e): storage limitation"

  # Financial: SOX/PCI require extended retention
  financial_decisions:
    duration: 7years
    reason: "SOX Section 802: record retention"

  # Security events: retain for investigation capability
  security_events:
    duration: 1year
    reason: "CC7.3: security event analysis"

  # Policy change history: retain indefinitely
  policy_changes:
    duration: indefinite
    reason: "CC8.1: change management evidence"

export:
  format: jsonl
  encryption: aes-256-gcm
  destinations:
    - type: s3
      bucket: "audit-logs-production"
      prefix: "veto/"
    - type: siem
      endpoint: "https://siem.internal/api/v1/ingest"
      api_key_env: "SIEM_API_KEY"

Log Integrity: Verification Evidence

A decision record that can be modified after the fact is weak evidence. Reviewers need assurance that logs have not been tampered with. Use append-only storage, content hashes, and write-once export so tampering becomes detectable instead of silently rewriting history.

For additional assurance, export CSV/JSON records and load them into WORM/Object Lock storage (S3 with Object Lock, Azure Immutable Blob Storage, or a dedicated write-once archive).

The EU AI Act Overlay

If your agent is classified as high-risk under the EU AI Act (see our EU AI Act guide), Article 12 requires "automatic recording of events (logs) over the lifetime of the system." Article 26(6) requires deployers to retain these logs for "a period of at least six months." Hosted retention is tiered; configure retention to meet or exceed that minimum for high-risk systems and align with your specific regulatory requirements.

What your auditor asks for

In AI-agent SOC 2 readiness work, these are the five questions reviewers usually ask, and the evidence Veto is designed to provide:

  1. "What access controls exist for the AI system?" : Policy YAML files showing per-tool, per-role authorization rules. Denial records showing enforcement.
  2. "How do you monitor the AI system's behavior?" : Decision view showing decision volume, denial rates, and approval queue depth. Alert configuration for anomalous patterns.
  3. "Can you show me a specific decision record?" : Full decision record with identity, tool call, policy evaluation, execution result, and (if applicable) human approval. Queryable by session, user, agent, or time range.
  4. "How are changes to the AI system authorized?" : Policy version history with author, diff, and effective date. Every policy change is itself an auditable event.
  5. "What is your record retention and integrity assurance?" : Configurable retention per category. Content-hash integrity where configured. Export to WORM/Object Lock storage when required. Encryption at rest and in transit.

First governed call

Veto generates decision records from the same authorization call. There is no separate logging SDK to integrate for decision capture. Every protect() call produces a decision record. Configure retention and export in the workspace, and your audit trail becomes inspectable evidence instead of application exhaust.

Sign up and produce a decision record your reviewer can inspect, or read about human review patterns for approval workflows that support GDPR Article 22 review.

FAQ

What should an AI agent decision record include?

At minimum: actor, tenant, tool name, arguments, resource, policy version, decision, reason, timestamp, approver when required, and resulting execution status. For regulated actions, keep tamper-evident records tied to change management and incident review.

Why are API logs insufficient for AI agent governance?

API logs show that a request happened. They usually do not show why the agent attempted it, which policy allowed it, whether approval was required, or who reviewed it. Runtime authorization logs answer those governance questions.

How does Veto support SOC 2 and GDPR evidence for agents?

Veto records policy decisions before tool execution and links them to users, tenants, tools, arguments, approvals, and policy versions. Those records support access control evidence, monitoring, incident response, and automated-decision reviews.

Related posts

Sign up