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.
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#
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#
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:
conversationcomposerconnectionsapprovalsrequestsfeedback
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#
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:
appSessionKeyuserIdentity
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.
Recommended native patterns#
- keep conversation state in the SDK, not in local component glue
- use
displayTextwhen your real prompt includes hidden scoping or app context - render
agent.requests.pendingas form cards or sheets - render
agent.approvals.pendingas explicit native confirmation surfaces - use
connections.needsAttentionandstatusLabelto 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
