# Agent conversations and actions

Source: /docs/reference/react/conversations-and-actions.md

An assistant interface has two kinds of state: saved conversation messages and the current streamed response. Use the manager to keep those together, and render browser actions separately from backend tool results.

## Start a conversation [#start-a-conversation]

Choose an implemented agent by its registered resource name. Preview mode selects its committed definition and committed API tool versions; deployed execution uses deployed dependencies. Ensure its declared tables have published data and its APIs have ready runtimes.

## Conversations and streaming [#conversations-and-streaming]

```text
agentManager.loadConversations(agentName, page?) -> Promise<void>
agentManager.selectConversation(id) -> Promise<void>
agentManager.loadMoreMessages() -> Promise<void>
agentManager.newConversation({name?, visibility?}?) -> void
agentManager.updateConversation({name?, visibility?}) -> Promise<void>
agentManager.consume(client.runAgent(agentName, options)) -> Promise<void>
agentManager.cancel() -> void
```

runAgent options: required message; optional conversationId, creation-only name/
visibility, data, actions, cancelSignal, timeoutMs. runAgent returns a lazy,
single-use AgentRun; no POST occurs until it is consumed. The manager supplies the
selected conversation when appropriate. newConversation clears the selection and
prepares a draft created atomically with its first message. Without a selection or
explicit ID, a run creates a new conversation. Use updateConversation to rename or
change visibility of an existing selection; run options do not edit it.

```tsx
import { useVortexify } from "@vortexify/react";

export function AssistantPanel() {
  const { client, ready, agentManager } = useVortexify();

  async function send() {
    if (!ready || agentManager.isRunning) return;
    try {
      await agentManager.consume(client.runAgent("order_assistant", {
        message: "Explain the current order workflow",
        data: { screen: "orders" },
      }));
    } catch {
      // The manager also records the failure in error for rendering below.
    }
  }

  return <section>
    <button disabled={!ready || agentManager.isRunning} onClick={() => void send()}>Ask</button>
    <p>{agentManager.text}</p>
    {agentManager.error && <p role="alert">{agentManager.error.message}</p>}
  </section>;
}
```

For conversation navigation, call `agentManager.loadConversations("order_assistant")` once the client is ready. Render that list, use `selectConversation(id)` when someone opens a conversation, and use `newConversation({visibility: "private"})` for a fresh draft. Show `isLoading` while history is loading and offer `loadMoreMessages()` when `nextMessageOffset` is not null.

This minimal panel renders current stream text. A full conversation UI should also
render persisted messages and conversation selection without duplicating the final
turn: the manager refreshes history after successful completion. State includes
agentName, conversations, activeConversation, messages, conversationId, messageId,
runId, text, tools, isRunning, isLoading, error, nextConversationOffset and
nextMessageOffset. A null next offset means no next page. Conversation/message
pages default to 50 and allow up to 100. Load later conversations with
`{offset: agentManager.nextConversationOffset}` when it is not null.

Visibility is private or public; default public means the authorized project
audience, not anonymous internet access. Server permission checks govern reads,
creation, submission and management. Switching conversations or unmounting cancels
local consumption; it does not stop an active backend turn or reconnect to it.
Only one active backend turn is allowed per conversation. Stream failures reject
consume and populate error; user cancellation resolves normally. Streams are not
automatically replayed/retried. Default stream timeout is configured timeout or
five minutes. Explicit cancelSignal is supported.

Low-level client conversation APIs are listAgentConversations(name, page?, options?),
getAgentConversation(name, id, options?), listAgentMessages(name, id, page?, options?),
createAgentConversation(name, {name?, visibility?}?, options?) and
updateAgentConversation(name, id, {name?, visibility?}, options?). Prefer the manager
for ordinary React UI. Advanced direct AgentRun iteration yields events without
executing frontend actions or managing React state.

## Inspect conversations in the platform [#inspect-conversations-in-the-platform]

The agent resource's **Conversations** view lists saved conversations and opens their messages and tool activity. **Files** shows its attached document previews, and **Code** shows the Python declaration. These are useful for checking what happened in a real app conversation; the app itself owns its chat composer and browser interactions.

## Data and frontend actions [#data-and-frontend-actions]

Agent data is a JSON object and may contain nested arrays/objects, unlike scalar
API inputs. Filter before sending and bound every data array to at most 100 items.
Message/data/action definitions together have a 256 KiB UTF-8 request limit and
JSON depth is limited to 32. Include sample metadata such as totalRows; oversize
requests reject rather than silently truncate. Never send secrets unnecessarily.

For example, use a backend API tool to retrieve a customer, and a browser action to open that customer's screen. Give the action a clear description so the assistant can choose it, and a schema so the manager can validate its arguments.

An action contains description, parameters and a local execute handler:

```ts
import type { FrontendActions } from "@vortexify/react";

const actions: FrontendActions = {
  openCustomer: {
    description: "Open a customer in this application.",
    parameters: {
      type: "object",
      properties: { customerId: { type: "string" } },
      required: ["customerId"],
      additionalProperties: false,
    },
    execute: ({ customerId }) => {
      if (typeof customerId !== "string") throw new Error("Invalid customer ID");
      return openCustomerPage(customerId).then(() => ({
        success: true as const, message: "Customer page opened.",
      })); // App helper: resolve when destination view is ready.
    },
  },
};
```

Pass actions in runAgent options. Names/descriptions/schemas reach the backend;
execute functions remain in the browser. Supported schema keys: type, description,
properties, required, boolean additionalProperties, items, enum. Types are object,
array, string, number, integer, boolean, null; the root must be object. Unsupported
keywords reject. Names match [A-Za-z0-9_-]{1,64} and cannot collide with backend tools.

The manager validates completed arguments and executes each frontend call once per
consumed run. It submits AgentToolResult to the backend, which waits and continues
with the actual result. History reads and read-only stream replays do not dispatch
actions. Canceling cannot undo side effects already started.

See [custom agent declarations](/docs/reference/python/agents), [analysis charts](/docs/reference/react/analysis-charts), and [add an assistant](/docs/building/add-an-assistant).

### Frontend action results [#frontend-action-results]

Every action `execute` handler must return an `AgentToolResult`, synchronously or
through a promise:

```ts
type AgentToolResult =
  | { success: true; message?: string; data?: JsonValue }
  | { success: false; message: string; data?: JsonValue };
```

The manager automatically submits the result to the backend. Thrown/rejected
errors, invalid arguments, missing handlers and invalid return values become
`{ success: false, message }`. Returning void or a boolean is invalid. JSON results
have a 256 KiB limit and maximum depth 32. Success means the operation described
by the handler completed: drafting a note is different from saving it.

Each action can set `timeoutMs` (integer 1000–120000, default 60000). The server
starts its deadline before dispatching completed arguments, persists pending state,
and polls every 0.5 seconds. The agent waits for a response before continuing with
the actual result. A timeout reports that the execution outcome is unknown.
The total run timeout is separate (five minutes by default).

The manager keeps reading SSE while executing browser handlers in dispatch order.
Canonical frontend results, including timeouts, arrive as `tool_call_result`
events and update `tools[].result`, status and error. History reads and read-only
replays do not execute handlers. Cancel/unmount stops local dispatch and response
submission; it cannot undo already-started side effects. Browser disconnects leave
the backend waiting until the tool deadline; explicit server stop/reset cancels
pending calls. Transport failures while submitting results reject `consume`.

For direct `AgentRun` iteration or a manual approval UI, use:

```ts
await client.respondToToolCall(
  agentName, conversationId, runId, callId,
  { success: true, message: "Approved", data: { approved: true } },
);
// An initialized AgentRun also exposes run.respondToToolCall(callId, result).
```

Only the user who started the run may answer its calls, with project editor access.
Identical accepted retries are idempotent. Conflicting, canceled or expired replies
return HTTP 409. The manager follows the canonical streamed result if a timeout
wins a response race. Messages expose nullable `interactiveRunId`,
`interactiveCallId`, `responseStatus`, `responseDeadline`, `response`, and
`respondedAt`; status is pending/responded/timed_out/canceled. Older messages keep
null metadata.

### Active page context [#active-page-context]

Register one active page with `useAgentView` inside `VortexifyProvider`:

```tsx
import { useAgentView } from "@vortexify/react";

useAgentView({
  id: "orders",
  description: "Orders list with shipping filters and selection.",
  getContext: () => ({
    shippingFilter,
    selectedOrderId,
    visibleOrders: orders.slice(0, 50),
    totalOrders: orders.length,
  }),
});
```

The provider registers on mount, reads the latest callback after renders, and
unregisters on unmount. Keep one active view per provider; the last mounted view
wins, and an old registration's cleanup cannot remove a newer registration.
Changing the view ID replaces its registration. This does not continuously send UI
changes to the backend.

At consumption time, `runAgent` adds a snapshot under the reserved
`data.current_view` key (`viewId`, `description`, `context`; null without an active
view). Explicit `data` fields are retained except that reserved key. Initial
context uses the request's existing limits: 100 items per array, depth 32, and
256 KiB including message and action definitions. `getContext` can return a
`JsonObject` or a promise; errors reject initial run preparation.

Provider clients also register the reserved `get_current_view_context` interactive
tool. It reads the active registration when called, so an existing run can follow
navigation and request fresh context. No active view or navigation during an
asynchronous context read returns a structured failure. Fresh tool results are
JSON, depth at most 32 and at most 256 KiB. Expose only the context the agent needs.

Actions declared in a `runAgent` invocation remain fixed for that run; changing
pages does not add new action definitions mid-run. A navigation action should
resolve after its destination view is ready if later tools depend on that view.
Use SPA navigation: a full document reload can disconnect the stream before the
SDK submits the result.
