Agent Context conrols the additional information to your AI agents beyond just the user’s message. This includes application state, user data, conversation history, and any other contextual information that helps the agent provide better responses. The underlying source of truth for this information is Cedar state, so it is helpful to understand how that works first.

How It Works

When a user sends a message, Cedar automatically gathers context from various sources:
  1. Subscribed States: Application state you’ve made available to the agent via useCedarState and subscribed to via useSubscribeStateToAgentContext
  2. Mentions: Specific data the user has referenced with @ mentions (registered using useStateBasedMentionProvider)
  3. Additional Context: Context entries you’ve manually added (via addContextEntry)
  4. Chat Input Content: The current editor content with mentions
Cedar provides several methods to compile and access this context for your AI agents.

Adding Context

Prerequisite – Before you can add any context or mentions you must register the relevant React state in Cedar using either useRegisterState or useCedarState. All examples below assume that registration step has already happened (or they demonstrate it explicitly).

Mentions System

Cedar’s mention system allows users to reference specific data using @ symbols. You can create mention providers that let users easily reference state data:
import { useStateBasedMentionProvider } from 'cedar-os';
import { FileText } from 'lucide-react';

function DocumentChat() {
	const [documents] = useCedarState('documents', [
		{ id: 'doc1', title: 'Project Proposal', type: 'pdf' },
		{ id: 'doc2', title: 'Meeting Notes', type: 'doc' },
	]);

	// Create a mention provider for documents
	useStateBasedMentionProvider({
		stateKey: 'documents',
		trigger: '@',
		labelField: 'title',
		searchFields: ['title', 'type'],
		description: 'Documents',
		icon: <FileText size={16} />,
		color: '#8b5cf6',
		order: 5, // Control display order when mentioned
	});

	return <ChatInput />;
}
For detailed information about mentions, including custom providers, multiple triggers, and advanced rendering, see the Mentions documentation.

State Subscription

Keep a Cedar state continuously in sync with the agent input context via useSubscribeStateToAgentContext:
import { useCedarState, useSubscribeStateToAgentContext } from 'cedar-os';
import { Settings } from 'lucide-react';

function AutoSyncChat() {
	// 1) Register / create the state in Cedar
	const [appState, setAppState] = useCedarState(
		'appState',
		{
			currentTab: 'dashboard',
			selectedItems: ['item1', 'item2'],
		},
		'UI navigation & selection info'
	);

	// 2) Subscribe that state to the agent context
	useSubscribeStateToAgentContext(
		'appState',
		(state) => ({
			'app-state': [
				{
					id: 'current-state',
					currentTab: state.currentTab,
					selectedItems: state.selectedItems,
				},
			],
		}),
		{
			icon: <Settings size={16} />,
			color: '#6b7280',
			order: 10, // Control display order of context badges
		}
	);

	return <ChatInput />;
}
For comprehensive examples and best practices for state subscription, see the Subscribing State documentation.

Advanced

This is for advanced understanding. Move on to Agentic State Access or Spells for better functionality.

Default Context Tokenization

Cedar’s sendMessage function automatically handles context based on your provider configuration:

For Structured Providers (Mastra, Custom)

// Inside sendMessage for Mastra/Custom providers
const editorContent = state.stringifyEditor(); // "Update @task-123 status"
const structuredContext = state.compileAdditionalContext(); // Raw object (no longer stringified)

// Sent as separate fields:
const llmParams = {
	prompt: editorContent, // Clean user text
	additionalContext: structuredContext, // Structured data object
	route: '/chat',
	userId: 'user-123',
};

For Text-Based Providers (OpenAI, Anthropic, AI-SDK)

// Inside sendMessage for direct LLM providers
const editorContent = state.stringifyEditor(); // "Update @task-123 status"
const fullContext = state.stringifyInputContext(); // Combined string

// Sent as unified prompt:
const llmParams = {
	prompt: fullContext, // Contains: "User Text: ... \n\nAdditional Context: {...}"
	model: 'gpt-4o-mini',
};

Backend Integration Examples

Mastra Backend Example

// Your Mastra agent receives structured data
export const chatAgent = {
	name: 'Chat Agent',
	instructions: 'You are a helpful assistant',
	model: 'gpt-4o-mini',

	async execute(input: { prompt: string; additionalContext?: any }) {
		const { prompt, additionalContext } = input;

		// Process user intent from clean prompt
		console.log('User wants:', prompt);

		// Access structured context programmatically
		if (additionalContext?.tasks) {
			const mentionedTasks = additionalContext.tasks
				.filter((entry) => entry.source === 'mention')
				.map((entry) => entry.data);

			console.log('Referenced tasks:', mentionedTasks);
		}

		// Your custom logic here...
	},
};

Custom Provider Backend Example

// Your custom endpoint receives both fields
app.post('/api/chat', async (req, res) => {
	const { prompt, additionalContext, userId } = req.body;

	// Clean user message for intent processing
	const userIntent = await processIntent(prompt);

	// Structured context for data operations
	const contextualData = {
		mentions: additionalContext?.tasks || [],
		userState: additionalContext?.['user-state'] || [],
		documents: additionalContext?.documents || [],
	};

	// Combine for LLM call
	const systemPrompt = `
    User Intent: ${userIntent}
    Available Context: ${JSON.stringify(contextualData)}
    
    Respond helpfully using the provided context.
  `;

	const response = await openai.chat.completions.create({
		model: 'gpt-4o-mini',
		messages: [
			{ role: 'system', content: systemPrompt },
			{ role: 'user', content: prompt },
		],
	});

	res.json({ content: response.choices[0].message.content });
});

Accessing Additional Context

You can access the context data in several ways:

Raw Context Access

import { useCedarStore } from 'cedar-os';

function ContextInspector() {
	const { additionalContext } = useCedarStore();

	// Access raw context structure
	const tasks = additionalContext.tasks || [];
	const mentionedDocuments = additionalContext.documents || [];

	return (
		<div>
			<h3>Current Context:</h3>
			{Object.entries(additionalContext).map(([key, entries]) => (
				<div key={key}>
					<h4>{key}</h4>
					{entries.map((entry) => (
						<div key={entry.id}>
							{entry.metadata?.label} ({entry.source})
						</div>
					))}
				</div>
			))}
		</div>
	);
}

Stringified Context Access

import { useCedarStore } from 'cedar-os';

function ContextStringifier() {
	const { stringifyEditor, compileAdditionalContext, stringifyInputContext } =
		useCedarStore();

	const inspectContext = () => {
		// Get just the user's text input
		const userText = stringifyEditor();
		console.log('User text:', userText);

		// Get additional context as object (includes state data, setters, and schemas)
		const contextData = compileAdditionalContext();
		console.log('Context data:', contextData);

		// Get combined input and context (what gets sent to AI)
		const fullContext = stringifyInputContext();
		console.log('Full context:', fullContext);
	};

	return <button onClick={inspectContext}>Inspect Context</button>;
}

Adding Specific Context

Manual Context Entries

Add context entries programmatically for specific use cases:
import { useCedarStore } from 'cedar-os';

function ErrorReportingChat() {
	const { addContextEntry, removeContextEntry } = useCedarStore();

	const addErrorContext = (error: Error) => {
		addContextEntry('errors', {
			id: `error-${Date.now()}`,
			source: 'manual',
			data: {
				message: error.message,
				stack: error.stack,
				timestamp: new Date().toISOString(),
				url: window.location.href,
			},
			metadata: {
				label: `Error: ${error.message}`,
				color: '#ef4444',
			},
		});
	};

	const addUserContext = (user: any) => {
		addContextEntry('user-info', {
			id: 'current-user',
			source: 'manual',
			data: {
				id: user.id,
				role: user.role,
				permissions: user.permissions,
			},
			metadata: {
				label: `User: ${user.name}`,
				color: '#3b82f6',
			},
		});
	};

	return (
		<div>
			<button onClick={() => addErrorContext(new Error('Sample error'))}>
				Add Error Context
			</button>
			<ChatInput />
		</div>
	);
}

Conditional Context

Add context based on application state or user actions:
import { useCedarStore } from 'cedar-os';
import { useEffect } from 'react';

function ConditionalContextChat() {
	const { addContextEntry, clearContextBySource } = useCedarStore();
	const currentRoute = useRouter().pathname;
	const selectedItems = useSelection();

	useEffect(() => {
		// Clear previous route context
		clearContextBySource('manual');

		// Add route-specific context
		if (currentRoute === '/dashboard') {
			addContextEntry('navigation', {
				id: 'dashboard-context',
				source: 'manual',
				data: {
					page: 'dashboard',
					widgets: getDashboardWidgets(),
					metrics: getCurrentMetrics(),
				},
				metadata: {
					label: 'Dashboard Context',
				},
			});
		}

		// Add selection context if items are selected
		if (selectedItems.length > 0) {
			addContextEntry('selection', {
				id: 'current-selection',
				source: 'manual',
				data: {
					items: selectedItems,
					count: selectedItems.length,
					types: [...new Set(selectedItems.map((item) => item.type))],
				},
				metadata: {
					label: `${selectedItems.length} items selected`,
				},
			});
		}
	}, [currentRoute, selectedItems]);

	return <ChatInput />;
}

Custom Context Processing

Create your own message sending logic with custom context filtering:
import { useCedarStore } from 'cedar-os';

function CustomContextProcessor() {
	const store = useCedarStore();

	const sendMessageWithFilteredContext = async (
		contextFilter?: (key: string, entries: ContextEntry[]) => ContextEntry[]
	) => {
		// Get the user's input
		const editorContent = store.stringifyEditor();

		// Get raw additional context
		const rawContext = store.additionalContext;

		// Apply custom filtering if provided
		let filteredContext = rawContext;
		if (contextFilter) {
			filteredContext = {};
			Object.entries(rawContext).forEach(([key, entries]) => {
				const filtered = contextFilter(key, entries);
				if (filtered.length > 0) {
					filteredContext[key] = filtered;
				}
			});
		}

		// Create custom context string
		const contextString = JSON.stringify(filteredContext, null, 2);
		const customPrompt = `User Text: ${editorContent}\n\nFiltered Context: ${contextString}`;

		// Send to AI with custom prompt
		const response = await store.callLLM({
			prompt: customPrompt,
			systemPrompt:
				'You are a helpful assistant with access to filtered context.',
		});

		// Handle response
		store.handleLLMResult(response);

		// Add user message to chat (persists by default if message storage is configured)
		store.addMessage({
			role: 'user',
			type: 'text',
			content: editorContent,
		});

		// Clear mentions after sending
		store.clearMentions();
	};

	// Example: Only include high-priority tasks
	const sendWithHighPriorityTasksOnly = () => {
		sendMessageWithFilteredContext((key, entries) => {
			if (key === 'tasks') {
				return entries.filter((entry) => entry.data.priority === 'high');
			}
			return entries; // Keep other context as-is
		});
	};

	// Example: Exclude sensitive data
	const sendWithoutSensitiveData = () => {
		sendMessageWithFilteredContext((key, entries) => {
			if (key === 'user-profile') {
				return entries.map((entry) => ({
					...entry,
					data: {
						...entry.data,
						email: undefined,
						phone: undefined,
						ssn: undefined,
					},
				}));
			}
			return entries;
		});
	};

	// Example: Only include recent items
	const sendWithRecentContextOnly = () => {
		const oneHourAgo = Date.now() - 60 * 60 * 1000;

		sendMessageWithFilteredContext((key, entries) => {
			return entries.filter((entry) => {
				const entryTime = entry.data.timestamp || entry.data.createdAt;
				return entryTime && new Date(entryTime).getTime() > oneHourAgo;
			});
		});
	};

	return (
		<div>
			<ChatInput />
			<div className='flex gap-2 mt-2'>
				<button onClick={sendWithHighPriorityTasksOnly}>
					Send with High Priority Tasks Only
				</button>
				<button onClick={sendWithoutSensitiveData}>
					Send without Sensitive Data
				</button>
				<button onClick={sendWithRecentContextOnly}>
					Send with Recent Context Only
				</button>
			</div>
		</div>
	);
}

Next Steps