car-reason 0.15.0

Code reasoning engine for Common Agent Runtime — adaptive, graph-driven, learning
Documentation
//! Reasoning action skills — seeds defaults and learns from sessions.
//!
//! Each reasoning action is stored as a skill in the memgine graph.
//! The graph's spreading activation + success/failure tracking drives
//! which actions are selected for which problems.

use car_memgine::graph::{SkillScope, SkillTrigger};
use car_memgine::MemgineEngine;

use crate::types::*;

/// Seed the default reasoning action skills into the memgine graph.
/// Idempotent — skips skills that already exist.
pub fn seed_defaults(engine: &mut MemgineEngine) {
    for config in default_action_configs() {
        let name = config.kind.skill_name();

        // Skip if already exists
        let existing = engine.find_skill("", "", &name, 1);
        if existing.iter().any(|(m, _)| m.name == name) {
            continue;
        }

        let keywords: Vec<String> = config
            .applicable_to
            .iter()
            .flat_map(|pc| pc.keywords())
            .collect();
        let keyword_str = keywords.join(" ");

        let code = serde_json::to_string(&config).unwrap();
        let description = match config.kind {
            ActionKind::Classify => "Classify the problem type (bug, refactor, feature, etc.)",
            ActionKind::Locate => "Find relevant code and files in the codebase",
            ActionKind::RetrievePatterns => "Retrieve similar past fixes and patterns from memory",
            ActionKind::Diagnose => "Analyze root cause of the problem",
            ActionKind::GenerateFix => "Generate a code fix or implementation",
            ActionKind::VerifyFix => "Verify the generated fix via static analysis",
            ActionKind::Explain => "Explain what was wrong and how the fix works",
        };

        let when = match config.kind {
            ActionKind::Classify => "Always runs first to determine problem type",
            ActionKind::Locate => "When the problem involves specific code that needs to be found",
            ActionKind::RetrievePatterns => "When graph memory has relevant past patterns",
            ActionKind::Diagnose => "After locating code, to identify root cause",
            ActionKind::GenerateFix => "After diagnosis, to produce a code solution",
            ActionKind::VerifyFix => "After generating a fix, to check correctness",
            ActionKind::Explain => "At the end, to provide human-readable explanation",
        };

        engine.ingest_skill_full(
            &name,
            &code,
            "car-reason",
            SkillTrigger {
                persona: String::new(),
                url_pattern: String::new(),
                task_keywords: keyword_str.split_whitespace().map(String::from).collect(),
                structured: None,
            },
            description,
            SkillScope::Domain("reasoning".into()),
            when,
            None,
            vec![],
            vec![],
        );
    }
}

/// Default configurations for all reasoning actions.
fn default_action_configs() -> Vec<ActionConfig> {
    use ActionKind::*;
    use ProblemClass::*;

    vec![
        ActionConfig {
            kind: Classify,
            applicable_to: vec![BugFix, Refactor, Architecture, NewFeature, Performance, TestWriting, Explanation, Unknown],
            prerequisites: vec![],
            prompt_template: "Classify this code problem into exactly one category: bug_fix, refactor, architecture, new_feature, performance, test_writing, or explanation.\n\nProblem: {problem}\n\nCategory:".into(),
            priority: 0,
        },
        ActionConfig {
            kind: Locate,
            applicable_to: vec![BugFix, Refactor, NewFeature, Performance, TestWriting],
            prerequisites: vec![Classify],
            prompt_template: "Given this {problem_class} problem, identify the relevant files, functions, and code sections.\n\nProblem: {problem}\n\nContext from memory:\n{context}\n\nRelevant locations:".into(),
            priority: 10,
        },
        ActionConfig {
            kind: RetrievePatterns,
            applicable_to: vec![BugFix, Refactor, NewFeature, Performance, TestWriting],
            prerequisites: vec![Classify],
            prompt_template: String::new(), // Not model-driven — uses memgine directly
            priority: 10,
        },
        ActionConfig {
            kind: Diagnose,
            applicable_to: vec![BugFix, Refactor, Performance],
            prerequisites: vec![Locate, RetrievePatterns],
            prompt_template: "Analyze the root cause of this {problem_class} problem.\n\nProblem: {problem}\n\nRelevant code locations:\n{locations}\n\nSimilar past patterns:\n{patterns}\n\nContext from memory:\n{context}\n\nRoot cause analysis:".into(),
            priority: 20,
        },
        ActionConfig {
            kind: GenerateFix,
            applicable_to: vec![BugFix, Refactor, NewFeature, Performance, TestWriting],
            prerequisites: vec![Diagnose],
            prompt_template: "Generate a code fix for this {problem_class} problem.\n\nProblem: {problem}\n\nDiagnosis: {diagnosis}\n\nContext from memory:\n{context}\n\nProvide the fix as a code block. Include the file path if known.\n\nFix:".into(),
            priority: 30,
        },
        ActionConfig {
            kind: VerifyFix,
            applicable_to: vec![BugFix, Performance],
            prerequisites: vec![GenerateFix],
            prompt_template: "Review this code fix for correctness. Check for: off-by-one errors, null/None handling, type mismatches, missing edge cases.\n\nOriginal problem: {problem}\n\nDiagnosis: {diagnosis}\n\nProposed fix:\n{code}\n\nVerification result (PASS/FAIL with explanation):".into(),
            priority: 40,
        },
        ActionConfig {
            kind: Explain,
            applicable_to: vec![BugFix, Refactor, Architecture, NewFeature, Performance, TestWriting, Explanation],
            prerequisites: vec![Classify], // Only needs classification; runs after whatever else completed
            prompt_template: "Explain this clearly and concisely for a developer.\n\nProblem: {problem}\n\n{diagnosis_section}{fix_section}\n\nExplanation:".into(),
            priority: 50,
        },
    ]
}

/// After a successful session, store the action sequence as a learned strategy skill.
pub fn learn_from_session(engine: &mut MemgineEngine, result: &ReasoningResult, accepted: bool) {
    if !accepted {
        return;
    }

    let sequence: Vec<ActionKind> = result
        .actions_taken
        .iter()
        .filter(|a| a.success)
        .map(|a| a.action)
        .collect();

    if sequence.is_empty() {
        return;
    }

    let strategy_name = format!(
        "strategy:{}:{:.4}",
        result.problem_class, result.overall_confidence,
    );

    let config = serde_json::json!({
        "problem_class": result.problem_class,
        "action_sequence": sequence,
        "avg_confidence": result.overall_confidence,
        "learned_from": result.session_id,
    });

    engine.ingest_skill_full(
        &strategy_name,
        &config.to_string(),
        "car-reason",
        SkillTrigger {
            persona: String::new(),
            url_pattern: String::new(),
            task_keywords: result
                .problem_class
                .keywords()
                .iter()
                .map(|s| s.to_string())
                .collect(),
            structured: None,
        },
        &format!(
            "Learned strategy for {} problems (confidence: {:.0}%)",
            result.problem_class,
            result.overall_confidence * 100.0
        ),
        SkillScope::Domain("reasoning".into()),
        &format!(
            "Apply when solving {} problems with similar context",
            result.problem_class
        ),
        None,
        vec![],
        vec![],
    );
}