Integrate an agent

Two changes. The owner approves the guard rather than the agent, and the agent calls requestAction instead of calling the venue.

// 1 — the owner, once: the guard is the sole approved spender.
    await usdc.write.approve([guard.address, allowance], { account: owner });

    // 2 — the agent, per action. `data` is the calldata it would have sent.
    const { request } = await publicClient.simulateContract({
      address: guard.address,
      abi: guardAbi,
      functionName: "requestAction",
      args: [
        mandateHash,      // bytes32 — the EIP-712 digest of the signed terms
        target,           // address — a venue the owner classified
        amountIn,         // uint256 — notional in tokenIn base units
        minAmountOut,     // uint256 — the agent's own floor
        data,             // bytes   — what to call on the target
      ],
      account: agent,
    });
    const hash = await walletClient.writeContract(request);

The guard pulls tokenIn from the owner, approves the target for exactly that amount, calls it with data, and measures what came back. Venue and notional are derived inside the guard, never accepted as parameters: a value the caller controls is not a constraint.

Reading the answer

A held action does not revert, so a receipt is not enough — read the flags:

const logs = parseEventLogs({ abi: guardEvents, logs: receipt.logs });
    const intent = logs.find((l) => l.eventName === "IntentRecorded");
    const held   = logs.find((l) => l.eventName === "ActionHeld");

    if (held) {
      // The sequence stopped. A human decides; the agent does not retry blind.
      const { flags } = intent.args;
    }

Never treat a hold as a transport failure. Retrying it is how an agent turns one refusal into a queue of refusals. The run ends where a supervisor's work begins — that is the product.