Every capability invocation is recorded. Allowed, denied, rate-limited, or failed: one event each, written by the dispatcher as the call goes through it.
The log is the answer to "what did my agents actually do", and it is the reason a single endpoint in front of your accounts is worth more than the same accounts wired up separately. Nothing else in the system knows the whole picture.
Reading it
$ lanes link audit tail --limit 25 --profile personal --workspace local
$ lanes link audit tail --denied-only --format md --profile personal --workspace local--denied-only is usually the interesting half. A refusal is the permission system working, and a
run of them tells you either that an agent is reaching for something you meant to withhold, or that
you narrowed something further than you meant to.
Verifying it
Each record carries the hash of the one before it, so the log is append-only in a way you can check rather than trust:
$ lanes link audit verify --profile personal --workspace localAn edit shows as a hash mismatch, a removal as a sequence gap, and a truncated run as a count that disagrees with its marker. It exits non-zero on a break.
What an entry keeps
The rule across every provider is the same: keep the shape, withhold the words.
Each provider declares which of its arguments are identifiers and which are content, because an audit log that reproduced the content would be a second copy of the thing it was meant to make accountable. A message id is kept. The message is not.
Three consequences worth stating outright:
A search query is withheld everywhere. gmail.users.messages.list records nothing at all, not
even q. A search term is frequently more revealing than its result, because it says what somebody
was looking for. The same reasoning covers memory.search, tasks.list, and
icloud_contacts.search_contacts.
Withholding is the default, not the exception. A provider that declares no redaction rules gets every argument value withheld. That is why a vendor-run MCP server gives you a thinner record than Gmail does: nobody authored a rule list for capabilities nobody here wrote, so nothing is assumed safe to keep.
Length can be a disclosure. A vault value is recorded as <withheld> rather than <string:40>,
because the length of a secret is itself worth something.
Each provider page says what its own calls keep and withhold.
The six results
allowed | it ran |
denied_by_policy | a rule you wrote refused it |
denied_default | nothing granted it; absent policy grants nothing |
denied_unauthenticated | no valid caller |
denied_rate_limited | too many, too fast |
denied_connection_unauthorized | the capability was allowed, that connection was not |
What is not audited
Two gaps, documented rather than glossed over. Both are also rows in the security model.
Failed authentication is logged, not audited. A refusal record needs a principal, and failing authentication is precisely not having one. A rejected credential goes to the endpoint's operational log instead, naming the reason and never the value presented.
Arguments that fail schema validation. A call to an advertised tool whose arguments are rejected is stopped by the protocol layer before dispatch runs. Nothing is invoked, the caller gets a clear error, and no row is written.
A third, narrower one: a client speaking the 2025-era protocol can enumerate which capabilities exist without leaving a refusal trace. It is not reachable unauthenticated and nothing is invoked either way. Security model has the full accounting.
Where it lives
With the workspace, alongside the other stores. It is never moved into a repository by
lanes link knowledge use github, and never will be: a log you can rewrite by force-pushing is not a
log.
Next: the security model for the guarantee table, or Capabilities for why each capability takes the shape it does.