//! Parent ⇄ sub-agent contract roundtrip (GL#450): brief produces a //! deterministic, budgeted pack; return distills the report into recallable //! parent knowledge. use lean_ctx::core::knowledge::ProjectKnowledge; use lean_ctx::core::memory_policy::MemoryPolicy; #[allow(clippy::too_many_arguments)] fn agent( action: &str, project_root: &str, message: Option<&str>, priority: Option<&str>, ) -> String { lean_ctx::tools::ctx_agent::handle( action, None, None, project_root, Some("parent-1"), message, None, None, None, None, priority, None, None, false, None, ) } #[test] fn brief_then_return_roundtrip() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let root = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&root); knowledge.remember( "architecture", "auth", "JWT RS256 authentication", "s1", 0.9, &policy, ); knowledge.remember("deploy", "host", "AWS eu-central-1", "s1", 0.8, &policy); knowledge.save().expect("save knowledge"); // Briefing pack: deterministic, budgeted, contract v1, relevant fact in. let pack1 = agent("brief", &root, Some("fix authentication bug"), Some("800")); let pack2 = agent("brief", &root, Some("fix authentication bug"), Some("800")); assert_eq!(pack1, pack2, "briefing pack must be byte-identical"); let parsed: serde_json::Value = serde_json::from_str(&pack1).expect("pack is valid JSON"); assert_eq!(parsed["contract_version"], 1); assert_eq!(parsed["budget_tokens"], 800); assert!( parsed["used_tokens"].as_u64().unwrap() <= 800, "pack must respect the budget" ); assert!( pack1.contains("JWT RS256"), "relevant auth fact must be briefed: {pack1}" ); assert!( parsed["return_format"] .as_str() .unwrap() .contains("category/key"), "pack must carry the return contract" ); // Return synthesis: contract lines become recallable parent facts. let out = agent( "return", &root, Some( "finding/root-cause: token clock skew breaks RS256 validation\n\ decision/fix: allow 30s leeway in JWT validation\n\ free-form chatter that is not contract formatted", ), None, ); assert!( out.contains("2 fact(s) distilled"), "return must report distilled count: {out}" ); assert!( out.contains("1 line(s) rejected"), "return must surface rejected lines: {out}" ); let after = ProjectKnowledge::load(&root).expect("knowledge after return"); let recalled = after.recall("clock skew"); assert!( recalled.iter().any(|f| f.key == "root-cause"), "distilled fact must be recallable in parent knowledge" ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; }