Agent SDK

Build custom React Native UI

Use the React Native App Agent binding to build native agent experiences with your own sheets, docks, and navigation.

9 sections

Use @emcy/agent-sdk/react-native when your assistant is a first-class native surface rather than a web widget inside a wrapper.

This is the right choice for:

  • docked native assistants
  • bottom-sheet agent panels
  • product-specific native transcript UI
  • app-native approvals and structured input collection
  • flows that need explicit storage or native auth-session integration

Basic usage#

TSX
import { useAppAgent } from "@emcy/agent-sdk/react-native";
 
export function AssistantDock() {
  const agent = useAppAgent({
    apiKey: "emcy_sk_xxxx",
    agentId: "ag_xxxxx",
    serviceUrl: "http://localhost:5150",
    appSessionKey: session.id,
    userIdentity: {
      subject: session.user.id,
      email: session.user.email,
      organizationId: session.organizationId,
      displayName: session.user.name,
    },
    clientTools,
    appContext,
    platform,
  });
 
  return null;
}

Why platform matters on native#

On native you often need to tell the App Agent how to handle:

  • durable storage
  • secure token storage
  • OAuth session launching
  • foreground and connectivity lifecycle

That belongs in platform.

Example platform object#

TS
const platform = {
  storage: {
    durable: {
      getItem: (key) => AsyncStorage.getItem(key),
      setItem: (key, value) => AsyncStorage.setItem(key, value),
      removeItem: (key) => AsyncStorage.removeItem(key),
    },
    secure: {
      getItem: (key) => SecureStore.getItemAsync(key),
      setItem: (key, value) => SecureStore.setItemAsync(key, value),
      removeItem: (key) => SecureStore.deleteItemAsync(key),
    },
  },
  auth: {
    openOAuthSession: async ({ authorizeUrl, redirectUri }) => {
      const result = await WebBrowser.openAuthSessionAsync(authorizeUrl, redirectUri);
      if (result.type === "success") {
        return { type: "success", url: result.url };
      }
      if (result.type === "dismiss") {
        return { type: "dismiss" };
      }
      return { type: "cancel" };
    },
    dismissOAuthSession: () => WebBrowser.dismissAuthSession(),
  },
};

Using the shared App Agent state#

The React Native hook exposes the same capability groups as the React hook:

  • conversation
  • composer
  • connections
  • approvals
  • requests
  • feedback

That parity is deliberate. Web and React Native should expose equivalent App Agent capabilities even if the UI rendering is completely different.

Rendering a native agent panel#

TSX
function ChecklistAssistantPanel() {
  const agent = useAppAgent(config);
 
  return (
    <View>
      <Text>{agent.conversation.statusLabel}</Text>
 
      {agent.conversation.renderedNodes.map((node) => {
        if (node.kind === "user") {
          return <Text key={node.id}>{node.content}</Text>;
        }
 
        if (node.kind === "assistant") {
          return <Text key={node.id}>{node.content}</Text>;
        }
 
        return <Text key={node.id}>{node.tools.length} tools</Text>;
      })}
 
      {agent.approvals.pending.map((approval) => (
        <View key={approval.id}>
          <Text>{approval.title}</Text>
          <Button title="Approve" onPress={() => agent.approvals.resolve(approval.id, true)} />
          <Button title="Reject" onPress={() => agent.approvals.resolve(approval.id, false)} />
        </View>
      ))}
    </View>
  );
}

Native same-user OAuth#

You still pass:

  • appSessionKey
  • userIdentity

That keeps MCP auth correctly scoped to the current signed-in host user.

If you want the App Agent to run OAuth through your own native auth-session plumbing, use the platform.auth contract or provide onAuthRequired.

  • keep conversation state in the SDK, not in local component glue
  • use displayText when your real prompt includes hidden scoping or app context
  • render agent.requests.pending as form cards or sheets
  • render agent.approvals.pending as explicit native confirmation surfaces
  • use connections.needsAttention and statusLabel to drive reconnect banners

Common mistakes#

  • storing your own duplicate conversation id instead of trusting the App Agent
  • forgetting appSessionKey, which can cause wrong-session auth reuse
  • treating the native binding as a re-export of the web binding
  • rebuilding pending-turn, approval, or request state locally