Human-in-the-loop for Eve

Let your Eve agent ask a human on their phone.

One file in agent/channels sends every approval your Eve agent raises to your user's phone.

What Eve gives you

approval: always(), never(), once()

Eve gets this right. Tools take an approval field, and eve/tools/approval ships always(), never() and once(). The runtime checks the policy before execute runs, instead of trusting the model to call an ask tool. There is also a built-in ask_question.

native
// agent/tools/issue-refund.ts
import { defineTool } from "eve/tools"
import { always } from "eve/tools/approval"
import { z } from "zod"

export default defineTool({
  description: "Refund an order",
  inputSchema: z.object({ amount: z.number() }),
  approval: always(),
  execute: async ({ amount }) => refund(amount),
})

Where it stops

  • The helpers decide up front. always() asks on every call, which teaches people to tap yes without reading.
  • Whoever is in the configured channel answers. The built-ins cannot ask a specific user of yours.
  • An approval is a moment in a channel. Nothing adds up to a record you can search or export.

The fix

Pushary gets your user's answer.

Eve parks the turn and shows approvals as Slack buttons. The Pushary channel puts the same request on your user's phone, waits for the tap without holding anything open, and saves the answer.

pusharyChannel() handles every request Eve parks on: any tool with an approval field, and the built-in ask_question. Your user taps Approve or Deny on their phone and the parked turn resumes. Nothing runs while they decide, so an approval can wait for hours.

bash
npm i @pushary/eve
one file
// agent/channels/pushary.ts
import { pusharyChannel } from "@pushary/eve"
export default pusharyChannel()

Or ask on purpose, from a tool

The channel covers what Eve already pauses on. To ask a question Eve would not stop for, add two tool files. pusharyAskHuman() waits for the answer and pusharyConnectPhone() returns the connect link. The model chooses when to call the ask tool, so it cannot block a refund. For that, put pusharyApproval() in the refund tool's approval field.

two tool files
// agent/tools/ask-human.ts
import { pusharyAskHuman } from "@pushary/eve"
export default pusharyAskHuman()

// agent/tools/connect-phone.ts
import { pusharyConnectPhone } from "@pushary/eve"
export default pusharyConnectPhone()

How it works

From install to the first answer

Connect your user once. Put the approval on the risky step. Pushary sends the question, waits for the answer and saves it.

  1. 01

    Install the package

    Run npm i @pushary/eve. Set PUSHARY_API_KEY and PUSHARY_WEBHOOK_SECRET, plus PUSHARY_CALLBACK_ORIGIN if the agent is not on Vercel.

  2. 02

    Connect your user once

    pusharyConnectPhone() returns a link. Your user opens it once on their phone with the free Pushary app. No account, no payment.

  3. 03

    Add the channel file

    Create agent/channels/pushary.ts with a default export of pusharyChannel(). Gate risky tools with Eve's approval field, and run user-scoped auth so each user answers for their own session.

  4. 04

    Act on the answer

    The tap posts to the answer route, which checks two signatures and resumes the parked turn. A no, an expired request or no answer means the tool does not run.

A question can be a yes or no, a choice from a list, or a typed answer. Every answer is saved.

Before you ship

Things that bite on Eve

once() remembers only an explicit yes
A denial, or carrying on without an answer, leaves the tool unrecorded, so the next call asks again. That is the safe behaviour, and it surprises people who read once() as fire-once. It also keys off the bare tool name and ignores compound approval keys.
The channel needs a public origin, and fails at runtime
pusharyChannel builds a signed callback URL from PUSHARY_CALLBACK_ORIGIN (or VERCEL_PROJECT_PRODUCTION_URL) and checks answers with PUSHARY_WEBHOOK_SECRET. Neither is checked at build time, so a missing one ships a working deploy that cannot receive answers. Set both before you ship.
A shared session has nobody to ask
The ask tool and the approval policy fall back to the session's signed-in user. An app-scoped or shared session has none, so they throw instead of asking the wrong person. Pass your own user id, or run user-scoped auth.

The Partner plan

Your agent asks your users, not just you

Ship your agent to your users. Each of them approves its risky actions from their phone, under your brand.

$99/ month, every framework included
Start free

Your users never make a Pushary account or pay us.

Your users answer on their own phones

Connect each user once with a link. They install the free Pushary app and answer from the lock screen, with no account of their own.

No answer means no

Run the action only after an explicit Approve. A request that is still waiting, expired or cancelled never counts as a yes. Pushary keeps every request, so your code can pick it up again after a restart.

Reaches them on their phone

The app comes first, because only the app puts Approve and Deny on the lock screen. Browser notifications come next. You build no delivery system.

Every answer saved

Every answer is saved with who answered and when. You can search and export it, and with a callback URL, each answer is also sent to your server as a signed webhook.

FAQ

Questions, answered

Is there an Eve package for Pushary?
Yes. @pushary/eve is on npm. It exports pusharyChannel() for agent/channels, pusharyApproval() for a tool's approval field, and pusharyAskHuman() and pusharyConnectPhone() as tool files. There is also @pushary/eve-extension, the mountable extension form of the two tools.
What does the Pushary channel cover?
Every request Eve parks on: any tool gated with approval from eve/tools/approval, and the built-in ask_question. Eve shows those as buttons in Slack. The channel sends the same requests to your user's phone as a push notification, and resumes the turn when they answer.
Who does the channel ask by default?
The session's signed-in user. Run user-scoped auth so each of your users is their own principal, and the channel asks that user. To always ask one fixed person, for a single-user agent or a scheduled run, pass your own user id to pusharyChannel().
What happens if nobody answers an Eve confirm?
The tool does not run. A declined, expired or unanswered confirm counts as not approved, so the agent skips the action and can say why it stopped. Pushary keeps the request, so an unanswered question is still visible afterwards.
Does this survive an Eve session resuming later?
Yes. Eve saves the parked session, and Pushary keeps the request on its side, each on its own. The request does not live in the process that created it, so a session that resumes minutes or hours later still gets the answer your user gave.
Do my users need a Pushary account or app?
No account, but they need the free Pushary app. They open your connect link once on their phone, tied to your own user id. They never sign up with Pushary, pay us, or hold a key.

Let your Eve agent ask before it acts.

Connect a user once, ask with one call, and run the action only on a yes. Every answer is saved.

Start free

Partner plan, $99 a month. Your users never make a Pushary account or pay us.