How do you lock AI agent tool arguments?
You lock AI agent tool arguments in Elaichi with frozen parameters. An admin sets a value on one tool inside a toolbox. Elaichi then leaves that field out of the schema the model is shown, and at execution it writes the admin's value over whatever the call contains. The model still composes the call. The admin composes the part nobody gets to argue about, such as the account a payment goes to.
A finance analyst opens Claude and asks it to wire this month's payroll funding. The payment tool takes a source account, a payee account, an amount, a currency and a memo. The amount and the memo are the analyst's to decide. The two accounts are not. The controller settled them once, and a language model should not re-decide them on a Tuesday.
The payment tool here is an illustration. Its field names are invented to show the mechanic, and they do not describe any bank's or payment provider's real API. The same mechanic applies to a CRM owner field, a support macro or a reporting workspace ID.
Payments make the case sharply, because where the money goes is exactly what payment fraud tries to change. The FBI's Internet Crime Complaint Center describes business email compromise as a scam aimed at people who make legitimate transfer-of-funds requests. Its September 2024 announcement puts exposed losses from the scam at about $55.5 billion between October 2013 and December 2023. Its first prevention tip is to verify requests to change account information through a second channel. An agent that reads email can be handed exactly that kind of request.
MCP (Model Context Protocol) is the standard way an AI assistant calls tools in other apps. A toolbox is a named set of tools assembled once and shared with people. In the MCP specification, every tool carries an inputSchema, a JSON Schema that defines the arguments it expects. That schema is how the model learns which fields to fill.
How do frozen parameters work?
A frozen parameter is a fixed value attached to one field of one toolbox entry, and it does two things. Elaichi removes the frozen field from the tool's schema before any client sees it. At execution, Elaichi merges the frozen value over the caller's arguments, so the caller cannot change it.
Technically, it is a per-entry map over the tool's flattened argument space. Both qualifiers matter.
Per-entry means the lock belongs to one toolbox entry, not to the tool in the abstract. An entry pairs one tool with one connection, and a connection is one connected account of one app. The same payment tool can appear twice in an organization. One copy sits in a sandbox toolbox with a test payee locked in, and the other in a payables toolbox with the real payroll account. Freezing is a property of the pairing, so the two never mix.
Flattened means every argument is addressable by its dot path, however deep it sits. A top-level field such as currency can be frozen. So can payee.account_id, and so can a field three levels deep. That matters when the field you care about is nested, as the payee is in this example.
Why doesn't the model see a frozen field?
Because Elaichi takes frozen fields out of the tool's schema before the client receives it. The schema is the argument shape the endpoint hands a client when the client lists tools. If payee.account_id is frozen, it is not in that shape. The model is not asked to fill it and cannot set it. The tool's description carries a note, such as "(frozen: payee.account_id=acct_payroll)", so the model can see that the field is locked, and often the value, but it has nothing to change.
That removes a duller failure as well. A model handed a field it cannot work out from the conversation may invent a value, or stop and ask the user for one. Both are bad outcomes on a payment. Leaving the field out of the schema leaves only the inputs the caller can actually supply.
This is the shape OWASP recommends in its guidance on excessive agency. It advises avoiding open-ended extensions in favor of ones with narrower functionality, and it names indirect prompt injection among the common triggers. A payment tool with an open payee field is open-ended in the one field that decides where money goes. Locking that field narrows the tool without anyone writing a new one.
The lock survives tool search. In Elaichi, connected tools are never listed one by one, however few there are. The model looks them up with search_tools, which returns the same reduced schema, and calls them through execute_tool. That second step only adds a layer of naming. Elaichi unwraps it to the same tool and arguments, and the call meets the same checks as any other.
Search also decides how the tool gets found. Ranking is purely lexical over the tool name, its description and the connector label. The word frozen is dropped from description tokens, because every frozen tool carries it as scaffolding. The frozen values themselves stay searchable on purpose. The reasoning, including the minimum score a match needs, is in how tool search is scored.
Which value wins when the model sends one anyway?
The frozen value wins, every time. The order is short and runs one way:
entry defaults < caller/model args < frozen params
Entry defaults are conveniences. An admin sets currency to USD so nobody has to type it on every call. The caller may change it, and being changeable is the whole point of a default.
Caller and model arguments are whatever arrives in the call. They beat defaults, and they carry the analyst's actual request as the model expressed it.
Frozen parameters are applied last and win outright. This is enforcement on the server, which is where the MCP specification puts it: servers must validate all tool inputs and implement proper access controls. Here is one illustrative payment call resolved against all three layers:
| Argument | Entry default | Model sent | Frozen | Executed |
|---|---|---|---|---|
amount |
none | 48210.00 |
none | 48210.00 |
currency |
USD |
none | none | USD |
memo |
Payroll funding |
October payroll |
none | October payroll |
source.account_id |
none | none | acct_operating |
acct_operating |
payee.account_id |
none | acct_7731 |
acct_payroll |
acct_payroll |
Read the currency row first: nobody overrode the default, so it stands. The memo row shows the caller beating a default. The source row shows a frozen field the model was never asked to fill and never sent. The last row is the frozen case that matters. Something in the model's context asked for a different payee. The analyst may have typed it, or it may have arrived in an email claiming the payroll provider had changed banks. The model can emit a field the schema never advertised. Elaichi merges the frozen value over it, and acct_payroll is what leaves the system. Sending the field cannot unlock it. The instruction can be written, and it cannot take effect.
How do you lock the payee on a payment tool?
Work in this order, because each step assumes the one before it. Pick whichever payments connector your finance team runs, such as Airwallex. Read the real field names from that tool's schema, not from this example.
- Connect the account once, from the account of someone who will stay responsible for it, and do not share the connection itself with the finance team. Credentials do not live in Elaichi. Each account's secrets sit in a separate credential service, encrypted at rest with AES-256-GCM, and that service owns token refresh. A failed refresh marks the connection
needs_reauthinstead of failing quietly. - Create the toolbox entry by pairing the payment tool with that connection. This is the object the lock attaches to, and the entry records you as the person who vouches for it.
- Freeze the fields that are policy rather than input, here the payee account and the source account. Write them as dot paths, exactly as they appear in the tool's flattened arguments. Leave amount and memo alone, because those are the analyst's job.
- Share the toolbox, not the connection, with the finance team, at
use. The team then runs the payment tool only through the entry. The connection is absent from their own tool list, and they cannot open or share it. Sharing works the same way everywhere: you grant view, use or edit on one resource to a person, a team or everyone in the organization. - List tools from a real client and read the schema back. The frozen field should be absent from the schema, not present with a value. If you can still see it, the freeze is on a different path than the one the tool uses.
- Check that nobody on the team holds the connection itself. Anyone with
useon it, directly or through a team or organization share, can call the payment tool unfrozen. Remove that share rather than writing a restriction, because a block on the payment tool withholds the frozen entry too. Revoking a share takes effect on the next call. - Run one real payment and open the audit log. Confirm the entry names the connection you expected. Then check the payee in the payments app itself, because the log records the one path argument that names the object, as the target id, and nothing else about the arguments.
When does locking an argument make the tool worse?
When the field genuinely varies. A frozen parameter buys certainty by removing range, and it is easy to overpay.
Lock the payee and the entry can only ever pay that account. For payroll funding or a monthly tax payment, that is exactly right. For an accounts payable team paying sixty suppliers, it is wrong. Sixty locked entries means sixty near-identical tools competing in a lexical ranking, and the model has to choose between them on name and description alone. In that case, leave the payee open and restrict the operation to the roles that should hold it. Verify any change to a supplier's bank details through a second channel, and read the audit trail.
There is a second case where a frozen parameter is the wrong answer. If nobody should ever send payments from an assistant, do not lock the payee. Block the operation. A locked argument on a tool that should not be reachable at all is a decision made one layer too late.
What doesn't a locked argument protect against?
It does not make the endpoint safe against prompt injection. The prompt-injection write gate lives in the Elaichi agent window and does not apply to a raw tool call. Freezing removes one argument from the set an injected instruction can influence. It does not stop the model from calling a tool it is permitted to call, with amounts it is permitted to choose. Other protections do hold on the endpoint: per-operation permission checks, the forbidden tool class, output redaction, OAuth scope limits and an audit row for each call that reaches execution.
It does not replace a restriction either, and the two can collide. Frozen parameters bind an entry. Restrictions bind a target, which is a role or a user, and Elaichi checks them at every place a member can reach a connector or tool, including listing, connecting, advertising, executing, the final outbound-request check and opening a stored tool file, on toolbox entries as well as direct calls. So a block on the payment tool withholds the frozen entry too. In Elaichi, a freeze binds only calls made through its toolbox entry, so share the toolbox with the people it governs, not the connection itself. If a member can connect their own account to the same app, calls through that account bypass the entry, and no restriction can stop them without stopping the entry as well. Keep payment credentials with the people who run payments, and treat the entry as the shape of the call rather than the gate on it.
What does the audit log show after a locked call?
It shows that the call happened and which account it went through, not what was in the payload. The audit log holds one entry for each connected-tool call that reaches execution (a call refused earlier writes none). Elaichi reads the recorded connection from the call as it ran, not from what was asked for. After an unexpected change, that is the first thing you want to know.
For each call that reaches execution, the entry captures the operation and tool, the connection and how the call was classified. It also records whether the call was approved, how it ended and, on a failure, an error code. Elaichi logs the one path argument that names the object, as the target id, and nothing else about the arguments. So the log tells you which payments account the agent used, and it does not tell you which payee was in the body. A call from an MCP client is recorded under the person who signed in, with surface mcp and the OAuth client named. The client is recorded rather than guessed.
A failed call produces two error messages. The caller gets one built from the third party's response. The audit trail gets a separate one that never draws on the request or the response. Audit records are visible across the organization, and Elaichi's in-product assistant can read them. Keeping the two apart means a third party's error text never leaves through the log.
What happens to a locked entry when someone leaves?
Removal and suspension cut access on the next call. In Elaichi, removing or suspending a member revokes every live grant in the same transaction as the membership change. The grant's revoked_at field is re-read on every call. Changing a role or a restriction is slower. It waits on a 60-second cache and then on the edge network, so allow about two minutes.
The offboarding preflight lists every connection the departing member owns. A private connection a shared toolbox relies on blocks the removal, and the fix is to transfer it to one other active member or delete it. A private connection that nothing beyond the member depends on cannot be transferred and is deleted with them. A connection is never handed to the organization, to a team, or to the admin running the removal. A shared connection the team still depends on is deleted only if you explicitly ask for that, so transfer it to someone who is staying and every grant on it stays as it was. Delegated toolbox entries show up as a warning that does not block the removal, and re-pinning the entry clears it. The contractor version is in what to do the day access ends.
When can you skip frozen parameters?
When every argument on a tool is genuinely the caller's business, freezing is configuration that buys nothing. A team of six with one payments account, one workspace and read-heavy tools gets most of the value from the audit trail alone. The same holds early in a rollout, while the list of connected apps is short enough to keep in your head. The wider version of that argument is in the case for waiting on a gateway.
There is also the case where the routing logic already lives somewhere else. If an internal service already picks the payment account from rules in your code, point the connector at that service and let it decide. You then own and run that service, which is a real cost. The shape of that bill is in self-hosted versus managed.
How do frozen parameters fit with roles and restrictions?
Frozen parameters are the narrowest control in the stack, and they answer a different question from the layers above them. Permissions decide which actions a member may take, with exactly one role per member. Resource sharing decides what a member can see at all. Restrictions decide which connectors and which individual tools a target may reach. Frozen parameters decide what a permitted call must contain.
That ordering is why the payment case works. The analyst keeps the tool. The controller keeps the payee. Neither has to trust the model's judgment about where the money goes.
For the same layering on a revenue team, read the Salesforce rollout walkthrough. More on roles, restrictions and the trail sits under governance, and the per-team sequences are under team playbooks. To see which apps you can lock arguments on, browse the connector catalog or the team use cases.