Preview · not yet published

·9 min read·agents · architecture · audit-trails

Agents need the same verbs people use

A typed action is a verb, an actor, a target, typed arguments and a receipt. When people and agents share one path, the log can show what an agent did.

Contents

In October 2026, Carl set a rule for our team. Everything we do needs "typed actions for the agentic layer", so that our agents "see exactly what we see and engage like we engage."

The idea is not new. Four existing ideas each carry a piece of it. This note puts the pieces together, shows what breaks without them, and says what it takes to build one.

#What a typed action is

In our definition, a typed action is one thing a caller can do, written down as a contract before any code. It has five parts.

PartThe question it answers
VerbWhat is being done? One short name for one thing.
ActorWho is doing it, and from which credential?
TargetWhat does it act on?
Typed argumentsWhich inputs, in which types, and what happens when they are wrong?
ReceiptWhich row on the log shows the call happened, or was refused?

The verb set is the contract. If the web page, the command line and the agent all call the same verb, the agent is subject to the same checks as a person calling it. Who may call which verb is decided per actor, so an agent can be given less than a person. The log of receipts then reads as a replay of what was attempted, by whom, and what was refused.

#The pieces already exist

The command pattern, 1994. The Gang of Four book states the intent of its Command pattern as "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations." The entry lists "Action, Transaction" as other names. A request becomes a thing you can pass around, store and log.

Event sourcing, written up in 2005. Martin Fowler's article starts from a gap. We can query the current state, "However there are times when we don't just want to see where we are, we also want to know how we got there." His answer is to store every change as an event. The most obvious gain, he writes, is that "we now have a log of all the changes." He calls that "a small gain", because a history could be kept other ways. The key, he writes, is "that we guarantee that all changes to the domain objects are initiated by the event objects." One path for every change.

Action types in Palantir Foundry. Palantir's documentation defines an action as "a single transaction that changes the properties of one or more objects, based on a user-defined logic." Its parameters are the inputs of an action type: "Each parameter is defined by a type, which dictates what kind of values it can take." Submission criteria are "the conditions that determine whether an action can be submitted". An action log records who submitted what, and when. The overview adds that "the same action logic and validations can be made available across all user-facing applications, ensuring consistent edits to the Ontology."

Tools in the Model Context Protocol, 2026. The current MCP specification gives every tool a name and an inputSchema, which is "JSON Schema defining expected parameters". Servers must "Validate all tool inputs". The list of tools may depend on who is asking, "returning only the tools the caller's granted scopes permit". Clients should "Log tool usage for audit purposes". MCP is the door an agent walks through. The tools page does not define what a receipt contains. That part is yours to build.

Put together, our reading is this. A typed action is a command with typed inputs, a named actor and a row on a log, offered to agents through a standard door.

#Why agents need the same verbs, not their own

Palantir's docs show the design. When an action log is enabled for an action type, it answers the question "What decision was made, by whom, when, and in what context?" It does not care which client made the call. The docs say the distinction "is about the path, not the client", and that "an action type applied through the API or an SDK produces an action log object just as it does in the user interface". A person clicking and a program calling leave the same kind of trail. Someone has to enable the log on each action type, and an object type can require that its actions have one.

Palantir's AI features write the same way. In AIP Logic, the docs say: "The Apply actions tool enables the LLM to use Actions to edit the Ontology." The model does not call tools or edit the data itself. "LLMs can only ask to use tools, and these tool calls are then executed by AIP Logic within the invoking user's permissions." In Palantir's chatbot tools, an action tool "can be configured to run automatically or to run after confirmation from the user". In Automate, you can "stage actions as proposals for human review instead of applying them automatically."

Two things follow. First, a shared path only helps if the log can tell who acted. Palantir's action log records the submitting user. In our row shape, the actor field names the agent, and a separate field names the person it acted for. Second, an agent that calls the same verb faces the same checks a person would face.

#What goes wrong without them

Four failures. Where a point is our view and not a source's, we say so.

The agent drives the screen. With no verb, an agent has to read and click the interface built for people. In our view that breaks when the layout moves, and it gives you no named verb to log. We wrote about the same failure for terminals: keystrokes sent to a pane can look successful while typing into the wrong place. In that note's words, "something observable was treated as proof of something else that was never observed." See A running terminal is not a receipt.

The side door. A path that skips the verb also skips the log, and in our view the checks. Palantir says so plainly about the log: "Edits applied outside an action type, such as direct writes to the backing datasource or legacy Foundry Forms writeback, do not produce action log objects." Its default is to close the door. On its permissions page, "by default, new object types only allow edits via actions." Builders can reopen it, and the page advises against that for new work.

A log of only the good days. Palantir's action log "records successful action submissions only. It does not record failures." Failures are tracked in a separate metric. In our view, the refused call is often the most useful row for anyone auditing an agent, because it shows what the agent tried. Our rule is that a refusal is a row too. In our wiki service, the code that builds a row throws an error if a refused or failed row has no reason.

A verb is not a fence. Typed actions do not replace access design. Palantir's permissions page warns that row and column access controls "do not extend to the action's write." Decide who may call each verb, then check that the verb cannot be used to get around that decision.

#What it takes to build

Here is the shape we aim for, in order.

  1. Name the verbs first. One line each, before any files exist. Our rule: a behavior counts as replaced only when a named action with a receipt does it.
  2. Say who may call each verb. A named credential, never "anyone on the network". If an agent acts for a person, it says so, and that is recorded as attribution only. It never widens what the agent may do.
  3. Type the inputs and refuse bad ones with a reason. Do not quietly turn a wrong input into a right one.
  4. Let the service write the receipt. The caller never writes its own. Give the service a way to read its rows back.
  5. Put one handler behind every door. Whether a call comes from a web page, a command line or an agent tool, it should reach the same code.
  6. Test the refusals. A refused call has to leave a row with a reason, and a test should prove it.

Our wiki service is designed around this shape. As written in its code, a page change is one of two verbs: a write, for the page's owner, or a proposal, for company pages (a founder's key can write directly too). Both carry the version of the page the caller read. If the page has moved since, the call is refused with the reason "page changed since you read it". Both are scanned for secrets and contact data, and a proposal must cite a source for each fact written as a list item. In the code there is no approval queue: a proposal that passes the checks is applied at once, and one that fails is refused with its problems named. A well-formed write or proposal, whether it succeeds or is refused, leaves one row: who, through which door, which verb, which target, what result. In the core code we read, reads write no row.

Our team room has a smaller version. Its board code refuses a card that asks a person for a decision unless it carries an evidence block, meant to be the output of a command, stored with the card, or a short reason saying it claims nothing about the state of a system.

#What we have not finished

Not everything follows the rule yet. We read the code of two services for this note and did not test them in use. When we wrote it, our voice tools wrote events that are close to receipts, but not in the shared row shape, and nothing read them back as a feed. Our rule document lists gaps like this, so nobody claims more than is true.

In our view, if a person can do it and an agent cannot, someone will build a side door. If an agent can do it and a person cannot see it, nobody will find out. Build the verb once, let both call it, and read the log.

Related field notes: Audit trails are what keep automated work accountable, A running terminal is not a receipt.

#Sources