@@ -1576,6 +1576,150 @@ impl AgentLoop {
15761576 None
15771577 }
15781578
1579+ /// Detect if context perception is needed based on user prompt.
1580+ ///
1581+ /// Returns `Some(PreContextPerceptionEvent)` if the prompt suggests the model
1582+ /// needs workspace knowledge (finding files, understanding code, etc.).
1583+ pub fn detect_context_perception_intent (
1584+ & self ,
1585+ prompt : & str ,
1586+ session_id : & str ,
1587+ workspace : & str ,
1588+ ) -> Option < PreContextPerceptionEvent > {
1589+ let lower = prompt. to_lowercase ( ) ;
1590+
1591+ // Pattern matching for different intents that suggest context perception is needed
1592+ let intents: & [ ( & [ & str ] , & str ) ] = & [
1593+ // Locate: finding files, functions, resources
1594+ (
1595+ & [
1596+ "where is" ,
1597+ "where are" ,
1598+ "find the file" ,
1599+ "find all" ,
1600+ "find files" ,
1601+ "who wrote" ,
1602+ "locate" ,
1603+ "search for" ,
1604+ "look for" ,
1605+ "search" ,
1606+ ] ,
1607+ "locate" ,
1608+ ) ,
1609+ // Understand: explaining how something works
1610+ (
1611+ & [
1612+ "how does" ,
1613+ "what does" ,
1614+ "explain" ,
1615+ "understand" ,
1616+ "what is this" ,
1617+ "how does this work" ,
1618+ ] ,
1619+ "understand" ,
1620+ ) ,
1621+ // Retrieve: recalling from memory/past
1622+ (
1623+ & [
1624+ "remember" ,
1625+ "earlier" ,
1626+ "before" ,
1627+ "previously" ,
1628+ "last time" ,
1629+ "past" ,
1630+ "previous" ,
1631+ ] ,
1632+ "retrieve" ,
1633+ ) ,
1634+ // Explore: understanding structure
1635+ (
1636+ & [
1637+ "how is organized" ,
1638+ "project structure" ,
1639+ "what files" ,
1640+ "show me the structure" ,
1641+ "explore" ,
1642+ ] ,
1643+ "explore" ,
1644+ ) ,
1645+ // Reason: asking why/causality
1646+ (
1647+ & [
1648+ "why did" ,
1649+ "why is" ,
1650+ "cause" ,
1651+ "reason" ,
1652+ "what happened" ,
1653+ "why does" ,
1654+ ] ,
1655+ "reason" ,
1656+ ) ,
1657+ // Validate: checking correctness
1658+ (
1659+ & [ "is this correct" , "verify" , "validate" , "check if" , "debug" ] ,
1660+ "validate" ,
1661+ ) ,
1662+ // Compare: comparing things
1663+ (
1664+ & [
1665+ "difference between" ,
1666+ "compare" ,
1667+ "versus" ,
1668+ " vs " ,
1669+ "different from" ,
1670+ ] ,
1671+ "compare" ,
1672+ ) ,
1673+ // Track: status/history
1674+ (
1675+ & [
1676+ "status" ,
1677+ "progress" ,
1678+ "how far" ,
1679+ "history" ,
1680+ "what's the current" ,
1681+ ] ,
1682+ "track" ,
1683+ ) ,
1684+ ] ;
1685+
1686+ // Detect target type from keywords
1687+ let target_type = if lower. contains ( "function" ) || lower. contains ( "method" ) {
1688+ "function"
1689+ } else if lower. contains ( "file" ) || lower. contains ( "config" ) {
1690+ "file"
1691+ } else if lower. contains ( "class" ) {
1692+ "entity"
1693+ } else if lower. contains ( "module" ) || lower. contains ( "package" ) {
1694+ "module"
1695+ } else if lower. contains ( "test" ) {
1696+ "test"
1697+ } else {
1698+ "unknown"
1699+ } ;
1700+
1701+ // Find matching intent
1702+ let matched_intent = intents
1703+ . iter ( )
1704+ . find ( |( patterns, _) | patterns. iter ( ) . any ( |p| lower. contains ( p) ) ) ;
1705+
1706+ matched_intent. map ( |( patterns, intent) | {
1707+ // Extract target name if possible (simplified extraction)
1708+ let target_name = extract_target_name_from_prompt ( prompt, patterns) ;
1709+
1710+ PreContextPerceptionEvent {
1711+ session_id : session_id. to_string ( ) ,
1712+ intent : intent. to_string ( ) ,
1713+ target_type : target_type. to_string ( ) ,
1714+ target_name,
1715+ domain : detect_domain_from_prompt ( prompt) ,
1716+ query : Some ( prompt. to_string ( ) ) ,
1717+ working_directory : workspace. to_string ( ) ,
1718+ urgency : "normal" . to_string ( ) ,
1719+ }
1720+ } )
1721+ }
1722+
15791723 /// Fire PreContextPerception hook and wait for harness decision.
15801724 async fn fire_pre_context_perception ( & self , event : & PreContextPerceptionEvent ) -> HookResult {
15811725 if let Some ( he) = & self . config . hook_engine {
@@ -2412,26 +2556,31 @@ impl AgentLoop {
24122556 . fire_intent_detection ( effective_prompt, & session_id_str, & workspace)
24132557 . await ;
24142558
2415- // Step 2: If harness returned an intent, use it; otherwise skip context perception
2416- if let Some ( detected) = harness_intent {
2559+ // Step 2: Build perception event from harness result, or fallback to local detection
2560+ let perception_event = if let Some ( detected) = harness_intent {
24172561 tracing:: info!(
24182562 intent = %detected. detected_intent,
24192563 confidence = %detected. confidence,
24202564 "Intent detected from AHP harness"
24212565 ) ;
2422-
2423- let perception_event = build_pre_context_perception_from_intent (
2566+ Some ( build_pre_context_perception_from_intent (
24242567 detected,
24252568 effective_prompt,
24262569 & session_id_str,
24272570 & workspace,
2428- ) ;
2571+ ) )
2572+ } else {
2573+ // Fallback to local keyword detection
2574+ tracing:: debug!( "No intent from harness, using local keyword detection" ) ;
2575+ self . detect_context_perception_intent ( effective_prompt, & session_id_str, & workspace)
2576+ } ;
24292577
2578+ if let Some ( perception_event) = perception_event {
24302579 // Step 3: Fire PreContextPerception hook to get harness decision
24312580 tracing:: info!(
24322581 intent = %perception_event. intent,
24332582 target_type = %perception_event. target_type,
2434- "Firing PreContextPerception hook"
2583+ "Context perception intent detected, firing AHP hook"
24352584 ) ;
24362585
24372586 let hook_result = self . fire_pre_context_perception ( & perception_event) . await ;
@@ -2450,32 +2599,33 @@ impl AgentLoop {
24502599 ) ;
24512600 self . apply_injected_context ( injected)
24522601 } else {
2453- // Failed to parse, skip context injection
2454- tracing:: warn!( "Failed to parse injected context, skipping" ) ;
2455- Vec :: new ( )
2602+ // Fall back to normal providers if parsing fails
2603+ tracing:: warn!(
2604+ "Failed to parse injected context, falling back to providers"
2605+ ) ;
2606+ self . resolve_context ( effective_prompt, session_id) . await
24562607 }
24572608 }
24582609 #[ cfg( not( feature = "ahp" ) ) ]
24592610 {
2460- let _ = modified_context;
2611+ // Without AHP, fall back to normal providers
2612+ let _ = modified_context; // suppress unused warning
24612613 self . resolve_context ( effective_prompt, session_id) . await
24622614 }
24632615 }
24642616 HookResult :: Block ( _) => {
2617+ // Harness blocked context injection - skip
24652618 tracing:: info!( "AHP harness blocked context injection" ) ;
24662619 Vec :: new ( )
24672620 }
24682621 _ => {
2469- // No modification, skip context injection
2470- Vec :: new ( )
2622+ // No modification or unknown result, proceed with normal providers
2623+ self . resolve_context ( effective_prompt , session_id ) . await
24712624 }
24722625 }
24732626 } else {
2474- // No harness registered for IntentDetection - skip context perception entirely
2475- tracing:: debug!(
2476- "No IntentDetection harness registered, skipping context perception"
2477- ) ;
2478- Vec :: new ( )
2627+ // No intent detected, proceed with normal providers
2628+ self . resolve_context ( effective_prompt, session_id) . await
24792629 }
24802630 } else {
24812631 Vec :: new ( )
0 commit comments