Wrap any Trigger.dev v4 task with policy-gated settlement in one line.
The missing middleware for risky workflows. Before a task sends money, refunds a customer, or deploys to prod, AgentChain evaluates policy and returns a receipt that is safe to settle against.
pnpm add @humbleaf/trigger-verify @humbleaf/agentchain-sdk// trigger/init.ts
import { tasks } from '@trigger.dev/sdk';
import { agentchainMiddleware } from '@humbleaf/trigger-verify';
tasks.middleware('agentchain', agentchainMiddleware({
apiKey: process.env.AGENTCHAIN_API_KEY!,
agentId: process.env.AGENTCHAIN_AGENT_ID!,
approvalMode: 'soft-fail',
}));import { task } from '@trigger.dev/sdk';
import { invoiceSafe, agentchain } from '@humbleaf/trigger-verify';
export const payInvoice = task({
id: 'pay-invoice',
...invoiceSafe({
vendor: (p) => p.vendorWallet,
amount: (p) => p.amount,
evidence: (p) => ({ invoiceHash: p.hash, vendorId: p.vendorId }),
}),
run: async (payload) => {
const receipt = agentchain.getReceipt();
if (receipt?.policyVerdict !== 'released') {
return { paid: false, approvalUrl: receipt?.approvalUrl };
}
const tx = await transferUSDC(payload.vendorWallet, payload.amount);
return { paid: true, tx, receiptId: receipt.receiptId };
},
});Trigger run: pay-invoice
Payload: { vendorWallet: "0xAcme", amount: 480, hash: "0xabc..." }
AgentChain: ✓ Policy passed (5 checks)
Result: { paid: true, tx: "0x...", receiptId: "rcpt_abc123" }
Trigger run: pay-invoice
Payload: { vendorWallet: "0xUnknown", amount: 480, hash: "0xdef..." }
AgentChain: ✗ Denied — COUNTERPARTY_NOT_APPROVED
Result: { paid: false, reason: "Vendor not on allowlist" }
Both results appear as receipts in your AgentChain dashboard with full policy evaluation details.
| Preset | Use Case | Policies Activated |
|---|---|---|
invoiceSafe() |
Pay invoices, rewards, refunds | Max value, counterparty allowlist, duplicate prevention, approval threshold |
deploySafe() |
Deploy to production | Evidence required, time window, restricted action escalation |
import { deploySafe } from '@humbleaf/trigger-verify';
export const deployService = task({
id: 'deploy-to-prod',
...deploySafe({
service: 'payments-api',
environment: 'production',
evidence: (p) => ({
gitSha: p.commitHash,
testsPassed: p.ciGreen,
rollbackArtifact: p.rollbackDigest,
}),
}),
run: async (payload) => { /* ... */ },
});Controls what happens when policy returns pending_approval:
| Mode | Behavior | Best For |
|---|---|---|
'throw' |
Task fails with AgentChainApprovalRequired |
Simple workflows |
'soft-fail' |
Receipt stored, task continues | Queue handoff, Slack alerts |
'wait' |
Polls until approved or timeout | Human-in-the-loop |
agentchainMiddleware({
apiKey: '...',
agentId: '...',
approvalMode: 'soft-fail', // ← your choice
});When approvalMode: 'soft-fail', use the approval URL for dashboard handoff:
if (receipt?.policyVerdict === 'pending_approval') {
await slack.send({
text: `⏳ Invoice $${payload.amount} needs approval`,
url: receipt.approvalUrl,
});
}MIT