How Audit Logging Tracks Changes in Agent-Native: A Complete Implementation Guide

Agent-Native records every mutation performed by human users or AI agents in an append-only SQL table called agent_audit_log, enforcing strict tenant isolation so that only authorized users within the same organization can access audit records.

The BuilderIO/agent-native framework provides comprehensive audit logging capabilities that capture every mutating operation in your application. The system stores these events in a dedicated SQL table with built-in privacy controls, ensuring that sensitive change histories remain accessible only to authorized users. This implementation creates an immutable trail of who changed what and when, while maintaining strict data isolation between tenants.

Core Storage Architecture

The agent_audit_log Table Schema

In packages/core/src/audit/store.ts, the ensureAuditTables() function creates the agent_audit_log table with optimized indexes for owner, organization, target, turn, actor, and created time fields. This structure supports efficient querying while maintaining the append-only nature of the audit trail.

Row Structure and Data Fields

Each audit row captures comprehensive metadata including identifiers (id, action, caller), actor information (kind, email), organizational context (org ID, thread/turn IDs), target details (type, id), execution status, and visibility settings. The schema stores a redacted input payload and visibility levels (private or org) to control access granularity.

Source

Writing Audit Events

Whenever the framework executes a mutating operation, it invokes insertAuditEvent(event) from packages/core/src/audit/store.ts. This helper constructs a database row from an AuditEvent object and appends it to the table, creating an immutable record of the change.

The implementation handles both human-initiated actions and automated AI agent operations, capturing the caller type and actor details for complete traceability.

Source

Reading Audit Events with Tenant Isolation

The system exposes audit data through two high-level actions that enforce strict read-scoping through the caller's userEmail and orgId in the action context (ctx).

list-audit-events: Located in packages/core/src/audit/actions/list-audit-events.ts, this action returns paginated results filtered by the caller's permissions. It supports filtering by target, actor kind, status, thread/turn, action name, and time range, but only returns rows where the user is the owner or the event is marked as organization-visible.

get-audit-event: Implemented in packages/core/src/audit/actions/get-audit-event.ts, this retrieves a single event—including the redacted input payload—if the caller has access rights.

Both actions utilize the scopeClause() function in store.ts to construct SQL WHERE clauses that enforce the same-tenant rule, ensuring audit data never leaks across organizational boundaries.

Source

Lifecycle Management and Data Retention

For long-term maintenance, the system provides deleteOldAuditEvents(cutoffMs) in packages/core/src/audit/store.ts. This utility enables background jobs to prune obsolete audit records based on configurable retention policies, helping manage database growth while preserving recent compliance data.

Source

Implementation Examples

Client-side developers can query audit trails using the framework's action hooks:

// List recent audit events for the current user
import { useActionQuery } from '@agent-native/core/client';

const { data, isLoading } = useActionQuery('list-audit-events', {
  limit: 50,
  sinceMs: Date.now() - 7 * 24 * 60 * 60 * 1000, // last week
});

To retrieve full details of a specific change:

// Fetch the full payload of a single event (e.g., to show a diff)
import { useActionQuery } from '@agent-native/core/client';

const { data: event } = useActionQuery('get-audit-event', {
  id: 'audit-12345',
});

Server-side manual insertion (typically handled automatically by the framework):

import { insertAuditEvent } from '@agent-native/core/audit/store';

await insertAuditEvent({
  id: crypto.randomUUID(),
  createdAt: Date.now(),
  action: 'update-record',
  caller: 'agent',
  actorKind: 'agent',
  actorEmail: null,
  orgId: ctx.orgId,
  threadId: ctx.threadId,
  turnId: ctx.turnId,
  targetType: 'record',
  targetId: recordId,
  status: 'success',
  summary: 'Updated record fields',
  input: JSON.stringify({ changes: {...} }),
  errorCode: null,
  ownerEmail: ctx.userEmail,
  visibility: 'private',
});

Summary

  • Immutable storage: All mutations append to the agent_audit_log table with comprehensive metadata and redacted inputs.
  • Automatic capture: The framework calls insertAuditEvent() during mutating operations, recording both human and AI agent actions.
  • Strict isolation: The scopeClause() function enforces tenant boundaries in packages/core/src/audit/store.ts, ensuring users only see audit events from their organization or private scope.
  • Flexible querying: list-audit-events and get-audit-event actions provide paginated access with filtering capabilities.
  • Retention control: Background jobs can prune old records using deleteOldAuditEvents() to manage database size.

Frequently Asked Questions

How does Agent-Native prevent audit log data from leaking between organizations?

The system implements row-level security through the scopeClause() function in packages/core/src/audit/store.ts. This generates SQL WHERE clauses that filter results based on the caller's userEmail and orgId, ensuring queries only return rows where the user is the owner or the event visibility is set to org within the same organization. This tenant isolation is enforced at the database query level, not just application logic.

What information is captured in each audit log entry?

Each row stores the action name, caller type (human or agent), actor email and kind, organization ID, thread and turn IDs for conversation context, target type and ID, execution status, a redacted input payload, timestamps, and visibility settings. This comprehensive metadata allows you to reconstruct exactly who changed what resource and when the change occurred.

Can I manually insert audit events for custom operations?

Yes, although the framework automatically logs most mutations, you can manually insert events by importing insertAuditEvent from @agent-native/core/audit/store. You must provide a complete AuditEvent object including unique ID, timestamps, action details, actor information, and visibility settings. This is useful for tracking changes in custom business logic that falls outside standard framework operations.

How do I query audit logs for a specific time period or target?

Use the list-audit-events action with filtering parameters. The action accepts filters for target type/ID, actor kind, status, thread/turn IDs, action name, and time ranges (sinceMs). The client-side useActionQuery hook provides a React-friendly interface for these queries, automatically handling pagination and permission scoping based on the current user's context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →