Skip to content

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

Merged
senamakel merged 20 commits into
tinyhumansai:mainfrom
senamakel:feat/agent-profiles
Jun 13, 2026
Merged

senamakel merged 20 commits into
tinyhumansai:mainfrom
senamakel:feat/agent-profiles

Conversation

@senamakel

@senamakel senamakel commented Jun 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • New top-level profiles core domain (relocated from agent/): persistent, user-selectable agent "flavours" — each with a custom name + SOUL.md, runtime defaults, and per-profile allowlists for memory sources, connectors, skills, and MCP servers. RPC namespace openhuman.profiles_{list,select,upsert,delete}.
  • Single enforcement gate at session construction (build_session_agent_inner) covering all six flavour dimensions: tools, agent-conversation recall, skills, MCP servers, Composio connectors, and memory sources.
  • Memory-source recall scoping via a per-turn task-local (memory::source_scope) read at the source-tree retrieval chokepoint (select_trees) and at the chunk-level memory tools — fail-open for non-source memory.
  • New Agent Profiles UI under /settings/profiles (list + editor), with 41 new i18n keys translated across all 14 locales.

Problem

Agents had no way to be given distinct "flavours". A user could not, for example, have a research persona that only sees certain memory sources, a narrow skill set, and a specific soul, separate from a general assistant. Profile scaffolding existed (AgentProfile struct, RPC, a Redux slice) but lived under agent/, lacked the selection dimensions, had no enforcement wiring, and had no editor UI.

Solution

  • Extracted src/openhuman/profiles/ following the canonical module shape (types/store/paths/prompt_section/schemas/mod); data file (agent_profiles.json) and struct name unchanged → no migration. Cleaned the RPC method names (profiles_{list,select,upsert,delete}).
  • Added memory_sources, include_agent_conversations (default true), allowed_skills, allowed_mcp_servers (all serde-optional, normalised; empty vec → None = "all").
  • Routed every per-profile filter through one seam: tools (visible-name filter), agent-conversation recall (loader toggle), skills (workflow-tool allowlist), MCP (McpServerRegistry::retaining_servers), connectors (filter_integrations), and memory sources (task-local with_source_scope → select_trees + chunk_source_allowed). None is the unrestricted sentinel for sub-agents/cron/CLI/unset profiles, so existing paths are unchanged.
  • Built ProfilesPanel + ProfileEditorPage with All/Selected allowlist editors; verbose [profiles] debug logging throughout.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/pr-ci.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • N/A: behaviour extends existing Persona/Profile matrix rows (5.4, 13.1.1); no new feature ID minted — Coverage matrix
  • N/A: no matrix feature IDs gained/lost — All affected feature IDs listed under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: does not touch release-cut smoke surfaces — Manual smoke checklist
  • N/A: no tracking issue — Linked issue closed via Closes #NNN

Impact

  • Desktop/CLI: per-profile gating only applies when a profile sets an allowlist; None everywhere = no behaviour change. Memory-source gate is fail-open (non-source memory — working/conversation/internal — always passes).
  • Security: narrows what an active profile can reach (tools/skills/MCP/connectors/memory); fail-closed for explicit out-of-allowlist source lookups.
  • Migration: none — agent_profiles.json schema and struct name preserved; new fields are back-compat optional.
  • i18n: 41 new keys with real translations in all 14 locales (parity + English-placeholder + bundle checks pass locally).

Related

  • Closes:
  • Follow-up PR(s)/TODOs: populate the editor's four allowlist pickers from their list RPCs (currently free-text chips); fold PersonaPanel SOUL.md into the default profile.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/agent-profiles
  • Commit SHA: a107ac2

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: cargo test --lib openhuman::profiles, openhuman::memory::source_scope, memory_tree::retrieval::source, mcp_client::registry, workflows::tools; pnpm test src/services/api/agentProfilesApi.test.ts; pnpm i18n:check + i18n:english:check
  • Rust fmt/check (if changed): cargo fmt --check, GGML_NATIVE=OFF cargo check --lib
  • N/A: Tauri shell unchanged — Tauri fmt/check

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: a selected agent profile with allowlists restricts its tools/skills/MCP/connectors/memory-source recall; unset = all.
  • User-visible effect: new Agent Profiles screen under Settings; selecting a profile changes the assistant's name, soul, memory, and capabilities.

Parity Contract

  • Legacy behavior preserved: None/unset allowlists are the default and leave every path unrestricted; agent_profiles.json schema unchanged.
  • Guard/fallback/dispatch parity checks: memory-source gate fail-open for non-source chunks; sub-agent/cron/CLI paths pass None scope.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added Agent Profiles settings: list, create, edit, delete, and activate profiles with badges and navigation.
    • Full-profile editor: identity, SOUL.md, runtime defaults, memory controls (including agent-conversation toggle) and capability allowlists (memory sources, connectors, skills, MCP servers).
    • Profile-scoped memory recall: per-profile memory-source filtering and tool/skill scoping applied at runtime.
  • Documentation

    • Added i18n strings for Agent Profiles across multiple languages.

Relocate agent profiles out of the agent/ domain into a dedicated
top-level src/openhuman/profiles/ module following the canonical module
shape (types/store/paths/prompt_section/schemas/mod). RPC moves to the
'profiles' namespace (openhuman.profiles_list / profile_{select,upsert,
delete}); data file (agent_profiles.json) and struct name unchanged.

Also folds in the new per-profile selection fields on AgentProfile:
memory_sources, include_agent_conversations (default true), allowed_skills,
allowed_mcp_servers — all serde-optional/back-compat, normalised via a
shared allowlist helper (empty -> None = 'all'). Enforcement wiring lands
in a follow-up commit.

Updated all importers (web channel, factory, task_dispatcher,
delegate_to_personality) and the relocated integration tests.
Thread the active AgentProfile into the session builder
(from_config_for_agent_with_profile -> build_session_agent_inner) so
per-profile enforcement has a single seam. Wire the profile's
include_agent_conversations flag into DefaultMemoryLoader: when off, the
[Prior conversations] and [Cross-chat context] blocks are suppressed
while [User working memory] is retained. Defaults to on for None/unset
callers (meet agent, task dispatcher) — no behaviour change there.

Adds a with_agent_conversations builder + unit test, and a [profiles]
session-gate debug log.
Thread per-profile allowlists into tool assembly via two new optional
params on all_tools_with_runtime (skill_allowlist, mcp_allowlist); the
all_tools wrapper passes None so its ~30 test callers are untouched.

- Skills: WorkflowList/WorkflowDescribe/RunWorkflow now carry an
  Option<HashSet<dir_name>> allowlist. List filters enumeration; Describe
  and Run reject disallowed ids with a clear error. None = all.
- MCP: McpServerRegistry::retaining_servers(&[name]) scopes the registry
  (case-insensitive) before the bridge tools are built, so list_servers/
  list_tools/call are all transparently constrained. None = all.

The session builder derives both allowlists from the active profile's
allowed_skills / allowed_mcp_servers. Adds unit tests for retaining_servers,
the skill_allowed helper, and describe rejection.
Scope the connected-integration view to the active profile's
composio_integrations allowlist at session build (None = all). Filtering
prewarmed_integrations before they feed both the system-prompt connected-
integrations surface and agent.connected_integrations means a profile only
ever sees — and can only delegate to — the toolkits it was granted.

Reuses the existing filter_integrations helper via a new HasToolkit impl
for ConnectedIntegration. Adds a unit test.
Rename the profiles controller functions to list/select/upsert/delete so
the methods read openhuman.profiles_{list,select,upsert,delete} instead of
the doubled profiles_profiles_list. Update all rust e2e callers
(domain_modules, worker_b_*) and the frontend agentProfilesApi + its test.

Extend the AgentProfile TS type with memorySources, includeAgentConversations,
allowedSkills, allowedMcpServers to mirror the Rust struct.
New profiles management surface under /settings/profiles (distinct from the
agent registry editor):
- ProfilesPanel: lists profiles, set-active, edit, delete (custom only),
  new-profile; wired to the existing agentProfileSlice thunks.
- ProfileEditorPage: create/edit a profile — name, id, description, SOUL.md,
  base agent, model, temperature, system-prompt suffix, the
  include-agent-conversations toggle, and four All/Selected allowlist editors
  (memory sources, connectors, skills, MCP servers).
- Routes + an Assistant-group entry in SettingsHome.

Adds 41 i18n keys under settings.profiles.* to en.ts with real translations
in all 13 other locales (parity + English-placeholder checks pass).
typecheck + lint (0 errors) green.
Gate which memory sources a profile recalls from at the source-tree
retrieval chokepoint. Adds a per-turn task-local (memory::source_scope)
mirroring thread_context: the web channel wraps the agent turn in
with_source_scope(profile.memory_sources) and source-tree retrieval reads it.

Because select_trees runs inside query_source's spawn_blocking (which does
not inherit task-locals), the allowlist is captured in the async task and
threaded explicitly into collect_hits_and_nodes -> select_trees. This covers
both the on-demand memory_tree_query_source tool and the tree prefetch path:
- explicit source_id lookups outside the allowlist return empty (fail-closed)
- all-trees / by-kind queries are filtered to allowlisted scopes
- None scope (sub-agents, cron, CLI, unset profiles) stays unrestricted

Allowlist entries match tree scope strings (what the agent passes as
source_id). Adds task-local + select_trees gating unit tests.
Close the residual: the raw chunk tools (memory_vector_search,
memory_chunk_context, memory_store_raw_chunks) bypass select_trees and read
list_chunks directly, so they previously ignored the profile memory-source
allowlist.

Add memory::source_scope::chunk_source_allowed(tags, source_id): fail-open for
anything without the 'memory_sources' tag (working memory, conversation, internal
chunks always pass); a tagged source chunk passes iff its identifier is allowed —
matched against either the raw source_id (Composio/channel scopes like slack:#eng)
or the registry id extracted from a mem_src:<id>:<item> composite (reader-based
sources). None scope = unrestricted.

Wired into all three tools (vector_search + raw_chunks post-filter results;
chunk_context blocks early when the target chunk's source is disallowed, since
its window shares that source). Adds unit tests covering tagged in/out,
non-source pass-through, empty-allowlist, and unrestricted cases.
@senamakel
senamakel requested a review from a team June 12, 2026 15:45
@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@senamakel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 hours, 27 minutes, and 14 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4cb773fa-cb30-459b-917b-90df64325504

📥 Commits

Reviewing files that changed from the base of the PR and between c4b0b97 and 87eb9c7.

📒 Files selected for processing (37)
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/panels/ProfileEditorPage.test.tsx
  • app/src/components/settings/panels/ProfilesPanel.test.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • src/openhuman/agent/task_dispatcher/executor.rs
  • src/openhuman/agent/task_dispatcher/types.rs
  • src/openhuman/channels/providers/web/run_task.rs
  • src/openhuman/channels/providers/web/session.rs
  • src/openhuman/channels/providers/web/types.rs
  • src/openhuman/channels/providers/web_tests.rs
  • src/openhuman/memory/source_scope.rs
  • src/openhuman/memory_search/tools/chunk_context.rs
  • src/openhuman/memory_search/tools/vector_search.rs
  • src/openhuman/memory_store/chunks/store.rs
  • src/openhuman/memory_store/chunks/store_tests.rs
  • src/openhuman/memory_store/retrieval/mod.rs
  • src/openhuman/memory_store/tools/raw_chunks.rs
  • src/openhuman/memory_tree/tree/rpc.rs
  • src/openhuman/profiles/mod.rs
  • src/openhuman/profiles/ops.rs
  • src/openhuman/profiles/schemas.rs
  • src/openhuman/tools/ops.rs
  • src/openhuman/workflows/tools.rs
  • tests/inference_agent_raw_coverage_e2e.rs
📝 Walkthrough

Walkthrough

This PR adds a comprehensive agent profiles feature with frontend settings (list/editor/routes and i18n), backend profiles RPC/controllers and persistent types, and runtime enforcement of profile-scoped allowlists for memory sources, workflows, and MCP servers plus a toggle for conversation recall.

Changes

Agent Profiles Feature

Layer / File(s) Summary
Frontend Settings UI for Profiles Management
app/src/components/settings/SettingsHome.tsx, app/src/components/settings/panels/ProfileEditorPage.tsx, app/src/components/settings/panels/ProfilesPanel.tsx, app/src/pages/Settings.tsx, app/src/lib/i18n/*
New React settings components and routing for agent profiles: ProfilesPanel listing with active/delete/edit controls, ProfileEditorPage form for create/edit flows, AllowlistField UI, and i18n translations added across language files.
Frontend API and Type Definitions
app/src/services/api/agentProfilesApi.ts, app/src/services/api/agentProfilesApi.test.ts, app/src/types/agentProfile.ts
Updated RPC method names to the openhuman.profiles_* namespace; extended AgentProfile TypeScript interface with optional memorySources, includeAgentConversations, allowedSkills, and allowedMcpServers.
Backend Profile Types and Storage
src/openhuman/profiles/types.rs, src/openhuman/profiles/store.rs
Adds serde AgentProfile and AgentProfilesState, DEFAULT_PROFILE_ID, normalization helpers, atomic JSON persistence, and built-in profile definitions including new allowlist/memory fields.
Profile Module Structure and Prompt Section
src/openhuman/profiles/mod.rs, src/openhuman/profiles/prompt_section.rs, src/openhuman/profiles/paths.rs
New openhuman::profiles domain with re-exports, AgentProfilePromptSection prompt block, path/helpers and integration filtering utilities.
RPC Endpoints and Controller Registration
src/openhuman/profiles/schemas.rs, src/core/all.rs
JSON-RPC handlers and schema definitions for profiles_list, profiles_select, profiles_upsert, and profiles_delete; controllers registered with the global registry.
Agent Session Construction with Per-Profile Configuration
src/openhuman/agent/harness/session/builder/factory.rs, src/openhuman/channels/providers/web/session.rs, src/openhuman/agent/task_dispatcher/executor.rs, src/openhuman/channels/runtime/startup.rs, src/openhuman/meet_agent/brain/llm.rs
Threads an optional AgentProfile into session builder, derives skill/MCP allowlists for tool setup, filters prewarmed Composio integrations, and configures memory loader conversation toggle.
Task-Local Memory Source Allowlist
src/openhuman/memory/source_scope.rs, src/openhuman/memory/mod.rs
Introduces per-tokio-task with_source_scope that normalizes allowlists and provides chunk_source_allowed/scope_allowed helpers to gate recall by tagged memory sources.
Memory Tools Enforcing Source Allowlists
src/openhuman/memory_search/tools/chunk_context.rs, src/openhuman/memory_search/tools/vector_search.rs, src/openhuman/memory_store/tools/raw_chunks.rs, src/openhuman/memory_tree/retrieval/source.rs, src/openhuman/channels/providers/web/run_task.rs
Memory tools and retrieval paths now consult chunk_source_allowed and thread the active source scope into blocking DB tasks; chat task execution is wrapped in with_source_scope(profile.memory_sources).
Workflow and MCP Server Scoping
src/openhuman/tools/ops.rs, src/openhuman/agent/tools/run_workflow.rs, src/openhuman/workflows/tools.rs, src/openhuman/mcp_client/registry.rs
Tool registry accepts optional skill and MCP allowlists; RunWorkflowTool, WorkflowListTool, and WorkflowDescribeTool support skill allowlists; MCP registry adds retaining_servers to filter servers by allowlist.
Memory Loader Conversation Recall Toggle
src/openhuman/agent_memory/memory_loader.rs
DefaultMemoryLoader gains include_agent_conversations (default true) and a builder method to suppress prior and cross-chat conversation sections.
Module Reorganization and Import Migration
src/openhuman/mod.rs, src/openhuman/agent/mod.rs, src/openhuman/agent/schemas.rs, src/openhuman/agent/tools/delegate_to_personality.rs
Removes openhuman::agent::profiles from the agent module, adds top-level openhuman::profiles, and updates imports and removed profile handlers from agent schemas.
E2E and Test Updates
tests/*, app/src/services/api/*
Updated tests and E2E suites to use openhuman.profiles_* RPC names, import openhuman::profiles, and include new AgentProfile fields in fixtures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • tinyhumansai/openhuman#3444: Related work on memory tools (chunk_context, vector_search) that this PR extends with per-profile allowlist gating.

Suggested reviewers

  • graycyrus
  • sanil-23

Poem

🐰 I hopped through code with keen delight,
Profiles tucked in day and night,
Memories gated, skills in line,
New routes, new prompts — oh how they shine! ✨

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. labels Jun 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
src/openhuman/memory_tree/retrieval/source.rs (2)

229-243: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The new debug path leaks redacted source identifiers.

This file explicitly avoids logging raw source_id values because scopes like slack:#... can reveal workspace structure, but this branch logs the full id again when a profile blocks it (and the no-tree branch below does the same). Keep the signal, but log only presence/kind or a redacted/hash form of the identifier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/memory_tree/retrieval/source.rs` around lines 229 - 243, The
debug branches that log raw source identifiers (the match handling around
source_id, the allowed(id) check, and the None branch after
store::get_tree_by_scope) leak sensitive scope strings; change those log
statements to avoid printing the raw id — either log only the presence and
TreeKind (e.g., "source blocked by profile memory-source scope" or "no tree for
source_id — returning empty list") or log a non-reversible hash/redaction of id
(compute a short hex(hash(id))) before including it; update the log::debug calls
that currently interpolate {id} so they reference the redacted/hash variable or
omit the identifier entirely.

219-257: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reader-backed memory sources are filtered out incorrectly here.

allowed only does an exact set.contains(scope), but this file documents tree scopes as raw metadata.source_id values, and the chunk-level gate already had to special-case mem_src:<id>:<item> composites. With the current code, a profile allowlisting src-rss-42 will still lose those reader-backed source trees in memory_tree_query_source, even though the raw chunk/context paths allow them.

Please reuse the same extract_mem_src_id(...) fallback here when checking source_id/t.scope, otherwise source-tree recall becomes inconsistent with the other memory-source gates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/memory_tree/retrieval/source.rs` around lines 219 - 257,
select_trees currently uses the closure allowed which only checks
set.contains(scope), causing reader-backed composite scopes like
"mem_src:<id>:<item>" to be rejected; update allowed (and the early source_id
check) to also try extract_mem_src_id(scope) and treat the extracted id as
allowed if present (i.e., allowed if set.contains(scope) ||
extract_mem_src_id(scope).map_or(false, |id| set.contains(&id))); apply this
change where select_trees filters Tree.scope and when checking the explicit
source_id before calling store::get_tree_by_scope so tree-level gating matches
the chunk/context gate used in memory_tree_query_source.
src/openhuman/profiles/paths.rs (1)

146-150: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden profile.id before using it as a filesystem path segment.

Unlike soul_md_path, this joins raw profile.id into a path with no component validation or canonicalization. A crafted id such as ../../secrets makes personality prompt loading read outside <workspace>/personalities, so a profile can disclose arbitrary MEMORY.md files into the session.

Suggested fix
 pub fn resolve_personality_memory_md(
     workspace_dir: &Path,
     profile: &AgentProfile,
 ) -> Option<String> {
+    let rel = Path::new(&profile.id);
+    if !is_safe_relative_path(rel) || rel.components().count() != 1 {
+        tracing::warn!(
+            profile_id = %profile.id,
+            "[personality] rejected unsafe profile id for MEMORY.md lookup"
+        );
+        return None;
+    }
+
     let path = workspace_dir
         .join("personalities")
-        .join(&profile.id)
+        .join(rel)
         .join("MEMORY.md");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/profiles/paths.rs` around lines 146 - 150, The code joins raw
profile.id into a filesystem path (used when constructing path for MEMORY.md)
allowing path-traversal like "../../secrets"; validate or sanitize profile.id
before using it in the path in the function that builds the path (the path
variable using
workspace_dir.join("personalities").join(&profile.id).join("MEMORY.md"), and
before calling std::fs::read_to_string). Fix by ensuring profile.id is
restricted to a single safe path component (reject or strip any components
containing ParentDir, separators, or NULs), or normalize and canonicalize the
final path (std::fs::canonicalize) and assert it starts_with
workspace_dir.join("personalities"), returning an error if validation fails;
alternatively use a filename-sanitization utility to produce a safe component
before joining.
src/openhuman/profiles/store.rs (2)

454-463: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rebuild the default profile from the builtin baseline during load normalization.

Lines 459-462 only force is_master and memory_dir_suffix for DEFAULT_PROFILE_ID. A persisted default entry still overrides agent_id, built_in, and every other invariant that upsert() preserves via built_in_default_profile(). That lets a stale or hand-edited agent_profiles.json change the fallback default profile into a non-orchestrator profile. Re-merge DEFAULT_PROFILE_ID with built_in_default_profile() here too, not just in upsert().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/profiles/store.rs` around lines 454 - 463, The loader currently
only sets is_master and memory_dir_suffix for entries with DEFAULT_PROFILE_ID,
allowing a persisted default to override invariants; modify normalise_profile
(or the load loop that iterates state.profiles) so that when profile.id ==
DEFAULT_PROFILE_ID you merge/replace the loaded record with the canonical
built_in_default_profile() values (preserving any allowed fields if needed)
rather than just toggling is_master and memory_dir_suffix—this mirrors what
upsert() enforces and prevents a hand-edited agent_profiles.json from turning
the default into a non-orchestrator profile; update the code path that inserts
into by_id to use the merged result.

135-267: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Serialize profile mutations; concurrent RPCs can drop updates.

Lines 135-267 implement select(), upsert(), and delete() as load() → mutate → save() against the same agent_profiles.json with no lock or compare-and-swap. Two overlapping requests can read the same snapshot and the later persist wins, which loses profile edits and can roll back active_profile_id. Please guard the whole mutation cycle with a shared lock (and ideally a file lock if multiple processes can touch the workspace).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/profiles/store.rs` around lines 135 - 267, The select(),
upsert(), and delete() methods perform load() → mutate → save() without
synchronization, so concurrent RPCs can clobber each other; wrap the entire
mutation cycle in a shared lock to serialize mutations (e.g., add a Mutex/RwLock
field on the store and lock it for the duration of load/mutate/save in select,
upsert, and delete), and if cross-process safety is required, also acquire a
file lock around the same critical section (the agent_profiles.json access)
before calling load() and hold it until after save() to ensure atomic
compare-and-swap semantics.
src/openhuman/agent/harness/session/builder/factory.rs (1)

345-365: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Thread allowed_tools through the builder, not after it returns.

This builder applies profile gates for skills, MCP servers, connectors, and conversation recall, but it never applies profile.allowed_tools. In the supplied code, the tool allowlist is only patched later via agent.set_visible_tool_names(...), after this function has already decided whether to add MemoryAccessSection and built the dispatcher/registry from the unrestricted tool set. That leaves build-time tool/prompt state out of sync with the selected profile.

Based on PR objectives: profile restrictions are supposed to be enforced at the session-construction seam.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/harness/session/builder/factory.rs` around lines 345 -
365, The builder currently applies profile.allowed_tools only after constructing
the full toolset and building dispatcher/registry, causing MemoryAccessSection
and dispatch logic to be built from an unrestricted set; instead thread the
profile.allowed_tools through the session builder so it is applied before
calling tools::all_tools_with_runtime (pass the allowlist into the call, similar
to profile_skill_allowlist/profile_mcp_allowlist), and ensure
MemoryAccessSection and dispatcher/registry are created from the filtered tool
list; keep agent.set_visible_tool_names only as a redundant UI sync if desired,
but do not rely on it for policy—make tools::all_tools_with_runtime,
MemoryAccessSection construction, and dispatcher/registry creation use the same
filtered allowed_tools set.
src/openhuman/channels/providers/web/session.rs (1)

182-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include the active profile in the session-cache fingerprint.

build_session_fingerprint still ignores profile.id and every profile-scoped field, even though build_session_agent now threads profile restrictions into agent construction. Two profiles with the same target_agent_id/model/temperature will fingerprint identically, so the cache can reuse an agent built for the wrong allowlists, memory-recall mode, or prompt suffix.

Based on PR objectives: the selected profile now changes the runtime session surface, so the cache key needs to reflect it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/channels/providers/web/session.rs` around lines 182 - 199,
build_session_fingerprint currently omits the active profile, causing identical
fingerprints for sessions that differ only by profile-scoped settings; update
build_session_fingerprint to accept the selected profile identifier (e.g.
profile_id: Option<String> or &str) and include that value in the returned
SessionCacheFingerprint (add a profile_id/profile_signature field to
SessionCacheFingerprint if needed). Make corresponding changes where
build_session_fingerprint is called so callers pass the active profile id, and
ensure the new fingerprint field is derived from the profile.id (or a stable
profile signature) so profile-scoped allowlists, memory-recall mode, and prompt
suffix affect cache keys.
🧹 Nitpick comments (6)
src/openhuman/memory/source_scope.rs (1)

53-66: ⚡ Quick win

Add a matching exit log for the task-local scope.

This new flow logs entry but never logs exit, so it's hard to tell whether a turn actually left the scoped section or where a leaked scope came from during async debugging. A symmetric post-await debug line here would make the new policy seam much easier to trace.

As per coding guidelines, new/changed Rust flows should log “entry/exit, branches, external calls, retries/timeouts, state transitions, and errors with stable grep-friendly prefixes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/memory/source_scope.rs` around lines 53 - 66, The
with_source_scope function logs entry but not exit; after awaiting
SOURCE_SCOPE.scope(value, fut).await add a symmetric debug log indicating exit
(include the same scope description as the entry) so you can trace when the
task-local scope completes; use the same computed value from
normalize(allowlist) and call log::debug! with a stable prefix like
"[memory:source_scope] exiting scope: ..." referencing with_source_scope and
SOURCE_SCOPE so grep finds both entry/exit pairs.

Source: Coding guidelines

tests/domain_modules_e2e.rs (1)

451-515: ⚡ Quick win

Add profiles to the schema-catalog assertions too.

This file is the /schema discovery E2E suite, but the new top-level domain is only exercised via direct openhuman.profiles_* calls here. If src/openhuman/profiles/schemas.rs stops exposing the profiles namespace in /schema, this file still passes. Please extend target_domain_schemas_are_exposed_over_http_schema_catalog to assert the profiles namespace and its four methods are discoverable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/domain_modules_e2e.rs` around lines 451 - 515, Update the test
target_domain_schemas_are_exposed_over_http_schema_catalog to assert the new
top-level "profiles" namespace is present in the /schema catalog and that its
four RPC methods ("openhuman.profiles_list", "openhuman.profiles_upsert",
"openhuman.profiles_select", "openhuman.profiles_delete") are discoverable;
locate the test in domain_modules_e2e.rs (where rpc and ok helpers are used) and
add assertions that the schema JSON contains a "profiles" entry and that each of
those method names appears in the exposed methods list for that namespace.
tests/personality_e2e.rs (1)

44-67: ⚡ Quick win

Extend the roundtrip coverage to the new profile fields.

make_profile now seeds memory_sources, include_agent_conversations, allowed_skills, and allowed_mcp_servers, but the lifecycle tests still never set or assert any non-default values for them. Since the store now normalizes/persists those fields, it would be worth extending personality_fields_roundtrip_through_upsert to verify they survive serialization and reload too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/personality_e2e.rs` around lines 44 - 67, The test helper make_profile
populates new fields (memory_sources, include_agent_conversations,
allowed_skills, allowed_mcp_servers) but the lifecycle test
personality_fields_roundtrip_through_upsert doesn't assert non-default values
round-trip; update that test to set explicit non-default values on the
AgentProfile created by make_profile (e.g., a non-empty memory_sources vector,
include_agent_conversations = false/true opposite of default, a sample
allowed_skills list, and a sample allowed_mcp_servers list), perform the
upsert/load cycle using the existing upsert and fetch methods, and add
assertions that the loaded profile's memory_sources,
include_agent_conversations, allowed_skills, and allowed_mcp_servers exactly
match the values you set so you verify the store normalization/persistence
behavior.
src/openhuman/profiles/types.rs (1)

9-87: ⚡ Quick win

Keep types.rs serde-only.

Lines 9-87 add DEFAULT_PROFILE_ID, default_true(), and profile_signature() to a file that the repo reserves for Serde domain types. Please move the non-type helpers/constants into a dedicated helper module or the owning layer so profiles/types.rs stays schema-only. Based on learnings and coding guidelines, src/openhuman/*/types.rs must contain Serde domain types only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/profiles/types.rs` around lines 9 - 87, The file adds non-Serde
helpers/constants (DEFAULT_PROFILE_ID, default_true, profile_signature) into a
schema-only module; move them into a dedicated helper module (e.g., create a new
module like profiles_helpers or profiles::helpers) or into the owning layer,
export them from there, and update references/imports accordingly (change the
#[serde(default = "default_true")] reference to point to the new default_true
location or add a use statement, and update any callers of DEFAULT_PROFILE_ID
and profile_signature to the new module path); leave src/openhuman/.../types.rs
containing only the Serde domain types.

Sources: Coding guidelines, Learnings

app/src/components/settings/panels/ProfileEditorPage.tsx (1)

402-412: ⚡ Quick win

Extract an interface for AllowlistField props.

The inline props object type here conflicts with the repo’s TypeScript rule for named object shapes. As per coding guidelines, always use interface for defining object shapes in TypeScript.

♻️ Suggested refactor
+interface AllowlistFieldProps {
+  label: string;
+  hint: string;
+  value: Allowlist;
+  onChange: (next: Allowlist) => void;
+}
+
 function AllowlistField({
   label,
   hint,
   value,
   onChange,
-}: {
-  label: string;
-  hint: string;
-  value: Allowlist;
-  onChange: (next: Allowlist) => void;
-}) {
+}: AllowlistFieldProps) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/ProfileEditorPage.tsx` around lines 402 -
412, The AllowlistField component currently declares its props via an inline
object type; extract a named interface (e.g., AllowlistFieldProps) and use it as
the component props type to satisfy the repo TypeScript rule. Define interface
AllowlistFieldProps { label: string; hint: string; value: Allowlist; onChange:
(next: Allowlist) => void; } and update the AllowlistField signature to use
AllowlistFieldProps. Ensure any imports/types (Allowlist) remain unchanged and
update any usages if necessary.

Source: Coding guidelines

src/openhuman/agent_memory/memory_loader.rs (1)

704-768: ⚡ Quick win

Also cover the JSONL cross-chat path in the new toggle test.

This test only exercises the fallback Memory::recall branch because workspace_dir is never set. The new opt-out also gates the primary ConversationStore::search_cross_thread_messages(...) path, so a with_workspace_dir(...) variant would protect the production branch from regressing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent_memory/memory_loader.rs` around lines 704 - 768, The test
currently only hits the Memory::recall fallback because DefaultMemoryLoader in
the test never sets a workspace_dir; add a variant that sets workspace_dir on
the loader to exercise the primary
ConversationStore::search_cross_thread_messages JSONL path so the opt-out gating
is covered for both branches. Concretely, duplicate or extend the "Opted out"
case to build a loader with .with_workspace_dir(...) (matching how
DefaultMemoryLoader exposes workspace configuration) and
.with_agent_conversations(false), call load_context(&mem, ...) and assert that
[User working memory] remains and both [Prior conversations] and
CROSS_CHAT_HEADER are suppressed (and that "Postgres"/"Redis" are absent) to
protect the production search_cross_thread_messages path from regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/ProfileEditorPage.tsx`:
- Around line 159-161: The breadcrumbs array currently hardcodes 'Settings';
replace that literal with a localized string from useT() (imported from
app/src/lib/i18n/I18nContext) so the label uses t(...). Update the first
breadcrumb entry in the breadcrumbs constant (the one with onClick: () =>
navigate('/settings')) to call t() (e.g., t('settings.title') or the appropriate
translation key) while leaving the onClick handler unchanged, ensuring
consistency with the existing t('settings.profiles.title') usage.
- Around line 89-146: The form currently allows submitting when the resolved
profile id is empty; update validation so the resolved id cannot be blank:
change the canSubmit computation to require the resolved id (isCreate ?
profileId.trim() || slugify(name).trim() : profileId.trim()) to be non-empty,
and in handleSubmit recompute id = (isCreate ? profileId.trim() || slugify(name)
: profileId).trim() and if id === '' setSubmitting(false), setError('Profile id
cannot be empty') (or similar) and return before building the AgentProfile or
dispatching upsertAgentProfile; ensure you reference existing, isCreate,
profileId, name, canSubmit and handleSubmit when applying this fix.
- Around line 89-114: The effect currently synchronously calls many setters
(useEffect) to hydrate form state (setName, setAgentId, setProfileId,
setDescription, setModel, setTemperature, setSystemPromptSuffix, setSoulMd,
setIncludeAgentConversations, setMemorySources, setComposioIntegrations,
setAllowedSkills, setAllowedMcpServers) which triggers the
react-hooks/set-state-in-effect warning; instead refactor so the form is
initialized from the profile rather than mutated in an effect: extract a
hydrateProfile(existing) pure initializer (or compute initialFormState via
useMemo based on existing) and pass that as the initial state to your form
component, or render the form as a keyed child (key={existing?.id || 'create'})
so React remounts with the correct initial state; if you cannot refactor, add a
single-line eslint-disable-next-line react-hooks/set-state-in-effect with a
concise justification above this useEffect.

In `@app/src/components/settings/panels/ProfilesPanel.tsx`:
- Line 66: The breadcrumbs array uses a hardcoded label 'Settings'; import and
call useT() from I18nContext in the ProfilesPanel component (e.g., const t =
useT()) and replace the literal with a translation key (e.g., t('settings') or
t('settings.title')) when constructing breadcrumbs (the breadcrumbs const).
Ensure useT is added to the component scope and the label passed to the existing
onClick/navigate remains unchanged.

In `@src/openhuman/agent/task_dispatcher/executor.rs`:
- Around line 47-49: The code builds a PersonalityContext from the resolved
profile (PersonalityContext::from_profile) but still calls
Agent::from_config_for_agent_with_profile(..., None) so the agent is created
without the profile's tool/MCP/connector/memory allowlists; update the
Agent::from_config_for_agent_with_profile calls to pass the resolved profile (or
profile.clone()) instead of None wherever they occur (the instance created after
profiles::load_profiles(...) and the second occurrence around the later block),
ensuring the profile is forwarded into agent construction so SOUL/MEMORY plus
allowlists are honored.

In `@src/openhuman/channels/providers/web/run_task.rs`:
- Around line 171-180: The session agent reuse is missing profile identity in
the session fingerprint so switching profiles can retain stale agent state;
update the session fingerprint/current_fp calculation (the code path that
caches/returns the agent used by agent.run_single inside
thread_context::with_thread_id and
crate::openhuman::memory::source_scope::with_source_scope) to incorporate a
stable digest of the resolved profile (e.g., profile ID +
allowlist/connector/toolset hash) or otherwise detect when resolved profile
changes and force a rebuild of the cached agent; ensure the change is applied
where the agent is created/cached so thread-scoped calls (with_thread_id) and
per-turn source_scope use an agent bound to the correct profile identity rather
than only model/temperature/provider params.

In `@src/openhuman/memory_search/tools/vector_search.rs`:
- Around line 158-168: The post-limit filtering currently performed with
chunk_source_allowed after calling list_chunks(...) (seen in
src/openhuman/memory_search/tools/vector_search.rs and similarly in
src/openhuman/memory_store/tools/raw_chunks.rs) causes scoped profiles to miss
allowed rows when disallowed sources occupy the initial page; move the
source-gate into the storage query (i.e., pass the allowed-source
predicate/allowed source IDs to the underlying list_chunks call) so the store
only returns permitted chunks, or implement a paging loop around list_chunks
that repeatedly requests additional pages and applies chunk_source_allowed until
you have collected the requested count of permitted chunks (use the existing
chunk_source_allowed(&c.metadata.tags, &c.metadata.source_id) predicate to test
rows).

In `@src/openhuman/profiles/schemas.rs`:
- Around line 212-235: The handler currently only rejects unknown agent IDs when
AgentDefinitionRegistry::global() returns Some, allowing profiles with non-empty
p.profile.agent_id to be persisted when the registry is None; change the logic
so that if p.profile.agent_id.trim() is non-empty and
AgentDefinitionRegistry::global() is None you return an error (e.g., Err with a
clear message) instead of proceeding—update the code around
AgentDefinitionRegistry::global(), the agent_id checks referencing
p.profile.agent_id, and the profile_upsert flow to "fail closed" when the
registry is unavailable.
- Around line 123-309: The handlers handle_profiles_list, handle_profile_select,
handle_profile_upsert and handle_profile_delete currently contain business logic
(config loading via config_rpc::load_config_with_timeout, AgentProfileStore
calls, AgentDefinitionRegistry validation, and store mutations) — extract those
flows into new domain functions in profiles/ops.rs (e.g., list_profiles,
select_profile, upsert_profile, delete_profile) that perform config lookup,
registry validation, store operations and return RpcOutcome<T>; then simplify
each handle_* in schemas.rs to only deserialize_params::<...>(params) and
delegate to the corresponding ops function, returning its RpcOutcome. Ensure ops
functions reference AgentProfileStore and AgentDefinitionRegistry exactly as
used now and that handlers keep only request_id tracing and parameter logging
before delegating.

In `@src/openhuman/tools/ops.rs`:
- Around line 191-192: The profile skill allowlist is only applied to
RunWorkflowTool but not to other workflow-related tools (AwaitWorkflowTool,
ReadWorkflowResourceTool, ListWorkflowRunsTool, ReadWorkflowRunLogTool, and the
create/install/uninstall workflow tools), allowing scoped profiles to still
access/modify out-of-scope workflows; update the tool registrations to thread
the same skill_allowlist into each workflow-related tool by calling the same
with_skill_allowlist(skill_allowlist.cloned()) (or applying a common
wrapper/gating function) when constructing AwaitWorkflowTool and each of the
other workflow tools so all lookup, read, list, await, create/install/uninstall
operations respect the profile scope.
- Around line 630-638: The mcp_allowlist filtering only wraps the generic
McpServerRegistry built via McpServerRegistry::from_config into mcp_registry but
does not prevent the earlier registration of legacy Gitbooks tools
(gitbooks_search, gitbooks_get_page) nor the legacy Gitbooks entry modeled in
src/openhuman/mcp_client/registry.rs; update the code so Gitbooks is excluded
when not present in mcp_allowlist by either applying the same
retaining_servers(...) filtering to the code path that registers
gitbooks_search/gitbooks_get_page or gating that registration on whether
"gitbooks" (or the registry's legacy Gitbooks identifier) is in the allowed set,
ensuring the effective registry and tool registrations are consistent with
mcp_allowlist.

---

Outside diff comments:
In `@src/openhuman/agent/harness/session/builder/factory.rs`:
- Around line 345-365: The builder currently applies profile.allowed_tools only
after constructing the full toolset and building dispatcher/registry, causing
MemoryAccessSection and dispatch logic to be built from an unrestricted set;
instead thread the profile.allowed_tools through the session builder so it is
applied before calling tools::all_tools_with_runtime (pass the allowlist into
the call, similar to profile_skill_allowlist/profile_mcp_allowlist), and ensure
MemoryAccessSection and dispatcher/registry are created from the filtered tool
list; keep agent.set_visible_tool_names only as a redundant UI sync if desired,
but do not rely on it for policy—make tools::all_tools_with_runtime,
MemoryAccessSection construction, and dispatcher/registry creation use the same
filtered allowed_tools set.

In `@src/openhuman/channels/providers/web/session.rs`:
- Around line 182-199: build_session_fingerprint currently omits the active
profile, causing identical fingerprints for sessions that differ only by
profile-scoped settings; update build_session_fingerprint to accept the selected
profile identifier (e.g. profile_id: Option<String> or &str) and include that
value in the returned SessionCacheFingerprint (add a
profile_id/profile_signature field to SessionCacheFingerprint if needed). Make
corresponding changes where build_session_fingerprint is called so callers pass
the active profile id, and ensure the new fingerprint field is derived from the
profile.id (or a stable profile signature) so profile-scoped allowlists,
memory-recall mode, and prompt suffix affect cache keys.

In `@src/openhuman/memory_tree/retrieval/source.rs`:
- Around line 229-243: The debug branches that log raw source identifiers (the
match handling around source_id, the allowed(id) check, and the None branch
after store::get_tree_by_scope) leak sensitive scope strings; change those log
statements to avoid printing the raw id — either log only the presence and
TreeKind (e.g., "source blocked by profile memory-source scope" or "no tree for
source_id — returning empty list") or log a non-reversible hash/redaction of id
(compute a short hex(hash(id))) before including it; update the log::debug calls
that currently interpolate {id} so they reference the redacted/hash variable or
omit the identifier entirely.
- Around line 219-257: select_trees currently uses the closure allowed which
only checks set.contains(scope), causing reader-backed composite scopes like
"mem_src:<id>:<item>" to be rejected; update allowed (and the early source_id
check) to also try extract_mem_src_id(scope) and treat the extracted id as
allowed if present (i.e., allowed if set.contains(scope) ||
extract_mem_src_id(scope).map_or(false, |id| set.contains(&id))); apply this
change where select_trees filters Tree.scope and when checking the explicit
source_id before calling store::get_tree_by_scope so tree-level gating matches
the chunk/context gate used in memory_tree_query_source.

In `@src/openhuman/profiles/paths.rs`:
- Around line 146-150: The code joins raw profile.id into a filesystem path
(used when constructing path for MEMORY.md) allowing path-traversal like
"../../secrets"; validate or sanitize profile.id before using it in the path in
the function that builds the path (the path variable using
workspace_dir.join("personalities").join(&profile.id).join("MEMORY.md"), and
before calling std::fs::read_to_string). Fix by ensuring profile.id is
restricted to a single safe path component (reject or strip any components
containing ParentDir, separators, or NULs), or normalize and canonicalize the
final path (std::fs::canonicalize) and assert it starts_with
workspace_dir.join("personalities"), returning an error if validation fails;
alternatively use a filename-sanitization utility to produce a safe component
before joining.

In `@src/openhuman/profiles/store.rs`:
- Around line 454-463: The loader currently only sets is_master and
memory_dir_suffix for entries with DEFAULT_PROFILE_ID, allowing a persisted
default to override invariants; modify normalise_profile (or the load loop that
iterates state.profiles) so that when profile.id == DEFAULT_PROFILE_ID you
merge/replace the loaded record with the canonical built_in_default_profile()
values (preserving any allowed fields if needed) rather than just toggling
is_master and memory_dir_suffix—this mirrors what upsert() enforces and prevents
a hand-edited agent_profiles.json from turning the default into a
non-orchestrator profile; update the code path that inserts into by_id to use
the merged result.
- Around line 135-267: The select(), upsert(), and delete() methods perform
load() → mutate → save() without synchronization, so concurrent RPCs can clobber
each other; wrap the entire mutation cycle in a shared lock to serialize
mutations (e.g., add a Mutex/RwLock field on the store and lock it for the
duration of load/mutate/save in select, upsert, and delete), and if
cross-process safety is required, also acquire a file lock around the same
critical section (the agent_profiles.json access) before calling load() and hold
it until after save() to ensure atomic compare-and-swap semantics.

---

Nitpick comments:
In `@app/src/components/settings/panels/ProfileEditorPage.tsx`:
- Around line 402-412: The AllowlistField component currently declares its props
via an inline object type; extract a named interface (e.g., AllowlistFieldProps)
and use it as the component props type to satisfy the repo TypeScript rule.
Define interface AllowlistFieldProps { label: string; hint: string; value:
Allowlist; onChange: (next: Allowlist) => void; } and update the AllowlistField
signature to use AllowlistFieldProps. Ensure any imports/types (Allowlist)
remain unchanged and update any usages if necessary.

In `@src/openhuman/agent_memory/memory_loader.rs`:
- Around line 704-768: The test currently only hits the Memory::recall fallback
because DefaultMemoryLoader in the test never sets a workspace_dir; add a
variant that sets workspace_dir on the loader to exercise the primary
ConversationStore::search_cross_thread_messages JSONL path so the opt-out gating
is covered for both branches. Concretely, duplicate or extend the "Opted out"
case to build a loader with .with_workspace_dir(...) (matching how
DefaultMemoryLoader exposes workspace configuration) and
.with_agent_conversations(false), call load_context(&mem, ...) and assert that
[User working memory] remains and both [Prior conversations] and
CROSS_CHAT_HEADER are suppressed (and that "Postgres"/"Redis" are absent) to
protect the production search_cross_thread_messages path from regressions.

In `@src/openhuman/memory/source_scope.rs`:
- Around line 53-66: The with_source_scope function logs entry but not exit;
after awaiting SOURCE_SCOPE.scope(value, fut).await add a symmetric debug log
indicating exit (include the same scope description as the entry) so you can
trace when the task-local scope completes; use the same computed value from
normalize(allowlist) and call log::debug! with a stable prefix like
"[memory:source_scope] exiting scope: ..." referencing with_source_scope and
SOURCE_SCOPE so grep finds both entry/exit pairs.

In `@src/openhuman/profiles/types.rs`:
- Around line 9-87: The file adds non-Serde helpers/constants
(DEFAULT_PROFILE_ID, default_true, profile_signature) into a schema-only module;
move them into a dedicated helper module (e.g., create a new module like
profiles_helpers or profiles::helpers) or into the owning layer, export them
from there, and update references/imports accordingly (change the
#[serde(default = "default_true")] reference to point to the new default_true
location or add a use statement, and update any callers of DEFAULT_PROFILE_ID
and profile_signature to the new module path); leave src/openhuman/.../types.rs
containing only the Serde domain types.

In `@tests/domain_modules_e2e.rs`:
- Around line 451-515: Update the test
target_domain_schemas_are_exposed_over_http_schema_catalog to assert the new
top-level "profiles" namespace is present in the /schema catalog and that its
four RPC methods ("openhuman.profiles_list", "openhuman.profiles_upsert",
"openhuman.profiles_select", "openhuman.profiles_delete") are discoverable;
locate the test in domain_modules_e2e.rs (where rpc and ok helpers are used) and
add assertions that the schema JSON contains a "profiles" entry and that each of
those method names appears in the exposed methods list for that namespace.

In `@tests/personality_e2e.rs`:
- Around line 44-67: The test helper make_profile populates new fields
(memory_sources, include_agent_conversations, allowed_skills,
allowed_mcp_servers) but the lifecycle test
personality_fields_roundtrip_through_upsert doesn't assert non-default values
round-trip; update that test to set explicit non-default values on the
AgentProfile created by make_profile (e.g., a non-empty memory_sources vector,
include_agent_conversations = false/true opposite of default, a sample
allowed_skills list, and a sample allowed_mcp_servers list), perform the
upsert/load cycle using the existing upsert and fetch methods, and add
assertions that the loaded profile's memory_sources,
include_agent_conversations, allowed_skills, and allowed_mcp_servers exactly
match the values you set so you verify the store normalization/persistence
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: affda97a-3724-4474-a6d0-e38f49201aba

📥 Commits

Reviewing files that changed from the base of the PR and between 7187ce3 and a107ac2.

📒 Files selected for processing (55)
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/panels/ProfileEditorPage.tsx
  • app/src/components/settings/panels/ProfilesPanel.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Settings.tsx
  • app/src/services/api/agentProfilesApi.test.ts
  • app/src/services/api/agentProfilesApi.ts
  • app/src/types/agentProfile.ts
  • src/core/all.rs
  • src/openhuman/agent/harness/session/builder/factory.rs
  • src/openhuman/agent/mod.rs
  • src/openhuman/agent/schemas.rs
  • src/openhuman/agent/task_dispatcher/executor.rs
  • src/openhuman/agent/tools/delegate_to_personality.rs
  • src/openhuman/agent/tools/run_workflow.rs
  • src/openhuman/agent_memory/memory_loader.rs
  • src/openhuman/channels/providers/web/run_task.rs
  • src/openhuman/channels/providers/web/session.rs
  • src/openhuman/channels/runtime/startup.rs
  • src/openhuman/mcp_client/registry.rs
  • src/openhuman/meet_agent/brain/llm.rs
  • src/openhuman/memory/mod.rs
  • src/openhuman/memory/source_scope.rs
  • src/openhuman/memory_search/tools/chunk_context.rs
  • src/openhuman/memory_search/tools/vector_search.rs
  • src/openhuman/memory_store/tools/raw_chunks.rs
  • src/openhuman/memory_tree/retrieval/source.rs
  • src/openhuman/mod.rs
  • src/openhuman/profiles/mod.rs
  • src/openhuman/profiles/paths.rs
  • src/openhuman/profiles/prompt_section.rs
  • src/openhuman/profiles/schemas.rs
  • src/openhuman/profiles/store.rs
  • src/openhuman/profiles/types.rs
  • src/openhuman/runtime_node/ops.rs
  • src/openhuman/tools/ops.rs
  • src/openhuman/workflows/tools.rs
  • tests/domain_modules_e2e.rs
  • tests/inference_agent_raw_coverage_e2e.rs
  • tests/personality_e2e.rs
  • tests/worker_b_domain_e2e.rs
  • tests/worker_b_raw_coverage_e2e.rs
💤 Files with no reviewable changes (2)
  • src/openhuman/agent/mod.rs
  • src/openhuman/agent/schemas.rs

Comment thread app/src/components/settings/panels/ProfileEditorPage.tsx
Comment thread app/src/components/settings/panels/ProfileEditorPage.tsx
Comment thread app/src/components/settings/panels/ProfileEditorPage.tsx
Comment thread app/src/components/settings/panels/ProfilesPanel.tsx Outdated
Comment thread src/openhuman/agent/task_dispatcher/executor.rs
Comment thread src/openhuman/memory_search/tools/vector_search.rs Outdated
Comment thread src/openhuman/profiles/schemas.rs
Comment thread src/openhuman/profiles/schemas.rs Outdated
Comment thread src/openhuman/tools/ops.rs
Comment thread src/openhuman/tools/ops.rs
- tests/inference_agent_raw_coverage_e2e.rs: RunWorkflowTool is no longer a
  unit struct (gained skill_allowlist) — use ::new(). This was the Rust Core
  Coverage job's compile failure.
- ProfileEditorPage: guard against submitting an empty/punctuation-only resolved
  profile id; localize the Settings breadcrumb via t('nav.settings'); annotate
  the profile-hydration effect with an eslint-disable + rationale.
- ProfilesPanel: localize the Settings breadcrumb.
- i18n: add settings.profiles.editor.idRequired across all 14 locales.
… session fingerprint

Addresses CodeRabbit correctness findings:
- task_dispatcher: ResolvedExecutor now carries the resolved AgentProfile for
  personality executors; run_autonomous passes it into the builder and wraps the
  turn in with_source_scope, so background runs honour the profile's
  tool/skill/MCP/connector + memory-source allowlists (not just SOUL/MEMORY).
- tools/ops: the legacy Gitbooks MCP tools (gitbooks_search/get_page) now honour
  the per-profile mcp_allowlist — a profile that scopes MCP servers and omits
  'gitbooks' no longer sees that surface.
- web session cache: add profile_signature to SessionCacheFingerprint so
  switching profiles on the same thread rebuilds the agent instead of leaking the
  prior profile's capability surface. Adds a cache-miss unit test.
…low_resource

Addresses CodeRabbit correctness findings:
- list_chunks now takes an explicit source_scope allowlist and applies the
  memory-source gate BEFORE the row limit (fetching a wider candidate set when a
  scope is active), so a disallowed-source-heavy prefix can no longer starve
  permitted rows. The three chunk tools (vector_search, chunk_context,
  raw_chunks) pass current_source_scope() and drop their now-redundant
  post-filters. Adds a before-limit unit test.
- read_workflow_resource honours the per-profile skill allowlist, closing the
  bundled-script/doc exfiltration vector for workflows outside a profile's set.
…validation

Addresses CodeRabbit findings:
- Move profile list/select/upsert/delete business logic out of schemas.rs into
  profiles/ops.rs per the OpenHuman domain contract (schemas.rs handlers now
  deserialize + delegate; ops.rs owns config load, agent_id validation, store
  mutation and returns the JSON payload). store.rs still owns persistence.
- Fail closed on profile_upsert when the AgentDefinitionRegistry is unavailable:
  a non-default agent_id can't be validated, so refuse rather than persist a
  possibly-broken reference. The implicit 'orchestrator' default stays valid
  without the registry, mirroring the session builder. Adds ops unit tests.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 12, 2026
agent_registry_and_profile_controllers_cover_success_and_errors still looked up
the profile controllers via all_agent_registered_controllers() with the old
function names (profiles_list / profile_{select,upsert,delete}). After the move
to the top-level profiles domain they live in all_profiles_registered_controllers()
under list/select/upsert/delete (openhuman.profiles_*). Repoint the lookups and
assert the new namespace. Fixes the Rust Core Coverage job.
After the upstream tinyhumansai#3633 (parallel inference) merge, run_chat_task's future is
very large. Adding the per-profile with_source_scope wrapper inlined the giant
run_single future into an extra ambient-scope layer, pushing the run_chat_task
frame past the default test-thread stack — every web-channel turn coverage test
aborted with 'stack overflow' under cargo-llvm-cov (and plain cargo test).

Box the run_single future so both ambient-scope wrappers (thread_id + source
scope) hold a pointer instead of inlining it. No behaviour change. Verified the
previously-aborting lib + channels_web_* e2e targets now pass.
…Page

The diff-cover >=80% gate failed: the new profiles UI components were 0% covered
(174 changed frontend lines). Add component tests rendering both with a real
Redux store + mocked agentProfilesApi:
- ProfilesPanel: list/active+source badges, new/edit navigation, set-active,
  delete-with-confirm (and cancel), empty state, load error.
- ProfileEditorPage: create slug-from-name + upsert dispatch, submit-disabled
  guard for empty/punctuation ids, edit-mode hydration, not-found, All/Selected
  allowlist chip entry, agent-conversations toggle into the payload.
Both files now ~86-90% line coverage.
@senamakel
senamakel merged commit 03678e3 into tinyhumansai:main Jun 13, 2026
18 of 19 checks passed
senamakel added a commit to senamakel/openhuman that referenced this pull request Jun 13, 2026
…o two-panel layout

Resolve merge conflicts and align the branch with the agent-profiles feature:

- Settings.tsx: keep the two-pane SettingsLayout route shell and add the new
  profiles routes (profiles, profiles/new, profiles/edit/:id).
- Remove the retired SettingsHome.tsx (deleted on this branch; superseded by the
  sidebar route registry).
- Wire a top-level "profiles" entry into settingsRouteRegistry under the
  Assistant nav group so the merged feature is reachable from the sidebar.

Accompanying branch fixes folded in:
- Brain & Connections pages: apply the settings-style centered max-w-6xl shell.
- SettingsHeader: resolve pathname via useInRouterContext so isolated panel
  unit tests render without a Router.
- Silence console.debug/info in the vitest setup to cut expected error-path noise.
- Update unit + Playwright specs to the restructured settings IA (route
  registry, retired breadcrumbs, two-pane sidebar nav testids, billing
  moved-to-web, task-sources → integrations, connections sidebar tabs).

Note: commit hook bypassed (--no-verify) to land the merge resolution; quality
suite runs next.
senamakel-droid added a commit to senamakel-droid/openhuman that referenced this pull request Jun 13, 2026
…ai#3611/tinyhumansai#3632/tinyhumansai#3643/tinyhumansai#3646)

Frontend IA refactors broke the WDIO/Appium E2E suite (baseline 83 failing
specs). Test-only changes plus two one-line data-testid additions. Validated
by running the full Linux E2E suite to green on a fork (see PR for RCAs).
senamakel-droid added a commit to senamakel-droid/openhuman that referenced this pull request Jun 13, 2026
…ai#3611/tinyhumansai#3632/tinyhumansai#3643/tinyhumansai#3646)

Frontend IA refactors broke the WDIO/Appium E2E suite (baseline 83 failing
specs). Test-only changes plus two one-line data-testid additions. Validated
by running the full Linux E2E suite to green on a fork (see PR for RCAs).
senamakel-droid added a commit to senamakel-droid/openhuman that referenced this pull request Jun 13, 2026
…ai#3611/tinyhumansai#3632/tinyhumansai#3643/tinyhumansai#3646)

Frontend IA refactors broke the WDIO/Appium E2E suite (baseline 83 failing
specs). Test-only changes plus two one-line data-testid additions. Validated
by running the full Linux E2E suite to green on a fork (see PR for RCAs).
senamakel added a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant