# How to Use the `needsApproval` Feature for Human-in-the-Loop Agent Actions in Agent-Native

> Master Agent-Native's needsApproval feature for human-in-the-loop agent actions. Block the agent loop and require explicit user confirmation for critical operations.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-07-02

---

**The `needsApproval` feature in Agent-Native lets you flag specific agent actions to require human confirmation before execution, pausing the agent loop and emitting an `approval_required` event until a user explicitly approves the operation.**

Agent-Native, an open-source agent framework from BuilderIO, provides built-in safeguards for high-stakes operations through human-in-the-loop controls. By adding the `needsApproval` option to your action definitions, you can force the production agent to pause execution and wait for explicit human confirmation before proceeding with sensitive operations like sending emails or processing payments.

## How `needsApproval` Works in Agent-Native Architecture

Agent-Native implements human-in-the-loop controls through a coordinated system spanning action definitions, the production agent loop, and SSE event handling.

### Action Registration with `defineAction`

Actions in Agent-Native are created using the `defineAction` function, which accepts an `ActionOptions` object containing the `needsApproval` field. In [`packages/core/src/action.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/action.ts), the type definition allows `needsApproval` to be either a boolean or a predicate function that receives arguments and context.

When you register an action in a template file (such as [`templates/mail/actions/send-email.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/actions/send-email.ts)), the `needsApproval` value is stored in the action's metadata as `ActionEntry.needsApproval`. This metadata persists alongside the action's name, description, and input schema.

### The Production Agent Loop

When the production agent processes a tool call, it checks the `actionEntry.needsApproval` property before invoking the action's `run()` method. According to the implementation in [`packages/core/src/agent/production-agent.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/agent/production-agent.ts), specifically within the `runToolCall` function, the agent performs the following logic:

- If `needsApproval` is truthy and the call has not been approved, the agent **pauses the turn immediately**
- The agent emits an `approval_required` SSE event to the client
- The action's `run()` implementation never executes during this paused state

This pause mechanism ensures that destructive or outbound operations cannot proceed automatically, creating a hard stop in the agent execution loop until human intervention occurs.

### Human Approval Flow

The front-end client listens for `approval_required` events through the SSE connection managed in [`packages/core/src/client/sse-event-processor.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/sse-event-processor.ts). When the UI receives this event, it displays an approval affordance (typically an "Approve" button) that captures user intent.

Upon approval, the client re-issues the tool call with a stable `approvalKey`. The production agent recognizes this key, marks the call as approved in its internal state, and resumes execution by invoking the action's `run()` method with the original arguments.

## Implementing `needsApproval` in Practice

You can implement approval gates using either static boolean flags or dynamic predicate functions that evaluate at runtime.

### Boolean Approval (Always Required)

For actions that should always require human confirmation regardless of context, set `needsApproval` to `true`. This pattern is ideal for irreversible operations like sending external communications.

```typescript
// templates/mail/actions/send-email.ts
import { defineAction } from "@agent-native/core";

export default defineAction({
  name: "send-email",
  description: "Send an email to a recipient",
  input: {
    to: { type: "string", required: true },
    subject: { type: "string", required: true },
    body: { type: "string", required: true },
  },
  // Human must approve every invocation
  needsApproval: true,

  async run({ to, subject, body }) {
    await emailProvider.send({ to, subject, body });
    return { success: true };
  },
});

```

This configuration ensures that every email sent through the agent requires explicit human confirmation before the `emailProvider.send()` method executes.

### Predicate-Based Approval (Conditional)

For actions where approval depends on specific arguments, provide a predicate function that receives the action arguments and context. This approach is documented in the framework's security skills and allows conditional gating based on business logic.

```typescript
// templates/dispatch/actions/charge-card.ts
import { defineAction } from "@agent-native/core";

export default defineAction({
  name: "charge-card",
  description: "Charge a credit card",
  input: {
    amountCents: { type: "number", required: true },
    currency: { type: "string", required: true },
  },

  // Require approval only for transactions over $100
  needsApproval: (args) => args.amountCents > 10_000,

  async run({ amountCents, currency }) {
    await paymentGateway.charge({ amountCents, currency });
    return { charged: true };
  },
});

```

The predicate function `(args) => args.amountCents > 10_000` evaluates during each tool call. If it returns `false`, the action executes immediately without pausing for approval.

### Handling Approval Events in the UI

Your client application must listen for `approval_required` events and handle the approval interaction. The following simplified React example demonstrates the pattern used in [`packages/core/src/client/sse-event-processor.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/sse-event-processor.ts):

```tsx
import { useSSE } from "@agent-native/core/client";

function AgentChat() {
  const { events, sendToolCall } = useSSE();
  
  const approvalEvent = events.find(e => e.type === "approval_required");
  
  if (approvalEvent) {
    return (
      <ApproveDialog
        actionName={approvalEvent.actionName}
        args={approvalEvent.args}
        onApprove={() => sendToolCall(approvalEvent.approvalKey)}
        onReject={() => {/* handle rejection */}}
      />
    );
  }

  // Render standard chat interface
  return <ChatInterface />;
}

```

The `approvalKey` provided in the event is a stable identifier that allows the agent to correlate the approval response with the paused tool call.

## Best Practices for Human-in-the-Loop Actions

According to the security documentation in templates like [`templates/mail/.agents/skills/security/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/.agents/skills/security/SKILL.md), follow these guidelines when implementing `needsApproval`:

- **Reserve for high-consequence operations** — Use `needsApproval` exclusively for outward-facing, hard-to-undo actions such as sending emails, charging credit cards, or deleting accounts.
- **Keep approvals rare** — The default value is `false`. Overusing approval gates degrades the agent's autonomy and creates a click-through wizard experience.
- **Layer with access controls** — Do not replace `accessFilter` or permission checks with `needsApproval`. This feature provides an additional human-blessing layer, not a substitute for security validation.
- **Document in skill files** — Include `needsApproval` usage examples in your template's [`.agents/skills/actions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/.agents/skills/actions/SKILL.md) documentation to clarify when and why human intervention is required.

## Summary

- **Action definition**: Add `needsApproval: true` or a predicate function to `defineAction` in [`packages/core/src/action.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/action.ts) to flag actions requiring human confirmation.
- **Agent pause**: The production agent in [`packages/core/src/agent/production-agent.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/agent/production-agent.ts) pauses execution and emits `approval_required` events when flagged actions are invoked.
- **UI handling**: Client applications using [`packages/core/src/client/sse-event-processor.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/sse-event-processor.ts) must listen for approval events and re-send tool calls with the provided `approvalKey` to resume execution.
- **Conditional logic**: Use predicate functions to gate approval based on runtime arguments, enabling conditional human-in-the-loop workflows.
- **Security layering**: Treat `needsApproval` as an additional safeguard rather than a replacement for access control mechanisms.

## Frequently Asked Questions

### What is the difference between `needsApproval` and `accessFilter` in Agent-Native?

`accessFilter` controls whether an action is available to a specific user based on permissions or roles, evaluated before the action is considered for execution. `needsApproval` pauses execution after the action is selected but before it runs, requiring explicit human confirmation regardless of permissions. Use `accessFilter` for authorization and `needsApproval` for operational safety.

### Can `needsApproval` be asynchronous?

Yes, the `needsApproval` predicate can be an async function returning `Promise<boolean>`. This allows you to perform asynchronous checks (such as querying a database or external service) to determine whether a specific invocation requires human approval. The agent loop awaits the predicate result before deciding whether to pause for approval.

### What happens if a user rejects an approval request?

When the UI receives an `approval_required` event, it can handle rejection by not sending the `approvalKey` back to the agent. The agent remains in a paused state for that specific tool call. You should implement a timeout or cancellation mechanism in your client application to formally abort the agent turn if the user rejects the operation, as the agent will otherwise wait indefinitely for the approval signal.

### Where is the `needsApproval` feature documented in the BuilderIO/agent-native repository?

The feature is documented in multiple locations: [`packages/core/src/action.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/action.ts) defines the TypeScript interface for the option; [`templates/mail/.agents/skills/actions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/.agents/skills/actions/SKILL.md) provides usage examples; and [`templates/mail/.agents/skills/security/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/.agents/skills/security/SKILL.md) explains the security rationale and best practices. The production implementation details are found in [`packages/core/src/agent/production-agent.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/agent/production-agent.ts) and the client-side handling is in [`packages/core/src/client/sse-event-processor.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/sse-event-processor.ts).