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.
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:
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";
// One action powers every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
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 SteveThen 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
- Defining Actions —
defineAction()anatomy, schema, and HTTP config - Access & Authorization — exposure flags,
ctxscoping, andauthorize - Run Context — the full
ctxfield table - Other Surfaces — native chat UI, CLI, A2A, MCP, and standard actions
- Advanced & Legacy — feature-flag gating and legacy CLI-style actions
- Overview — the
app/side of this same contract - Data & Sync —
useActionQuery,useActionMutation, andcallActionin the UI - Agent Surfaces — how one action grows from chat tool to full screen