Shared State (Tandem Mode)

Learn how to use Shared State (Tandem Mode) to sync your app’s UI with the agent in real time, enabling two-way updates and contextual, state-aware experiences

Overview

Shared State creates a two-way synchronization between your application's UI state (like a form or settings panel) and the AI agent. This powerful feature allows the agent to both read the current state of your UI in real-time and write changes back to it leveraging LLM.


Prerequisite

This page assumes you’ve already initialized your Agent

API Reference

The entire process is managed by two main methods on the agent instance.

shareState(key, state, handler, stateDescription)

This is the core method for establishing and updating the synchronized state. It should be called once on initialization and then again every time the user-side state changes.

ParameterTypeRequiredDescription
keystringYesA unique string identifier for this piece of state (e.g., "contact_form").
stateobjectYesA snapshot of the current UI state object (e.g., { name: "", email: "" }).
handler(key: string, nextState: any) => voidNoCrucial for two-way sync. This parameter is optional, but required to allow the agent to push updates back to your UI.
stateDescriptionstringNoA free-form string that adds context about the state. Can describe the type/model, usage instructions, or provide a human-readable explanation.

Shared State

// Initial sync: Register the state key, initial data, and the handler
const agentKey = "YOUR_AGENT_KEY";
const agent = window.foldspace?.agent(agentKey);
agent.current?.shareState(stateKey, form, handleStateChange, stateDescription);

stateDescription for agent guidance

The stateDescription field is optional, but it plays an important role in providing context to both developers and the agent. While concise by design, it can carry different kinds of guidance:

  • Naming conventions: e.g., "All keys in this state should be snake_case".
  • Model structure hints: e.g., "If 'items' is an empty array, each entry should contain { id: string, quantity: number }".
  • Purpose description: e.g., "Tracks the shipping address details required for checkout".

In short, stateDescription is a flexible way to enrich the state with descriptive or instructional metadata, helping ensure that both the UI and the agent interpret the state consistently.


clearState(key)

Tells the agent to stop tracking a piece of state. This is critical for cleanup when a component unmounts or the user navigates away, preventing memory leaks.

ParameterTypeRequiredDescription
keystringYesThe unique string identifier for the state you wish to clear.

React + TypeScript Example

This example shows how to synchronize a React form component's state with the agent using hooks.

import { useEffect, useRef, useState } from "react";

type FormState = {
  name: string;
  email: string;
  company?: string;
};

interface Props {
  agentKey: string;
}

export default function SharedStateForm({ agentKey }: Props) {
  // 1. Local UI state is kept minimal.
  const [form, setForm] = useState<FormState>({ name: "", email: "", company: "" });

  // 2. The agent instance is created once and stored in a ref.
  const agentRef = useRef<any | null>(null);
  const stateKey = "contact_form";
  const stateDescription = `
    type FormState = {
      name: string;
      email: string;
      company?: string;
    };

    interface Props {
      agentKey: string;
    }`;

  // 3. `useEffect` handles the entire lifecycle: setup, subscription, and cleanup.
  useEffect(() => {
    // Initialize the agent
    agentRef.current = window.foldspace?.agent(agentKey);

    // Agent -> UI: Define the handler to receive updates FROM the agent
    const handleStateChange = (key: string, nextState: unknown) => {
      if (key === stateKey) {
        setForm(nextState as FormState); // Update local state
      }
    };

    // Initial sync: Register the state key, initial data, and the handler
    agentRef.current?.shareState(stateKey, form, handleStateChange, stateDescription);

    // Cleanup function: Tell the agent to stop tracking on unmount
    return () => {
      agentRef.current?.clearState(stateKey);
      agentRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [agentKey]);

  // 4. UI -> Agent: Call this on user input to keep the agent's snapshot fresh.
  const updateField = <K extends keyof FormState>(key: K, value: FormState[K]) => {
    const next = { ...form, [key]: value };
    setForm(next);
    // Send the fresh snapshot to the agent
    agentRef.current?.shareState(stateKey, next);
  };

  // 5. Render your form inputs and call updateField on change.
  return <>...</>;
}