Cedar’s mention system allows users to reference specific data, people, or objects in their messages using @ symbols. This provides a natural way to give agents precise context about what the user is referring to.

State-Based Mentions

Automatically allows the user to @ mention any registered state items. The state must first be a cedarState. See the Agentic State documentation.
import { useState } from 'react';
import { useRegisterState, useStateBasedMentionProvider } from 'cedar-os';

function TodoApp() {
	const [todos, setTodos] = useState([
		{ id: 1, text: 'Buy groceries', category: 'shopping' },
		{ id: 2, text: 'Call dentist', category: 'health' },
	]);

	// Register the state
	useRegisterState({
		key: 'todos',
		value: todos,
		setValue: setTodos,
		description: 'Todo items',
	});

	// Enable mentions for todos
	useStateBasedMentionProvider({
		stateKey: 'todos',
		trigger: '@',
		labelField: 'text',
		searchFields: ['text', 'category'],
		description: 'Todo items',
		icon: 'πŸ“',
		color: '#3b82f6',
	});

	return <ChatInput />;
}

useMentionProvider

UseMentionProvider allows you to add anything as a mention, which formats and adds even non-state or other items as part of the input context.
import { useMentionProvider } from 'cedar-os';

function UserMentions() {
	const users = [
		{
			id: 1,
			name: 'Alice Johnson',
			email: 'alice@company.com',
			role: 'Designer',
		},
		{ id: 2, name: 'Bob Smith', email: 'bob@company.com', role: 'Developer' },
	];

	useMentionProvider({
		id: 'users',
		trigger: '@',
		label: 'Users',
		description: 'Team members',
		icon: 'πŸ‘€',
		getItems: (query) => {
			const filtered = query
				? users.filter(
						(user) =>
							user.name.toLowerCase().includes(query.toLowerCase()) ||
							user.email.toLowerCase().includes(query.toLowerCase()) ||
							user.role.toLowerCase().includes(query.toLowerCase())
				  )
				: users;

			return filtered.map((user) => ({
				id: user.id.toString(),
				label: `${user.name} (${user.role})`,
				data: user,
				metadata: {
					icon: 'πŸ‘€',
					color: '#10b981',
				},
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: {
				label: item.label,
				icon: 'πŸ‘€',
				color: '#10b981',
			},
		}),
	});

	return <ChatInput />;
}

Custom Mention Providers

Create custom mention sources:
import { useMentionProvider } from 'cedar-os';

function CustomMentions() {
	// Static mention provider for commands
	useMentionProvider({
		id: 'commands',
		trigger: '/',
		label: 'Commands',
		description: 'Available commands',
		icon: '⚑',
		color: '#f59e0b',
		getItems: (query) => {
			const commands = [
				{ id: 'help', name: 'help', description: 'Show available commands' },
				{ id: 'reset', name: 'reset', description: 'Reset the conversation' },
				{ id: 'export', name: 'export', description: 'Export chat history' },
			];

			const filtered = query
				? commands.filter(
						(cmd) =>
							cmd.name.toLowerCase().includes(query.toLowerCase()) ||
							cmd.description.toLowerCase().includes(query.toLowerCase())
				  )
				: commands;

			return filtered.map((cmd) => ({
				id: cmd.id,
				label: cmd.name,
				data: cmd,
				metadata: {
					icon: '⚑',
					color: '#f59e0b',
				},
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: {
				label: item.label,
				icon: '⚑',
				color: '#f59e0b',
			},
		}),
	});

	// Dynamic mention provider for files
	useMentionProvider({
		id: 'files',
		trigger: '#',
		label: 'Files',
		description: 'Project files',
		icon: 'πŸ“„',
		color: '#8b5cf6',
		getItems: async (query) => {
			// Simulate async file search
			const files = await searchFiles(query);
			return files.map((file) => ({
				id: file.id,
				label: file.name,
				data: file,
				metadata: {
					icon: 'πŸ“„',
					color: '#8b5cf6',
				},
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: {
				label: item.label,
				icon: 'πŸ“„',
				color: '#8b5cf6',
			},
		}),
	});

	return <ChatInput />;
}

// Mock function for demonstration
async function searchFiles(query: string) {
	// Simulate API call
	return [
		{ id: '1', name: 'README.md', path: '/README.md', type: 'markdown' },
		{ id: '2', name: 'package.json', path: '/package.json', type: 'json' },
	].filter(
		(file) => !query || file.name.toLowerCase().includes(query.toLowerCase())
	);
}

Custom Rendering

Customize how mentions appear in different contexts:
import { useMentionProvider } from 'cedar-os';

function CustomRendering() {
	useMentionProvider({
		id: 'users',
		trigger: '@',
		label: 'Users',
		icon: 'πŸ‘€',
		getItems: (query) => {
			// ... get items logic
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: { label: item.label, icon: 'πŸ‘€' },
		}),

		// Custom rendering in the mention menu
		renderMenuItem: (item) => (
			<div className='flex items-center gap-2 p-2'>
				<span className='text-lg'>πŸ‘€</span>
				<div>
					<div className='font-medium'>{item.label}</div>
					<div className='text-sm text-gray-500'>{item.data.role}</div>
				</div>
			</div>
		),

		// Custom rendering in the editor
		renderEditorItem: (item, attrs) => (
			<span className='bg-blue-100 text-blue-800 px-1 rounded'>
				πŸ‘€ {item.label}
			</span>
		),

		// Custom rendering in context badges
		renderContextBadge: (entry) => (
			<div className='bg-gray-100 px-2 py-1 rounded text-sm'>
				πŸ‘€ {entry.metadata?.label}
			</div>
		),
	});

	return <ChatInput />;
}

Multiple Mention Types

Support different mention triggers:
import { useMentionProvider } from 'cedar-os';

function MultiMentionChat() {
	const users = [
		{ id: '1', name: 'Alice', role: 'Designer' },
		{ id: '2', name: 'Bob', role: 'Developer' },
	];

	const channels = [
		{ id: '1', name: 'general', topic: 'General discussion' },
		{ id: '2', name: 'dev', topic: 'Development updates' },
	];

	const commands = [
		{ id: '1', name: 'help', description: 'Show help' },
		{ id: '2', name: 'reset', description: 'Reset chat' },
	];

	// @ for users
	useMentionProvider({
		id: 'users',
		trigger: '@',
		label: 'Users',
		icon: 'πŸ‘€',
		getItems: (query) => {
			const filtered = query
				? users.filter((u) =>
						u.name.toLowerCase().includes(query.toLowerCase())
				  )
				: users;
			return filtered.map((user) => ({
				id: user.id,
				label: user.name,
				data: user,
				metadata: { icon: 'πŸ‘€' },
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: { label: item.label, icon: 'πŸ‘€' },
		}),
	});

	// # for channels/topics
	useMentionProvider({
		id: 'channels',
		trigger: '#',
		label: 'Channels',
		icon: 'πŸ“’',
		getItems: (query) => {
			const filtered = query
				? channels.filter((c) =>
						c.name.toLowerCase().includes(query.toLowerCase())
				  )
				: channels;
			return filtered.map((channel) => ({
				id: channel.id,
				label: channel.name,
				data: channel,
				metadata: { icon: 'πŸ“’' },
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: { label: item.label, icon: 'πŸ“’' },
		}),
	});

	// / for commands
	useMentionProvider({
		id: 'commands',
		trigger: '/',
		label: 'Commands',
		icon: '⚑',
		getItems: (query) => {
			const filtered = query
				? commands.filter((c) =>
						c.name.toLowerCase().includes(query.toLowerCase())
				  )
				: commands;
			return filtered.map((command) => ({
				id: command.id,
				label: command.name,
				data: command,
				metadata: { icon: '⚑' },
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: { label: item.label, icon: '⚑' },
		}),
	});

	return <ChatInput />;
}

Contextual Mentions

Show different mentions based on context:
import { useEffect } from 'react';
import { useMentionProvider } from 'cedar-os';

function ContextualMentions() {
	const currentPage = useCurrentPage();

	// Conditional mention providers
	useEffect(() => {
		if (currentPage === 'projects') {
			const projects = getProjects();
			useMentionProvider({
				id: 'projects',
				trigger: '@',
				label: 'Projects',
				icon: 'πŸ“',
				getItems: (query) => {
					const filtered = query
						? projects.filter((p) =>
								p.name.toLowerCase().includes(query.toLowerCase())
						  )
						: projects;
					return filtered.map((project) => ({
						id: project.id,
						label: project.name,
						data: project,
						metadata: { icon: 'πŸ“' },
					}));
				},
				toContextEntry: (item) => ({
					id: item.id,
					source: 'mention',
					data: item.data,
					metadata: { label: item.label, icon: 'πŸ“' },
				}),
			});
		} else if (currentPage === 'tasks') {
			const tasks = getTasks();
			useMentionProvider({
				id: 'tasks',
				trigger: '@',
				label: 'Tasks',
				icon: 'βœ…',
				getItems: (query) => {
					const filtered = query
						? tasks.filter((t) =>
								t.title.toLowerCase().includes(query.toLowerCase())
						  )
						: tasks;
					return filtered.map((task) => ({
						id: task.id,
						label: task.title,
						data: task,
						metadata: { icon: 'βœ…' },
					}));
				},
				toContextEntry: (item) => ({
					id: item.id,
					source: 'mention',
					data: item.data,
					metadata: { label: item.label, icon: 'βœ…' },
				}),
			});
		}
	}, [currentPage]);

	return <ChatInput />;
}

// Mock functions for demonstration
function useCurrentPage() {
	return 'projects'; // or 'tasks'
}

function getProjects() {
	return [
		{ id: '1', name: 'Website Redesign' },
		{ id: '2', name: 'Mobile App' },
	];
}

function getTasks() {
	return [
		{ id: '1', title: 'Fix login bug' },
		{ id: '2', title: 'Add dark mode' },
	];
}

Mention Data in Context

Access mentioned items in your agent context:
// When user types: "Update the status of @task-123 to completed"
// The agent receives:
{
  message: "Update the status of @task-123 to completed",
  mentions: [
    {
      id: "task-123",
      type: "tasks",
      data: {
        id: "task-123",
        title: "Implement user authentication",
        status: "in-progress",
        assignee: "Alice"
      },
      position: { start: 23, end: 32 }
    }
  ]
}

Advanced Mention Features

Filtering and Validation

You can implement custom filtering and validation logic within your mention providers:
import { useMentionProvider } from 'cedar-os';

function ValidatedMentions() {
	const currentUser = getCurrentUser();

	useMentionProvider({
		id: 'team-users',
		trigger: '@',
		label: 'Team Members',
		icon: 'πŸ‘€',
		getItems: (query) => {
			const allUsers = getAllUsers();

			// Filter by permissions - only show users in same team
			const allowedUsers = allUsers.filter(
				(user) => user.teamId === currentUser.teamId && user.active
			);

			// Filter by search query
			const filtered = query
				? allowedUsers.filter((user) =>
						user.name.toLowerCase().includes(query.toLowerCase())
				  )
				: allowedUsers;

			return filtered.map((user) => ({
				id: user.id,
				label: user.name,
				data: user,
				metadata: {
					icon: 'πŸ‘€',
					color: user.active ? '#10b981' : '#6b7280',
				},
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: {
				label: item.label,
				icon: 'πŸ‘€',
				// Validate data is still current
				valid: item.data.active,
			},
		}),
	});

	return <ChatInput />;
}

// Mock functions
function getCurrentUser() {
	return { id: '1', teamId: 'team-a' };
}

function getAllUsers() {
	return [
		{ id: '1', name: 'Alice', teamId: 'team-a', active: true },
		{ id: '2', name: 'Bob', teamId: 'team-a', active: false },
		{ id: '3', name: 'Charlie', teamId: 'team-b', active: true },
	];
}

Rich Context Information

Provide detailed context data through the mention system:
import { useMentionProvider } from 'cedar-os';

function RichContextMentions() {
	useMentionProvider({
		id: 'project-tasks',
		trigger: '#',
		label: 'Tasks',
		icon: 'βœ…',
		getItems: async (query) => {
			const tasks = await fetchTasks(query);
			return tasks.map((task) => ({
				id: task.id,
				label: `${task.title} (${task.status})`,
				data: {
					...task,
					// Include rich context data
					assignee: task.assignee,
					dueDate: task.dueDate,
					priority: task.priority,
					comments: task.comments,
				},
				metadata: {
					icon: getTaskIcon(task.status),
					color: getTaskColor(task.priority),
				},
			}));
		},
		toContextEntry: (item) => ({
			id: item.id,
			source: 'mention',
			data: item.data,
			metadata: {
				label: item.label,
				icon: item.metadata?.icon,
				color: item.metadata?.color,
				// Additional metadata for agent context
				type: 'task',
				status: item.data.status,
				priority: item.data.priority,
			},
		}),

		// Rich preview in mention menu
		renderMenuItem: (item) => (
			<div
				className='p-2 border-l-2'
				style={{ borderColor: item.metadata?.color }}>
				<div className='flex items-center justify-between'>
					<span className='font-medium'>{item.data.title}</span>
					<span className='text-xs bg-gray-100 px-2 py-1 rounded'>
						{item.data.status}
					</span>
				</div>
				<div className='text-sm text-gray-500 mt-1'>
					Due: {item.data.dueDate} β€’ Assigned to: {item.data.assignee?.name}
				</div>
			</div>
		),
	});

	return <ChatInput />;
}

// Helper functions
function getTaskIcon(status: string) {
	const icons = {
		todo: '⏳',
		'in-progress': 'πŸ”„',
		done: 'βœ…',
		blocked: '🚫',
	};
	return icons[status] || 'πŸ“‹';
}

function getTaskColor(priority: string) {
	const colors = {
		high: '#ef4444',
		medium: '#f59e0b',
		low: '#10b981',
	};
	return colors[priority] || '#6b7280';
}

async function fetchTasks(query: string) {
	// Mock async task fetching
	return [
		{
			id: '1',
			title: 'Fix login bug',
			status: 'in-progress',
			priority: 'high',
			dueDate: '2024-01-15',
			assignee: { name: 'Alice' },
		},
	];
}

Next Steps