bctx-conductor 0.1.6

bctx-conductor — Spiral Cycle agent runtime, SignalGraph, PassageRun
Documentation
use super::orient::{QuestionKind, SituPicture};
use anyhow::Result;
use atlas::{
    invocation::{SkillContext, SkillInput},
    SkillRegistry,
};
use serde_json::json;

pub struct DecisionPacket {
    pub invocations: Vec<(String, SkillInput)>,
    pub rationale: String,
}

pub fn select_skills(
    situ: &SituPicture,
    registry: &SkillRegistry,
    ctx: &SkillContext,
) -> Result<DecisionPacket> {
    let mut invocations: Vec<(String, SkillInput)> = Vec::new();
    let mut rationale_parts: Vec<String> = Vec::new();

    for q in &situ.open_questions {
        match q.kind {
            QuestionKind::InvestigateErrors => {
                if registry.get("scout").is_some() {
                    invocations.push((
                        "scout".into(),
                        SkillInput::new(json!({
                            "command": "echo '[investigate errors — no command set]'",
                            "working_dir": ctx.working_dir,
                            "dry_run": true
                        })),
                    ));
                    rationale_parts.push("scout(dry_run) for error investigation".into());
                }
            }
            QuestionKind::RecallRelatedFacts => {
                if registry.get("archivist").is_some() {
                    invocations.push((
                        "archivist".into(),
                        SkillInput::new(json!({
                            "query": q.detail,
                            "top_k": 5,
                            "min_confidence": 0.3
                        })),
                    ));
                    rationale_parts.push(format!("archivist(\"{}\")", q.detail));
                }
            }
            QuestionKind::DescribeProjectLayout => {
                if registry.get("cartograph").is_some() {
                    invocations.push((
                        "cartograph".into(),
                        SkillInput::new(json!({
                            "root": ctx.working_dir,
                            "depth": 2,
                            "output_format": "compact"
                        })),
                    ));
                    rationale_parts.push("cartograph(depth=2)".into());
                }
            }
        }
    }

    let rationale = if rationale_parts.is_empty() {
        "no open questions".into()
    } else {
        rationale_parts.join(", ")
    };

    Ok(DecisionPacket {
        invocations,
        rationale,
    })
}