Skip to main content
Wildo.ai Coming soon

Audit trail

Know who acted, even when no person was there

Every audit record says whose authority was used, a person, a person through an outside tool, a machine, the application itself or nobody identifiable, and names the AI agent when one did the work.

An event retains attribution to a person, machine or system, with an explicit unattributed case.

Know who acted, even when no person was there

Wildo distinguishes a person, a machine, the application itself and an action whose actor could not be resolved. Automated work stays recognizable in the same trail as human activity.

That distinction helps an investigator separate expected background work from an action that deserves a closer look.

When an AI agent does the work, the record says so without pretending the agent is a principal. The authority is still the person’s, the outside tool’s or the machine’s, and the record separately names the agent, its conversation, the tool it called and, for an approved action, the decision that approved it.

Example — Identify who acted, including automated work

A scheduled cleanup removes an expired invitation. Its actor is the system. An invitation declined through a person’s authorized action can retain that person’s attribution, even when an internal service completes the work.

A member asks the assistant to create a task. The audit row names the member as a signed-in user and names the assistant, the conversation and the create tool as the agent that performed it.

For engineers

Resource lifecycle emitters use resolveAuditActorId. They pass the originating execution context instead of inventing a user for background work. Explicit on-behalf-of attribution has priority, then a signed-in user, then a machine or system sentinel.

Selected source from audit-actor.backend.utils.ts:

export function resolveAuditActorId(executionContext: ExecutionContext<any> | undefined): string {
  const onBehalfOfUserId = executionContext?.auditActorUserId;
  if (typeof onBehalfOfUserId === 'string' && onBehalfOfUserId.length > 0) {
    return onBehalfOfUserId;
  }

  const initiatorIds = executionContext?.initiatorIds;

  const userId = initiatorIds?.userId;
  if (typeof userId === 'string' && userId.length > 0) {
    return userId;
  }

  if (initiatorIds?.machineCredential) {
    return AuditActorSentinel.MACHINE;
  }

  return isSystemInitiatedExecution(executionContext?.executionType)
    ? AuditActorSentinel.SYSTEM
    : AuditActorSentinel.UNATTRIBUTED;
}

The resolved value is a user ID or a named AuditActorSentinel. A machine sentinel describes the actor category; the execution context and machine-authentication events identify its credential. Do not query it as though it were a user ID.

Preserve the initiating person across internal work

The internal context’s auditActorUserId carries attribution without granting that person’s permissions to the service. The service still runs under its explicitly authorized execution context. Application extensions should preserve that separation when a user action starts background work.

An UNATTRIBUTED result remains distinguishable from routine system work. Monitoring policy decides how to investigate it; an empty identity should never silently become “the system did it.”

Record which kind of authority acted, and which agent

Beside the actor identifier, every audit row carries actorKind, one of AuditActorKind: a signed-in user, a person acting through an outside OAuth client they delegated to (with that client named), a machine credential, the application acting on its own behalf, an unauthenticated request where none was expected, or an unattributed one. The framework fills it on every row. An application emitting its own event passes its request context, or names the principal with the exported helpers such as userAuditPrincipal or SYSTEM_AUDIT_PRINCIPAL.

There is deliberately no agent kind: an agent always acts under someone else’s authority. The row’s agentActor answers the separate question. It carries identifiers only (the agent’s reference, its execution, the actor system, the tool and, for an approval-gated tool, the approving decision), never the conversation’s text or the tool’s arguments. The framework opens an agent frame around each tool call, so a write from a factory-built tool, a hand-written tool or a system write inside a tool all carry it without the tool having to remember. Nothing authorizes on this frame; it is evidence only.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.