Skip to main content

audit-log

Durable, access-scoped, append-only record of who changed what app data, when, and whether it was the agent or a human. Use when adding an activity feed or change history, declaring what a mutating action targets, auditing sensitive reads, or answering "what did the agent change / who edited this".

Source facts

Repository
BuilderIO/agent-native
Last source activity
October 1, 2026 at 18:59
Detected SKILL.md language
English
Stars
7,065
Forks
640

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
audit-log
description
Durable, access-scoped, append-only record of who changed what app data, when, and whether it was the agent or a human. Use when adding an activity feed or change history, declaring what a mutating action targets, auditing sensitive reads, or answering "what did the agent change / who edited this".
scope
dev
metadata
{"internal":true}
# Audit Log ## Rule Every mutating action automatically records an audit event — **no wiring needed**. The framework captures who/what/when/from-where at the `defineAction` seam, redacts credentials, and attributes the change to a human or the agent (with the agent thread/turn that caused it). You only touch `audit` config to make events *more useful* (declare the target) or to opt a read in / a noisy write out. This is distinct from: - **observability** — sampled agent-run telemetry (traces, evals), developer-facing. - **tracking** — fire-and-forget product analytics to external SaaS. Audit is complete, durable, locally queryable, and scoped to the data it describes. ## Declare the target so it lands in the owner's trail By default an event is scoped to the **actor** (you see your own changes and the agent's changes on your behalf). To make a change to a *shared* resource show up in the **owner's** audit trail, declare the target: ```ts defineAction({ description: "Delete a recording", schema: z.object({ id: z.string() }), audit: { // type + id label the event; ownerEmail/orgId/visibility scope who can read it. target: (args, result, meta) => ({ type: "recording", id: args.id, // Optional — defaults to the actor. Set when editing someone else's resource. ownerEmail: result?.ownerEmail, visibility: "org", }), summary: (args) => `Deleted recording ${args.id}`, }, run: async (args, ctx) => { /* ... */ }, }); ``` `target`, `ownerEmail`, `visibility`, and `summary` are all optional. The minimum useful addition is `target: () => ({ type, id })`. ## Defaults and how to override them - **Mutations** (anything not GET / `readOnly`) are audited automatically. - **Read-only** actions are skipped. Audit a sensitive read (secret access, bulk export) with `audit: { onRead: true }`. - **High-frequency framework actions** (app-state sync, context-xray, navigate, appearance) are skipped by default. Force one on with `audit: { enabled: true }`. - **Opt a noisy write out** with `audit: { enabled: false }`. - **Skip capturing arguments** (large/sensitive payloads) with `audit: { recordInputs: false }`. Inputs are credential-redacted regardless. - **Refusals** — a thrown error with `statusCode` 401 or 403 records as `status: "denied"`, so a refused attempt shows up as an attempt. - **Probes** — a health or capability probe sends `{ "__probe__": true }` as its only argument. If it is rejected it records nothing; if it executes, or carries any other argument, it records like any call. Do not invent another probe shape in a client; the recorder only knows this one. - **App** — every event records the app that wrote it (`app.id`, else `app.name`, the same key usage uses). ## Who reads an event `visibility` decides who reads an event besides its owner (`ownerEmail`, which defaults to the actor): | Visibility | Readers | Recorded for | |---|---|---| | `private` (default) | The owner only, admins included | Personal content and personal connections | | `org` | Every member of `orgId` | Changes to resources shared with the org; integration-triggered runs | | `admins` | Owners and admins of `orgId` | Organization settings and admin actions | The `admins` events today: default model (`agent-default-model`), app member roles, app permission roles, workspace app access, org member role changes (`org-member-role`), file storage (`file-storage`), and org-scoped Builder.io connect and disconnect (`builder-connection`). A member's personal Builder.io connection is `private`. ## Record an organization settings or admin change Use the helpers in `@agent-native/core/audit` instead of hand-setting `visibility`, so every org setting lands in the organization trail the same way: ```ts import { orgAdminAudit, recordOrgAdminAuditEvent } from "@agent-native/core/audit"; defineAction({ // ... audit: orgAdminAudit({ targetType: "org-thing", targetId: (args) => args.id, summary: (args) => `Set the thing to ${args.value}`, }), }); // From a Nitro route (OAuth callback, upload) that is not an action: await recordOrgAdminAuditEvent({ action: "builder-connect", targetType: "builder-connection", summary: "Connected Builder.io for the organization", userEmail, orgId, }); ``` Pass `personal: true` to `recordOrgAdminAuditEvent` when the change affects only the actor. New settings actions (restrict personal keys, service providers) use `orgAdminAudit`. ## Reading the log These actions are available to the agent and the frontend in every app, scoped in SQL to the caller — they never leak another tenant's rows: - `list-audit-events` — filter by `targetType`/`targetId`, `actorKind` (`agent` | `human` | `system`), `status`, `threadId`/`turnId`, `action`, `app`, `sinceMs` (inclusive), `beforeMs` (exclusive), with `limit` and `offset` paging; returns `hasMore` and `nextOffset`. `includeApps: true` also returns `apps`, the app ids with events in the scope, for an app filter. `scope: "organization"` reads only the org's shared trail (`org` and `admins` events) and is refused with a 403 for anyone but owners and admins. This is the Settings audit log. - `get-audit-event` — one event by id, with its redacted input payload. Owners and admins can open `admins` events. - `export-audit-events` — bulk CSV/NDJSON export (same filters minus `limit` and `offset`, plus `format` and `maxRows`) for offline/compliance pulls; itself audited via `onRead`. Call them from the UI with `useActionQuery` to build an activity feed or a "who changed this" line — never hand-write a fetch to the audit table. Settings › Organization › Audit log (`/settings/audit`, owners and admins) is that trail's page, in `packages/core/src/client/settings/shell/pages/audit.tsx`. Its range and app filters are `sinceMs` and `app`; "Show N more" is the next `offset` page; a row opens `get-audit-event`. When a user asks what changed in the org, call `list-audit-events` with `scope: "organization"` instead of reading the page. ## Never - Don't write a parallel "history" table for a resource — declare an `audit.target` and read it back instead. - Don't put secrets in `summary` or rely on inputs being safe — redaction covers credential-shaped values, but keep summaries free of sensitive data. - Don't expose an update/delete path for audit rows. The log is append-only; the only deletion is the retention purge (`AGENT_NATIVE_AUDIT_RETENTION_DAYS`, default 365 days; `0` = keep forever). Global kill switch: `AGENT_NATIVE_AUDIT_ENABLED=false`.
View on GitHub