agentId (browser) / firstflowAgentId (server). Both refer to the same record; the two names exist because the browser SDK and the server SDK tag their traffic from opposite sides of the runtime.
Most teams create one agent per product, plus a separate agent for staging vs.
production so test traffic never triggers real experiences. An agent is the
unit of isolation nothing leaks across agents.
How it works
An agent is not just a folder. It is the routing key for the entire runtime cycle. When a message arrives, the backend looks up the agent, evaluates that agent’s active experiences and audience, composes a widget, and pushes it to that agent’s connected browsers and nothing else. Three identifiers move through the system, all derived from the same agent record. The browser SDK sendsagentId; the server SDK tags every observed LLM call with firstflowAgentId; the backend resolves both to an internal workspaceId, which is the room key for realtime delivery. Get the id wrong on either side and traffic is silently routed to a different agent’s experiences.
The agent is also the scope of eligibility. Triggers, audience, schedule, frequency, and the optional LLM classifier are all stored on experiences that belong to one agent and evaluated server-side against that agent’s data. The browser cannot reach across agents, and it cannot force a widget to show it sends agentId and reports activity; the backend decides.
What an agent scopes
Every concept in FirstFlow hangs off exactly one agent. These are the things you create and tune per agent:- Experiences the guides, tours, surveys, and announcements you ship. Each has a
type(guide | tour | survey | announcement), astatus(draft | active | paused), a flow graph, and settings. - Audience the end-users who interact with your product and the segments you group them into. Audience data is only recorded when the agent’s
userTrackingEnabledflag is on. - Conversations & sessions every chat session the server SDK observes, keyed by the
sessionIdyou own. - Settings widget theme, AI model assignment, the user-tracking policy, and slash commands (covered below).
Keys
Each agent exposes two distinct credentials with opposite trust levels. Using the wrong one in the wrong place is the most common setup mistake. The publishable key (pk_live_…) is browser-safe and ships in client code alongside agentId. It authenticates @firstflow/react and is not a secret it can only do what a visitor’s browser is allowed to do (open a socket, report activity). The API key (ff_live_…) is a server secret read from the FIRSTFLOW_API_KEY environment variable by @firstflow/sdk. It authorizes observed LLM traffic and must never reach the browser.
In a Next.js app the split looks like this the publishable key is inlined into the client provider, the API key stays in server-only environment:
agt_8f2c… value is identical on both sides that is what binds the observed conversation on your server to the widgets pushed to the browser. See Wrap your LLM client for the full server setup and the provider reference for the browser side.
Settings worth knowing
Agent settings are configuration that applies to every experience in the agent, set once on the agent’s Settings page rather than per experience.
Theme and slash commands are delivered to the browser automatically: when the provider connects, the backend sends an
agent.config event and @firstflow/react applies them. You do not fetch or pass them they ride the socket.
Slash commands
Slash commands are agent-level shortcuts a user can trigger from the chat composer. EachAgentCommand either launches an experience or fires a host-handled action key:
useFirstflow() as commands and dispatches one with triggerCommand(commandId). A type: "experience" command asks the backend to run that experience’s flow for the current user; a type: "action" command emits an action key your app handles itself. See Commands for the rendering and dispatch contract.
Specific cases and limits
The agent boundary is strict, which removes a few options you might expect:- Type is per experience, not per agent. An agent freely mixes guides, tours, surveys, and announcements. The immutable
guide | tour | survey | announcementchoice is made when you create each experience. - You cannot trigger a widget from the client. Eligibility lives server-side against the agent’s experiences. The browser SDK calls
notifyActivity()and renders what the backend pushes it never decides what shows. This is why client-only integrations cannot force a widget. - No cross-agent reuse. Segments, experiences, taxonomy, and commands are all agent-scoped. Staging and production are separate agents with separate keys and separate data on purpose.
userTrackingEnabledgates analytics globally. If it is off, per-experience analytics still won’t populate audience or session data for that agent the policy is enforced at ingestion.
apiUrl prop and the server SDK with FIRSTFLOW_API_BASE_URL.