# Audit logs

Polyant keeps three audit trails: what an agent's tools did, who changed who could do what, and who changed a configuration or an account. The panel reads all of them in the [Monitor](https://docs.polyant.ai/admin-panel/activity). The tool trail is the Monitor's **Tools** type; with managed services the other two are its **Changes**. The earlier **Audit Logs** screen and the organization's **Audit** page redirect there.

## The three audit tiers

Polyant keeps them separate on purpose, and the three line up with the three tenancy levels. Knowing which one to open saves a lot of searching:

| Trail | Tier | Answers | Where |
|---|---|---|---|
| **Tool audit** | workspace | What did an agent's tools do? | the workspace [Monitor](https://docs.polyant.ai/admin-panel/activity), **Tools** type, or one agent's **Monitor** section |
| **Authorization audit** | organization | Who changed who could do what? | the organization's Monitor, **Changes** (managed services, advanced audit); see [Authorization audit](https://docs.polyant.ai/admin-panel/authz-audit) |
| **Management audit** | installation, and each organization | Who changed a configuration, and who touched an account? | the **Changes** of the Monitor: the workspace's for changes recorded against it, the organization's for its whole trail, the Admin Console's (`/platform/monitor`) for the whole deployment (managed services) |

A tool call that failed and a role that was widened are different questions, and they sit under different types of the same timeline.

> **Managed services.** Reading the management and authorization trails in the panel is available with Polyant managed services. The open-source build records management changes and serves the tool trail.

The organization's Monitor combines management and authorization events in one newest-first list under **Changes**. Management events appear without advanced audit; authorization events appear only with that licence. The organization's **Audit** (`/organizations/{org}/settings/organization/audit`), its earlier **Management audit** and **Authorization audit** addresses, and the Admin Console's `/platform/settings/audit` redirect to those **Changes**. The API routes behind the old pages remain: `GET /api/organizations/{orgSlug}/audit-logs` (both trails), `/management-audit-logs` and `/authz-audit-logs`, each under `org.audit:read`, and `GET /api/platform/audit-logs` for platform admins.

The management trail is always paginated, and **an organization can read its own**, under `org.audit:read` - the same key that gates the authorization trail beside it, because it is the same disclosure and two switches for one idea would leave nobody able to say why one was granted and not the other. Owner and Admin hold it; Member and Viewer do not. Unlike the authorization trail, it is **not** gated on the advanced-audit feature: who changed what is the base compliance record of a managed-services deployment, not an upsell. In a workspace's Monitor the changes recorded against that workspace are read under `audit_log:read`. Every row carries the organization and workspace it belongs to, which is what makes that possible: the table used to record who did what to which target and never *where*, so filtering by tenant was impossible and platform-admin-only was a consequence of the schema rather than a decision. A platform-level action - the bootstrap, a cross-tenant operation - genuinely belongs to no tenant, and the columns are left empty rather than given an invented owner.

On upgrade, rows that name an agent are attributed through its slug, which is unique across the deployment. What cannot be resolved stays unattributed, because the alternative is an organization admin opening the page on day one, seeing nothing, and concluding it is broken.

**What gets a row is no longer a short list.** The rule is that a mutation somebody would have to explain afterwards must leave something to explain it from, and the product now applies it to every mutating route: an agent's own settings, its prompts, tools, skills and skill versions, its hooks, its scheduled tasks (recorded *before* a manual run fires, since the run is fire-and-forget), its channels and MCP servers, organizations and workspaces through their lifecycle, management API keys, invitations, the skill catalogue, governance policies, provider admissions, connections and role bindings, the grant of the deployment's AWS identity, dev-mode grants and tokens, shared knowledge libraries, a conversation destroyed by hand, memories written or purged, and a person changing their own password.

The exemptions are written down rather than assumed - a read-only probe, an agent's icon, starting a two-factor enrolment that changes no standing. **A guardrail enforces this per handler**, not per file: a controller that audits some of its mutations used to vouch for the ones it did not, which is exactly how one route lost its row in a rebase and nothing went red.

What a row never carries is the thing that would make the trail itself a disclosure: a secret's value, an API key or its hash, a dev token, a provider connection's credentials, a knowledge document's content, a channel's token or phone number. Names, types, sizes and target ids only.

## The tool audit

Every tool call an agent makes writes one row: the agent, the tool, the action, the input (redacted for sensitive tools), the output or the error, the duration, and the conversation and assistant message that issued it. Reading it needs `audit_log:read` (Admin and above), and the rows are scoped to the caller's organization and narrowed to the active workspace, so "all agents" means *all agents you can see*, never the whole deployment.

In the panel a tool call is a **Tools** row of the [Monitor](https://docs.polyant.ai/admin-panel/activity): the drawer shows its **Arguments**, its **Result** and any error, and links to the conversation. Rows older than the **Analytics retention** [installation policy](https://docs.polyant.ai/admin-panel/installation-policies) (90 days by default) are deleted by the daily housekeeping, and deleting a conversation deletes its tool calls.

### Reading it through the API

`GET /api/audit-logs` returns the rows, filtered by `instanceId`, `toolName`, `action`, `search` (over the details), `conversationId`, `from` and `to`, and paged with `limit` (default 50, at most 200) and `offset`. `GET /api/audit-logs/stats` returns the total, the error count, the error rate and the counts by tool and by action for `instanceId`, `from` and `to`. Both require `audit_log:read` and apply the same organization and workspace scoping as the panel.

Each row carries `messageId`, the assistant message that issued the call. Opening the [conversation](https://docs.polyant.ai/admin-panel/conversations#deep-links-into-a-turn) with `?around=<messageId>` and the `#msg-<messageId>` fragment loads the window **containing** that turn and scrolls to it. The id is missing in four cases: a pre-LLM hook, the opt-out gate, `spawnTask`, and every row written before the migration that added `tool_audit_logs.message_id`. That column is nullable and deliberately not backfilled - the assistant message a historical tool call belonged to is not recoverable after the fact - so an old row points to its conversation and not to its turn.

## See also

- [Monitor](https://docs.polyant.ai/admin-panel/activity) - the timeline that reads these trails, with live updates.
- [Authorization audit](https://docs.polyant.ai/admin-panel/authz-audit) - role and membership changes.
- [Roles and permissions](https://docs.polyant.ai/reference/roles) - who holds `audit_log:read` and `org.audit:read`.
