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.
// 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.
npm i @pushary/eve// 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.
// 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.
- 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.
- 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.
- 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.
- 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.
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?
What does the Pushary channel cover?
Who does the channel ask by default?
What happens if nobody answers an Eve confirm?
Does this survive an Eve session resuming later?
Do my users need a Pushary account or app?
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 freePartner plan, $99 a month. Your users never make a Pushary account or pay us.