The quarterly access review is a spreadsheet, and one row will not close. March 14, 02:14. An opportunity in Salesforce moved to Closed Won. The Salesforce audit trail names an account. The person who owns that account was asleep, and says so. The owner column stays blank.
AI actions in access review evidence go missing for a structural reason. The record was written by the application that was acted on. That application saw an authenticated API call and nothing more. It did not see a model, a client, or the employee who typed a sentence into a chat window. MCP, the Model Context Protocol, is the standard AI clients use to call tools in other systems, and it sits one layer above where that row was written.
What changed when an agent started acting for a person
An access review verifies that the identities holding access should hold it, and it samples past actions as evidence. For years the two kinds of actor were obvious. A person clicked a button. A scheduled job ran under a service account. The division held up in an audit because it held up in the logs.
An assistant acting for a person is neither. It runs inside that person's session, against that person's connected account, and it decides the call from a vague instruction. Naming the human on the row is true and incomplete. The human did not construct the request.
Why AI actions in access review evidence come back blank
Attribution is being reconstructed afterwards from fields that were never built to carry it. Each SaaS application records the credential that called it. When a person uses an assistant over a connected account, that credential is the one the person already uses by hand. Sometimes it is a shared service account somebody created for automation two years ago. Either way the row looks like ordinary API traffic.
Reviewers then guess. The usual guesses are the hour of the day, the call rate and the user agent string. Each is a heuristic. An access review resting on a heuristic comes apart at the first follow-up question in a customer security questionnaire (Verizon's DBIR tracks how attribution failures compound).
The blank row is not a logging failure in the destination application. It recorded exactly what reached it. The missing fact belongs to the place where the tool call was authorized and issued.
Can a user agent string tell you an agent was involved?
Not reliably. A user agent is a string the caller sets, so it describes what the caller chose to say about itself. A script on a laptop and an assistant acting for the same person can present the same header, because both use a library rather than a browser. Vendors set the header inconsistently, and a proxy in the path can rewrite it before it lands.
There is a second problem with reading it later. The header identifies the software that opened the connection. It does not say whether a person asked for the action, read the result, or approved it. Two calls with identical headers can be a nightly sync and someone typing a request into Cursor.
A user agent is evidence of a client. An access review asks for the actor.
actor_kind is a field, not an inference
In Elaichi, whether an action was taken by an AI is recorded at the point of action. actor_kind is a field on the record, and its values include user, system, staff, scim, api_token and ai_assistant. Nothing downstream has to infer it. A reviewer filters on the field and gets the list.
That works because of where the call passes. Every SaaS account the company uses is connected once, and the tools those accounts expose are served through one organization-wide MCP endpoint, POST /mcp, standard MCP over Streamable HTTP, stateless, behind OAuth. OAuth is the sign-in flow that issues a scoped grant instead of handing over a password. There are no per-toolbox URLs and no embedded tokens. The grant belongs to a named member, so the record carries the member and the kind of actor on the same row.
Connector credentials do not live in Elaichi. A separate credential service holds per-account secrets, encrypted at rest, and owns refresh. A failed refresh marks the connection needs_reauth rather than failing quietly.
Actor names resolve server-side when the trail is read. A member who has left the organization renders as "Former member" rather than dropping out of the list. In a quarter where somebody resigned, that is the difference between a gap and an answer.
What one tool-call attempt records
Elaichi writes one entry per tool-call attempt, succeeded or failed, and both name the account. The connection recorded is the account actually reached, taken from the execution rather than from the intent. After an unexpected change, the first question is usually which of two connected Notion workspaces the agent wrote to. That question has an answer.
Each entry records the operation and the tool, the connection, the classification, whether the call was approved, the outcome, and an error code. Argument names and counts are logged. Argument values never are. Plan the evidence pack around that limit: you can show that a delete carried three arguments, not what was in them.
One record shape, one log tenant per organization
Elaichi writes audit events and application logs in a single record shape. One query answers what happened, instead of two exports lined up by timestamp. The trail is append-only, newest-first and cursor-paginated, and it filters by free text, category, actor, action kind and time.
Each organization has its own log tenant, enforced in the type system rather than by a WHERE clause. A dropped WHERE clause leaks; a wrong tenant returns nothing. That failure direction matters more than usual, because the in-product assistant can read the audit log. Customer-visible audit and internal application logs are separate tenants.
One trade-off to plan around: the trail is eventually consistent. A row may take a moment to appear. Pull evidence after the review period has closed, and do not build an alarm on the absence of a row.
Why the trail carries an error code and not the vendor's message
Elaichi keeps two error strings for a failed call. The one returned to the caller is derived from the third party's response body, and it is never written anywhere else. The one written to the audit trail is never derived from the request or the response.
The reason is blast radius. Audit records are visible to the organization, readable by the in-product assistant, and forwarded to whatever SIEM the customer configured, where a SIEM is the log platform a security team searches. A remote error body reaching any of those is third-party payload leaving the system through the log pipe.
The cost to a reviewer is real. You get a code, the operation and the account, not the vendor's prose about why the call failed. State that trade in the evidence pack rather than discovering it during an audit call.
Why promoted metadata comes from a fixed list
In Elaichi, promoted metadata is a fixed allowlist, not a rule that flattens whatever arrives on the event. Metadata keys can be influenced by users and are unbounded in practice. Unbounded flattening would let one organization's traffic grow the field namespace for its whole tenant, which degrades every query run against it.
For an access review, that constraint is useful. The fields in this quarter's extract are the fields in next quarter's extract. A new column is a request with a review behind it, not something that appears because an agent sent an unusual payload.
Proving that a role or restriction change took effect
Auditors ask what happened after a review found something, and two timings apply in Elaichi. Mixing them up is how an evidence pack becomes wrong.
Grant revocation, member removal and member suspension take effect on the next call. The revocation state is re-read from the organization store on every call, and removing or suspending a member revokes every live grant in the same transaction as the membership change.
A role change or a restriction change takes effect within about two minutes. Resolution runs through a 60-second cache plus edge propagation, on MCP, the console and REST alike. Record the applied time, not the requested time.
The shape of the controls is worth a line each. Each member holds exactly one role, enforced by a unique index, so every role is a complete persona. Restrictions decide which connectors and which individual tools a target may reach, and the targets are role or user only. restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything. The organization default is the absence of a rule, which means allow-all.
When one AI client and a manual review are still fine
If one team uses one assistant against read-only connections, manual review is proportionate. Export the assistant's workspace logs, export the target application's logs, line up the timestamps, and ask the four people who had access. At that size a control plane such as Elaichi is overhead that buys nothing, and waiting is a defensible choice.
The point where the method stops working is usually visible in advance. A second client appears, so Claude, ChatGPT and Cursor are all in scope. Connections start doing writes and deletes rather than reads. Contractors get access. Or a customer asks, in writing, how you tell an agent action from a human one. At that point manual correlation produces a paragraph of reasoning where the reviewer wanted a field.
Building the evidence pack for the next review
Work backwards from the row you could not close. Decide which columns the reviewer needs, then confirm each one is a recorded field rather than something a person reconstructs.
Give the reviewer their own seat. The Elaichi Auditor role is free and read-only, so a compliance reviewer does not consume a license. Auditor lacks tool:execute, the permission that gates the whole MCP endpoint ahead of every scope, so tools/list is empty for that seat and a call returns an in-band error naming the permission.
Forward the trail if your team lives in a log platform. Export to Datadog is implemented. Splunk HEC and Microsoft Sentinel are accepted but not yet delivering. Read the log type filter twice: absent means forward everything, and an empty list means forward nothing. Region also selects which regional log instance the trail lands in, and eu and us are hard residency while apac is a best-effort placement hint. Eu and us are the two hard-residency zones rather than a hard residency zone.
Two limits belong in the pack rather than in a footnote. The trail does not contain the prompt, because an MCP server never sees one, and The prompt-injection write gate lives in the Elaichi agent window and does not apply to a raw tool call. Deleting an organization tears down the workspace without a path to purge its log tenant, and the product reports that by naming the residue. Neither sentence is comfortable to write. Both are better written by you than found by an auditor.
The product overview covers how one endpoint serves every connected account, the security page lists what is enforced in code, and what to do when a contractor leaves handles the offboarding half of the same problem. For scope, browse the connector catalog, now at 450+, or the team use cases.