meerkat-mob 0.5.0

Multi-agent orchestration runtime for Meerkat
Documentation
//! Built-in prefab mob definitions.

use crate::definition::{BackendConfig, MobDefinition, SkillSource, WiringRules};
use crate::ids::MobId;
use std::collections::BTreeMap;

/// Built-in prefab templates.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Prefab {
    CodingSwarm,
    CodeReview,
    ResearchTeam,
    Pipeline,
}

impl Prefab {
    /// Stable prefab key used in CLI and MCP inputs.
    pub fn key(self) -> &'static str {
        match self {
            Self::CodingSwarm => "coding_swarm",
            Self::CodeReview => "code_review",
            Self::ResearchTeam => "research_team",
            Self::Pipeline => "pipeline",
        }
    }

    /// Parse prefab key.
    pub fn from_key(key: &str) -> Option<Self> {
        match key {
            "coding_swarm" => Some(Self::CodingSwarm),
            "code_review" => Some(Self::CodeReview),
            "research_team" => Some(Self::ResearchTeam),
            "pipeline" => Some(Self::Pipeline),
            _ => None,
        }
    }

    /// All known prefabs.
    pub fn all() -> [Self; 4] {
        [
            Self::CodingSwarm,
            Self::CodeReview,
            Self::ResearchTeam,
            Self::Pipeline,
        ]
    }

    /// Build a full mob definition for this prefab.
    pub fn definition(self) -> MobDefinition {
        let mut definition =
            MobDefinition::from_toml(self.toml_template()).unwrap_or_else(|_| MobDefinition {
                id: MobId::from(self.key()),
                orchestrator: None,
                profiles: BTreeMap::new(),
                mcp_servers: BTreeMap::new(),
                wiring: WiringRules::default(),
                skills: BTreeMap::new(),
                backend: BackendConfig::default(),
                flows: BTreeMap::new(),
                topology: None,
                supervisor: None,
                limits: None,
                spawn_policy: None,
                event_router: None,
            });

        definition.skills.insert(
            "orchestrator".to_string(),
            SkillSource::Inline {
                content: orchestrator_skill(self).to_string(),
            },
        );
        definition.skills.insert(
            "worker".to_string(),
            SkillSource::Inline {
                content: worker_skill(self).to_string(),
            },
        );
        definition
    }

    /// User-editable TOML template for this prefab.
    pub fn toml_template(self) -> &'static str {
        match self {
            Self::CodingSwarm => include_str!("../prefabs/coding_swarm.toml"),
            Self::CodeReview => include_str!("../prefabs/code_review.toml"),
            Self::ResearchTeam => include_str!("../prefabs/research_team.toml"),
            Self::Pipeline => include_str!("../prefabs/pipeline.toml"),
        }
    }
}

fn orchestrator_skill(prefab: Prefab) -> &'static str {
    match prefab {
        Prefab::CodingSwarm => {
            "## Role\nLead a coding swarm.\n\n## Available Tools\nUse mob tools to spawn/retire/wire workers and task tools for shared work.\n\n## Communication\nUse peers() for discovery and PeerRequest for directed work. Handle mob.peer_added and mob.peer_retired events.\n\n## Coordination Pattern\nFan out coding tasks to workers and aggregate results by task ID.\n\n## Worker Lifecycle\nPrefer reusing existing workers; spawn only for uncovered skill gaps; retire idle workers after merge.\n\n## Completion\nWhen all tasks are completed and verified, summarize and call mob.complete()."
        }
        Prefab::CodeReview => {
            "## Role\nCoordinate multi-reviewer code audits.\n\n## Available Tools\nUse mob tools for topology, task tools for review tickets.\n\n## Communication\nRoute code-review requests by specialization; process peer_added/peer_retired updates.\n\n## Coordination Pattern\nParallel review lanes (correctness, integration, quality) with final synthesis.\n\n## Worker Lifecycle\nSpawn reviewers for missing domains, reuse active reviewers, retire on finish.\n\n## Completion\nComplete when all blocking findings are resolved and call mob.complete()."
        }
        Prefab::ResearchTeam => {
            "## Role\nRun structured research with synthesis.\n\n## Available Tools\nUse mob tools to shape the team and task tools to track hypotheses and sources.\n\n## Communication\nAssign research questions via PeerRequest and consolidate updates from peers.\n\n## Coordination Pattern\nDiverge on exploration, converge on synthesis and recommendations.\n\n## Worker Lifecycle\nSpawn domain researchers when required, retire workers with completed lanes.\n\n## Completion\nComplete when all research tasks are closed and a final synthesis is delivered."
        }
        Prefab::Pipeline => {
            "## Role\nDrive staged pipeline execution.\n\n## Available Tools\nUse mob tools for stage workers and task tools for stage handoffs.\n\n## Communication\nUse directed peer requests to pass artifacts from stage to stage.\n\n## Coordination Pattern\nSequential stage progression with explicit dependencies.\n\n## Worker Lifecycle\nSpawn per-stage workers, reuse workers per stage, retire at stage completion.\n\n## Completion\nComplete when terminal stage succeeds and cleanup is done."
        }
    }
}

fn worker_skill(prefab: Prefab) -> &'static str {
    match prefab {
        Prefab::CodingSwarm => "Implement assigned code tasks and report concise diffs.",
        Prefab::CodeReview => "Review assigned changes and report concrete findings with evidence.",
        Prefab::ResearchTeam => {
            "Gather evidence and return sourced summaries for assigned questions."
        }
        Prefab::Pipeline => "Execute your stage deterministically and emit handoff artifacts.",
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_prefabs_are_parseable_and_stable() {
        for prefab in Prefab::all() {
            assert_eq!(Prefab::from_key(prefab.key()), Some(prefab));
        }
        assert_eq!(Prefab::from_key("unknown"), None);
    }

    #[test]
    fn test_prefab_definitions_validate() {
        for prefab in Prefab::all() {
            let def = prefab.definition();
            let diagnostics = crate::validate::validate_definition(&def);
            assert!(
                diagnostics.is_empty(),
                "prefab {} must validate: {diagnostics:?}",
                prefab.key()
            );
        }
    }

    #[test]
    fn test_prefab_orchestrator_skill_contract_sections() {
        let required = [
            "## Role",
            "## Available Tools",
            "## Communication",
            "## Coordination Pattern",
            "## Worker Lifecycle",
            "## Completion",
        ];
        for prefab in Prefab::all() {
            let def = prefab.definition();
            let orchestrator = match &def.skills["orchestrator"] {
                SkillSource::Inline { content } => content,
                SkillSource::Path { .. } => panic!("orchestrator skill must be inline"),
            };
            for section in required {
                assert!(
                    orchestrator.contains(section),
                    "prefab {} missing section {section}",
                    prefab.key()
                );
            }
        }
    }

    #[test]
    fn test_prefab_toml_templates_parse() {
        for prefab in Prefab::all() {
            let parsed = crate::definition::MobDefinition::from_toml(prefab.toml_template())
                .expect("prefab toml should parse");
            assert_eq!(
                parsed.id.as_str(),
                prefab.key(),
                "prefab toml id must match prefab key"
            );
        }
    }

    #[test]
    fn test_pipeline_prefab_definition_carries_flow_truth() {
        let definition = Prefab::Pipeline.definition();
        let flow = definition
            .flows
            .get(&crate::ids::FlowId::from("pipeline"))
            .expect("pipeline prefab should define a durable pipeline flow");

        assert!(
            !flow.steps.is_empty(),
            "pipeline flow should have concrete steps"
        );
        assert!(
            definition.topology.is_some(),
            "pipeline prefab should carry topology"
        );
        assert!(
            definition.supervisor.is_some(),
            "pipeline prefab should carry supervisor ownership"
        );
        assert!(
            definition.limits.is_some(),
            "pipeline prefab should carry runtime limits"
        );
    }
}