08-harness-engineering-tool-ecosystems

Tool Calling & Function Orchestration

Why a schema-valid tool call still isn't safe to execute blindly, and the validation and sandboxing layer that sits between a model and a live API.

August 14, 2026
tool-callingorchestrationsandboxingharnessgen-ai

Schema-Valid Isn't the Same as Safe

The multi-agent customer-ops system from the last phase works well in testing. Then, in production, the refund worker calls issue_refund with amount_cents: 5000000 — fifty thousand dollars, on a nineteen-dollar order. The call is perfectly schema-valid: Phase 2's structured output guarantees amount_cents is an integer, and it is. Nothing about the schema said it had to be reasonable. The team had quietly assumed "the model's tool call passed schema validation" meant "it's safe to execute against a live payment API," and those turned out to be two different guarantees entirely.

This is harness engineering: the layer of code that sits between "the model decided to call a tool" and "the tool actually ran," and it's where a surprising amount of an agent system's real reliability work lives — not in the model, not in the prompt, but in the plumbing most tutorials skip because it isn't as interesting to write about as the agent loop itself.

Validation Beyond the Schema

Schema validation (Phase 2) checks shape: is amount_cents an integer, is order_id a string. It has no concept of whether $50,000 makes sense for a $19 order — that's a business-rule check, and it has to happen explicitly, in the harness, before a tool with real side effects actually runs. For the refund agent, that means: does the requested amount fall within the order's actual total, does the order actually exist and belong to the customer asking, has a refund for this order already been issued. None of these are things a JSON Schema can express, and none of them are optional just because the model's output technically validated.

⚠️

Treat every tool call's arguments as untrusted input, the same way you'd treat a user-submitted API request — because structurally, that's what it is. A model can generate schema-valid arguments that are still wrong, and for any tool with a real side effect (moving money, sending an email, modifying a record), the harness is the only thing standing between a model's mistake and a real-world consequence.

Sandboxing: Contain What a Tool Can Actually Touch

For tools that execute code, run shell commands, or touch a filesystem — less common for a refund agent, common for coding or data-analysis agents — the harness also needs to bound what a tool execution can actually do: a timeout so a hung call doesn't block the whole agent loop indefinitely, resource limits so a runaway computation doesn't take down the host process, and restricted permissions so a tool meant to read order data can't accidentally (or maliciously, if compromised via injected input) write to something it shouldn't. Anthropic's tool use documentation covers the API mechanics of defining and invoking tools; sandboxing is the operational discipline layered on top, specific to what a given tool is actually capable of touching once it runs.

Orchestrating Multiple Calls in One Turn

Phase 2 mentioned a model can request several tool calls in a single turn. The harness has to decide how to actually run them: calls with no dependency on each other — checking order status and checking customer loyalty tier, say — can execute concurrently, cutting latency roughly to the slowest single call instead of the sum of all of them. Calls where one genuinely depends on another's result — you can't calculate a refund amount before you've looked up the order — have to run sequentially, in the right order, regardless of what order the model happened to list them in. Getting this distinction wrong in either direction causes real problems: running dependent calls in parallel by accident sends a calculation a null result it needed from an earlier step; running independent calls sequentially by habit just wastes latency for no benefit.

Check yourself

A tool call to issue_refund passes JSON Schema validation (amount_cents is a valid integer) but requests a refund far larger than the order total. What layer should catch this, and why didn't schema validation already?


What's Next

Every tool in the refund, shipping, and account agents right now is hand-wired directly into that specific agent's code — which becomes a real problem the moment more than one agent, or more than one framework, needs to reuse the same tool.

Frequently asked questions

Isn't business-rule validation just duplicating logic the model should already be reasoning about via the system prompt?

The system prompt shapes what the model is likely to request; it doesn't guarantee it. A model can misread context, hallucinate a number, or simply make a reasoning error, especially under adversarial or edge-case input. Harness-level validation is the difference between 'the model was instructed not to do this' and 'the system is structurally incapable of doing this' — the same distinction Phase 2 drew between a polite instruction and a schema-enforced contract, applied here to actions instead of output format.

Should every tool call be validated with the same level of scrutiny?

No — validation depth should scale with a tool's blast radius. A read-only order-lookup tool needs far less scrutiny than one that moves money or sends a customer-facing email. Spending equal engineering effort validating every tool call regardless of consequence is itself a design mistake — it's a budget, like the token budgets from Phase 3, better spent where the risk actually concentrates.

How do I decide whether two tool calls in one turn are safe to run in parallel?

The concrete test is data dependency: does either call's arguments or logic require the other call's result to already exist? If not, they're safe to parallelize. Some agent frameworks require this to be declared explicitly per tool; others infer it from whether one call's arguments reference another's output. Either way, treat it as something to verify deliberately, not assume by default.