Skip to main content

Run Agents Inside Your App

After an agent exists, you need to choose the session type first, then choose the runtime UI model.

Two agent session types

agent_access tickets support two session identifiers:
  1. static
  2. signin

static session

Use this when your backend wants full control over conversation partitioning.
  1. You must send context_group in the ticket request.
  2. The same context_group is used for agent contexts/messages throughout that session.
  3. Best when you want strict tenant keys like workspace:<id>:user:<id>.

signin session

Use this when agent context should follow the currently active signed-in account.
  1. Set agent_session_identifier: "signin".
  2. context_group is derived from the active sign-in user ID.
  3. If there is no active sign-in, the session is rejected.

Flow A: static agent session

import { WachtClient } from "@wacht/backend";

const client = new WachtClient({
  apiKey: process.env.WACHT_BACKEND_API_KEY!,
  baseUrl: "https://api.wacht.dev",
});

const ticketResponse = await client.post<{ ticket: string; expires_at: number }>(
  "/session/tickets",
  {
    ticket_type: "agent_access",
    agent_ids: [agentId],
    agent_session_identifier: "static",
    context_group: `workspace:${workspaceId}:user:${userId}`,
    expires_in: 60 * 30,
  },
);

const ticket = ticketResponse.ticket;

Flow B: signin agent session

import { WachtClient } from "@wacht/backend";

const client = new WachtClient({
  apiKey: process.env.WACHT_BACKEND_API_KEY!,
  baseUrl: "https://api.wacht.dev",
});

const ticketResponse = await client.post<{ ticket: string; expires_at: number }>(
  "/session/tickets",
  {
    ticket_type: "agent_access",
    agent_ids: [agentId],
    agent_session_identifier: "signin",
    expires_in: 60 * 30,
  },
);

const ticket = ticketResponse.ticket;

What context_group means

context_group is the partition key for agent runtime state. It decides which execution contexts/messages are considered part of the same conversation bucket for that session scope. Practical guidance:
  1. Use deterministic keys, for example workspace:<workspaceId>:user:<userId>.
  2. Never use a global/shared value across tenants.
  3. Keep one format across your app so support and debugging are predictable.
  4. In signin mode, Wacht derives this from active sign-in user ID.

Runtime UI model (independent from session type)

Either session type can be used with vanity UI or custom UI hooks.

Option 1: Open vanity UI

const url = `${deployment.backend_host}/vanity/agents?ticket=${ticket}`;
window.open(url, "_blank");

Option 2: Build custom UI with hooks

import { useAgentSession, useAgentContexts, useAgentContext } from "@wacht/react-router";

export function AgentChat({ ticket }: { ticket: string }) {
  const { hasSession, sessionLoading, activeAgent, contextGroup } = useAgentSession(ticket);
  const { contexts, createContext } = useAgentContexts({ enabled: hasSession });

  const currentContextId = contexts[0]?.id;
  const { messages, sendMessage, isExecuting } = useAgentContext({
    contextId: currentContextId || "",
    agentName: activeAgent?.name || "",
  });

  if (sessionLoading) return <div>Loading agent session...</div>;
  if (!hasSession) return <div>No valid agent session.</div>;

  return (
    <div>
      <div>Context group: {contextGroup}</div>
      <button onClick={() => createContext({ title: "New support chat" })}>New chat</button>
      <button
        disabled={!currentContextId || isExecuting}
        onClick={() => sendMessage("Summarize this workspace's current webhook health")}
      >
        Ask agent
      </button>
      <pre>{JSON.stringify(messages, null, 2)}</pre>
    </div>
  );
}

Runtime checklist

  1. Tickets should be short-lived and created only after your own authz checks.
  2. Scope agent_ids and session type (static/signin) to your tenancy model.
  3. For static, enforce a strict context_group format.
  4. For signin, ensure an active sign-in exists before ticket use.
  5. Handle expired tickets with a re-issue flow.
  6. Exchanging a new agent_access ticket replaces the older active agent session for that browser session.
  1. React Router useAgentSession
  2. React Router useAgentContexts
  3. React Router useAgentContext
  4. Node SDK AI API
  5. Frontend Agent APIs