Quickstart
Install the guard, wire its ports, state a policy, and halt on a fingerprint that disagrees.
Node ≥ 24 (or Bun, or Deno — see runtimes).
npm install @integraledger/agent-guardThe guard peers the @integraledger/lcp-* protocol line as ^0.13.0. Your package manager installs one
copy that satisfies both it and any sibling; see one protocol line
for why that shape matters.
The whole gate
import {
type BuyerPolicy,
type GuardedSigner,
makeCachingFetcher,
nodeDnsLookup,
parseProposalFromChallenge,
transact,
} from "@integraledger/agent-guard";
declare const challenge: unknown; // the 402 body the seller returned
declare const policy: BuyerPolicy; // your risk posture — caps, jurisdictions, forbidden clauses
declare const signer: GuardedSigner; // your key. Reachable only on Proceed.
const now = () => new Date().toISOString();
const fetcher = makeCachingFetcher({ httpFetch: fetch, now, lookup: nodeDnsLookup });
const proposal = parseProposalFromChallenge(challenge, {
level: 3,
sellerAssurance: "domain-controlled",
});
const result = await transact(proposal, policy, { fetcher, now }, signer);result.kind === "signed" only on Proceed. On Decline or Escalate the signer is never called.
Reading the result
import {
type BuyerPolicy,
type GuardedSigner,
type GateProposal,
type GatePorts,
transact,
} from "@integraledger/agent-guard";
declare const proposal: GateProposal;
declare const policy: BuyerPolicy;
declare const ports: GatePorts;
declare const signer: GuardedSigner;
const result = await transact(proposal, policy, ports, signer);
if (result.kind === "signed") {
// result.signature — produced by YOUR signer, over the atrHash the gate verified.
// result.atrHash — the fingerprint that was proved before the key was reached.
} else if (result.decision.kind === "decline") {
// result.decision.haltClass — "verification-failure" | "policy-rejection" | …
// result.decision.code — e.g. "gate/fingerprint-mismatch"
// result.decision.detail — one sentence you can report to the seller
} else {
// result.decision.kind === "escalate"
// result.decision.bytes — the EXACT terms to display to a human approver
// result.decision.hash — sha256(bytes): what is displayed is what would be signed
// result.decision.reason — why it escalated
}The decline codes page lists every code the gate can return and what a seller should
do about each.
A minimal policy
Every field is a decision you are making on the record. There are no defaults, because a default here is a risk posture nobody chose.
import type { BuyerPolicy } from "@integraledger/agent-guard";
const policy: BuyerPolicy = {
requiredLevel: 2, // 2 = a fingerprint must be advertised
acceptableJurisdictions: ["US-NY"], // or "any"
acceptableDisputeMethods: ["arbitration"],
maxCommitment: { // per unit, in base units, as strings
"eip155:8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": "5000000",
},
forbiddenClauseCategories: ["unilateral-amendment"],
requiredAssurance: "domain-controlled", // or "any"
onNotAttempted: "decline", // how a coverage gap resolves
};Field by field, with the reasoning behind each: buyer policy.
A signer the gate can gate
GuardedSigner is the seam that makes "before any signing key is invoked" true rather than aspirational.
Give the gate an object whose sign is the only route to your key, and the guarantee holds by
construction.
import type { GuardedSigner } from "@integraledger/agent-guard";
declare function signWithMyKey(hash: `0x${string}`): Promise<`0x${string}`>;
const signer: GuardedSigner = {
async sign(verifiedAtrHash) {
// Reached ONLY on Proceed. `verifiedAtrHash` is the fingerprint that was proved.
return { signature: await signWithMyKey(verifiedAtrHash) };
},
};The gate binds the atrHash it verified. Your signer signs through its own authority artifact — a grant
or an acceptance — by its own contract, never a bare hash.
Keeping a record
Pass a log and the gate records every decision it reaches, including the document the decision was about.
import {
InMemoryOrc4Log,
type GatePorts,
type TermsFetcher,
} from "@integraledger/agent-guard";
declare const fetcher: TermsFetcher;
const log = new InMemoryOrc4Log();
const ports: GatePorts = {
fetcher,
now: () => new Date().toISOString(),
log,
};
// log.entries — timestamp, decision, haltClass, code, detail, atrHash, legalContextUrlInMemoryOrc4Log is a real, complete sink rather than a stand-in; a deployment injects a durable one by
implementing the same one-method interface.
Next
- Ports — what you are trusting when you wire the fetcher, and why there are no defaults.
- Verify before sign — the order the gate runs its steps in, and what each one can conclude.
- Protocols — you are not on x402? Start here.
Last updated on