Course 4 built a custom audit log that answers “which ability did which user call, and did it succeed.” That’s exactly the right log for debugging and security review. A compliance review asks a related but different set of questions: what personal data, if any, left our systems, which external AI provider received it, under what stated purpose, and when. Nothing built into the MCP Adapter or the PHP AI Client SDK answers those questions for you, same as Course 4 found for the debugging case. This lesson extends the same pattern, deliberately, to cover them.
Course 4’s “Building Your Own Audit Log for Agent Actions” lesson. This lesson extends that table and wrapper function rather than starting over, read that lesson first if you haven’t already.
Step 1: What a compliance reviewer actually asks that a debug log doesn’t answer
Course 4’s audit table stores ability, user_id, input, error, duration_ms, and
a timestamp, exactly right for “did this call succeed, and how long did it take.” A
compliance reviewer, asked to demonstrate what happened when a customer requested to
know what data an AI feature processed about them, needs a different, overlapping set of
answers.
| Question | Course 4’s columns | What’s missing |
|---|---|---|
| Which action ran, for which user, did it succeed? | ability, user_id, error, duration_ms | Nothing, this is already covered. |
| Which external AI provider received data, if any? | Not captured | A provider column. |
| Why was this data sent, what was the stated purpose? | Not captured | A purpose column. |
| Did the data sent include a personal-data category? | Not captured | A data_category flag. |
Step 2: Extend the existing table, don’t build a second one
Add the compliance-specific columns to the same table Course 4 created, rather than maintaining two separate logs that can drift out of sync with each other.
// File: my-plugin.php
function my_plugin_extend_audit_table_for_compliance() {
global $wpdb;
$table = $wpdb->prefix . 'ai_agent_audit_log';
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
// dbDelta() can add new nullable columns to an existing table safely.
dbDelta( "CREATE TABLE {$table} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ability VARCHAR(191) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
input LONGTEXT NULL,
error TEXT NULL,
duration_ms FLOAT NULL,
provider VARCHAR(64) NULL,
purpose VARCHAR(191) NULL,
data_category VARCHAR(64) NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY ability (ability),
KEY user_id (user_id),
KEY provider (provider)
) {$wpdb->get_charset_collate()};" );
}
register_activation_hook( __FILE__, 'my_plugin_extend_audit_table_for_compliance' );
Step 3: Populate the new fields wherever a prompt is constructed
Course 4’s wrapper logged ability calls. Compliance also needs the direction Lesson 5 covered: WordPress code calling an AI model directly through the PHP AI Client SDK. Wrap that call site the same way, with the new fields filled in deliberately, not inferred:
// File: my-plugin.php
function my_plugin_log_ai_call( array $entry ) {
global $wpdb;
$wpdb->insert( $wpdb->prefix . 'ai_agent_audit_log', array(
'ability' => $entry['ability'] ?? 'direct_ai_call',
'user_id' => get_current_user_id(),
'input' => $entry['input'], // Already redacted, per Lesson 5.
'provider' => $entry['provider'],
'purpose' => $entry['purpose'],
'data_category' => $entry['data_category'],
'created_at' => current_time( 'mysql' ),
) );
}
function my_plugin_summarize_ticket( int $ticket_id ) {
$ticket = my_plugin_get_ticket( $ticket_id );
$safe_body = my_plugin_redact_pii( $ticket->subject . "\n" . $ticket->body ); // Lesson 5.
my_plugin_log_ai_call( array(
'input' => $safe_body,
'provider' => 'anthropic',
'purpose' => 'support_ticket_summarization',
'data_category' => 'support_content',
) );
return ai_client()->using_provider( 'anthropic' )->generate_text(
"Summarize this support ticket in two sentences:\n\n" . $safe_body
);
}
The purpose field is worth taking seriously, not filling with a generic value. “Why
was this data sent” is exactly the question a compliance reviewer, or a user exercising a
data-access request, will ask, and a specific, consistent purpose string per feature
answers it directly.
The same rule from Course 4 applies here with more weight: this table now explicitly documents what left your system for compliance purposes. Storing an unredacted version defeats the entire point, and creates a second place unredacted PII can leak from.
Step 4: Treat the log itself as sensitive
A table recording what personal data was sent, to which provider, and why, is itself a record of personal data processing, which makes the log a thing worth protecting and governing, not just a technical artifact.
Test it: answer a compliance question from the log
wp-env run cli wp eval '
global $wpdb;
$rows = $wpdb->get_results( $wpdb->prepare(
"SELECT ability, provider, purpose, data_category, created_at FROM {$wpdb->prefix}ai_agent_audit_log WHERE user_id = %d ORDER BY id DESC LIMIT 20",
123
) );
print_r( $rows );
'
If you can run this and get a clear, honest answer to “what AI processing happened involving this user, with which provider, and why,” the log is doing its compliance job, not just its debugging one.
Recap
A debugging-oriented audit log and a compliance-oriented one overlap heavily but aren’t
identical. Extending Course 4’s existing table with provider, purpose, and
data_category columns, populated deliberately at every point a prompt is constructed
(both ability calls and direct PHP AI Client SDK calls), turns one audit trail into
something that answers both kinds of question. Treat the resulting table as sensitive in
its own right: restrict access, define retention, and make sure someone could actually
query it to answer a real compliance question if asked.
Resources & further reading
- MCP Adapter repository, observability guide, GitHub
- $wpdb class reference, developer.wordpress.org
- WordPress MCP Security & Authentication, “Building Your Own Audit Log for Agent Actions”, Course 4