@itz4blitz/agentful 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1130 @@
1
+ ---
2
+ name: conversation
3
+ description: Natural language understanding, intent classification, context management, reference resolution, and conversation history analysis for agentful
4
+ model: sonnet
5
+ tools: Read, Write, Edit, Glob, Grep
6
+ ---
7
+
8
+ # Conversation Skill
9
+
10
+ This skill provides natural language processing capabilities for understanding user intent, managing conversation context, resolving references, and maintaining conversation history.
11
+
12
+ ## Core Functions
13
+
14
+ ### 1. Intent Classification
15
+
16
+ ```typescript
17
+ /**
18
+ * Classify user intent with confidence score
19
+ * @param message - User's current message
20
+ * @param conversation_history - Recent conversation context
21
+ * @param product_spec - Product features and requirements
22
+ * @returns Intent classification with confidence
23
+ */
24
+ function classify_intent(
25
+ message: string,
26
+ conversation_history: ConversationMessage[],
27
+ product_spec: ProductSpec
28
+ ): IntentClassification {
29
+ const intents = [
30
+ 'feature_request',
31
+ 'bug_report',
32
+ 'question',
33
+ 'clarification',
34
+ 'status_update',
35
+ 'mind_change',
36
+ 'context_switch',
37
+ 'approval',
38
+ 'rejection',
39
+ 'pause',
40
+ 'continue'
41
+ ];
42
+
43
+ // Analyze message patterns
44
+ const patterns = {
45
+ feature_request: /(?:add|create|implement|build|new|feature|support|enable)/i,
46
+ bug_report: /(?:bug|broken|error|issue|problem|wrong|doesn't work|fix)/i,
47
+ question: /(?:how|what|where|when|why|who|which|can you|explain)/i,
48
+ clarification: /(?:what do you mean|clarify|elaborate|more detail)/i,
49
+ status_update: /(?:status|progress|where are we|what's left)/i,
50
+ mind_change: /(?:actually|wait|never mind|forget that|change|instead)/i,
51
+ context_switch: /(?:let's talk about|switching to|moving on|about)/i,
52
+ approval: /(?:yes|ok|sure|go ahead|approved|sounds good|let's do it)/i,
53
+ rejection: /(?:no|stop|don't|cancel|never mind)/i,
54
+ pause: /(?:pause|hold on|wait|brb|later)/i,
55
+ continue: /(?:continue|resume|let's continue|back)/i
56
+ };
57
+
58
+ // Score each intent
59
+ const scores = {};
60
+ for (const [intent, pattern] of Object.entries(patterns)) {
61
+ const matches = message.match(pattern);
62
+ const baseScore = matches ? matches.length * 0.3 : 0;
63
+
64
+ // Context-aware scoring
65
+ let contextBonus = 0;
66
+ if (conversation_history.length > 0) {
67
+ const lastMessage = conversation_history[conversation_history.length - 1];
68
+ if (lastMessage.role === 'assistant' && intent === 'clarification') {
69
+ contextBonus += 0.2; // User asking for clarification after assistant response
70
+ }
71
+ if (lastMessage.intent === 'question' && intent === 'clarification') {
72
+ contextBonus += 0.15; // Follow-up clarification
73
+ }
74
+ }
75
+
76
+ scores[intent] = Math.min(0.95, baseScore + contextBonus);
77
+ }
78
+
79
+ // Find highest scoring intent
80
+ const topIntent = Object.entries(scores).reduce((a, b) =>
81
+ a[1] > b[1] ? a : b
82
+ );
83
+
84
+ return {
85
+ intent: topIntent[0],
86
+ confidence: topIntent[1],
87
+ alternative_intents: Object.entries(scores)
88
+ .filter(([_, score]) => score > 0.3)
89
+ .sort((a, b) => b[1] - a[1])
90
+ .slice(1, 4)
91
+ .map(([intent, score]) => ({ intent, confidence: score }))
92
+ };
93
+ }
94
+
95
+ interface IntentClassification {
96
+ intent: string;
97
+ confidence: number;
98
+ alternative_intents: Array<{ intent: string; confidence: number }>;
99
+ }
100
+ ```
101
+
102
+ ### 2. Feature Extraction
103
+
104
+ ```typescript
105
+ /**
106
+ * Extract which feature/user is talking about
107
+ * @param message - User's current message
108
+ * @param product_spec - Product features and requirements
109
+ * @returns Extracted feature reference
110
+ */
111
+ function extract_feature_mention(
112
+ message: string,
113
+ product_spec: ProductSpec
114
+ ): FeatureMention {
115
+ const mentioned = [];
116
+
117
+ // Direct feature name matches
118
+ if (product_spec.features) {
119
+ for (const [featureId, feature] of Object.entries(product_spec.features)) {
120
+ const featureName = feature.name || featureId;
121
+ if (message.toLowerCase().includes(featureName.toLowerCase())) {
122
+ mentioned.push({
123
+ type: 'direct',
124
+ feature_id: featureId,
125
+ feature_name: featureName,
126
+ confidence: 0.9
127
+ });
128
+ }
129
+ }
130
+ }
131
+
132
+ // Hierarchical structure
133
+ if (product_spec.domains) {
134
+ for (const [domainId, domain] of Object.entries(product_spec.domains)) {
135
+ // Check domain mention
136
+ if (message.toLowerCase().includes(domain.name.toLowerCase())) {
137
+ mentioned.push({
138
+ type: 'domain',
139
+ domain_id: domainId,
140
+ domain_name: domain.name,
141
+ confidence: 0.85
142
+ });
143
+ }
144
+
145
+ // Check feature mentions within domain
146
+ if (domain.features) {
147
+ for (const [featureId, feature] of Object.entries(domain.features)) {
148
+ const featureName = feature.name || featureId;
149
+ if (message.toLowerCase().includes(featureName.toLowerCase())) {
150
+ mentioned.push({
151
+ type: 'feature',
152
+ domain_id: domainId,
153
+ feature_id: featureId,
154
+ feature_name: featureName,
155
+ confidence: 0.9
156
+ });
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ // Subtask mentions
164
+ const subtaskPattern = /(?:subtask|task|item)\s+(\d+)/i;
165
+ const subtaskMatch = message.match(subtaskPattern);
166
+ if (subtaskMatch) {
167
+ mentioned.push({
168
+ type: 'subtask_reference',
169
+ reference: subtaskMatch[1],
170
+ confidence: 0.7
171
+ });
172
+ }
173
+
174
+ // Return highest confidence mention or null
175
+ if (mentioned.length === 0) {
176
+ return { type: 'none', confidence: 0 };
177
+ }
178
+
179
+ return mentioned.sort((a, b) => b.confidence - a.confidence)[0];
180
+ }
181
+
182
+ interface FeatureMention {
183
+ type: 'direct' | 'domain' | 'feature' | 'subtask_reference' | 'none';
184
+ domain_id?: string;
185
+ domain_name?: string;
186
+ feature_id?: string;
187
+ feature_name?: string;
188
+ reference?: string;
189
+ confidence: number;
190
+ }
191
+ ```
192
+
193
+ ### 3. Bug Description Extraction
194
+
195
+ ```typescript
196
+ /**
197
+ * Extract bug details from conversation context
198
+ * @param message - User's current message
199
+ * @param conversation_history - Recent conversation context
200
+ * @returns Bug description with context
201
+ */
202
+ function extract_bug_description(
203
+ message: string,
204
+ conversation_history: ConversationMessage[]
205
+ ): BugDescription {
206
+ const bugInfo = {
207
+ description: message,
208
+ steps_to_reproduce: [],
209
+ expected_behavior: null,
210
+ actual_behavior: null,
211
+ related_feature: null,
212
+ severity: 'unknown',
213
+ context_messages: []
214
+ };
215
+
216
+ // Look for "expected" vs "actual" patterns
217
+ const expectedPattern = /(?:expected|should|supposed to):\s*(.+?)(?:\.|$)/i;
218
+ const actualPattern = /(?:actually|but it|instead):\s*(.+?)(?:\.|$)/i;
219
+
220
+ const expectedMatch = message.match(expectedPattern);
221
+ const actualMatch = message.match(actualPattern);
222
+
223
+ if (expectedMatch) {
224
+ bugInfo.expected_behavior = expectedMatch[1].trim();
225
+ }
226
+ if (actualMatch) {
227
+ bugInfo.actual_behavior = actualMatch[1].trim();
228
+ }
229
+
230
+ // Look for numbered steps
231
+ const stepPattern = /^\d+\.\s*(.+)$/gm;
232
+ const steps = message.match(stepPattern);
233
+ if (steps) {
234
+ bugInfo.steps_to_reproduce = steps.map(step => step.replace(/^\d+\.\s*/, ''));
235
+ }
236
+
237
+ // Infer severity from keywords
238
+ const severeKeywords = ['crash', 'broken', 'fail', 'critical', 'blocking'];
239
+ const minorKeywords = ['typo', 'cosmetic', 'minor', 'polish'];
240
+
241
+ if (severeKeywords.some(kw => message.toLowerCase().includes(kw))) {
242
+ bugInfo.severity = 'high';
243
+ } else if (minorKeywords.some(kw => message.toLowerCase().includes(kw))) {
244
+ bugInfo.severity = 'low';
245
+ }
246
+
247
+ // Gather relevant context from conversation history
248
+ const relevantContext = conversation_history
249
+ .filter(msg => {
250
+ const msgTime = new Date(msg.timestamp);
251
+ const now = new Date();
252
+ const hoursDiff = (now.getTime() - msgTime.getTime()) / (1000 * 60 * 60);
253
+ return hoursDiff < 2; // Last 2 hours
254
+ })
255
+ .slice(-5); // Last 5 messages
256
+
257
+ bugInfo.context_messages = relevantContext;
258
+
259
+ return bugInfo;
260
+ }
261
+
262
+ interface BugDescription {
263
+ description: string;
264
+ steps_to_reproduce: string[];
265
+ expected_behavior: string | null;
266
+ actual_behavior: string | null;
267
+ related_feature: string | null;
268
+ severity: 'high' | 'medium' | 'low' | 'unknown';
269
+ context_messages: ConversationMessage[];
270
+ }
271
+ ```
272
+
273
+ ### 4. Ambiguity Detection
274
+
275
+ ```typescript
276
+ /**
277
+ * Detect unclear or ambiguous requests
278
+ * @param message - User's current message
279
+ * @returns Ambiguity analysis
280
+ */
281
+ function detect_ambiguity(message: string): AmbiguityAnalysis {
282
+ const ambiguities = [];
283
+ let confidence = 0;
284
+
285
+ // Check for pronouns without context
286
+ const pronounPattern = /\b(it|that|this|they|them|those)\b/gi;
287
+ const pronouns = message.match(pronounPattern);
288
+ if (pronouns && pronouns.length > 0) {
289
+ // If message starts with pronoun, highly likely needs context
290
+ if (/^(it|that|this|they|them)/i.test(message.trim())) {
291
+ ambiguities.push({
292
+ type: 'pronoun_without_antecedent',
293
+ severity: 'high',
294
+ text: pronouns[0],
295
+ message: 'Pronoun at start of message without clear referent'
296
+ });
297
+ confidence = Math.max(confidence, 0.8);
298
+ }
299
+ }
300
+
301
+ // Check for vague verbs
302
+ const vagueVerbs = ['fix', 'update', 'change', 'improve', 'handle'];
303
+ const vagueVerbPattern = new RegExp(`\\b(${vagueVerbs.join('|')})\\b\\s+(?:it|that|this)`, 'i');
304
+ const vagueMatch = message.match(vagueVerbPattern);
305
+ if (vagueMatch) {
306
+ ambiguities.push({
307
+ type: 'vague_action',
308
+ severity: 'medium',
309
+ text: vagueMatch[0],
310
+ message: 'Action verb without specific target'
311
+ });
312
+ confidence = Math.max(confidence, 0.6);
313
+ }
314
+
315
+ // Check for short messages (< 10 words)
316
+ const wordCount = message.split(/\s+/).length;
317
+ if (wordCount < 10 && wordCount > 1) {
318
+ ambiguities.push({
319
+ type: 'insufficient_detail',
320
+ severity: 'low',
321
+ text: message,
322
+ message: 'Message is very short, may lack detail'
323
+ });
324
+ confidence = Math.max(confidence, 0.4);
325
+ }
326
+
327
+ // Check for multiple possible intents
328
+ const actionWords = message.split(/\s+/).filter(word =>
329
+ /^(add|create|fix|update|delete|remove|test|check|verify|deploy|build|run)/i.test(word)
330
+ );
331
+ if (actionWords.length > 2) {
332
+ ambiguities.push({
333
+ type: 'multiple_actions',
334
+ severity: 'medium',
335
+ text: actionWords.join(', '),
336
+ message: 'Multiple actions detected, unclear priority'
337
+ });
338
+ confidence = Math.max(confidence, 0.7);
339
+ }
340
+
341
+ return {
342
+ is_ambiguous: ambiguities.length > 0,
343
+ confidence,
344
+ ambiguities,
345
+ suggestion: ambiguities.length > 0 ? generate_clarification_suggestion(ambiguities) : null
346
+ };
347
+ }
348
+
349
+ interface AmbiguityAnalysis {
350
+ is_ambiguous: boolean;
351
+ confidence: number;
352
+ ambiguities: Array<{
353
+ type: string;
354
+ severity: 'high' | 'medium' | 'low';
355
+ text: string;
356
+ message: string;
357
+ }>;
358
+ suggestion: string | null;
359
+ }
360
+
361
+ function generate_clarification_suggestion(ambiguities: any[]): string {
362
+ const highSeverity = ambiguities.find(a => a.severity === 'high');
363
+ if (highSeverity) {
364
+ return 'Could you please provide more specific details? What specifically are you referring to?';
365
+ }
366
+ return 'I want to make sure I understand correctly. Could you provide a bit more detail?';
367
+ }
368
+ ```
369
+
370
+ ### 5. Clarification Suggestions
371
+
372
+ ```typescript
373
+ /**
374
+ * Generate helpful clarifying questions
375
+ * @param ambiguous_message - The ambiguous user message
376
+ * @param product_spec - Product features and requirements
377
+ * @returns Suggested clarifying questions
378
+ */
379
+ function suggest_clarification(
380
+ ambiguous_message: string,
381
+ product_spec: ProductSpec
382
+ ): ClarificationSuggestion {
383
+ const questions = [];
384
+
385
+ // Feature-specific clarification
386
+ const featureMention = extract_feature_mention(ambiguous_message, product_spec);
387
+ if (featureMention.type === 'none') {
388
+ // No feature mentioned, ask which one
389
+ if (product_spec.features) {
390
+ const featureNames = Object.values(product_spec.features)
391
+ .map(f => f.name || f.id)
392
+ .slice(0, 5);
393
+ questions.push({
394
+ type: 'feature_selection',
395
+ question: `Which feature are you referring to? For example: ${featureNames.join(', ')}`,
396
+ options: featureNames
397
+ });
398
+ } else if (product_spec.domains) {
399
+ const domainNames = Object.values(product_spec.domains)
400
+ .map(d => d.name)
401
+ .slice(0, 5);
402
+ questions.push({
403
+ type: 'domain_selection',
404
+ question: `Which domain would you like to work on? For example: ${domainNames.join(', ')}`,
405
+ options: domainNames
406
+ });
407
+ }
408
+ }
409
+
410
+ // Action-specific clarification
411
+ const actions = ambiguous_message.match(/(?:add|create|fix|update|delete|remove|test|check)/gi);
412
+ if (actions && actions.length > 1) {
413
+ questions.push({
414
+ type: 'action_priority',
415
+ question: `I see multiple actions: ${actions.join(', ')}. Which would you like me to focus on first?`,
416
+ options: actions
417
+ });
418
+ }
419
+
420
+ // Bug report clarification
421
+ if (/bug|broken|error|issue/i.test(ambiguous_message)) {
422
+ questions.push({
423
+ type: 'bug_details',
424
+ question: 'Could you provide more details about the bug? What should happen vs. what actually happens?',
425
+ options: null
426
+ });
427
+ }
428
+
429
+ // Generic clarification if no specific ones
430
+ if (questions.length === 0) {
431
+ questions.push({
432
+ type: 'generic',
433
+ question: 'Could you please provide more details about what you\'d like me to do?',
434
+ options: null
435
+ });
436
+ }
437
+
438
+ return {
439
+ primary_question: questions[0].question,
440
+ follow_up_questions: questions.slice(1),
441
+ suggested_responses: questions[0].options || []
442
+ };
443
+ }
444
+
445
+ interface ClarificationSuggestion {
446
+ primary_question: string;
447
+ follow_up_questions: Array<{ type: string; question: string; options: string[] | null }>;
448
+ suggested_responses: string[];
449
+ }
450
+ ```
451
+
452
+ ## Conversation State Management
453
+
454
+ ### State Structure
455
+
456
+ ```typescript
457
+ /**
458
+ * Conversation state schema
459
+ * Stored in: .agentful/conversation-state.json
460
+ */
461
+ interface ConversationState {
462
+ // Current context
463
+ current_feature: {
464
+ domain_id?: string;
465
+ feature_id?: string;
466
+ feature_name?: string;
467
+ subtask_id?: string;
468
+ } | null;
469
+
470
+ current_phase: 'idle' | 'planning' | 'implementing' | 'testing' | 'reviewing' | 'deploying';
471
+
472
+ last_action: {
473
+ type: string;
474
+ description: string;
475
+ timestamp: string;
476
+ result?: any;
477
+ } | null;
478
+
479
+ // Related features context
480
+ related_features: Array<{
481
+ feature_id: string;
482
+ feature_name: string;
483
+ relationship: 'dependency' | 'similar' | 'related';
484
+ }>;
485
+
486
+ // User preferences
487
+ user_preferences: {
488
+ communication_style: 'concise' | 'detailed' | 'balanced';
489
+ update_frequency: 'immediate' | 'summary' | 'on_completion';
490
+ ask_before_deleting: boolean;
491
+ test_automatically: boolean;
492
+ };
493
+
494
+ // Session tracking
495
+ session_start: string;
496
+ last_message_time: string;
497
+ message_count: number;
498
+
499
+ // Context health
500
+ context_health: {
501
+ is_stale: boolean;
502
+ last_confirmed_intent: string;
503
+ ambiguity_count: number;
504
+ clarification_count: number;
505
+ };
506
+ }
507
+ ```
508
+
509
+ ### Reference Resolution
510
+
511
+ ```typescript
512
+ /**
513
+ * Resolve pronouns and references to previous messages
514
+ * @param message - Current message with potential references
515
+ * @param conversation_history - Message history
516
+ * @param state - Current conversation state
517
+ * @returns Resolved message with references expanded
518
+ */
519
+ function resolve_references(
520
+ message: string,
521
+ conversation_history: ConversationMessage[],
522
+ state: ConversationState
523
+ ): ResolvedMessage {
524
+ let resolved = message;
525
+ const references = [];
526
+
527
+ // Replace "it", "that", "this" with actual referents
528
+ const pronounMap = {
529
+ 'it': state.current_feature?.feature_name,
530
+ 'that': state.last_action?.description,
531
+ 'this': state.current_feature?.feature_name
532
+ };
533
+
534
+ for (const [pronoun, referent] of Object.entries(pronounMap)) {
535
+ if (referent) {
536
+ const pattern = new RegExp(`\\b${pronoun}\\b`, 'gi');
537
+ if (pattern.test(message)) {
538
+ resolved = resolved.replace(pattern, referent);
539
+ references.push({
540
+ original: pronoun,
541
+ resolved: referent,
542
+ type: 'pronoun'
543
+ });
544
+ }
545
+ }
546
+ }
547
+
548
+ // Resolve "the feature", "the bug", etc.
549
+ const definiteReferences = {
550
+ 'the feature': state.current_feature?.feature_name,
551
+ 'the bug': state.last_action?.type === 'bug_fix' ? state.last_action.description : null,
552
+ 'the task': state.current_feature?.subtask_id
553
+ };
554
+
555
+ for (const [phrase, referent] of Object.entries(definiteReferences)) {
556
+ if (referent) {
557
+ resolved = resolved.replace(new RegExp(phrase, 'gi'), referent);
558
+ references.push({
559
+ original: phrase,
560
+ resolved: referent,
561
+ type: 'definite_reference'
562
+ });
563
+ }
564
+ }
565
+
566
+ return {
567
+ original: message,
568
+ resolved,
569
+ references,
570
+ confidence: references.length > 0 ? 0.85 : 1.0
571
+ };
572
+ }
573
+
574
+ interface ResolvedMessage {
575
+ original: string;
576
+ resolved: string;
577
+ references: Array<{
578
+ original: string;
579
+ resolved: string;
580
+ type: string;
581
+ }>;
582
+ confidence: number;
583
+ }
584
+ ```
585
+
586
+ ### Context Loss Recovery
587
+
588
+ ```typescript
589
+ /**
590
+ * Detect and handle context loss (>24h gaps)
591
+ * @param conversation_history - Full conversation history
592
+ * @param state - Current conversation state
593
+ * @returns Context recovery recommendation
594
+ */
595
+ function detect_context_loss(
596
+ conversation_history: ConversationMessage[],
597
+ state: ConversationState
598
+ ): ContextRecovery {
599
+ const now = new Date();
600
+ const lastMessage = conversation_history[conversation_history.length - 1];
601
+ const lastMessageTime = new Date(lastMessage?.timestamp || state.session_start);
602
+
603
+ const hoursDiff = (now.getTime() - lastMessageTime.getTime()) / (1000 * 60 * 60);
604
+
605
+ if (hoursDiff > 24) {
606
+ // Context is stale
607
+ return {
608
+ is_stale: true,
609
+ hours_since_last_message: Math.round(hoursDiff),
610
+ recommendation: 'summarize_and_confirm',
611
+ message: `It's been ${Math.round(hoursDiff)} hours since our last conversation. Before I continue, let me confirm what we were working on.`,
612
+ context_summary: {
613
+ last_feature: state.current_feature?.feature_name,
614
+ last_phase: state.current_phase,
615
+ last_action: state.last_action?.description
616
+ },
617
+ suggested_confirmation: `We were working on ${state.current_feature?.feature_name || 'a feature'}. Would you like to continue with that, or would you prefer to start something new?`
618
+ };
619
+ }
620
+
621
+ return {
622
+ is_stale: false,
623
+ hours_since_last_message: Math.round(hoursDiff),
624
+ recommendation: 'continue',
625
+ message: null
626
+ };
627
+ }
628
+
629
+ interface ContextRecovery {
630
+ is_stale: boolean;
631
+ hours_since_last_message: number;
632
+ recommendation: 'summarize_and_confirm' | 'continue' | 'reset';
633
+ message: string | null;
634
+ context_summary?: {
635
+ last_feature?: string;
636
+ last_phase?: string;
637
+ last_action?: string;
638
+ };
639
+ suggested_confirmation?: string;
640
+ }
641
+ ```
642
+
643
+ ### User Preference Learning
644
+
645
+ ```typescript
646
+ /**
647
+ * Learn and adapt to user preferences
648
+ * @param conversation_history - Recent conversation history
649
+ * @param state - Current conversation state
650
+ * @returns Updated user preferences
651
+ */
652
+ function learn_user_preferences(
653
+ conversation_history: ConversationMessage[],
654
+ state: ConversationState
655
+ ): UserPreferences {
656
+ const preferences = { ...state.user_preferences };
657
+
658
+ // Analyze user's communication style
659
+ const userMessages = conversation_history.filter(m => m.role === 'user').slice(-20);
660
+ const avgMessageLength = userMessages.reduce((sum, m) =>
661
+ sum + m.content.split(/\s+/).length, 0
662
+ ) / userMessages.length;
663
+
664
+ if (avgMessageLength < 10) {
665
+ preferences.communication_style = 'concise';
666
+ } else if (avgMessageLength > 30) {
667
+ preferences.communication_style = 'detailed';
668
+ } else {
669
+ preferences.communication_style = 'balanced';
670
+ }
671
+
672
+ // Detect preference for updates
673
+ const statusRequests = userMessages.filter(m =>
674
+ /status|progress|where are we|what's left/i.test(m.content)
675
+ ).length;
676
+
677
+ if (statusRequests > 3) {
678
+ preferences.update_frequency = 'summary';
679
+ } else if (statusRequests === 0 && userMessages.length > 10) {
680
+ preferences.update_frequency = 'on_completion';
681
+ }
682
+
683
+ // Detect safety preference
684
+ const confirmationRequests = userMessages.filter(m =>
685
+ /are you sure|double check|verify|confirm/i.test(m.content)
686
+ ).length;
687
+
688
+ preferences.ask_before_deleting = confirmationRequests > 2;
689
+
690
+ // Detect testing preference
691
+ const testMentions = userMessages.filter(m =>
692
+ /test|testing|tests|coverage/i.test(m.content)
693
+ ).length;
694
+
695
+ preferences.test_automatically = testMentions > 2;
696
+
697
+ return preferences;
698
+ }
699
+
700
+ interface UserPreferences {
701
+ communication_style: 'concise' | 'detailed' | 'balanced';
702
+ update_frequency: 'immediate' | 'summary' | 'on_completion';
703
+ ask_before_deleting: boolean;
704
+ test_automatically: boolean;
705
+ }
706
+ ```
707
+
708
+ ## Conversation History Management
709
+
710
+ ### History File Structure
711
+
712
+ ```bash
713
+ # Stored in: .agentful/conversation-history.json
714
+ {
715
+ "version": "1.0",
716
+ "session_id": "uuid",
717
+ "started_at": "2026-01-18T00:00:00Z",
718
+ "messages": [
719
+ {
720
+ "id": "msg-uuid",
721
+ "role": "user|assistant|system",
722
+ "content": "Message text",
723
+ "timestamp": "2026-01-18T00:00:00Z",
724
+ "intent": "feature_request",
725
+ "entities": {
726
+ "feature_id": "login",
727
+ "domain_id": "authentication"
728
+ },
729
+ "references_resolved": ["it -> login feature"]
730
+ }
731
+ ],
732
+ "context_snapshot": {
733
+ "current_feature": "login",
734
+ "current_phase": "implementing",
735
+ "related_features": ["register", "logout"]
736
+ }
737
+ }
738
+ ```
739
+
740
+ ### History Operations
741
+
742
+ ```typescript
743
+ /**
744
+ * Add message to conversation history
745
+ */
746
+ function add_message_to_history(
747
+ message: ConversationMessage,
748
+ history_path: string = '.agentful/conversation-history.json'
749
+ ): void {
750
+ let history = read_conversation_history(history_path);
751
+
752
+ history.messages.push({
753
+ ...message,
754
+ id: message.id || generate_uuid(),
755
+ timestamp: message.timestamp || new Date().toISOString()
756
+ });
757
+
758
+ // Keep only last 100 messages to prevent file bloat
759
+ if (history.messages.length > 100) {
760
+ history.messages = history.messages.slice(-100);
761
+ }
762
+
763
+ Write(history_path, JSON.stringify(history, null, 2));
764
+ }
765
+
766
+ /**
767
+ * Read conversation history
768
+ */
769
+ function read_conversation_history(
770
+ history_path: string = '.agentful/conversation-history.json'
771
+ ): ConversationHistory {
772
+ try {
773
+ const content = Read(history_path);
774
+ return JSON.parse(content);
775
+ } catch (error) {
776
+ // Initialize new history
777
+ return {
778
+ version: "1.0",
779
+ session_id: generate_uuid(),
780
+ started_at: new Date().toISOString(),
781
+ messages: [],
782
+ context_snapshot: null
783
+ };
784
+ }
785
+ }
786
+
787
+ /**
788
+ * Get recent conversation context
789
+ */
790
+ function get_recent_context(
791
+ history_path: string = '.agentful/conversation-history.json',
792
+ message_count: number = 10
793
+ ): ConversationMessage[] {
794
+ const history = read_conversation_history(history_path);
795
+ return history.messages.slice(-message_count);
796
+ }
797
+ ```
798
+
799
+ ## Integration with Orchestrator
800
+
801
+ ### Delegation Interface
802
+
803
+ ```typescript
804
+ /**
805
+ * Determine if delegation to another skill is needed
806
+ * @param intent - Classified intent from user message
807
+ * @param entities - Extracted entities (features, domains, etc.)
808
+ * @returns Delegation decision
809
+ */
810
+ function determine_delegation(
811
+ intent: IntentClassification,
812
+ entities: FeatureMention
813
+ ): DelegationDecision {
814
+ // Feature-related intents -> delegate to appropriate agent
815
+ if (['feature_request', 'bug_report', 'status_update'].includes(intent.intent)) {
816
+ if (entities.type === 'feature' || entities.type === 'direct') {
817
+ return {
818
+ should_delegate: true,
819
+ target_skill: determine_skill_for_feature(entities),
820
+ context: {
821
+ intent: intent.intent,
822
+ feature_id: entities.feature_id,
823
+ domain_id: entities.domain_id
824
+ }
825
+ };
826
+ }
827
+ }
828
+
829
+ // Validation request -> delegate to validation skill
830
+ if (/test|check|validate|verify/i.test(intent.intent)) {
831
+ return {
832
+ should_delegate: true,
833
+ target_skill: 'validation',
834
+ context: {
835
+ intent: intent.intent
836
+ }
837
+ };
838
+ }
839
+
840
+ // Product tracking -> delegate to product-tracking skill
841
+ if (['status_update', 'progress'].includes(intent.intent)) {
842
+ return {
843
+ should_delegate: true,
844
+ target_skill: 'product-tracking',
845
+ context: {
846
+ intent: intent.intent
847
+ }
848
+ };
849
+ }
850
+
851
+ return {
852
+ should_delegate: false,
853
+ target_skill: null,
854
+ context: null
855
+ };
856
+ }
857
+
858
+ interface DelegationDecision {
859
+ should_delegate: boolean;
860
+ target_skill: string | null;
861
+ context: any;
862
+ }
863
+
864
+ function determine_skill_for_feature(entities: FeatureMention): string {
865
+ // Map domains/features to appropriate skills
866
+ const domainSkillMap = {
867
+ 'authentication': 'backend',
868
+ 'user-management': 'backend',
869
+ 'database': 'backend',
870
+ 'frontend': 'frontend',
871
+ 'ui': 'frontend',
872
+ 'testing': 'tester'
873
+ };
874
+
875
+ if (entities.domain_name) {
876
+ const domainKey = Object.keys(domainSkillMap).find(key =>
877
+ entities.domain_name.toLowerCase().includes(key)
878
+ );
879
+ if (domainKey) {
880
+ return domainSkillMap[domainKey];
881
+ }
882
+ }
883
+
884
+ return 'backend'; // Default
885
+ }
886
+ ```
887
+
888
+ ### Completion Tracking Integration
889
+
890
+ ```typescript
891
+ /**
892
+ * Update conversation state after action completion
893
+ * @param state - Current conversation state
894
+ * @param action_result - Result from delegated skill
895
+ * @returns Updated conversation state
896
+ */
897
+ function update_conversation_state(
898
+ state: ConversationState,
899
+ action_result: ActionResult
900
+ ): ConversationState {
901
+ const updated = { ...state };
902
+
903
+ // Update last action
904
+ updated.last_action = {
905
+ type: action_result.type,
906
+ description: action_result.description,
907
+ timestamp: new Date().toISOString(),
908
+ result: action_result
909
+ };
910
+
911
+ // Update phase based on action result
912
+ if (action_result.status === 'complete') {
913
+ if (state.current_phase === 'implementing') {
914
+ updated.current_phase = 'testing';
915
+ } else if (state.current_phase === 'testing') {
916
+ updated.current_phase = 'reviewing';
917
+ }
918
+ }
919
+
920
+ // Update message count and time
921
+ updated.message_count++;
922
+ updated.last_message_time = new Date().toISOString();
923
+
924
+ // Clear ambiguity count on successful action
925
+ if (action_result.status === 'success') {
926
+ updated.context_health.ambiguity_count = 0;
927
+ updated.context_health.clarification_count = 0;
928
+ }
929
+
930
+ return updated;
931
+ }
932
+ ```
933
+
934
+ ## Edge Case Handling
935
+
936
+ ### Mind Changes
937
+
938
+ ```typescript
939
+ /**
940
+ * Detect and handle user mind changes
941
+ * @param message - Current message
942
+ * @param state - Current conversation state
943
+ * @returns Mind change detection result
944
+ */
945
+ function detect_mind_change(
946
+ message: string,
947
+ state: ConversationState
948
+ ): MindChangeDetection {
949
+ const mindChangePatterns = [
950
+ /actually/i,
951
+ /wait/i,
952
+ /never mind/i,
953
+ /forget that/i,
954
+ /change\s+my\s+mind/i,
955
+ /instead/i,
956
+ /stop\s+(?:that|it)/i
957
+ ];
958
+
959
+ const hasMindChange = mindChangePatterns.some(pattern => pattern.test(message));
960
+
961
+ if (hasMindChange) {
962
+ return {
963
+ detected: true,
964
+ confidence: 0.8,
965
+ previous_intent: state.last_action?.type,
966
+ suggestion: 'I understand you\'d like to change direction. What would you like to do instead?',
967
+ reset_context: /never mind|forget that|stop/i.test(message)
968
+ };
969
+ }
970
+
971
+ return {
972
+ detected: false,
973
+ confidence: 0,
974
+ previous_intent: null,
975
+ suggestion: null,
976
+ reset_context: false
977
+ };
978
+ }
979
+
980
+ interface MindChangeDetection {
981
+ detected: boolean;
982
+ confidence: number;
983
+ previous_intent: string | null;
984
+ suggestion: string | null;
985
+ reset_context: boolean;
986
+ }
987
+ ```
988
+
989
+ ### Stale Context Handling
990
+
991
+ ```typescript
992
+ /**
993
+ * Handle stale context when references no longer make sense
994
+ * @param message - Current message
995
+ * @param state - Current conversation state
996
+ * @returns Stale context handling response
997
+ */
998
+ function handle_stale_context(
999
+ message: string,
1000
+ state: ConversationState
1001
+ ): StaleContextResponse {
1002
+ // Check if current feature is referenced but stale
1003
+ const featureReference = extract_feature_mention(message, null);
1004
+
1005
+ if (featureReference.type === 'none' && state.current_feature) {
1006
+ // User said "it" but current feature is old
1007
+ const hoursSinceLastAction = state.last_action
1008
+ ? (Date.now() - new Date(state.last_action.timestamp).getTime()) / (1000 * 60 * 60)
1009
+ : Infinity;
1010
+
1011
+ if (hoursSinceLastAction > 4) {
1012
+ return {
1013
+ is_stale: true,
1014
+ suggestion: `I'm not sure what you're referring to. We last worked on ${state.current_feature.feature_name} ${Math.round(hoursSinceLastAction)} hours ago. Are you still referring to that?`,
1015
+ confirm_needed: true
1016
+ };
1017
+ }
1018
+ }
1019
+
1020
+ return {
1021
+ is_stale: false,
1022
+ suggestion: null,
1023
+ confirm_needed: false
1024
+ };
1025
+ }
1026
+
1027
+ interface StaleContextResponse {
1028
+ is_stale: boolean;
1029
+ suggestion: string | null;
1030
+ confirm_needed: boolean;
1031
+ }
1032
+ ```
1033
+
1034
+ ## Usage Example
1035
+
1036
+ ```typescript
1037
+ // Complete conversation processing flow
1038
+ async function process_conversation(userMessage: string): Promise<ConversationResponse> {
1039
+ // 1. Load conversation state and history
1040
+ const state = load_conversation_state();
1041
+ const history = read_conversation_history();
1042
+ const productSpec = load_product_spec();
1043
+
1044
+ // 2. Check for context loss
1045
+ const contextRecovery = detect_context_loss(history.messages, state);
1046
+ if (contextRecovery.is_stale) {
1047
+ return {
1048
+ type: 'context_recovery',
1049
+ message: contextRecovery.message,
1050
+ confirmation_needed: true
1051
+ };
1052
+ }
1053
+
1054
+ // 3. Detect mind changes
1055
+ const mindChange = detect_mind_change(userMessage, state);
1056
+ if (mindChange.detected && mindChange.reset_context) {
1057
+ state.current_feature = null;
1058
+ state.current_phase = 'idle';
1059
+ save_conversation_state(state);
1060
+ }
1061
+
1062
+ // 4. Resolve references
1063
+ const resolved = resolve_references(userMessage, history.messages, state);
1064
+
1065
+ // 5. Classify intent
1066
+ const intent = classify_intent(resolved.resolved, history.messages, productSpec);
1067
+
1068
+ // 6. Extract entities
1069
+ const entities = extract_feature_mention(resolved.resolved, productSpec);
1070
+
1071
+ // 7. Detect ambiguity
1072
+ const ambiguity = detect_ambiguity(resolved.resolved);
1073
+ if (ambiguity.is_ambiguous && ambiguity.confidence > 0.6) {
1074
+ const clarification = suggest_clarification(resolved.resolved, productSpec);
1075
+ return {
1076
+ type: 'clarification_needed',
1077
+ message: clarification.primary_question,
1078
+ suggestions: clarification.suggested_responses
1079
+ };
1080
+ }
1081
+
1082
+ // 8. Determine delegation
1083
+ const delegation = determine_delegation(intent, entities);
1084
+
1085
+ // 9. Add user message to history
1086
+ add_message_to_history({
1087
+ role: 'user',
1088
+ content: userMessage,
1089
+ intent: intent.intent,
1090
+ entities: entities,
1091
+ references_resolved: resolved.references
1092
+ });
1093
+
1094
+ // 10. Return delegation decision or response
1095
+ if (delegation.should_delegate) {
1096
+ return {
1097
+ type: 'delegate',
1098
+ target_skill: delegation.target_skill,
1099
+ context: delegation.context
1100
+ };
1101
+ }
1102
+
1103
+ return {
1104
+ type: 'response',
1105
+ message: generate_response(intent, entities, state)
1106
+ };
1107
+ }
1108
+ ```
1109
+
1110
+ ## File Locations
1111
+
1112
+ ```
1113
+ .agentful/
1114
+ ├── conversation-state.json # Current conversation state
1115
+ ├── conversation-history.json # Full message history
1116
+ └── user-preferences.json # Learned user preferences (optional)
1117
+ ```
1118
+
1119
+ ## Best Practices
1120
+
1121
+ 1. **Always resolve references** before processing intent
1122
+ 2. **Check for context loss** after long gaps (>24h)
1123
+ 3. **Detect ambiguity early** and ask clarifying questions
1124
+ 4. **Learn user preferences** over time
1125
+ 5. **Track related features** for better context
1126
+ 6. **Handle mind changes gracefully** without losing all context
1127
+ 7. **Delegate appropriately** based on intent and entities
1128
+ 8. **Keep history manageable** (last 100 messages)
1129
+ 9. **Update state after every action**
1130
+ 10. **Provide summaries** after context recovery