//! Do a mission agent's pinned skills actually reach its prompt? //! //! Before this test the honest answer was no, for every skill and every role. //! The catalogue's only delivery channel was the `clawmates_skills` MCP server, //! and a mission claw could not reach it: `provision_claw` wrote a constant //! bundle list, the runtime config defines no such bundle, and mission claws //! run on `claude_cli`, which is text-only and cannot surface a tool call. //! //! So the skills were authored, bound, listed in the boot log as bound — and //! structurally unreadable. That is why this is a test and not a comment: the //! failure produced no error anywhere, and every layer reported success. use cm_api::topology_exec::{MissionTap, ZeroClawDriveExecutor}; use cm_domain::{Agent, AgentId, AgentStatus, Role, User, UserId, Workspace, WorkspaceId}; /// `agents.managed_by` is a real FK, so the owner has to exist. async fn seed_user(pool: &sqlx::PgPool, ws: WorkspaceId) -> UserId { let user = User { id: UserId::new(), workspace_id: ws, email: format!("owner-{}@example.com", Uuid::now_v7().simple()), role: Role::Owner, display_name: "Owner".into(), created_at: time::OffsetDateTime::UNIX_EPOCH, }; cm_db::repo::users::insert(pool, &user).await.unwrap(); user.id } use std::collections::HashMap; use uuid::Uuid; /// A claw with one template-bound, pinned skill. Returns its runtime alias. async fn seed_claw_with_pinned_skill(pool: &sqlx::PgPool, body: &str) -> (String, WorkspaceId) { let ws = Workspace { id: WorkspaceId::new(), name: "Skill Delivery Test".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(pool, &ws).await.unwrap(); let user = seed_user(pool, ws.id).await; let agent = Agent { id: AgentId::new(), workspace_id: ws.id, name: "Scout".into(), job_title: "researcher".into(), system_prompt: String::new(), avatar: String::new(), accent: String::new(), wallpaper: String::new(), managed_by: user, status: AgentStatus::Online, }; cm_db::repo::agents::insert(pool, &agent, &cm_domain::AccessPolicy::default()) .await .unwrap(); // A template with one role, and a skill pinned to it. let template_id = Uuid::now_v7(); sqlx::query( "INSERT INTO team_templates (id, key, name, description, category, stack, default_topology, risk_profile, mcp_bundles, version) VALUES ($1, $2, 'Delivery Test', 'test', 'research', '{}', 'pipeline', 'research_readonly', '{}', 1)", ) .bind(template_id) .bind(format!("delivery_test_{}", template_id.simple())) .execute(pool) .await .unwrap(); sqlx::query( "INSERT INTO template_roles (template_id, slot, order_idx, system_prompt) VALUES ($1, 'researcher', 0, 'you research')", ) .bind(template_id) .execute(pool) .await .unwrap(); let skill_id = Uuid::now_v7(); sqlx::query( "INSERT INTO skills (id, workspace_id, name, title, author, description, when_to_use, tags, source_kind, current_version, body) VALUES ($1, NULL, $2, $2, 'system', 'a procedure the agent must follow', 'always', '{}', 'builtin', 1, $3)", ) .bind(skill_id) .bind(format!("delivery-test-skill-{}", skill_id.simple())) .bind(body) .execute(pool) .await .unwrap(); sqlx::query( "INSERT INTO template_role_skills (template_id, slot, skill_id, pin_in_context, order_idx) VALUES ($1, 'researcher', $2, true, 0)", ) .bind(template_id) .bind(skill_id) .execute(pool) .await .unwrap(); cm_db::repo::agent_template_link::upsert( pool, agent.id.as_uuid(), template_id, 1, "researcher", ) .await .unwrap(); ( cm_api::runtime_provision::claw_alias(agent.id.as_uuid()), ws.id, ) } fn executor(pool: &sqlx::PgPool, workspace_id: WorkspaceId) -> ZeroClawDriveExecutor { ZeroClawDriveExecutor::new( "http://127.0.0.1:1".into(), "unused".into(), HashMap::new(), "default".into(), ) .with_tap(MissionTap { pool: pool.clone(), workspace_id: workspace_id.as_uuid(), mission_id: Uuid::now_v7(), phase_id: None, run_id: None, }) } #[tokio::test] async fn a_pinned_skill_body_reaches_the_turn_prompt() { let pool = cm_testkit::test_pool().await; const MARKER: &str = "Never review a paper from its title alone."; let (alias, ws) = seed_claw_with_pinned_skill(&pool, MARKER).await; let text = executor(&pool, ws) .pinned_skills_text(&alias) .await .expect("a claw with a pinned template skill must produce skill text"); assert!( text.contains(MARKER), "the skill BODY must be present, not just its name — there is no \ `skills.read` tool on the mission path, so an index would name a \ procedure the agent has no way to fetch. Got:\n{text}" ); } #[tokio::test] async fn an_agent_with_no_pinned_skills_adds_nothing() { let pool = cm_testkit::test_pool().await; let ws = Workspace { id: WorkspaceId::new(), name: "No Skills".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(&pool, &ws).await.unwrap(); let user = seed_user(&pool, ws.id).await; let agent = Agent { id: AgentId::new(), workspace_id: ws.id, name: "Bare".into(), job_title: "researcher".into(), system_prompt: String::new(), avatar: String::new(), accent: String::new(), wallpaper: String::new(), managed_by: user, status: AgentStatus::Online, }; cm_db::repo::agents::insert(&pool, &agent, &cm_domain::AccessPolicy::default()) .await .unwrap(); let alias = cm_api::runtime_provision::claw_alias(agent.id.as_uuid()); assert!( executor(&pool, ws.id) .pinned_skills_text(&alias) .await .is_none(), "an agent with no bound skills must add no section at all — an empty \ `# Your skills` heading tells the model it has skills and then shows \ it none" ); } #[test] fn the_composed_prompt_carries_the_skill_and_omits_the_heading_when_empty() { let base = "You are the \"researcher\" agent.\n\nTask: read the papers"; let with = cm_api::topology_exec::compose_turn_prompt(base, Some("## arxiv-daily\nDo not re-search.")); assert!(with.contains("Task: read the papers"), "the base turn must survive"); assert!(with.contains("Do not re-search."), "the skill body must be in the prompt"); assert!(with.contains("# Your skills"), "the section needs a heading"); for empty in [None, Some(""), Some(" \n ")] { let without = cm_api::topology_exec::compose_turn_prompt(base, empty); assert_eq!( without, base, "with no skills the prompt must be byte-identical to the base — an \ empty heading announces skills the agent does not have" ); } }