Compatibility: Pactmark 0.1.x. The public defineTool facade currently accepts operation.kind: "read". Write effects are exercised through the runtime effect surface — see Limits.

The shape

Getting the declaration right

Figure 1. Every field on the left is enforced somewhere and contributes to the registration digest. Filling them in accurately is the work; the function body is usually the easy part.

id and implementationVersion are different axes

The id must match namespace.name@version — that version is the contract. The implementationVersion describes the code behind it. Change the body without changing behaviour and you bump the implementation version; change the schema or the security posture and you need a new contract version.
Do not reuse a contract version after changing what a tool can reach. The registration digest will change anyway and existing grants will stop matching — but a reused version makes the change invisible to humans reading a diff.

requiredScopes is a demand, not a description

These are the scopes a grant must already carry for the call to proceed. Keep them narrow and resource-shaped (billing:invoice:read), not role-shaped (admin).

egress is an exact origin allowlist

networkEnforcement: "required" means the host must supply a real egress broker; a runtime that cannot enforce the allowlist refuses the tool rather than running it unprotected. Always call context.egress.fetch. A raw global fetch bypasses the broker, and on a portable runtime it may not exist at all.

maxCallsPerRun and timeoutMs are ceilings

They are checked by the runtime, not by your function. Pick numbers you would be comfortable seeing in an incident report — a looping model will find the ceiling.

Return values are validated

execute returns z.input<O>, and the result is parsed against the output schema before it becomes a ToolCallCompleted event or reaches model context. A tool that returns an unexpected shape fails with KAF_SCHEMA_INVALID rather than poisoning the run.
Tool results that flow back into context are also bounded by maxToolResultToContextBytes on the model resource profile. A tool that returns a megabyte does not silently blow up the context window; it hits a declared limit.

Treat every external result as untrusted

Content that comes back from a tool, an HTTP call or an MCP server is data, not instruction. It is admitted into context under explicit bounds, and it never grants authority.

Testing a tool

@pactmark/testing is a development dependency and is never part of a production export. It provides fake clocks, deterministic id generators, fake model drivers, crash injection and reusable adapter contract suites.

Policy and grants

Turning requiredScopes into an issued, expiring, revocable grant.

MCP integration

Adopting external tools without adopting external authority.