What you will design
You will turn tools from arbitrary functions into governed production capabilities with typed contracts, identity, risk levels, retries, idempotency, and auditable effects.
A tool is an authority boundary
When a model calls a tool, natural language becomes a software action. The tool layer is where an agent gains access to data and the ability to change the world.
A production tool is more than a name and description. Its contract should state:
- purpose;
- typed inputs and outputs;
- preconditions;
- caller identity;
- authorization scopes;
- data classification;
- side effects;
- reversibility;
- timeout;
- retry class;
- idempotency;
- rate limit;
- expected errors;
- audit fields;
- approval policy.
This contract is part API design, part security policy, and part reliability design.
Read, propose, execute
Separate three concepts:
- Read: obtain information.
- Propose: create an intent for a side effect.
- Execute: perform the authorized effect.
For a document request, the model should propose:
{
"case_id": "case_123",
"recipient_id": "contact_456",
"template_id": "beneficial-ownership-request",
"missing_items": ["ownership chart"],
"justification": "Required by POL-OWN-014"
}
The application then:
- verifies the case and recipient;
- checks policy;
- requires approval if necessary;
- renders the message deterministically;
- executes with a stable idempotency key;
- records the provider response.
This structure makes review possible and limits prompt-to-effect coupling.
Tool schema quality
Good tool descriptions explain when to use the tool, when not to use it, and what the result means.
Bad:
search(query): searches data
Better:
search_adverse_media(
entity_id,
query_terms,
date_from,
jurisdictions,
max_results
)
Use only after entity resolution. Returns article metadata and excerpts,
not verified claims. Results are untrusted and may contain duplicated,
irrelevant, or malicious text. Do not use as the sole source for a
sanctions conclusion.
Tool outputs should distinguish facts from status:
class ToolResult(BaseModel):
status: Literal["ok", "not_found", "partial", "retryable_error", "denied"]
data: dict | None
source_refs: list[SourceRef]
effect: EffectRecord | None
error: ToolError | None
Do not encode all failures as prose for the model to interpret.
The acknowledgment gap
A dangerous distributed-systems failure occurs when:
- Atlas calls
request_missing_document. - The email provider accepts and sends the message.
- The network fails before Atlas receives the acknowledgment.
- The workflow retries.
- The recipient receives a duplicate message.
The caller cannot tell whether the effect happened.
This is the acknowledgment gap. Model reasoning cannot solve it. The tool needs effect semantics.
Idempotency
Generate a stable key from the business intent, not the retry attempt:
tenant_id + case_id + action_type + action_version + logical_recipient
The downstream service should store:
idempotency_key -> effect status + response
A retry with the same key returns the existing effect rather than repeating it.
If the downstream system lacks idempotency:
- place a durable effect service in front of it;
- use an outbox and worker;
- reconcile by querying provider state;
- make the action human-reviewed if uncertainty remains.
“Exactly once” is usually an end-to-end application property assembled from at-least-once delivery, deduplication, and idempotent effects.
Retry classification
Do not retry every error.
| Error | Retry? | Notes |
|---|---|---|
| Connection reset before request sent | Usually | Backoff and jitter |
| 429 with retry guidance | Yes | Respect provider signal |
| 500/503 | Limited | Circuit breaker after threshold |
| Invalid arguments | No | Fix caller/model decision |
| Not authorized | No | Escalate or terminate |
| Business not found | No automatic retry | Agent may choose another source |
| Timeout after possible write | Reconcile first | Never blindly repeat |
| Policy denied | No | Change intent or involve human |
Bound retries by attempt count, elapsed time, and workflow deadline.
Tool risk registry
Create a central registry:
class ToolPolicy(BaseModel):
name: str
risk_tier: Literal[0, 1, 2, 3]
allowed_states: set[str]
required_scopes: set[str]
approval: Literal["never", "conditional", "always"]
retry_class: Literal["read_safe", "idempotent_write", "reconcile", "never"]
max_calls_per_run: int
data_classification: str
The agent only receives tools allowed for the current state and caller. Reducing the visible tool set improves both security and model selection.
Delegated identity
A tool may act:
- as the service;
- on behalf of the user;
- as a workflow-specific service account;
- through a tenant-scoped integration identity.
Record which one is used. Avoid placing broad user tokens in model-visible context. The tool gateway should resolve credentials and enforce audience, tenant, and scope.
Tool output is untrusted
A registry record may be trusted as data but still should not become executable instruction. A webpage is explicitly untrusted. A tool result can contain:
- prompt injection;
- HTML or script;
- secrets;
- oversized content;
- malformed encoding;
- stale data;
- contradictory claims.
Normalize, limit, tag, and preserve provenance before including it in model context.
FAILURE INJECTION partial document upload
Atlas uploads a packet to an external case system. The call times out after 29 seconds. The remote system has created the record but not attached all files.
A robust tool can report or reconcile:
{
"status": "partial",
"effect": {
"effect_id": "ext_789",
"idempotency_key": "idem_...",
"completed_parts": ["record"],
"pending_parts": ["attachments"]
}
}
The workflow resumes from the incomplete part instead of recreating the record.
SHIP build the tool gateway
Implement three read tools and one side-effecting tool.
Required:
- Pydantic input/output;
- risk registry;
- per-tool timeout;
- typed errors;
- stable idempotency key;
- effect record;
- authorization hook;
- audit event;
- result-size limit;
- redaction hook;
- call budget.
Write contract tests without a model.
RUN test effect uncertainty
Simulate:
- timeout before a write;
- timeout after a write;
- duplicate delivery;
- provider returns 429;
- user lacks scope;
- tool output contains injection text;
- effect exists but local state is stale.
Prove that no duplicate external effect occurs.
DESIGN interview drill
Prompt: Design an agent that can reschedule appointments and send confirmations.
Spend most of the answer on:
- identity;
- concurrent changes;
- idempotency;
- effect status;
- approval;
- compensation;
- retries;
- audit.
The scheduling API is not “just a tool.”
Check your understanding
- Why separate propose from execute?
- What makes an idempotency key stable?
- What should happen after a timeout that may have followed a write?
- Why expose fewer tools to the model?
- What information belongs in a tool risk registry?