Authority and Audit
Who each action runs as, how policies bound everything it reaches, and what Twisp records about every call.
Agents act on money, so two questions matter for every action: what is it allowed to do, and what did it actually do. Twisp answers the first with policies that every call must pass, at every layer of the call stack. It answers the second with a record of every execution, call, input, output, and model exchange, linked so you can walk from any one to everything around it.
Each action is a principal
Every action runs as its own client principal, arn:twisp:database:::action/<actionId>. Its policies become that client's policies when the action is created or updated. Twisp adds grants scoped to the action's references: permission to read and invoke each tool and skill it lists, and to use a tool's sessions. A tool's own policies are never added to its callers.
The action's principal is the outermost authority for work that no caller started: an execution started by a trigger, a schedule, or a session. When a client calls or starts an action, the client is the outermost authority instead. Read a client's effective policies with auth { client(principal: ...) }.
A change to an action's policies applies to the executions of the version it creates at once. Other invocations of earlier versions, such as retries, pick up the new policies within 5 minutes. Policy syntax, effects, and assertions are described in Security and Auth. See Principal and policies.
The policy chain
Every operation a tool or agent performs is checked against a chain of policy lists, one for each layer of the call stack, and every layer must allow it:
| Layer | Policies |
|---|---|
| Outermost | The client that started the work: the caller of invokeAction or startAction, the principal signed in to an MCP server, or the action's own client for a triggered or session execution. |
| MCP server | The server's policies, for a call made through an MCP server. |
| Agent | The agent version's policies, when an agent is on the call stack. |
| Each tool | The policies of each tool version on the call stack, from the first call down to the one performing the operation. |
An explicit DENY in any layer denies the operation. Operations are attributed to the outermost client. Because every layer must allow an operation, a layer can only narrow what the layers above it allow:
- Declaring or offering a tool never grants its caller anything. A tool can do only what its callers can.
- An agent's policies bound everything it can reach, through any tool.
- An MCP server grants nothing: a user cannot do through it what they could not do themselves, and the server's policies bound what anyone does through it.
- A tool with no policies can perform no operation. A skill grants nothing either.
- A new tool version never gives a caller more, because its policies only narrow.
A permission error names the denied operation, the resource, and the layer and policy that denied it. Through an MCP server, the external agent sees the operation, the resource, and the layer by name; the full error stays on the execution for you. See Policy chain.
INVOKE
INVOKE is the permission to run an action. A client needs it on a tool to call it with invokeAction or start it with startAction, on an MCP server to send it requests, and on an action to rerun or replay its executions. Writing a definition that references tools or skills requires permission to read and invoke each of them, so you cannot build an agent or server around a tool you could not call.
Nested calls need no separate grant. When a tool or agent calls a tool it declares, the reference in its definition authorizes the call. The call still runs under the whole policy chain above it, extended by the called tool's own policies. Likewise, a principal using an MCP server needs INVOKE on the server, not on its tools: the server's references authorize those calls. See Nested calls.
Started work keeps the starter's authority
Work that runs on its own, such as a callable agent started with startAction or an agent an MCP request runs as a task, runs under the authority of whoever started it, exactly as if they had called it. Starting work never lends it the action's own client.
That authority is read again at each invocation, so a change reaches work already queued:
- Revoked or narrowed. Revoking the starter's client, or narrowing its policies, stops queued and retried work at its first invocation that starts 5 minutes after the change, or sooner.
- Expired. The start records the starter's token claims. An invocation that would begin after the token expires is refused, and the execution fails with
action_access_deniedand a hint that the credential it was started with expired.
An agent's memory is the exception: it is the agent's own record, as its executions and transcripts are. The agent recalls, searches, saves, and deletes its memories as itself, so neither its policies nor its starter's need to grant financial.AgentMemory, and nothing it calls gains access to its memory. Reading or deleting memory through the API still needs those grants.
The started execution's startedBy records how it was started: by a client, or by another execution, such as the MCP request that created a task. See Starting a tool.
Reruns and replays
A rerun or replay is new work, so whose authority it runs under is defined too:
- Rerunning a triggered execution runs under the action's own policies, read afresh, so a rerun after an update that grants what the original was denied succeeds at once.
- Rerunning a call or a started execution is a new start by you, under your authority.
- Replaying an agent turn a client called or started, or an MCP request, runs under both you and the principal it replays, whose policies are evaluated with the claims it recorded, then the action's policies. It can do nothing either of you could not.
- Replaying a triggered or session turn runs under the agent's own policies, as the turn did.
Both need INVOKE on the action. See Preview and replay and Replays.
What is recorded
Every run of every action is an execution, and every call one execution makes to another is an execution too. Together they record:
| Record | What it holds |
|---|---|
| Executions | The action, the exact version that ran, the event or effective input, the status, each invocation's output or error, and its parent, the execution that called it. |
| Invocations | Each attempt, with the exact version every tool and skill reference resolved to, so you know which contracts were in force. |
| Calls | Each call's parent, its position among its parent's calls, its effective input with fixed input applied, and its output or error. Calls that a failed program rolled back stay recorded, with rolledBackByExecutionId. |
| Transcripts | For an agent, every model request and response, with its reasoning and token usage, recorded before the agent acts on it, and every tool call's input and output. |
| Output uses | Which tool answers, skill guidance, and retrieved memory reached an invocation, each naming the execution that produced it. |
| MCP requests | The method, tool, arguments, stated intent, principal, signed-in user, host application, self-reported client, and _meta, such as trace context. |
| Write sets | For a preview or replay, each record version it would have written. |
| Memory | Each memory record names the execution, invocation, agent version, and session that wrote it. |
Stated intent and trace context
For an agent running inside Twisp, the transcript shows why it acted: what it was shown, what it reasoned, and what it said. Twisp never sees an external agent's reasoning, so an MCP server asks for it: each tool call carries an intent, a sentence from the agent saying why it made the call, recorded on the request and never passed to the tool. It is the external model's own account, evidence to read beside what the call did, and is never used for authorization.
Everything in a request's _meta other than the protocol's own fields is recorded with it, up to 16 KiB, including W3C trace context and your own run IDs. Send them, and every Twisp execution can be found from your traces. See Twisp in Your Agent Platform.
Lineage and call paths
lineage walks every execution an execution caused, or that caused it, from any execution in a call tree. It follows two kinds of edge:
CALL. A call one execution made to another: an agent's tool call, a tool's nested call, a program's call, a skill load.START. Work one execution started to run on its own, such as an MCP request that created a task.
DOWNSTREAM lists every call and start beneath an execution, depth first, in the order they were made. UPSTREAM lists its callers to the top, and on through the execution that started the top. The walk continues through executions you may not read, listing each with its identifier and a null execution, so the shape of the tree is complete without disclosing what you may not see.
callPath on an execution gives its position in its tree directly: one frame per call from the top down to it. See Lineage and Call paths.
What did this agent do, and why?
For an external agent, start from its session. Each request names its tool, its arguments, and the reason the agent gave:
query RunRequests($server: UUID!, $session: String!) {
actions {
executions(where: { actionId: $server, sessionId: $session }, sort: ASC, first: 100) {
nodes {
executionId
created
status
mcpRequest {
method
name
arguments
intent
subject
meta
}
}
}
}
}
Then walk from any request to every call and write beneath it:
query WhatItDid($id: UUID!) {
actions {
execution(id: $id) {
lineage(first: 50) {
nodes {
depth
via
execution {
actionId
actionVersion
status
invocation {
output
error {
code
message
}
}
}
}
}
}
}
}
For an agent running in Twisp, read the turn's transcript for its reasoning, invocation.uses for the evidence and guidance it was given, and children or lineage for what it did. Each transcript tool.input and tool.output event names the call it records, so you can move between what the agent said and what the call did. See Transcripts.
The record does not change
Every change to an action or execution writes a new version, and history lists them; action(id, version) and execution(id, version) read an exact one. What the reference guarantees about that history:
- A terminal execution never changes status again, and no transcript event can be added after an invocation's outcome.
- A deleted action can no longer be edited, and its definition, history, and executions remain readable.
- Deleting a memory record removes it from recall; the record and its versions remain readable.
- Closing a session stops it; its inputs and turns remain readable.
- Nothing an MCP server returns leaves Twisp before the execution recording it commits.
What Twisp cannot know is outside it: an external model's prompt and reasoning beyond its stated intent, what it did with a result, content a host served from its own cache, and whether a client's self-reported name is true. An investigation can always answer what an agent did in Twisp and what Twisp told it.
Where to go next
Security and Auth
Clients, principals, and policy syntax.
Executions reference
Execution fields, filters, history, lineage, and call paths.
Preview and replay
Run changes against real data without committing them.