close

Overview

Actions are the single source of truth for anything your app does. Define one with defineAction() and drop it in actions/. Every surface below picks it up automatically. You write the logic once. No surface gets its own copy, and none can drift out of sync with the others.

Most apps build a separate API layer between the frontend and the database, one that only the browser can call. An agent that wants the same capability needs its own, separate integration, written and maintained again. Agent-native apps skip that split. The UI and the agent call the exact same action, so there is only one implementation to write, and only one place for a bug to hide.

Traditional frontend vs. Agent Native
Traditional
app/ (React frontend)
API layer only the frontend calls it
Database
vs
Agent Native
app/ (React frontend)
Agent
Actionsequal partners, same operations
SQL database

A traditional frontend calls its own API layer, which only it can reach. In Agent Native, app/ and the agent both call the same actions, so every capability built here is agent-callable for free.

If the UI and the agent both need to do something, reach for an action instead of a custom route. For the rare case where a route-shaped protocol is the right call, visit Prefer Actions For App Operations.

Where Actions Fit

app/ (visit Client Overview) and the agent both resolve to the same actions and the same SQL database. There is no separate "app backend" this layer talks to that the agent can't also reach. Building a feature means defining an operation here and getting a UI, an agent tool, and every other surface for it at the same time.

actions/ itself is a flat folder. Each file is one action: a defineAction() call exported as the file's default export. The framework scans this folder on startup and mounts every file it finds. There is no registration step and no index file to keep up to date.

An action's run() body is regular server code, so it commonly reaches into a few other directories:

5 files
reply-to-email.tsOne action per file. Exports a defineAction() call, and the framework mounts it automatically.
schema.tsDrizzle table definitions most actions read and write through.
access.tsShared access guards (accessFilter, assertAccess, authorize) reused across actions.
feature-flags.tsFlag definitions an action and a server plugin both check.

Nothing outside actions/ should import an action's run function directly. Call it through one of the surfaces above instead: the agent tool, a UI hook, HTTP, MCP, A2A, or the CLI. Every one of those paths runs the same schema validation, access checks, and audit trail.

Start with one action

The first operation in a chat-first app can be tiny. In the Chat template, replace actions/hello.ts or add a new file beside it:

// actions/hello.ts
import { defineAction } from "@agent-native/core/action";
import { z } from "zod";

export default defineAction({
  description: "Say hello from the local agent.",
  schema: z.object({
    name: z.string().default("world"),
  }),
  http: { method: "GET" },
  readOnly: true,
  run: async ({ name }) => {
    return { message: `Hello, ${name}!` };
  },
});

Run it from the same folder:

pnpm action hello '{"name":"Steve"}'

The CLI accepts a JSON object as the action input, which matches the structured tool calls agents already make. Simple flags still work for quick manual runs:

pnpm action hello --name Steve

Then run the app-agent loop against the folder:

npx agent-native agent "Call hello for Steve and explain the result"

That is the same app-agent loop your scheduled jobs, chat UI, external MCP tools, and future screens will use. Chat and domain templates are for adding UI around actions, not a required prerequisite for the action itself.

What's next