From Agent Action to Audit Evidence

Estimated reading time: 6 minutes

Follow one action from its request to the evidence a reviewer can inspect. Start with a harmless, read-only tool in a test workspace. A successful tool response, a closed action, and a valid evidence bundle answer different questions; this guide keeps those results separate.

Before you start

  • Register a managed MCP server and authorize the calling identity and tool in your workspace. Use the connection guide for initial setup.
  • Have your administrator confirm that protected actions are enabled for your organization. The proof.actions feature is required for the evidence reads below; a successful ordinary call does not establish that this feature is enabled.
  • Obtain your deployment's API base URL and organization ID. Keep credentials in a secret manager or local environment excluded from version control.
  • Use a separate, personal management key (pk_…) for review. It needs audit:read, organization membership, and the user's protected_actions.view permission. Organization, application, and service-account keys cannot read this protected-action evidence.
  • Bundle export additionally requires the organization-owner or compliance-officer role and compliance.view. Request the minimum access appropriate to your role.

The SDK methods below are available in the source version that includes PraesidiaProof / client.proof. Confirm your installed version exposes them. If it does not, use the HTTP example or obtain the matching SDK release from your deployment operator.

1. Execute a controlled action

With an already configured TypeScript PraesidiaGuard, call guard.protectAction({ target: { protocol: 'mcp', mcpServerId, toolName, arguments } }). In Python, use client.agents.protect_action(server_id=..., tool_name=..., arguments=...).

Choose a tool whose arguments and expected effect you understand. These wrappers use Praesidia's managed MCP route. Registered protected HTTP targets have a separate approval and receipt flow; use the matching runtime recipe in the ecosystem catalog. Arbitrary HTTP destinations and native framework tools are not automatically governed.

Retain the returned actionId when present. A structured pre-dispatch denial raises ProtectedActionDeniedError; an HTTP error from an earlier authorization gate raises the SDK's API error instead. Inspect the error and do not retry a denied action automatically. A dispatched tool can also return an error result without raising either exception. If no action ID is returned, confirm the feature and capture path with your administrator before treating the request as evidenced.

The managed route provides Praesidia-observed evidence, at most grade C. It does not establish an independent target's signed acknowledgment or prove an external side effect completed.

2. Inspect the action with your review credential

Set PRAESIDIA_REVIEW_API_KEY, PRAESIDIA_ORG_ID, PRAESIDIA_BASE_URL, and PRAESIDIA_ACTION_ID for the intended deployment and action. Keep the review key separate from the running agent's credentials.

TypeScript

The TypeScript snippets use ES modules and top-level await.

import { PraesidiaProof } from '@praesidia/sdk';

const required = (name: string): string => {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
};

const reviewConfig = {
  apiKey: required('PRAESIDIA_REVIEW_API_KEY'),
  orgId: required('PRAESIDIA_ORG_ID'),
  baseUrl: required('PRAESIDIA_BASE_URL'),
};
const proof = new PraesidiaProof(reviewConfig);
const actionId = required('PRAESIDIA_ACTION_ID');
const action = await proof.get(actionId);
const events = await proof.events(actionId);
const scope = await proof.captureScope();

console.log({
  actionId: action.actionId,
  state: action.state,
  closure: action.closure,
  evidenceGrade: action.evidenceGrade,
  verificationStatus: action.verificationStatus,
  eventCount: events.length,
  captureEdges: scope.length,
});

Python

import os
from praesidia import Praesidia

review = Praesidia(
    api_key=os.environ['PRAESIDIA_REVIEW_API_KEY'],
    org_id=os.environ['PRAESIDIA_ORG_ID'],
    base_url=os.environ['PRAESIDIA_BASE_URL'],
)
action_id = os.environ['PRAESIDIA_ACTION_ID']
action = review.proof.get(action_id)
events = review.proof.events(action_id)
scope = review.proof.capture_scope()
print({
    'actionId': action['actionId'],
    'state': action['state'],
    'closure': action['closure'],
    'evidenceGrade': action['evidenceGrade'],
    'verificationStatus': action['verificationStatus'],
    'eventCount': len(events),
    'captureEdges': len(scope),
})

HTTP

curl --fail-with-body \
  "${PRAESIDIA_BASE_URL%/}/organizations/${PRAESIDIA_ORG_ID}/protected-actions/${PRAESIDIA_ACTION_ID}" \
  -H "Authorization: Bearer ${PRAESIDIA_REVIEW_API_KEY}"

The same action path with /events returns the ordered signed events. Preserve actionSeq as a decimal string and preserve redacted null payloads. Do not write raw event content to shared logs by default.

Under the same /organizations/{orgId}/protected-actions base path, /capture-scope describes supported, partial, and unsupported capture paths, and /coverage-summary returns aggregate recorded-action counts. Neither endpoint counts every action occurring outside the configured capture paths.

3. Export the signed bundle

Choose explicit UTC timestamps that cover your test, with an end later than the start and a range no greater than 90 days. Set PRAESIDIA_EVIDENCE_FROM and PRAESIDIA_EVIDENCE_TO accordingly. The export covers the organization's selected time window and may contain other actions; handle it as sensitive evidence.

Continue with the review configuration from your language's example above. Both examples create a new file and refuse to overwrite an existing export.

TypeScript

import { writeFile } from 'node:fs/promises';
import { PraesidiaAudit } from '@praesidia/sdk';

const audit = new PraesidiaAudit(reviewConfig);
const bundle = await audit.exportBundle({
  from: required('PRAESIDIA_EVIDENCE_FROM'),
  to: required('PRAESIDIA_EVIDENCE_TO'),
});
await writeFile('praesidia-evidence.zip', bundle, { flag: 'wx' });

Python

from pathlib import Path

# Continue with the authorized review client above.
bundle = review.audit.export_bundle(
    from_date=os.environ['PRAESIDIA_EVIDENCE_FROM'],
    to_date=os.environ['PRAESIDIA_EVIDENCE_TO'],
)
destination = Path('praesidia-evidence.zip')
with destination.open('xb') as output:
    output.write(bundle)

These methods return ZIP bytes, unlike the ordinary JSON or CSV audit-log export. SDK downloads are bounded to 128 MiB. The verifier additionally limits the ZIP itself to 72 MiB and applies limits to expanded contents; reduce the date window if an export exceeds either set of limits. You can also export through Trust & Safety → Forensics with the required access.

4. Verify independently

Obtain the verifier distribution and the deployment's platform public key through an authenticated channel independent of the bundle. With Node.js 22.12 or later, run the following from the directory of an already built audit-verifier source checkout:

node dist/cli.js /absolute/path/praesidia-evidence.zip \
  --platform-key /absolute/path/trusted-platform-public-key.pem \
  --json

Do not accept a key merely because it is packaged with the evidence. Review the complete component results and capture scope as well as the overall status. An unsupported component means that proof was not available for that check; an overall valid legacy bundle need not establish protected-action continuity.

Result Meaning and next action
valid / exit 0 The verifier accepted the applicable cryptographic checks. Inspect unsupported components and scope before deciding what the evidence establishes.
invalid / exit 1 At least one required check failed. Preserve the report and investigate; do not downgrade the check to obtain a green result.
Format or I/O error / exit 2 Confirm the ZIP, path, verifier version, and file integrity before retrying.
incomplete / exit 3 Evidence is insufficient for a full verdict, for example when a relevant payload was legitimately redacted. Record the gap.

Fresh events may not yet have Merkle inclusion evidence. Obtain a later export after the deployment has produced the required proofs. Disabling a verifier component reduces what is checked; it does not repair missing evidence.

5. Test the control, then expand coverage

Run an allowed test and a deliberately denied test, then exercise revocation or freeze on a disposable workflow. Confirm the expected response, decision record, action timeline, and evidence limits. Keep unresolved or unknown outcomes visible.

For an agent-specific starting configuration, review the OWASP Agentic — Security Starter governance pack when it is available in your deployment. It creates starter controls and ten unassessed risk-register entries. Configure the evaluator and enforcement paths, tune the tool rules, and attach test evidence before lowering any risk assessment.

Continue with model routing and usage, guardrails, and the workspace starting paths.