feat(profiles): top-level agent profiles — soul, memory, skills, MCP, connectors (#3632)

This commit is contained in:
Steven Enamakel
2026-06-12 17:02:42 -07:00
committed by GitHub
parent 75a6f2891b
commit 03678e3b6d
65 changed files with 3755 additions and 998 deletions
+8 -8
View File
@@ -451,11 +451,11 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
let profiles_initial = rpc(
&harness.rpc_base,
31_001,
"openhuman.agent_profiles_list",
"openhuman.profiles_list",
json!({}),
)
.await;
let initial = ok(&profiles_initial, "agent_profiles_list initial");
let initial = ok(&profiles_initial, "profiles_list initial");
assert_eq!(
initial.get("activeProfileId").and_then(Value::as_str),
Some("default")
@@ -464,7 +464,7 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
let upsert_profile = rpc(
&harness.rpc_base,
31_002,
"openhuman.agent_profile_upsert",
"openhuman.profiles_upsert",
json!({
"profile": {
"id": "E2E Planner",
@@ -480,7 +480,7 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
}),
)
.await;
let upserted = ok(&upsert_profile, "agent_profile_upsert");
let upserted = ok(&upsert_profile, "profiles_upsert");
assert!(
upserted
.get("profiles")
@@ -494,12 +494,12 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
let select_profile = rpc(
&harness.rpc_base,
31_003,
"openhuman.agent_profile_select",
"openhuman.profiles_select",
json!({ "profile_id": "e2e-planner" }),
)
.await;
assert_eq!(
ok(&select_profile, "agent_profile_select")
ok(&select_profile, "profiles_select")
.get("activeProfileId")
.and_then(Value::as_str),
Some("e2e-planner")
@@ -508,11 +508,11 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
let delete_profile = rpc(
&harness.rpc_base,
31_004,
"openhuman.agent_profile_delete",
"openhuman.profiles_delete",
json!({ "profile_id": "e2e-planner" }),
)
.await;
let deleted = ok(&delete_profile, "agent_profile_delete");
let deleted = ok(&delete_profile, "profiles_delete");
assert!(
deleted
.get("profiles")
+43 -16
View File
@@ -59,18 +59,10 @@ use openhuman_core::openhuman::agent::multimodal::{
contains_image_markers, count_image_markers, extract_ollama_image_payload, parse_image_markers,
prepare_messages_for_provider, MultimodalError,
};
use openhuman_core::openhuman::agent::personality_paths::{
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
HasToolkit, PersonalityContext,
};
use openhuman_core::openhuman::agent::pformat::{
build_registry, parse_call as parse_pformat_call, render_signature, render_signature_from_tool,
PFormatParamType, PFormatRegistry, PFormatToolParams,
};
use openhuman_core::openhuman::agent::profiles::{
AgentProfile, AgentProfileStore, AgentProfilesState, DEFAULT_PROFILE_ID,
};
use openhuman_core::openhuman::agent::prompts::{
render_ambient_environment, render_subagent_system_prompt, render_tools, ConnectedIntegration,
GatedIntegrationTool, LearnedContextData, NamespaceSummary, PersonalityRosterEntry,
@@ -176,6 +168,17 @@ use openhuman_core::openhuman::inference::{
DeviceProfile,
};
use openhuman_core::openhuman::memory::{Memory, MemoryCategory, MemoryEntry, RecallOpts};
use openhuman_core::openhuman::profiles::{
all_profiles_controller_schemas, all_profiles_registered_controllers,
};
use openhuman_core::openhuman::profiles::{
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
HasToolkit, PersonalityContext,
};
use openhuman_core::openhuman::profiles::{
AgentProfile, AgentProfileStore, AgentProfilesState, DEFAULT_PROFILE_ID,
};
use openhuman_core::openhuman::security::SecurityPolicy;
use openhuman_core::openhuman::todos::ops::BoardLocation;
use openhuman_core::openhuman::tools::{Tool, ToolResult, ToolSpec};
@@ -1116,6 +1119,14 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
.iter()
.all(|controller| controller.rpc_method_name().starts_with("openhuman.agent_")));
// Profiles moved to their own top-level domain (`openhuman.profiles_*`).
let profile_schemas = all_profiles_controller_schemas();
let profiles = all_profiles_registered_controllers();
assert_eq!(profile_schemas.len(), profiles.len());
assert!(profiles.iter().all(|controller| controller
.rpc_method_name()
.starts_with("openhuman.profiles_")));
let status = call(controller(&registered, "server_status"), json!({}))
.await
.expect("server status");
@@ -1155,7 +1166,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
assert_eq!(reload.pointer("/status"), Some(&json!("noop")));
assert_eq!(reload.pointer("/registry_initialised"), Some(&json!(true)));
let list = call(controller(&registered, "profiles_list"), json!({}))
let list = call(controller(&profiles, "list"), json!({}))
.await
.expect("profiles list");
assert_eq!(
@@ -1170,7 +1181,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
.any(|profile| profile.pointer("/id") == Some(&json!("research"))));
let unknown_agent = call(
controller(&registered, "profile_upsert"),
controller(&profiles, "upsert"),
json!({
"profile": {
"id": "Bad Agent",
@@ -1185,7 +1196,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
assert!(unknown_agent.contains("agent definition 'unknown-agent-id' not found"));
let upserted = call(
controller(&registered, "profile_upsert"),
controller(&profiles, "upsert"),
json!({
"profile": {
"id": " My Research Profile ",
@@ -1220,7 +1231,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
);
let selected = call(
controller(&registered, "profile_select"),
controller(&profiles, "select"),
json!({ "profile_id": "my-research-profile" }),
)
.await
@@ -1231,7 +1242,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
);
let missing_select = call(
controller(&registered, "profile_select"),
controller(&profiles, "select"),
json!({ "profile_id": "missing-profile" }),
)
.await
@@ -1239,7 +1250,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
assert!(missing_select.contains("agent profile 'missing-profile' not found"));
let delete_builtin = call(
controller(&registered, "profile_delete"),
controller(&profiles, "delete"),
json!({ "profile_id": DEFAULT_PROFILE_ID }),
)
.await
@@ -1247,7 +1258,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
assert!(delete_builtin.contains("built-in agent profile"));
let deleted = call(
controller(&registered, "profile_delete"),
controller(&profiles, "delete"),
json!({ "profile_id": "my-research-profile" }),
)
.await
@@ -1359,6 +1370,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
soul_md: Some(" inline soul ".to_string()),
soul_md_path: None,
composio_integrations: Some(vec![" gmail ".to_string(), String::new()]),
memory_sources: None,
include_agent_conversations: true,
allowed_skills: None,
allowed_mcp_servers: None,
memory_dir_suffix: None,
is_master: true,
sort_order: Some(50),
@@ -1393,6 +1408,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
soul_md: None,
soul_md_path: None,
composio_integrations: Some(vec![]),
memory_sources: None,
include_agent_conversations: true,
allowed_skills: None,
allowed_mcp_servers: None,
memory_dir_suffix: None,
is_master: false,
sort_order: None,
@@ -1410,6 +1429,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
let reused = store
.upsert(AgentProfile {
memory_sources: None,
include_agent_conversations: true,
allowed_skills: None,
allowed_mcp_servers: None,
memory_dir_suffix: None,
description: "updated".to_string(),
..second_profile.clone()
@@ -1719,6 +1742,10 @@ fn agent_personality_paths_cover_safe_fallbacks_and_integration_filters() {
soul_md: Some("inline soul".into()),
soul_md_path: Some("personality-soul.md".into()),
composio_integrations: Some(vec!["gmail".into(), "slack".into()]),
memory_sources: None,
include_agent_conversations: true,
allowed_skills: None,
allowed_mcp_servers: None,
memory_dir_suffix: Some("-7".into()),
is_master: false,
sort_order: Some(10),
@@ -3471,7 +3498,7 @@ async fn agent_public_tools_cover_validation_and_metadata_paths() {
assert!(clarification.output().contains("Which target?"));
assert!(clarification.output().contains("unit, coverage"));
let run_workflow = RunWorkflowTool;
let run_workflow = RunWorkflowTool::new();
assert_eq!(run_workflow.name(), RUN_WORKFLOW_TOOL_NAME);
assert_eq!(
run_workflow.parameters_schema().pointer("/required/0"),
+12 -8
View File
@@ -18,14 +18,6 @@ use std::sync::Arc;
use serde_json::json;
use tempfile::tempdir;
use openhuman_core::openhuman::agent::personality_paths::{
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
HasToolkit, PersonalityContext,
};
use openhuman_core::openhuman::agent::profiles::{
built_in_profiles, AgentProfile, AgentProfileStore, DEFAULT_PROFILE_ID,
};
use openhuman_core::openhuman::agent::prompts::types::LearnedContextData;
use openhuman_core::openhuman::agent::prompts::{
IdentitySection, PersonalityRosterEntry, PersonalityRosterSection, PromptContext,
@@ -36,6 +28,14 @@ use openhuman_core::openhuman::memory::{NamespaceDocumentInput, UnifiedMemory};
use openhuman_core::openhuman::memory_conversations::{
ensure_thread, list_threads, update_thread_title, ConversationStore, CreateConversationThread,
};
use openhuman_core::openhuman::profiles::{
built_in_profiles, AgentProfile, AgentProfileStore, DEFAULT_PROFILE_ID,
};
use openhuman_core::openhuman::profiles::{
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
HasToolkit, PersonalityContext,
};
// ─────────────────────────────────────────────────────────────────────────────
// Test helpers
@@ -57,6 +57,10 @@ fn make_profile(id: &str, name: &str) -> AgentProfile {
soul_md: None,
soul_md_path: None,
composio_integrations: None,
memory_sources: None,
include_agent_conversations: true,
allowed_skills: None,
allowed_mcp_servers: None,
memory_dir_suffix: None,
is_master: false,
sort_order: None,
+8 -8
View File
@@ -257,10 +257,10 @@ async fn worker_b_schema_catalog_exposes_all_controller_methods() {
"openhuman.agent_get_definition",
"openhuman.agent_reload_definitions",
"openhuman.agent_triage_evaluate",
"openhuman.agent_profiles_list",
"openhuman.agent_profile_select",
"openhuman.agent_profile_upsert",
"openhuman.agent_profile_delete",
"openhuman.profiles_list",
"openhuman.profiles_select",
"openhuman.profiles_upsert",
"openhuman.profiles_delete",
"openhuman.tools_composio_execute",
"openhuman.tools_web_search",
"openhuman.tools_seltz_search",
@@ -487,7 +487,7 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
"not found",
),
(
"openhuman.agent_profile_upsert",
"openhuman.profiles_upsert",
json!({
"profile": {
"id": "bad-worker-b-profile",
@@ -501,7 +501,7 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
"not found",
),
(
"openhuman.agent_profile_select",
"openhuman.profiles_select",
json!({ "profile_id": "missing-worker-b-profile" }),
"not found",
),
@@ -539,12 +539,12 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
let profiles = rpc(
&harness.rpc_base,
20_200,
"openhuman.agent_profiles_list",
"openhuman.profiles_list",
json!({}),
)
.await;
assert_eq!(
ok(&profiles, "agent_profiles_list")
ok(&profiles, "profiles_list")
.get("activeProfileId")
.and_then(Value::as_str),
Some("default")
+7 -7
View File
@@ -496,7 +496,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
let upsert = rpc(
&harness.rpc_base,
301,
"openhuman.agent_profile_upsert",
"openhuman.profiles_upsert",
json!({
"profile": {
"id": "worker-b-custom",
@@ -517,7 +517,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
}),
)
.await;
let profiles = ok(&upsert, "agent_profile_upsert")
let profiles = ok(&upsert, "profiles_upsert")
.get("profiles")
.and_then(Value::as_array)
.expect("profiles after upsert");
@@ -534,12 +534,12 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
let select = rpc(
&harness.rpc_base,
302,
"openhuman.agent_profile_select",
"openhuman.profiles_select",
json!({ "profile_id": "worker-b-custom" }),
)
.await;
assert_eq!(
ok(&select, "agent_profile_select")
ok(&select, "profiles_select")
.get("activeProfileId")
.and_then(Value::as_str),
Some("worker-b-custom")
@@ -548,7 +548,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
let delete_default = rpc(
&harness.rpc_base,
303,
"openhuman.agent_profile_delete",
"openhuman.profiles_delete",
json!({ "profile_id": "default" }),
)
.await;
@@ -560,12 +560,12 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
let delete_custom = rpc(
&harness.rpc_base,
304,
"openhuman.agent_profile_delete",
"openhuman.profiles_delete",
json!({ "profile_id": "worker-b-custom" }),
)
.await;
assert_eq!(
ok(&delete_custom, "agent_profile_delete")
ok(&delete_custom, "profiles_delete")
.get("activeProfileId")
.and_then(Value::as_str),
Some("default"),