AgentsOpen SourceFreeActiveMachine-verified· intermediate · ~15 min setup

Eve: gate the dangerous tool behind a human, in one field

Make an Eve agent's irreversible tool stop and wait for a person above a threshold, using the needsApproval predicate, and verify the file is shaped right before you trust it.

by Shilpa Mitra· verified today· v1.0.0

Run this workflow

CI-verified, 2/2 fixtures passing.

Build this with your agent

One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.

Intended Use

Anyone giving an Eve agent a tool that costs money or cannot be undone. CI writes the files and runs a node structure check: refund_payment.ts exports a defineTool with a Zod inputSchema and a needsApproval function, and agent.ts declares a model. A type-and-structure check, no npm install, no model run. The agent actually pausing for approval is fenced.

Not for

  • Treating approval as containment, it limits blast radius, it does not contain a bad tool; the model still decides when to call it, so scope the tool narrowly
  • Gating everything, if every call needs approval people rubber-stamp the prompts and the gate stops meaning anything; gate the consequential subset

The Stack

Tested Against

github.com/vercel/eve + vercel.com/docs/eve (2026-06, eve@0.11.x)node@20

Side effects & data flow

Network
none, local only
Writes
./agent/agent.ts, ./agent/tools/refund_payment.ts, ./lint.mjs
Credentials
none required

Prerequisites

  • Node 20+
  • An Eve project (npx eve@latest init) to actually run it

Steps

  1. 1

    Author the gated tool and structure-check it

    Write agent.ts (names the model) and agent/tools/refund_payment.ts (a typed defineTool whose needsApproval predicate returns true above a threshold). CI runs a node check that the files are shaped right. The agent pausing for a human only happens on a live run, so that step is fenced.

    mkdir -p agent/tools
    cat > agent/agent.ts <<'TS'
    import { defineAgent } from "eve";
    
    export default defineAgent({
      model: "anthropic/claude-opus-4.8",
      name: "billing-assistant",
    });
    TS
    cat > agent/tools/refund_payment.ts <<'TS'
    import { defineTool } from "eve/tools";
    import { z } from "zod";
    import { processRefund } from "../lib/billing";
    
    export default defineTool({
      description: "Refund a payment to a customer by payment id.",
      inputSchema: z.object({
        paymentId: z.string(),
        amountUsd: z.number().positive(),
      }),
      needsApproval: ({ toolInput }) => toolInput.amountUsd >= 100,
      async execute({ paymentId, amountUsd }) {
        const result = await processRefund(paymentId, amountUsd);
        return { ok: result.ok, refundId: result.id };
      },
    });
    TS
    cat > lint.mjs <<'MJS'
    import { readFileSync } from "node:fs";
    const agent = readFileSync("agent/agent.ts", "utf8");
    const tool = readFileSync("agent/tools/refund_payment.ts", "utf8");
    function fail(m) { console.error("BAD: " + m); process.exit(1); }
    if (!agent.includes("defineAgent")) fail("agent.ts has no defineAgent");
    const mi = agent.indexOf("model:");
    if (mi < 0) fail("agent.ts declares no model");
    const mseg = agent.slice(mi, mi + 60);
    if (!mseg.includes(String.fromCharCode(34)) && !mseg.includes("'")) fail("model has no string value");
    if (!tool.includes("defineTool")) fail("refund_payment.ts has no defineTool");
    if (!tool.includes("inputSchema") || !tool.includes("z.object")) fail("tool has no Zod inputSchema");
    const ai = tool.indexOf("needsApproval");
    if (ai < 0) fail("tool has no needsApproval");
    if (!tool.slice(ai, ai + 80).includes("=>")) fail("needsApproval is not a function");
    console.log("config OK: agent.ts declares a model, refund_payment.ts exports a defineTool with a Zod inputSchema and a needsApproval function");
    MJS
    node lint.mjs
  2. 2

    Run it and approve a refund (the live step, not checked by CI)

    Scaffold a real project with npx eve@latest init, run eve dev, and ask it to refund an amount over the threshold; the agent pauses at refund_payment and waits for your approval, then resumes. The model deciding and the tool firing are fenced.

Eval, 2 fixtures

Last passed: verified today
  • approval-okcontainstimeout 30s · max $0

    Expected: config OK: agent.ts declares a model, refund_payment.ts exports a defineTool with a Zod inputSchema and a needsApproval function

  • clean-exitexit_codetimeout 30s · max $0

    Expected: 0

Results

An agent that can touch real systems should not do the irreversible thing unsupervised. In Eve a tool is one typed file and needsApproval is one predicate: return true and the agent pauses at that call, waits for a human without burning compute, then resumes exactly where it stopped once approved.

Did this work for you?

Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.

Liked this workflow?

Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).