Skip to content

Part I · Concepts / Ch. 08

Chapter 8: Fitting Handles onto Capabilities — Tool ​

Xia A fits an orange-handled tool into a guarded machine while Lao Z stands by and a small result rests in a tray.

Xiao A added two "capabilities" to his Agent: one called run, whose argument was a string, and one called read_file. The model chose run almost every time, with arguments handed over in a mess, and he stared at the log without a clue.

"I clearly gave it the capability. Why doesn't it know how to use it?" he asked Lao Z.

"Given is one thing; usable is another," Lao Z said. "Function Calling defines the protocol for structured calls, but 'able to call a function' is not 'having a usable capability'. Whether a model picks the right tool usually comes down to the interface wording, the argument boundaries, and the failure results."

This chapter discusses Tool design. It does not cover loop scheduling.

Align the names first ​

TermEnglish / abbreviationWorking definition in this bookWhat it is not
ToolToolA concrete capability the host can declare, validate, and executea stretch of natural language in the model's output
Tool specificationToolSpecThe declaration of name, description, input schema, and result contractthe executor itself
runtime validationruntime validationSchema and business checks on the actual values a model givestype declarations taking effect on their own
execution boundaryexecution boundaryWhere side effects really happen — permissions, paths, network, subprocessesa prohibition written in the prompt
output budgetoutput budgetLimits on the size and shape of tool results entering the model contextdiscarding the raw audit record

8.1 A Tool Is an Executable Contract ​

"So what exactly is a Tool?" Xiao A asked.

"A Tool contains at least a name, a model-facing description, an input schema, an execution implementation, and result semantics," Lao Z said. "Function Calling is the mechanism by which the model expresses a request; a Tool is the concrete capability unit that gets declared and executed. Think of it as one tool in a toolbox: the handle carries a label that says 'what this is, how to use it, what it produces', and it is your program that actually swings it."

ts
// Illustration
const readFile = {
  name: "read_file",
  description: "Read a UTF-8 text file inside the workspace; pass a relative path.",
  input: { path: "string" },
  execute: async ({ path }) => ({ /* normalized result */ })
}

"Description and execution — why keep them apart? Can't you just write them together?"

"Because they are read by two different parties," Lao Z said. "The description is for the model: it decides whether the model picks this tool and how it fills in the arguments. The execution is for the host: it decides how the action actually lands. Mixed together, the model gets distracted by execution details, and the host gets misled by descriptions the model copied verbatim. Kept apart, the model only ever sees the operator manual, and the execution details stay under control on the host side."

"Can the model see the execution result?"

"It sees the result, not the process," Lao Z said. "What command the tool ran, which bytes it read, which network path it took — the model perceives none of that. It only gets back the structured result the executor is willing to hand over. Real side effects happen only at the execution boundary, and the model cannot cross it."

Xiao A thought for a moment. "So a tool is really a two-layer contract: one layer facing the model, one layer facing the host."

"Exactly. The model-facing layer decides whether the tool gets picked and whether the arguments are right. The host-facing layer decides whether the action is safe and whether it should run at all. The two layers evolve independently and do not contaminate each other."

"How does each of the two layers drift?"

"They drift in different directions," Lao Z said. "The most common drift on the model-facing layer is that the description changed but the label visible to the model did not: the executor gained retries internally, yet the description still says 'returns immediately on failure', and the model decides whether to retry on its own from that — and gets it wrong. The most common drift on the host-facing layer is that permissions narrowed but the tool description stayed as it was: the model still requests out-of-bounds paths according to the old description, the host refuses again and again, and the model has no idea why. The symptoms differ, but the root cause is the same: the model's assumptions about the tool have come apart from the tool's actual behaviour."

Source of driftHow it shows on the model-facing layerHow it shows on the host-facing layer
Description and implementation disagreeThe model fills arguments and picks tools from the old descriptionThe host receives requests that do not match the description
Narrowed permissions not syncedThe model still requests out-of-bounds actionsThe refusal rate climbs and the model does not know why
Result semantics changedThe model reads results using the old fieldsDownstream parsing no longer matches the new format

"So the two layers have to be versioned together," Lao Z said. "Change the executor, then look back and check whether the description is stale. Change the description, then look back and check whether the permission policy still lines up. The landing point is to record both the 'description version' and the 'implementation version' in the call log: the model chose the tool under one version of the description, the executor ran another version of the implementation, and when something breaks you put the two version numbers side by side and know at once whether the model acted on an old label or the implementation moved ahead of the description. Version numbers are not there to assign blame; they are there to shrink the search space: first see which layer changed, then see whether that change was right."

8.2 The Description and Schema Are the Model's Operator Manual ​

Xiao A remembered the run from the opening. "What does the model base its decision on when choosing between tools?"

"On the label," Lao Z said. "A description should answer: when to use it, what it must not be used for, how the input is to be read, and what the result is. A schema should express required fields, enums, ranges, and mutual exclusions as far as it can. Do not cram several independent intentions into one vague run(action, data) tool — both the model and the auditor will struggle to judge the actual impact. Your run is the textbook case: the label is too blurry, so the model can only guess."

Xiao A jumped in: "Got it — so the finer you split it, the better?"

"No," Lao Z shook his head. "Too many near-identical tools raise the mis-selection rate and the prompt cost; one all-purpose mega-tool widens the permissions and the test surface. Take as the default granularity one action that can be authorized, can fail, and can be audited independently. That is usually the most governable."

Xiao A was still not convinced. "What exactly is wrong with the all-purpose run? Isn't it enough that the arguments are valid?"

"Valid arguments do not mean a valid action," Lao Z said. "The schema of run(action, data) can only constrain action to be a string and data to be an object. It cannot constrain whether the call is ls or rm -rf. Which action the model chose you only piece together afterwards from the log, so authorization, auditing, and rollback are all out of the question."

"How fine is fine enough?"

"Look at whether the action can be authorized on its own," Lao Z said. "Reading a file and writing a file have completely different permissions, so they should be two tools, each with its own approval policy. But 'read a.ts' and 'read b.ts' need no split into two tools — they share one authorization. The split follows the authorization boundary, not the number of functions."

"Is there a counter-example of splitting too fine?"

"There is," Lao Z said. "Split 'read a file' into read_text_file, read_json_file, and read_yaml_file. Their authorization policy is identical, their failure semantics are identical, and the split only adds to the model's selection burden. The model has to choose among three, and a wrong choice costs an extra round."

Granularity choiceExampleThe problem
Too coarsethe all-purpose run(action, data)the schema cannot capture the action's semantics; authorization and auditing are impossible
Too fineread_text_file / read_json_file / read_yaml_fileone authorization split into three tools, raising the mis-selection rate
Rightread_file(path) alongside write_file(path, content)read and write authorized separately, same-class files sharing one tool

"For the concrete call, read four signals." Lao Z laid the comparison out in a table:

SignalPoints toward coarserPoints toward finer
Authorization policyseveral tools share the same permissioneach action needs a different approval
Failure semanticssame cause, same handlingthe consequences differ a lot
Audit needmerged records are enoughevery action must be traceable on its own
Selection frequencythe model almost always picks the same onethe model must clearly tell them apart

"If all four point to coarse, merge them. If all four point to fine, split them. If they contradict each other, weigh authorization first: the authorization boundary is the hardest to retrofit afterwards, and the other three can be compensated by descriptions and logs."

Key point

The description and the schema tell the model when to use a tool and how to fill in the arguments; granularity follows "can it be authorized, can it fail, and can it be audited on its own" — finer is not better, and larger is not more complete.

8.3 Result Design Determines the Quality of the Next Step ​

Xiao A moved the topic to return values. "What should I care about in what a tool returns?"

"A tool result should distinguish success, empty result, input error, permission denied, recoverable fault, and unrecoverable fault, and carry context that a later decision can use," Lao Z said. "For example, 'file not found: src/a.ts; checked the workspace root' lets the model fix the path far more easily than exposing a raw system exception, and it leaks less about the environment as well."

"Then what about long output? Just cut it off?"

"Not that simply," Lao Z said. "Long output needs truncation, pagination, or a summary, and you keep the location of the raw record so the user or the executor can still trace back to it. Sending a whole log file to the model with no upper limit wastes context and may also widen sensitive content into a scope that does not need it."

"Truncation or pagination — which one?"

"It depends on whether the model still needs the rest," Lao Z said. "If the remaining content is useless to the task, truncate: return a summary plus a line saying 'N lines omitted'. If the rest is evidence a later step needs, paginate: return a cursor so the model can turn the pages on demand. Truncation throws away; pagination leaves a door. Choose wrong and either the context blows up, or the model stalls because it cannot get the evidence."

"And the raw record?"

"Do not lose it, but it need not enter the context," Lao Z said. "The executor keeps the complete raw log in a file or in storage and returns to the model only a summary plus the reference location. Auditing wants the raw record; the model wants the conclusion. Do not squeeze both into the same return value."

Xiao A picked it up: "So result design is really keeping two sets of books."

"Right — one for the model to use, one for people to check."

"Can the result status classification be drawn as a complete table?"

"It can." Lao Z listed the six states:

Result stateMeaningThe model's sensible next stepTypical misuse
successthe action finished and returned valid datause the result to advance the tasktreating it as always successful and skipping field checks
emptya legal run that matched nothingchange the query or accept that there is no datamistaking it for a failure and retrying
input_errorarguments valid in shape but wrong in meaning (the path does not exist)fix the argumentsa generic error that leaves the model to guess
deniedthe path or action crossed the boundarygive up or request authorizationretrying a refusal as if it were a timeout
recoverable faulta temporary lock, rate limitingwait, then retrygiving up straight away as if it were unrecoverable
unrecoverable faulta remote crash, corrupted datahand off to a human or take another routeblind retries that amplify the fault

"For the model, these six states are six different forks," Lao Z said. "If you only ever return 'success' or 'failure', the model cannot tell inside that one big basket of 'failure' whether to retry, change the arguments, or give up. It can only guess, and a wrong guess means spinning its wheels or crossing a line."

"Can the state be right while the content misleads?"

"It can, which is exactly why state and content must agree," Lao Z said. "The commonest counter-example: the state says success while the content smuggles in an exception stack; or the state says truncated yet the full text comes back anyway. The model decides its next step from the state. Lie in the state and it walks the wrong road. The state is the navigation, the summary is the reason, the reference is the evidence — the three have to line up. When they do not, return the conservative state rather than reporting only good news."

8.4 Capability Minimization and Failure Semantics ​

"How much permission should a tool have?"

"A tool should hold only the minimum permission needed to finish the current task," Lao Z said. "Read, write, network, subprocess, and credential access are declared separately; dangerous actions need an explicit approval point. The model gains no extra permission through a tool description, and the executor must not loosen its policy because the model sounded firm."

Xiao A tried to fit it to the schema: "path is declared as a string — isn't the permission locked down then?"

"That locks down the type, not the range," Lao Z said. "path: string accepts any string, including ../../etc/passwd. A schema being satisfied does not mean the path is inside the workspace, and it does not mean this tool should read it. The last gate for permission sits at the execution boundary: check whether the path crosses the line and whether it is in an allowed directory, instead of assuming everything the model gives is fine."

"And failure? Is throwing an error enough?"

"Throwing an error is nowhere near enough," Lao Z said, his tone turning serious. "For an Agent, a failure result is the input to the next decision, so errors must be stable, classifiable, and observable. You cannot just return 'something went wrong'."

"Is that the same thing 8.3 covered under result classification?"

"They are the two ends of one line," Lao Z said. "8.3 covers how to design results on the success path; here it is the failure path. Same envelope: a status code, a model-facing summary, and a traceable reference. A success return has to let the model carry on; a failure return has to tell the model whether it can carry on and in which direction. Neither should be a bare exception."

"How does a failure result have to read to be usable?"

"Three elements: the state classification, a sentence aimed at the model, and a traceable reference." Lao Z wrote two returns side by side:

text
Bad failure return: { "error": "ENOENT" }
Good failure return:
{ "status": "input_error",
  "summary": "file not found: src/a.ts; checked the workspace root",
  "ref": "log/2024-01-15/run#42" }

"With the bad one, the model sees ENOENT and can only guess: wrong path? permission? a disk problem? The good one tells the model straight out, 'the path you gave is not in the workspace', so its next step is to change the path or give up."

8.5 Output Is the Next Round's Input Interface ​

"Any advice on the result format?"

"Tool output serves people and serves the model, so it needs a stable envelope instead of concatenating exception stacks and unbounded logs into the context as they come," Lao Z said. "A practical result contains a status, a model-facing summary, bounded structured data, a truncation marker, and a reference location people can trace."

json
// Illustration
{ "status": "truncated", "summary": "found 200 matches, returned the first 20",
  "items": ["…"], "continuation": { "cursor": "next" } }

"Then what about the number of tools? More is better?"

"The number of tools follows the authorization boundary — 8.2 covered that. Applied to output, the arithmetic is the same," Lao Z said. "Separate read, search, and write tools are easy to authorize and test one by one; over-splitting adds selection steps, rounds, and context load. The starting point is whether the action can be authorized, can fail, and can be audited on its own — not the number of functions."

Xiao A thought of a case. "What if the result itself contains sensitive content? Say a config file with a key in it."

"The executor has to redact before the content enters the context; you cannot count on the model to keep the secret," Lao Z said. "The model will not stay silent just because you told it 'do not leak'. Next round it may write that passage straight into a logging tool, or hand it back to the user."

"Should the envelope's fields carry a version number too?"

"Better if they do," Lao Z said. "The model learns one format from older tool results; you rename a field in the envelope and it is still parsing with the old format. That is not a stupid model, it is version drift. Keep a schema_version on the envelope and bump it when you change a field, so a new version tells the model to stop reading it the old way. Put the envelope version in the call log together with the tool description version, and when something breaks you can tell whether the result format changed or the downstream parser fell behind."

"But the old results are still sitting in the context."

"Old envelopes and old descriptions are one and the same problem," Lao Z said. "The old format in the model's memory does not disappear because you bumped a version number — it grew out of your past call history. A version number keeps new calls from reading the old format, but results already inside the context still have to be re-fetched if they need to be current. The version number governs future calls; clearing the cache governs past traces. Do not merge the two into one task."

Illustration: when search_text matches too many items, return the page size and a cursor, so the model can narrow the query or ask for the next page; do not push a whole dependency directory back into the context. If a tool returns a config file that contains a key, the executor should redact it before it enters the model context, rather than hoping the model keeps the secret on its own.

8.6 Where Tool Ends and Its Neighbours Begin ​

"Where are the boundaries between Tool and the concepts covered earlier?"

"Function calling solves how the model expresses a tool call; ToolSpec solves how a callable capability is described; the execution boundary decides whether a request can really affect files, the network, or processes," Lao Z said. "Two more concepts come later and I will just name them here: a Hook is a mechanism that inserts checks or observation into the lifecycle, covered in detail in Chapter 11; MCP is the protocol boundary that connects external tools and resources, covered in detail in Chapter 12. Neither one automatically replaces a Tool's input validation and least privilege."

text
tool call → runtime validation → policy check → execute
  → success / empty / denied / invalid_input / timeout / truncated
  → bounded structured result → next round of context

"I can think of one counter-example: the tool succeeds and then dumps a flood of logs, blowing up the context," Xiao A answered first.

"Right, that is the first one," Lao Z said. "Counter-example one: a tool succeeds and produces a hundred thousand lines of log; the executor should paginate, truncate, or keep a reference — never inject all of it into the context. Counter-example two: the schema is satisfied but the path is out of bounds or the command is dangerous; it must be rejected at the execution boundary. The gain is that each class of capability can be authorized, tested, and audited on its own; the price is that descriptions, result semantics, and output budgets need continuous maintenance."

8.7 Tools Must Be Testable on Their Own ​

"With this many boundaries, how do you make sure the tool itself is sound?" Xiao A asked.

"Test the tool on its own — no model in the loop: feed it arguments directly and assert on the results," Lao Z said. "Tool tests and Agent tests are two layers. Agent tests check whether the model picked the right tool and passed the right arguments. Tool tests check whether, given the arguments, the executor did the job. Model-level tests cannot cover the executor's boundaries. A request whose schema is valid but whose path is out of bounds may never be produced by the model, yet the tool test must construct it directly and assert that it is rejected."

"What should a tool test cover, at minimum?"

"Cover it category by category, along the result semantics," Lao Z said:

Test dimensionInput coveredOutput asserted
Happy pathvalid argumentsthe expected result and the correct status
Empty resulta query with no matchan empty status rather than an error
Input errormissing fields, wrong typesan invalid_input status
Permission deniedout-of-bounds paths, dangerous commandsa denied status
Recoverable failurea locked file, rate limitingfailure information carrying context
Unrecoverablearguments that are always invalida clear failure, no retry
Output truncationan over-long resultthe truncation marker and the reference location

"There is one more class that gets missed most often," Lao Z said. "Robustness tests of the executor itself: whether concurrent calls cross data, whether repeated calls have idempotency protection, whether a timeout leaks resources. None of that involves the model, but when it breaks it hides best — it shows up as occasional data mix-ups, and the log carries no trace of the model choosing the wrong tool."

Xiao A scanned the table. "What is the relationship between tool tests and model evaluation?"

"Complementary," Lao Z said. "Tool tests prove the tool is right; model evaluation proves the model can use the tool. Test the tool first: if it is wrong, however smart the model is, it will use it wrongly. Once the tool is right, then test whether the model picks it and passes the arguments correctly."

"What if a tool depends on an external service? Do the tests really send requests?"

"They must not, and they should not," Lao Z said. "Tool tests run the executor inside controllable boundaries: a temporary directory instead of the real filesystem, fixed responses instead of network calls, and an injectable clock as well. There is one goal only: same input, same result. Once a test really sends an external request, network jitter merges 'the tool is broken' and 'the network was bad today' into the same red mark, and you cannot tell whether to fix code or blame the environment. Put real end-to-end calls in integration tests, with a failure signal that stands apart."

"If the tool tests pass, nothing goes wrong in production?"

"That I cannot promise," Lao Z said. "Tests cover the inputs you constructed; they cannot cover the shape of real data. The model may produce argument combinations your tests never wrote, and an external system may return fields your fake implementation does not have. So tool tests guarantee 'correct behaviour for these inputs', not 'correct behaviour for any input'. After launch you still need observability: every result envelope leaves its status and reference on record, so an unfamiliar combination of states becomes visible instead of waiting for an incident to discover it."

"For tools brought in over MCP, who is responsible for validation?"

"The caller is," Lao Z said. "MCP connects external capabilities in, but it does not decide for you whether this tool is usable under your permission policy. The protocol solves interoperability; authorization always sits on the caller's side. So an MCP tool passes exactly the same checks as your own tools before it enters the context."

"And what is the relationship between Hooks and the execution boundary?"

"A Hook is a checkpoint you hang on; the execution boundary is the line the check has to hold," Lao Z said. "A Hook lets you insert an approval or an observation 'before execution', but what actually rejects an out-of-bounds action is the policy check at the execution boundary. The Hook provides the moment; the policy provides the judgment. Without a policy, a Hook just takes a look and lets it through."

"With all these mechanisms together, which stretch does each one guard?"

Lao Z drew the whole chain:

text
model request
  │
  ├─ schema validation          ← shape constraints (types, required fields, enums)
  │
  ├─ Hook: before execution     ← insert approvals, observations, logging
  │
  ├─ policy check               ← permissions, paths, action legality
  │     └─ execution boundary   ← side effects actually happen here
  │
  ├─ Hook: after execution      ← record, redact, notify
  │
  └─ result envelope            ← status + summary + reference → back into the context

"Each station blocks a different kind of error," Lao Z said. "The schema blocks format errors, the policy blocks permission errors, Hooks block process errors, and the result envelope blocks information errors. Drop any station and that class of error lands straight on the next one — or lands as an incident."

LayerWhat it blocksWhat it cannot block
schema validationwrong field types, missing required fields, values outside the enumout-of-bounds paths, wrong meaning
Hook (before execution)a missing approval or observation in the flowthe legality of the action itself
policy check (execution boundary)out-of-bounds paths, insufficient permission, dangerous actionswhether the business intent is right
result envelopeinformation overload, sensitive leakage, ambiguous statusside effects that already happened upstream

"Among these mechanisms, who answers for the consequences?"

"The execution boundary answers for consequences; the other layers answer for signals," Lao Z said. "Schema validation says 'the argument shape is wrong', the Hook says 'the flow is missing an approval', the policy says 'the action crosses the line' — all of them speak up front. What makes a side effect happen or not happen is the execution boundary. After an incident, look first at whether the execution boundary acted on the signals it received: signals all correct and the executor still ran, that is the executor's problem; a signal itself missing, that is upstream's problem."

8.8 The Tool Lifecycle and Undo ​

Having walked the call chain, Xiao A turned the question around. "Apart from being called, are there other moments that matter for a tool?"

"A tool does not come into existence at the moment it is called," Lao Z said. "It has a full lifecycle: registration, discovery, invocation, return, retirement. The last chapters kept talking about invocation and return. Registration and retirement get ignored, and they are exactly the hardest places to pin down when you debug."

"What can go wrong with registration?"

"Duplicate registration, overriding registration, ghost registration," Lao Z said. "Duplicate registration: two modules register a tool under the same name, the latter overrides the former, and the model thinks it is using A's semantics while B actually executes. Ghost registration: a tool was registered but never removed from the tool list, so the model keeps seeing it and keeps having the chance to mis-pick it. The registry needs paired register and deregister operations, and deregistration leaves a trace in the log."

"What if the registry lists a tool but the executor has no matching implementation?"

"That is the most hidden fault in the lifecycle," Lao Z said. "If the tool list and the executable implementation come from two separate registries, they will diverge one day: the registry says 'this tool exists' and the executor says 'not implemented'. The model picks it according to the registry, and the call drops straight into an exception you never considered. It reports no permission error, no path error — it just fails quietly. The tool list and the implementation should come from one registry, with register, implement, and deregister managed as pairs instead of each side keeping its own list."

"And retirement?"

"A tool may be withdrawn, renamed, or have its permissions narrowed," Lao Z said. "A retired tool should be removed from the tool list visible to the model, not left there so the model picks it and then gets refused. A tool the model cannot see can never be mis-picked; a tool it can see but is always refused for makes the model go around in circles."

Xiao A followed the thread of side effects. "What about undo? If a tool acts wrongly, can you take it back?"

"It depends on the side-effect type," Lao Z said. "Read-only operations have no undo problem: read it wrong, read it again. For write operations it depends on whether an undo interface exists — a file edited wrongly can be restored through version control, but a network request already sent cannot be taken back. Undo is not a default capability of a tool; it has to be designed explicitly: either provide the inverse operation, or stop the action at an approval point before the side effect lands. Where there is no undo, the only guardrail is the check before execution."

Side-effect typeUndo capabilityGuardrail when there is no undo
read-onlyno undo needednone
write a filerestorable via version controlapproval before the write, verification after it
outbound network callusually not undoableconfirmation before sending, an audit record
deletiondepends on the target systemmandatory approval, a second confirmation

"What is it when the tool's own execution times out?"

"You have to separate 'still running' from 'finished but the result never came back'," Lao Z said. "A timeout while the executor is still working is an interruption: you can wait or give up, but you should know the process may still be writing. A timeout after the action completed, with only the response undelivered, is a lost result — retry blindly there and the side effect may run twice. The test is whether you can determine that the action took effect: if the log already says so, do not retry; if you cannot determine it, constrain the retry with an idempotency key."

Xiao A turned it over for a while. "So in tool design, simply offering fewer dangerous tools is itself a safety measure."

"Right. Fewer tools, and the dangerous surface the model can choose from shrinks; more tools, and each one needs its own guardrail," Lao Z said. "The cost of adding a tool is not only writing the implementation. There is registration management, lifecycle maintenance, and undo design as well, and all of it should be settled before you start."

"Then where does this chapter's conclusion lead into the next one?"

"A tool is a handle fitted onto the model, but however many handles you have, you can swing only one at a time," Lao Z said. "A real task has to string read, change, and verify round after round, and that is the next chapter's subject, Agent Loop: the model proposes a step, the tool lands a step, and it goes back to the model to decide the next one. Tools solve 'can it be done'; the loop solves 'should it keep being done'."

Summary ​

Tools let the model actually get its hands on the work. A complete tool has five parts: the capability, the description, the schema, the executor, and the result semantics — none of them optional. The specification and the executor belong to two sides: the specification is the manual shown to the model, telling it what the tool is called, what arguments it takes, and what it does; the executor is the hand that actually works, and only the host program can call it. This separation lets the model choose the right tool and use it rightly from the description, while real side effects happen only at the execution boundary, whose details the model cannot perceive. Runtime validation filters along this chain, because a model does produce values outside the schema, and without validation dirty data goes straight into the executor.

What decides whether a tool can safely enter an automated flow is three hard constraints: least permission decides what it can touch, the output boundary decides how much of the context its result takes up, and readable failure decides whether the model can recover from an error. All three rest on engineering, not on "please be careful" in a prompt. Readable failure matters because returning "file not found: src/a.ts" lets the model fix the path far better than returning "Error: ENOENT". Permission control lands in the execution layer rather than the description layer, because a description is only text the model can ignore, while a refusal from the executor is something the model cannot get around.

Hands-on Verification ​

Design the schema and the result format for search_text, covering four cases separately: no match, an out-of-bounds path, too many results, and a normal hit. Ask a colleague to choose the tool and construct the arguments from the description alone; if they keep misusing it, fix the description or the granularity before you consider adding another tool.