A React Native app for the ultimate thinking partner.
1const { LettaClient } = require('@letta-ai/letta-client');
2
3// Get token and optional agent ID from command line or environment
4const token = process.env.LETTA_API_KEY;
5const agentId = process.argv[2];
6
7if (!token) {
8 console.error('ERROR: No LETTA_API_KEY environment variable set');
9 console.error('Usage: LETTA_API_KEY=<token> node analyze_message_grouping.js [agent-id]');
10 process.exit(1);
11}
12
13const client = new LettaClient({ token });
14
15(async () => {
16 try {
17 // If no agent ID provided, list available agents
18 if (!agentId) {
19 console.log('No agent ID provided. Listing available agents...\n');
20 const agents = await client.agents.list();
21 console.log(`Found ${agents.length} agents:\n`);
22 agents.forEach((agent, idx) => {
23 console.log(`[${idx + 1}] ${agent.name || 'Unnamed'}`);
24 console.log(` ID: ${agent.id}`);
25 console.log(` Created: ${agent.created_at}`);
26 console.log('');
27 });
28 console.log('Usage: node analyze_message_grouping.js <agent-id>');
29 process.exit(0);
30 }
31
32 console.log(`Fetching messages from agent ${agentId}...\n`);
33
34 const messages = await client.agents.messages.list(agentId, {
35 limit: 200,
36 use_assistant_message: true,
37 });
38
39 console.log(`\n========================================`);
40 console.log(`LOADED ${messages.length} MESSAGES`);
41 console.log(`========================================\n`);
42
43 // Sort chronologically
44 const sorted = [...messages].sort((a, b) => {
45 const timeA = new Date(a.date || 0).getTime();
46 const timeB = new Date(b.date || 0).getTime();
47 return timeA - timeB;
48 });
49
50 // Print all messages with grouping info
51 console.log('ALL MESSAGES (CHRONOLOGICAL):\n');
52 sorted.forEach((msg, idx) => {
53 console.log(`[${idx}]`);
54 console.log(` ID: ${msg.id}`);
55 console.log(` Type: ${msg.messageType}`);
56 console.log(` Step ID: ${msg.stepId || 'none'}`);
57 console.log(` Created: ${msg.date}`);
58
59 if (msg.messageType === 'reasoning_message') {
60 console.log(` Reasoning: ${msg.reasoning?.substring(0, 80) || 'none'}...`);
61 } else if (msg.messageType === 'assistant_message') {
62 console.log(` Content: ${msg.content?.substring(0, 80) || 'none'}...`);
63 } else if (msg.messageType === 'tool_call_message') {
64 console.log(` Tool: ${msg.content?.substring(0, 80) || 'none'}...`);
65 } else if (msg.messageType === 'tool_return_message') {
66 console.log(` Return: ${msg.content?.substring(0, 80) || 'none'}...`);
67 } else if (msg.messageType === 'user_message') {
68 const contentStr = typeof msg.content === 'string'
69 ? msg.content
70 : JSON.stringify(msg.content).substring(0, 80);
71 console.log(` Content: ${contentStr}...`);
72 }
73 console.log('');
74 });
75
76 // Group by ID and analyze
77 console.log('\n========================================');
78 console.log('GROUPING ANALYSIS (by message ID)');
79 console.log('========================================\n');
80
81 const groupedById = new Map();
82 for (const msg of sorted) {
83 if (!groupedById.has(msg.id)) {
84 groupedById.set(msg.id, []);
85 }
86 groupedById.get(msg.id).push(msg);
87 }
88
89 // Find groups with multiple messages
90 const multiMessageGroups = Array.from(groupedById.entries())
91 .filter(([id, msgs]) => msgs.length > 1);
92
93 console.log(`Found ${multiMessageGroups.length} groups with multiple messages sharing same ID:\n`);
94
95 multiMessageGroups.forEach(([id, msgs], idx) => {
96 console.log(`\nGROUP ${idx + 1}: ID=${id}`);
97 console.log(` Contains ${msgs.length} messages:`);
98 msgs.forEach((msg, i) => {
99 console.log(` [${i}] ${msg.messageType} (step: ${msg.stepId?.substring(0, 16) || 'none'}...)`);
100 if (msg.messageType === 'reasoning_message') {
101 console.log(` Reasoning: "${msg.reasoning?.substring(0, 60)}..."`);
102 }
103 });
104 });
105
106 // Group by step_id and analyze
107 console.log('\n\n========================================');
108 console.log('GROUPING ANALYSIS (by step_id)');
109 console.log('========================================\n');
110
111 const groupedByStep = new Map();
112 for (const msg of sorted) {
113 if (msg.stepId) {
114 if (!groupedByStep.has(msg.stepId)) {
115 groupedByStep.set(msg.stepId, []);
116 }
117 groupedByStep.get(msg.stepId).push(msg);
118 }
119 }
120
121 console.log(`Found ${groupedByStep.size} unique step IDs:\n`);
122
123 // Show a few examples of step groupings
124 let stepCount = 0;
125 for (const [stepId, msgs] of groupedByStep.entries()) {
126 if (stepCount++ > 10) break; // Only show first 10
127
128 console.log(`\nSTEP: ${stepId}`);
129 console.log(` Contains ${msgs.length} messages with ${new Set(msgs.map(m => m.id)).size} unique IDs:`);
130 msgs.forEach((msg, i) => {
131 console.log(` [${i}] ${msg.messageType} (id: ${msg.id.substring(0, 16)}...)`);
132 if (msg.messageType === 'reasoning_message') {
133 console.log(` Reasoning: "${msg.reasoning?.substring(0, 60)}..."`);
134 }
135 });
136 }
137
138 // Analyze reasoning accumulation pattern
139 console.log('\n\n========================================');
140 console.log('REASONING ACCUMULATION PATTERNS');
141 console.log('========================================\n');
142
143 // Find messages where reasoning appears multiple times with same ID
144 const reasoningGroups = multiMessageGroups.filter(([id, msgs]) =>
145 msgs.filter(m => m.messageType === 'reasoning_message').length > 1
146 );
147
148 console.log(`Found ${reasoningGroups.length} groups with MULTIPLE reasoning messages:\n`);
149
150 reasoningGroups.forEach(([id, msgs], idx) => {
151 const reasoningMsgs = msgs.filter(m => m.messageType === 'reasoning_message');
152 const assistantMsg = msgs.find(m => m.messageType === 'assistant_message');
153 const toolCallMsg = msgs.find(m => m.messageType === 'tool_call_message');
154
155 console.log(`\nMULTI-REASONING GROUP ${idx + 1}: ID=${id.substring(0, 16)}...`);
156 console.log(` Message composition:`);
157 msgs.forEach((msg, i) => {
158 console.log(` [${i}] ${msg.messageType}`);
159 });
160
161 console.log(`\n Reasoning messages (${reasoningMsgs.length} total):`);
162 reasoningMsgs.forEach((msg, i) => {
163 console.log(` [${i}] "${msg.reasoning?.substring(0, 100)}..."`);
164 });
165
166 if (assistantMsg) {
167 console.log(`\n Assistant message: "${assistantMsg.content?.substring(0, 100)}..."`);
168 }
169 if (toolCallMsg) {
170 console.log(`\n Tool call: "${toolCallMsg.content?.substring(0, 100)}..."`);
171 }
172
173 console.log(`\n RECOMMENDATION:`);
174 if (toolCallMsg && reasoningMsgs.length > 1) {
175 console.log(` → Use LAST reasoning for tool call`);
176 console.log(` → Reasoning [${reasoningMsgs.length - 1}]: "${reasoningMsgs[reasoningMsgs.length - 1].reasoning?.substring(0, 60)}..."`);
177 } else if (assistantMsg) {
178 console.log(` → Use FIRST reasoning for assistant`);
179 console.log(` → Reasoning [0]: "${reasoningMsgs[0].reasoning?.substring(0, 60)}..."`);
180 }
181 });
182
183 } catch (e) {
184 console.error('Error:', e.message);
185 console.error('Full error:', e);
186 }
187})();