Agent Surfaces
A surface is the way users (or other systems) interact with your app: a chat window, a dashboard page, a background job, an API call from another agent. Agent-Native lets you mix and match these without rebuilding your core logic, because every surface runs the same underlying actions. If you're new to Agent-Native, read Key Concepts first.
How surfaces relate to each other
The four main product shapes sit on a spectrum from most interactive to fully headless. What makes them composable is that they share the same actions and SQL-backed app state. Use the framework agent loop when a surface needs an agent. Adding a new surface doesn't mean rewriting what's underneath — you're adding a new way to reach the same operations.
One action surface, four product shapes, each adding UI without changing the operation underneath.
Choose a starting point
Chat is the most common entry point. Apps typically grow inline UI as output gets richer, then add full app pages when users need persistent objects to browse and share. The same actions power the buttons, scheduled jobs, and external agents that come later. Use the Embedded sidecar when adding an agent to a product you already own, or Automation-first for work that runs without a browser. Here's the full picture:
| Surface | Use it when | Start with |
|---|---|---|
| Rich chat | Users talk to the agent, see tool calls, and keep a thread history. | Chat template, <AgentChatSurface> |
| Native inline UI | Action results should render as tables, charts, cards, or approvals in chat. | Native Chat UI, chatUI.renderer |
| Generated inline UI | The agent should create temporary or reusable controls inside chat on the fly. | Generative UI, render-inline-extension |
| Full application | Users need durable screens, shared data, navigation, and collaboration. | Templates, actions, SQL state, context awareness |
| Embedded sidecar | You already have a SaaS app and want an agent beside it with page context. | createAgentNativeEmbeddedPlugin(), AgentNativeEmbedded |
| Automation-first | Jobs, scripts, or other agents call the work directly with no browser UI. | agent-native create --headless, defineAction, HTTP, CLI, MCP, A2A |
| Rich chat on your agent | You built the agent elsewhere and want Agent-Native's chat UI around it. | AgentChatRuntime, <AssistantChat runtime={runtime}> |
Rich chat on Agent-Native
Use the built-in chat when the user should talk to the agent, see tool calls, approve work, inspect native results, and keep a durable thread history.
For a full app starting point, use the Chat template:
npx --yes @agent-native/core@latest create my-chat-app --template chatThe simplest full-page chat:
import { AgentChatSurface } from "@agent-native/toolkit/app/chat";
export default function ChatRoute() {
return <AgentChatSurface mode="page" className="h-screen" />;
}When an app has both a full-page chat tab and an AgentSidebar, use the same
storageKey on both surfaces, enable chatViewTransition, and install the
chat-home handoff helpers in the layout. Ordinary in-app links out of the chat
page can then morph the full chat into the sidebar while keeping the active
thread:
import { AgentChatSurface, AgentSidebar } from "@agent-native/toolkit/app/chat";
import {
useAgentChatHomeHandoff,
useAgentChatHomeHandoffLinks,
} from "@agent-native/core/client/agent-chat";
import { useLocation } from "react-router";
function ChatRoute() {
return (
<AgentChatSurface mode="page" storageKey="my-app" chatViewTransition />
);
}
function AppLayout({ children }: { children: React.ReactNode }) {
const location = useLocation();
const handoffActive = useAgentChatHomeHandoff({
storageKey: "my-app",
activePath: location.pathname,
enabled: location.pathname !== "/chat",
});
useAgentChatHomeHandoffLinks({ storageKey: "my-app", chatPath: "/chat" });
return (
<AgentSidebar
storageKey="my-app"
chatViewTransition
openOnChatRunning={handoffActive}
>
{children}
</AgentSidebar>
);
}The simplest embedded chat with your own chrome:
import { AssistantChat } from "@agent-native/toolkit/app/chat";
export function ProjectChat({ threadId }: { threadId: string }) {
return <AssistantChat threadId={threadId} />;
}Actions can return explicit native widget results so chat output is not just text. Tables, charts, and typed product cards render as first-party React components in the chat, without iframes. See Native Chat UI. When the agent needs arbitrary generated controls instead of a predefined React widget, use Generative UI: it renders sandboxed Alpine/Tailwind UI inline, can read app state and slot context, and can send selected values back to chat.
Request-scoped action surfaces
Use resolveActionSurface when the selected agent or thread must expose only a
server-authorized subset of native actions. The callback runs for every
interactive chat request after prepareRequest. Returned names form a hard
allowlist for that request: omitted actions are absent from provider schemas,
the execution registry, plan-mode preloading, and tool-search discovery.
createAgentChatPlugin({
actions,
nativeActionsInDev: true,
resolveActionSurface: async ({ threadId, availableActionNames }) => ({
allowedActionNames: await loadAllowedActions(
threadId,
availableActionNames,
),
}),
});When the same resolver also handles unrestricted chat, return
{ mode: "default" }. This preserves the normal initialToolNames filtering
and tool-search discovery instead of loading every available schema on the
first model request. The explicit mode is preserved across durable
continuations.
An empty list exposes no native actions. Unknown names fail the request instead
of widening access. Every allowed action is loaded directly on the first model
request; include tool-search explicitly only when discovery inside the
already-authorized catalog is wanted. The callback scopes interactive agent
chat only—it does not change HTTP, MCP, A2A, job, or trigger exposure. Default
framework guidance and spawned sub-agents inherit the same surface. Trusted
shell tools are automatically reduced to the request-filtered sandbox because
an unrestricted shell cannot enforce a hard action boundary.
Native inline UI
Use this when your actions return structured data — a list of records, a chart dataset, a status summary — that should render as a real UI component inside the chat thread rather than a plain text description. You define a chatUI renderer on the action, and Agent-Native renders it as a first-party React component: no iframes, no separate rendering path.
This is the right choice when the output has a clear, reusable shape that you'd design once and use across many agent responses. For controls the agent needs to create dynamically at runtime, see Generated inline UI instead.
See Native Chat UI for the full renderer API, widget library, and BYO agent runtime integration.
Generated inline UI
Use this when the agent needs to create a control that doesn't exist yet as a pre-built widget — a custom form, a picker built around the current context, a one-off calculator. Unlike native widgets, generated UI is composed by the agent at runtime from Alpine.js and Tailwind, runs sandboxed in an iframe, and can send selected values back into the chat thread.
Generated UI can be transient (rendered once and discarded) or saved as a reusable extension that persists for the user.
See Generative UI for the full API, sandbox constraints, and extension persistence model.
Full application
Use the full app path when users need durable objects and workflows: forms, dashboards, calendars, inboxes, editors, documents, assets, or reports.
Full apps add product UI around the same action and agent contract:
SQL state
App data, navigation, settings, and chat history are durable in SQL. The agent reads and writes the same rows the UI does.
Context awareness
The agent knows the current route, selection, and focused object, so "edit this" always means the right thing.
Live sync
Agent changes update the UI through live sync, and UI changes update the agent's context. The framework uses SSE with polling fallback, so users do not need to refresh manually.
Deep links
Action results can open the right app view directly: a chart links to the dashboard, a draft links to the inbox.
Native chat widgets
Tables, charts, cards, approvals, and typed results render as first-party React components inline in chat.
Generative UI and extensions
The agent can create inline controls on the fly, and save reusable mini-apps when a workflow needs to persist.
Start from the Chat template when you want a minimal app around your actions, or from a domain template when you want a complete product shape.
Full-page Manage Agent
Every Agent-Native app eventually needs a place where users can configure their agent: set standing instructions, review what it's done, connect MCP servers, manage automations, and control access. Building that UI from scratch is a lot of work. Agent-Native ships a pre-built full-page component, AgentTabsPage, that covers all of it across twelve tabs.
First-party templates don't mount it: Settings covers the same ground (Agent › Model, Instructions, Memory, Skills, Files, and Sub-agents, plus the app's Automations and MCP server pages), /agent redirects into Settings, and agentPageHref="/settings/agent" on AgentSidebar opens Settings › Agent › Model. Mount AgentTabsPage on its own route when an app wants the whole agent surface on one page.
import { AgentTabsPage } from "@agent-native/toolkit/app/agent-page";
export default function AgentRoute() {
return <AgentTabsPage />;
}The shared page currently provides twelve tabs across two groups:
| Group | Tab | Shows |
|---|---|---|
| Resources | Files | The existing ResourcesPanel for personal or organization files |
| Resources | Instructions | Always-on AGENTS.md-style rules |
| Resources | Agents | Custom sub-agent profiles |
| Resources | Memory | Long-term recall notes |
| Resources | Skills | Reusable workflows |
| Resources | Learnings | Corrections and patterns captured over time |
| Resources | Remote agents | A2A connections to other agent-native apps (replaces what the Connections tab used to show) |
| Agent | Snapshots | A scope preview, token budget, ordered system sections grouped by provenance/governance/source, and the latest live-thread snapshot. Renamed from "Context"; old #context links redirect here. |
| Agent | Connections | MCP server management only |
| Agent | Automations | Personal and organization Scheduled/Event automations with pause/resume, details, and delete flows. The stable compatibility URL remains /agent#jobs. |
| Agent | Settings | Agent model, API keys, limits, voice, and automation settings |
| Agent | MCP | The app MCP URL, an A2A agent card when available, and shared setup guides for Claude, ChatGPT, Cursor, Claude Code, Codex, and other clients. Find it in Settings → MCP server or use /mcp/connect for the full connect flow and token fallback. |
The page shows personal (user-scope) data only. There is no org-level toggle today. It is a thin shell over existing components and access checks, not a new admin console: Connections describes what the app can call; MCP describes how external clients connect to it.
Not yet included:
- Organization-scoped view
- Grants and scope editing
- Revocation UI
- Per-iteration provenance history
Embedded sidecar
Use the embedded sidecar when the main product already exists and you want an agent beside it.
The server plugin mounts Agent-Native routes into your host app and resolves host identity server-side:
import { createAgentNativeEmbeddedPlugin } from "@agent-native/core/server";
export default createAgentNativeEmbeddedPlugin({
databaseUrl: process.env.AGENT_NATIVE_DATABASE_URL,
auth: getHostSession,
actions: hostActions,
});The React sidecar passes page context and host commands:
import { AgentNativeEmbedded } from "@agent-native/core/client/host";
export function AppShell({ children }) {
return (
<AgentNativeEmbedded
getContext={() => ({
route: { pathname: window.location.pathname },
selection: { text: window.getSelection()?.toString() || undefined },
})}
onNavigate={(payload) =>
router.navigate((payload as { path: string }).path)
}
onRefresh={() => queryClient.invalidateQueries()}
>
{children}
</AgentNativeEmbedded>
);
}How it connects
The two pieces work as a bridge: the host app passes page context (current route, selected text, focused object) into AgentNativeEmbedded, and the agent sends commands back out through onNavigate and onRefresh. The server plugin handles identity — it resolves the host session so the agent acts as the right user without a separate login. Nothing in the host app needs to change; the plugin attaches Agent-Native's routes alongside your existing ones.
route · selection
host commands
mounted by the server plugin
The plugin mounts Agent-Native routes server-side; the React sidecar streams page context in and host commands out.
See Embedding SDK for host auth, database isolation, iframe/picker mode, and lower-level bridge APIs.
Automation-first app
Use the automation-first path when no one needs a custom browser screen while the work runs: scheduled jobs, integrations, backend workflows, CLI loops, another agent, or an existing product calling into Agent-Native.
This is the shape to reach for when automation is the product surface. You send a request from the terminal, Slack, email, a scheduled job, another agent, or Chat ("summarize my unread emails," "post the daily metrics to Slack," "find the candidates who replied last week") and the agent acts and returns the result wherever it belongs. It is still a real app, not a stateless prompt: actions, auth sessions, app state, thread/run history, settings, credentials, and share records all live in SQL.
Pick this pattern when:
- The work happens in the background. Most of the value is created while the user isn't looking: triage agents, daily-report agents, on-call responders.
- The output leaves the app. The agent posts to Slack, sends email, or updates a third-party system; there's nothing to browse in-app.
- The domain is one-shot. Research bot, summary generator, report writer with no persistent object that needs a list view.
- You're prototyping an automation. Ship the operation now; add chat or app pages when users need to inspect and steer it.
If your product is built around persistent objects users browse, pivot, and share (emails, events, documents, charts), pick a full application or a template instead; those add a full UI plus the agent.
What ships in the box
An automation-first app skips dashboard work, and it is channel-agnostic from day one. The same agent runs from the web, Slack, Telegram, email, and other agents because everything goes through the same actions. The trade-off is there is no "browse-everything-at-a-glance" view; if users need that, start from Chat or add a small status page or list view.
When you add the built-in Chat shell, the framework provides five management surfaces you don't have to build: Chat (the main input), Resources (skills, memory, instructions, sub-agents, and connected MCP servers), Automations, Thread history, and Settings. Those are usually enough: talk to it, see what it's done, configure how it behaves. Reach for Chat when you're ready to add that browser UI, or the Dispatch template for a workspace-style starting point with Slack/Telegram, scheduled jobs, and shared secrets out of the box.
The smallest no-browser local path is a scaffold plus one action:
npx --yes @agent-native/core@latest create my-agent --headless
cd my-agent
pnpm installThen define the durable operation:
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: "Summarize this week's submissions.",
readOnly: true,
schema: z.object({ formId: z.string() }),
run: async ({ formId }) => {
return { formId, summary: "34 submissions, up 18% from last week." };
},
});One action is then callable as:
- HTTP:
POST /_agent-native/actions/summarize-week - CLI:
pnpm action summarize-week --formId form_123 - App-agent CLI:
pnpm agent "Summarize form_123" - MCP: from Claude, ChatGPT, Codex, Cursor, OpenCode, Copilot, and other MCP hosts
- A2A: from another agent-native app or agent peer
- UI: through
useActionQuery,useActionMutation, orcallAction - Agent tool: from the built-in chat loop
This is not a no-database or stateless mode. The app-agent loop stores sessions, threads, runs, settings, credentials, application state, and share records in SQL. Local development uses PGlite; hosted automation-first apps should use a persistent PostgreSQL database.
If you need the whole agent loop headlessly from the project folder, use:
pnpm agent "Summarize this week's forms."If another app or script needs to call the whole agent, use
agentNative.invoke("analytics", "...") or the agent-native invoke CLI. That
keeps cross-app work on the A2A path while local work stays on actions.
Workers, jobs, integration webhooks, and custom hosts can drive the agent loop directly through the server API. This is lower-level than actions — you provide the engine, model, messages, tools, actions, an event sink, and an abort signal yourself:
import { runAgentLoop } from "@agent-native/core/server";
await runAgentLoop({
engine,
model,
systemPrompt,
tools,
actions,
messages,
send,
signal,
});For most apps, scheduled prompts and integration webhooks already call this loop for you. Reach for it directly only when building a custom no-browser host, eval runner, or server-side orchestration surface. See Server: Production agent handler for the full signature.
Running against a folder
If your goal is "run an agent against this folder," start with the app-agent
loop in that folder: scaffold the automation-first app, add actions/instructions, run
pnpm agent "...". That keeps the work inside the same action/runtime/state
contract the app will use in production.
External coding harnesses are a separate product surface for embedding Claude Code, Codex, Pi, Cursor, Mastra, or similar runtimes inside an Agent-Native app. Use them when you are building a coding-agent product, not as the default way to start a local agent-native workflow.
Cloud repo access
For cloud automation-first apps that need repository access, use the GitHub connector plus token CRUD model: list repositories, search files, read files, create or edit files, delete files, and revoke access through provider-scoped credentials. In local development, set the target repository explicitly:
GITHUB_REPOSITORY=owner/repo pnpm agent "Read README.md and suggest the next action."Do not treat a VM clone or long-lived sandbox checkout as the primary cloud repo-access model. Sandboxes still matter for isolated code execution, but repository access should be explicit, permissioned, auditable, and revocable through the connector layer.
Sharing sessions and runs
Automation-first sessions and runs are durable objects. Shareability should be phased: read/share links first, so teammates can inspect sanitized prompts, outputs, and run status; permissioned writable collaboration later, so continuing a run, approving actions, editing schedules, or changing configuration goes through explicit access checks.
Rich chat on your agent
Use this path when your agent is already built with another framework or
runtime and you want Agent-Native's chat UI around it. AgentChatRuntime is the
boundary: your runtime streams normalized events, and Agent-Native renders the
composer, transcript, tool calls, approvals, native widgets, and app layout.
import { AssistantChat } from "@agent-native/toolkit/app/chat";
import { createHttpAgentChatRuntime } from "@agent-native/core/client/agent-chat";
const runtime = createHttpAgentChatRuntime({
endpoint: "/api/support-agent/chat",
});
export function SupportAgentChat() {
return <AssistantChat runtime={runtime} threadId="support" />;
}Ready-made runtime helpers exist for OpenAI Agents, OpenAI Responses, the Claude Agent SDK, the Vercel AI SDK, and AG-UI, plus the normalized HTTP runtime above for any other agent (Mastra, Flue, Eve, LangGraph, or a custom service). ACP is not the end-user app chat or A2A transport, and Agent-Native does not currently claim A2UI support. ACP is supported in one specific place: driving a local coding agent (Gemini CLI, Claude Code, …) through the harness layer, not as the chat runtime here.
Native Chat UI: BYO agent runtimes
is the canonical home for the event shapes, the runtime helpers, and chatUI
tool-result metadata. Start there when wiring an external agent into the chat.
What's next
- Actions
Define the operation once. Every surface above calls the same one.
- Native Chat UI
Render typed action results as tables, charts, and cards directly in chat.
- Generative UI
Generate transient or persisted sandboxed UI inline in chat.
- Automation-First Apps
The full no-browser pattern for jobs, queues, scripts, and external agents.
- External Agents
Connect MCP-compatible hosts to your app as a tool server.
- A2A Protocol
Call agents from other agent-native apps over the A2A standard.