close

Drop-in Agent

Developer page. This page is for developers embedding the agent into a React app. For the end-user experience of working with the agent, see Using Your Agent.

You don't need to build Agent-Native from scratch. The agent chat, resources tab, CLI terminal, voice input, and all the related infrastructure ship as a handful of React components you drop into any app.

Prerequisite: the server has to be running the agent-chat-plugin (it auto-mounts in every template). If you're starting from scratch, see Server. Need the public API map instead of a tutorial? See Component API.

The components at a glance

Component What it is Use it when
<AgentSidebar> Wraps your root app layout and adds a toggleable side panel containing the full agent You want the agent available alongside your app on every screen
<AgentToggleButton> Opens/closes <AgentSidebar> (put it in your header) Pair with <AgentSidebar>
<AgentPanel> The raw panel itself — chat + CLI + resources tabs You want full control over layout, or a dedicated agent page
<AgentChatSurface> A pre-wired panel/page chat surface You want chat without the sidebar wrapper
<AssistantChat> Lower-level chat renderer with composer/history hooks You need custom chrome around the standard conversation UI
sendToAgentChat() Programmatically send a message to the chat A button that hands work to the agent instead of running inline
useActionMutation() Typesafe frontend wrapper around an action The UI needs to run the same operation an agent tool would run

All of these are exported from @agent-native/core/client.

The mount model
<AgentPanel>same panel, no wrapper — you own the layout

<AgentSidebar> wraps your existing layout. Your routes render in the main area; the agent panel mounts beside them. <AgentPanel> is the same panel without the wrapper.

The most common setup is a sidebar that opens from the right on any screen. Wrap your existing root layout with <AgentSidebar>; whatever you pass as children stays in the main app area. The agent chat is the side panel.

Wrapping the root layout with <AgentSidebar>
app/root.tsx
1import { Outlet } from "react-router";
2import { AgentSidebar, AgentToggleButton } from "@agent-native/core/client/agent-chat";
3 
4export default function Root() {
5 return (
7 emptyStateText="How can I help?"
14 defaultSidebarWidth={420}
15 position="right"
16 >
17 <header>
21 <main>
25 );
26}
Line 6Wrapper

<AgentSidebar> wraps your whole layout. It adds the toggleable side panel; everything you pass as children stays in the main app area.

Lines 8–12Starter prompts

suggestions render as clickable chips on the empty chat.

Line 13Context-aware chips

dynamicSuggestions merges screen-aware prompts (e.g. "Summarize this selection") with your static ones. On by default.

Lines 18–20Toggle button

Put <AgentToggleButton /> anywhere in your header to open and close the panel.

Lines 22–24Your app

<Outlet/> (your routes) renders in the main area, untouched.

That's it. The user now has a toggleable agent on every page — with chat history, resources tab, CLI terminal, voice input, and a wider drawer. State persists across reloads via localStorage.

Props

  • children — your app's normal layout and routes. Rendered in the main area; the agent panel mounts beside it on desktop and over it on mobile.
  • emptyStateText — greeting shown when the chat has no messages. Default: "How can I help you?".
  • suggestions — starter prompts rendered as clickable chips when empty.
  • dynamicSuggestions — context-aware prompt chips merged with suggestions. Enabled by default; pass false to show only static suggestions, or { max, includeStatic, getSuggestions } to customize.
  • defaultSidebarWidth — initial pixel width (mount-only; user resize and saved value override). Default: 380.
  • position"left" or "right". Default: "right".
  • defaultOpen — whether the sidebar starts open (desktop only). Default: false.

The other 20%: <AgentPanel>

When you need full control over layout — a dedicated /chat route, an embedded panel in a side column you manage, or a popup — render <AgentPanel> directly:

// app/routes/agent.tsx
import { AgentPanel } from "@agent-native/core/client/agent-chat";
export default function AgentRoute() {
  return (
    <div className="h-screen">
      <AgentPanel defaultMode="chat" className="h-full" />
    </div>
  );
}

<AgentPanel> gives you the raw tabs (Chat / CLI / Resources) without the sidebar wrapper or the collapse button. Put it wherever you want; you control the surrounding layout.

Selected props

  • defaultMode"chat" or "cli". Default: "chat".
  • className — CSS class for the outer container.
  • onCollapse — if provided, a collapse button appears in the header.
  • isFullscreen — set this when you want a Claude-style centered column for a page-level or custom panel surface. The sidebar uses the wider drawer instead.
  • storageKey — namespace for localStorage keys. Useful when you render multiple panels (different app instances or workspaces) in the same page.

Full props: AgentPanelProps in @agent-native/core/client.

Programmatic messages: sendToAgentChat()

A button that hands work off to the agent instead of creating a separate AI path with an inline llm() call. See Why build apps this way for why that distinction matters. For visible, user-initiated AI work, open the sidebar explicitly so the user can see, steer, and continue the same thread:

import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
<Button
  onClick={() =>
    sendToAgentChat({
      message: "Generate a chart showing signups by source",
      context: `Dashboard ID: ${dashboardId}, date range: last 30 days`,
      submit: true,
      openSidebar: true,
    })
  }
>
  Generate chart
</Button>;

Options

  • message — the visible prompt shown in chat.
  • context — hidden context appended to the prompt (selected text, cursor position, current entity id — anything the agent should know but the user shouldn't see twice).
  • submittrue to auto-run, false to prefill but wait. Omit to use the project default.
  • newTab — create a separate chat thread for this prompt.
  • background — with newTab, run without focusing the new thread. The hidden run is tracked in RunsTray.
  • openSidebar — use true for visible user-initiated AI work. Set to false only for explicitly silent background or system-initiated sends. The default opens the sidebar so the user sees the response.
  • type"content" (default) keeps the work in the embedded app agent, which operates through app tools unless its frame is intentionally granted workspace and write tooling. "code" routes to a separate code-capable frame (for repository source changes, see Frames).

sendToAgentChat returns a stable tabId you can use to track the chat run.

For silent work, pair newTab, background, and openSidebar: false:

sendToAgentChat({
  message: "Summarize the selected thread and save the summary",
  context: `Thread id: ${threadId}`,
  submit: true,
  newTab: true,
  background: true,
  openSidebar: false,
});

This is still a full agent run with tools, actions, thread state, and run tracking. It simply does not steal focus from the user's current sidebar state.

Keep follow-up and revision prompts in the existing sidebar thread. Do not add a second freeform input beside the generated result. A small local form or popover is appropriate for structured values the UI must validate, but the conversation itself belongs in the AgentSidebar composer.

When the same route is embedded as an MCP App, submitted sendToAgentChat() calls are forwarded to the host chat where supported; see Agent Chat for the MCP App bridge behavior.

If you want a loading state, use the useSendToAgentChat() hook — it returns both send and isGenerating:

import { useSendToAgentChat } from "@agent-native/core/client/agent-chat";
const { send, isGenerating } = useSendToAgentChat();

When the stock sidebar isn't the fit

<AgentSidebar> and <AgentPanel> cover most apps. When you need to own the layout around the agent, or you want to power the conversation with an agent you built elsewhere, drop down a layer — but keep letting the framework own the runtime, actions, and SQL-backed state:

  • Own the chrome around the standard runtime. Use <AgentChatSurface> for a dedicated chat route, or <AssistantChat> when you want custom headers, tabs, and empty states around the standard conversation. The full layer map — every component, hook, composer, and adapter, with import paths — lives in Component API.
  • Bring your own agent runtime. If an agent you built elsewhere should power the conversation while Agent-Native keeps the composer, transcript, tool cards, approvals, and native widgets, pass an AgentChatRuntime to <AssistantChat runtime={...} />. The connectors (createHttpAgentChatRuntime() and the OpenAI / Claude / Vercel AI / AG-UI helpers) and the event contract are documented in Native Chat UI — BYO agent runtimes.

Whichever layer you pick, keep actions and SQL-backed app state as the contract, and avoid posting directly to /_agent-native/agent-chat from product UI. If a named helper is missing for a real custom surface, add that helper first so client code does not learn a second, ad hoc transport.

Choosing agent chat versus an action

Use useActionQuery or useActionMutation directly for deterministic local operations such as CRUD, validation, provider reads, and persistence. If the user experiences the workflow as research, analysis, generation, recommendation, synthesis, or other multi-step reasoning, start it with sendToAgentChat({ openSidebar: true }) and let the agent orchestrate focused actions. Deterministic implementation alone is not a reason to hide an AI-shaped workflow inside one opaque action.

Typesafe actions from the UI: useActionMutation()

When the UI needs to run the same operation an agent tool would run, use useActionMutation:

import { useActionMutation } from "@agent-native/core/client/hooks";
const { mutate, isPending } = useActionMutation("reply-to-email");

<Button onClick={() => mutate({ emailId, body: "Thanks!" })}>
  Send Reply
</Button>;

Type-safe arguments come from the zod schema in your defineAction(). See Actions for the full action system.

useActionMutation vs sendToAgentChat. Run the operation directly with useActionMutation when the user clicked a deterministic button ("Send reply"). Hand it to sendToAgentChat when the work needs the agent's reasoning, tools, or multi-step planning. Never call an inline llm() from UI; that creates a separate AI path instead of using the shared agent loop.

Selection + cursor awareness

The agent can see what the user has selected — text, cells, slides, contacts — via the navigation and selection keys in application state. The empty chat also uses those keys to offer dynamic suggestions such as "Summarize this selection" or "Improve this slide" when the current screen makes them relevant. If you'd like Cmd-I (or similar) to send a selected range into the chat as context, see Context Awareness.

Putting it all together

A typical drop-in setup:

// app/root.tsx
import {
  AgentSidebar,
  AgentToggleButton,
  sendToAgentChat,
} from "@agent-native/core/client/agent-chat";
export default function Root() {
  return (
    <AgentSidebar suggestions={["Draft a reply", "Summarize selection"]}>
      <Header>
        <AgentToggleButton />
      </Header>

      <Main>
        <YourRoutes />
      </Main>
    </AgentSidebar>
  );
}
// Anywhere else in the app
<Button
  onClick={() =>
    sendToAgentChat({
      message: "Summarize this thread",
      context: `Thread id: ${threadId}`,
      submit: true,
      openSidebar: true,
    })
  }
>
  Summarize
</Button>

The user sees a chat button in the header, can open it, and can talk to the agent. Your buttons hand work to that same agent instead of running one-shot LLM calls.

What's next

  • ActionsdefineAction() and useActionMutation()
  • Context Awareness — selection, navigation, view-screen
  • Agent Resources — what the Resources tab contains (skills, memory, MCP servers, scheduled jobs)
  • Voice Input — the microphone in the chat composer