@fidacy/langchain
v0.3.0
Published
Gate LangChain tool calls behind a signed Fidacy verdict. Wraps the tool itself, so a denied payment never executes: callbacks can only observe, this can stop.
Maintainers
Readme
@fidacy/langchain
Gate LangChain tool calls behind a signed Fidacy verdict. A denied payment never executes.
npm i @fidacy/langchain @langchain/coreimport { guardTools } from "@fidacy/langchain";
// No key, no signup: the offline judge decides your first 20 calls.
const tools = guardTools([payTool, searchTool]);
// payTool is now assessed before every call. searchTool is returned untouched.Two ways it decides
Offline, out of the box. With no credential the deterministic local judge runs: deny-by-default, your own limits, a hash-chained log on your machine. The first 20 decisions on an install are free and need no account, so you can watch a payment get blocked before deciding whether you want any of this.
Set the limits that fit your case:
const tools = guardTools([payTool], {
mandate: { payees: ["acme-inc"], perTxMax: 5_000, maxTotal: 50_000 },
});A blocked call tells you what was stopped, in money terms, and prints the exact edit that would allow it.
Hosted, with an account. Pass apiKey and the engine decides instead,
returning a verdict signed with a stable key that anyone can verify against the
public JWKS, plus a Bitcoin-anchored audit chain that outlives your process.
Free key, no card: https://fidacy.com/claim
Why not a callback handler
LangChain callbacks (handleToolStart and friends) are observational. They fire
around the call and cannot cancel it, so a guardrail built on them watches the
money leave. This replaces the tool's own execution path, which is the only place
that can actually stop a payment.
Fail-closed
If the engine is unreachable, the key is wrong, or the request times out, the call is denied. A payment firewall that opens when it breaks is not a firewall.
review also blocks by default. Set reviewIsDeny: false to let it through.
What a denial looks like
guardTool throws FidacyDenied with the signed proof attached:
try {
await tools[0].invoke({ payee: "acme", amount: 5000 });
} catch (e) {
if (e instanceof FidacyDenied) {
e.decision; // "deny" | "review" | "unavailable"
e.verdict?.riskPayloadJws; // detached JWS, verify offline with @fidacy/verify
e.verdict?.assessmentId;
}
}Options
| Option | Default | What it does |
| --- | --- | --- |
| apiKey | FIDACY_ENGINE_API_KEY | Engine credential. Omit it to stay offline |
| mandate | none | Offline limits: payees, perTxMax, maxTotal, currency |
| engineUrl | https://api.fidacy.com | Engine base URL |
| client | built from apiKey | Bring your own @fidacy/sdk client |
| isPayment | name heuristic | Which tools guardTools gates |
| toMandate | payee/amount/currency + raw args | Build the mandate the engine assesses |
| reviewIsDeny | true | Whether review blocks |
| onDecision | none | Observe every decision, including approvals |
guardTool(tool, opts) gates one tool unconditionally. guardTools(list, opts)
gates only the ones isPayment matches and returns the rest by identity, so
non-payment tools carry no cost.
The wrapped tool keeps its name, description and schema, so the model sees no difference.
Anonymous telemetry
The adapter reports anonymous usage so we can tell which surfaces are alive: an install marker, an "agent active" ping, the result class of local decisions (allow, deny_payee, deny_cap and so on) and the moment the free-trial wall is shown. Every field is a closed enum. It never carries payees, amounts, currencies, tool arguments or any content, and it never sits on the path of a decision: failures are swallowed and nothing blocks.
Disable it entirely with:
export FIDACY_DISABLE_TELEMETRY=1The anonymous id lives in ~/.fidacy/config.json, shared with @fidacy/mcp,
so the free-decision counter and a later account claim stay consistent across
Fidacy tools on the same machine.
Apache-2.0 · https://fidacy.com
