Compatibility: Pactmark 0.1.x. The current public authoring facade exposes kind: "read" operations. Write-effect authoring, decision challenges and reconciliation commands exist in the runtime surface and are being brought to the public facade — see Limits.

A tool is a declaration plus a function

Figure 1. Everything on the left contributes to the tool’s registration digest. That is why a tool cannot quietly become more dangerous while keeping an old grant.
The tool id is constrained to a namespace.name@version shape, and implementationVersion is separate on purpose: the contract version tells consumers what to expect, the implementation version makes a behaviour change visible even when the contract is unchanged.

What the execution context gives you

SafeRunContext
tenantId, runId, stepId, purposeCode, dataClass — enough to make a correct decision, and deliberately not enough to identify a human.
EgressHttpClient
The only network client a tool should use. It applies the declared allowlist, redirect policy, TLS verification and byte/time limits.
ArtifactWriter
Writes bytes and returns { artifactId, contentDigest }. Content addressing happens here, not later.
AbortSignal
Cancellation and deadline propagation. A tool that ignores it will be bounded by timeoutMs anyway, but it will waste the budget first.

Effects: the interesting half

A read observes. An effect changes something outside the run — and once it might have happened, your options narrow permanently. Figure 2. The strategy is registered on the tool, not chosen at failure time. That ordering is the whole safety property: you cannot decide it is safe to retry after you already want it to be.

Why “retry on timeout” is a bug

A timeout tells you the response did not arrive. It tells you nothing about whether the request was processed. Systems that retry on timeout are correct only when every target is idempotent — which is an assumption, not a fact. Pactmark makes you state the assumption up front:

native

You are claiming the target is idempotent for this exact effect key. Repeating is safe.

transactional

The effect commits together with local state, so a rollback removes it.

reconcilable

You cannot repeat it, but you can look it up afterwards and learn what happened.

none

No safe repetition exists. The effect parks and waits for an authorised human decision.

The effect ledger

Around every external boundary, Pactmark writes preparation, acknowledgement and reconciliation state. EffectPrepared carries an effectKey and the registered strategy; EffectDispatched carries an attempt number; EffectAcknowledged carries the acknowledgement and a result digest. When the outcome is unknown, EffectUncertain and then EffectNeedsReconciliation are recorded — and the schema forces effectMayHaveOccurred: true. You cannot record uncertainty while implying safety. Resolution has exactly two shapes: abandon_uncertain raises KAF_EFFECT_ABANDONED_UNCERTAIN for anything that later depends on it. That is intentional friction.

Compensation is a new authorised run

Compensation is not “undo”. CompensationRequested starts a separate compensation run with its own authority, its own WorkOrder and its own evidence. It is a business action that happens to reverse another one, and it is authorised as such.
Human guesses are not receipts. “It probably went through” is not a reconciliation resolution, and the API gives you nowhere to put it.

Defining tools

A practical walkthrough with egress, scopes and testing.

Incident recovery

Containment, classification and reconciliation when something has gone wrong.