From 42fe6d7d60a14a720d16b34601bd94e456998bb8 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 12:45:55 +0300 Subject: [PATCH 01/11] feat(kernel): realign DomainGroup with the family directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime axis was the half of kernelization the flat tree had been blocking. With `src/openhuman/` now one directory per family (#5328), `DomainGroup` can name each one instead of sweeping half the controller surface into `Platform`. Adds seven variants — Inference, Integrations, Automation (cron + subconscious), Runtimes (runtime + sandbox), Desktop, Hosted, Relay (tinyplace) — and retags 33 of the 45 `Platform` push sites. `Platform` now holds only the kernel surfaces with no family of their own: platform/, tools/, http_host/, test_support/. This is not cosmetic. It fixes two defects the catch-all was hiding: 1. `harness()` claimed "agent + memory + threads + config + security" but silently dropped ten namespaces into `Platform`: agent::{agentbox, harness_init, artifacts, learning}, security::{credentials, devices}, config::{workspace, migration_helpers}, memory::people, skills::webhooks. An agent harness that never registers `harness_init` is a latent bug. 2. `StoreInitPlan.people` keyed on `Platform` while `people` moved under `memory/` and its controllers are tagged `Memory`. Left alone, harness() would register the people RPC surface with no store behind it. `embedded()` no longer sets `platform: true` just to reach credentials and config — Desktop and Hosted are their own families now and stay off, which is what an embedded host actually wants. Also splits `DomainSubscriberPlan.platform`, which bundled subscribers now owned by four different families (webhooks→Skills, notifications→Desktop, composio + task_sources→Integrations, devices→Security, learning→Agent). Learning gets its own idempotency token rather than `group_first_time(DomainGroup::Agent)`: the Agent block already consumes that token, so whichever ran second would have silently skipped. `tool_group()` gains matching rules for the new families. A missing entry there leaks a gated tool under a custom DomainSet — the #4808 review finding — and it is not compiler-enforced, so it is called out in AGENTS.md alongside the store and subscriber plan keys. New: `DomainSet::kernel()` (threads + config + security; agent and memory OFF, because they are the two largest subsystems and the ones an alternative driver would replace) and `examples/embed_kernel.rs`, which was run, not just compiled — it prints memory serving requests and `agent_list_definitions` returning "unknown method", demonstrating that absence, not a failing stub, is the contract. Verified: default, --all-targets, gates-off, Tauri-shell builds clean; clippy -D warnings clean in both Cargo worlds; fmt clean in both; core:: 681 gates-on / 561 gates-off (up from 676/556 — the five new tests); tools 866 passed with --test-threads=1. The parallel-run failures in `all_tools_executes_*_family_against_fake_backend` are pre-existing shared-port interference, reproduced on the base commit. Co-authored-by: Medulla --- AGENTS.md | 11 +- .../2026-08-02-core-kernel-domain-reorg.md | 15 +- examples/embed_kernel.rs | 81 +++++++++ src/core/all.rs | 114 +++++++++---- src/core/all_tests.rs | 158 ++++++++++++++++++ src/core/jsonrpc.rs | 76 ++++++++- src/core/jsonrpc_tests.rs | 8 + src/core/runtime/builder.rs | 85 ++++++++++ src/core/runtime/context.rs | 21 ++- src/openhuman/tools/ops.rs | 59 ++++++- 10 files changed, 577 insertions(+), 51 deletions(-) create mode 100644 examples/embed_kernel.rs diff --git a/AGENTS.md b/AGENTS.md index 21f0d8b797..0e93a7a51d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -251,7 +251,16 @@ Modules: `all`, `auth`, `cli`, `dispatch`, `event_bus/`, `jsonrpc`, `logging`, ` Two independent runtime axes on `CoreBuilder` (`src/core/runtime/builder.rs`): - **`ServiceSet`** selects which *background services / transports* run (`rpc_http`, `socketio`, `cron`, `channels`, `heartbeat`, …). Presets: `desktop()` / `headless_api()` / `none()`. -- **`DomainSet`** selects which *domain families* exist at runtime, one flag per `DomainGroup` (`src/core/all.rs`). Presets: `full()` (default — byte-identical to before #4796), `harness()` (agent + memory + threads + config + security only), `none()`. Every controller is tagged with its `DomainGroup` at the single registration site in `src/core/all.rs`; the live surface (controllers/`/schema`/dispatch, agent tools, stores, subscribers) is filtered by the ambient `CoreContext::domains()`. A gated domain's controllers become unknown-method, its agent tools absent, its stores/subscribers uninitialized. `examples/embed_headless.rs` uses `DomainSet::harness()`. Per-gate Cargo `[features]` (children #4797–#4804) narrow the compile-time surface further; `DomainSet` is the runtime axis they compose with. +- **`DomainSet`** selects which *domain families* exist at runtime, one flag per `DomainGroup` (`src/core/all.rs`). Presets: `full()` (default — byte-identical to before #4796), `harness()` (agent + memory + threads + config + security only), `none()`. Every controller is tagged with its `DomainGroup` at the single registration site in `src/core/all.rs`; the live surface (controllers/`/schema`/dispatch, agent tools, stores, subscribers) is filtered by the ambient `CoreContext::domains()`. A gated domain's controllers become unknown-method, its agent tools absent, its stores/subscribers uninitialized. `examples/embed_headless.rs` uses `DomainSet::harness()`; `examples/embed_kernel.rs` uses `DomainSet::kernel()` — the floor (threads + config + security, with `agent`/`memory` OFF) that a host opts subsystems back into by field assignment. Per-gate Cargo `[features]` (children #4797–#4804) narrow the compile-time surface further; `DomainSet` is the runtime axis they compose with. + +**`DomainGroup` tracks family directories 1:1.** After the domain reorg (#5328) each variant names a `src/openhuman/` family, so the runtime axis stopped sweeping half the surface into the `Platform` catch-all. Groups: the harness families (`Agent`, `Memory`, `Threads`, `Config`, `Security`), the compile-gate families (`Flows`, `Skills`, `Mcp`, `Meet`, `Channels`, `Web3`, `Voice`, `Media`, `Medulla`), the families carved out of `Platform` (`Inference`, `Integrations`, `Automation` = cron + subconscious, `Runtimes` = runtime + sandbox, `Desktop`, `Hosted`, `Relay` = tinyplace), and `Platform` itself — now only the kernel surfaces with no family of their own (`platform/`, `tools/`, `http_host/`, `test_support/`). + +That realignment fixed two real defects, both pinned by tests in `src/core/all_tests.rs`: + +- `harness()` claimed "agent + memory + threads + config + security" but silently dropped `agent::{agentbox, harness_init, artifacts, learning}`, `security::{credentials, devices}`, `config::{workspace, migration_helpers}`, `memory::people` and `skills::webhooks` into `Platform`. An agent harness that never registers `harness_init` is a latent bug. +- `embedded()` had to set `platform: true` purely to reach credentials and config, which dragged the desktop and hosted-backend surfaces along with it. Those are `Desktop` / `Hosted` now and stay off. + +**Adding a family directory means four edits, all compiler-enforced:** the `DomainGroup` variant (`src/core/all.rs`), the `DomainSet` field + `allows()` arm + every preset (`src/core/runtime/builder.rs`). Two more are *not* compiler-enforced and are the usual source of drift — `tool_group()` in `src/openhuman/tools/ops.rs` (a missing entry leaks a gated tool under a custom `DomainSet`; this is the #4808 review finding) and the `StoreInitPlan` / `DomainSubscriberPlan` keys (`runtime/context.rs`, `core/jsonrpc.rs`). Registering a controller whose store keys on a different group gives you a live RPC surface with no store behind it. ### Compile-time domain gates (Cargo `[features]`) diff --git a/docs/specs/2026-08-02-core-kernel-domain-reorg.md b/docs/specs/2026-08-02-core-kernel-domain-reorg.md index b1436bf5ef..c7e2dfc9b3 100644 --- a/docs/specs/2026-08-02-core-kernel-domain-reorg.md +++ b/docs/specs/2026-08-02-core-kernel-domain-reorg.md @@ -330,8 +330,17 @@ RPCs are unregistered and the `MemoryDiffTool` is absent. 2. Every family directory maps 1:1 to a gate or is declared kernel in this document. 3. `kernel-floor.limits` reaches 222 names / 2 native. 4. Each gate has both-ways tests in `src/core/all_tests.rs` and `tools/ops_tests.rs`. -5. `DomainGroup` gains at most four variants (`Integrations`, `Automation`, `Relay`, `Runtimes`); - the rest stay `Platform` at the runtime axis. `DomainSet::kernel()` exists, with - `examples/embed_kernel.rs`. +5. ✅ **Done.** `DomainGroup` gained seven variants, not four: `Inference`, `Integrations`, + `Automation` (cron + subconscious), `Runtimes` (runtime + sandbox), `Desktop`, `Hosted`, + `Relay` (tinyplace). The extra three over the original estimate are `Inference`, `Desktop` + and `Hosted` — carving those out is what lets `embedded()` stop setting `platform: true` + just to reach credentials and config. `Platform` now holds only `platform/`, `tools/`, + `http_host/`, `test_support/`. `DomainSet::kernel()` and `examples/embed_kernel.rs` exist; + the example runs and demonstrates memory-on / agent-unknown-method. + + The realignment also fixed two defects the flat tree had hidden: `harness()` was dropping + ten namespaces (including `harness_init`) into `Platform` despite claiming their families, + and `StoreInitPlan.people` keyed on `Platform` while its controllers moved to `Memory` — + which would have registered the people RPC surface with no store behind it. 6. Hand off to `kernel.md`'s subsystem registry (`src/core/subsystem/`, `Driver`, `Guard`, `subsystems_status`). diff --git a/examples/embed_kernel.rs b/examples/embed_kernel.rs new file mode 100644 index 0000000000..b36f959cd4 --- /dev/null +++ b/examples/embed_kernel.rs @@ -0,0 +1,81 @@ +//! Embed the OpenHuman core at its **kernel floor**, then opt one subsystem in. +//! +//! [`DomainSet::kernel`] is the smallest useful runtime surface: threads, +//! config, and security — the transport, dispatch, policy and identity a host +//! needs before it has decided what the core is *for*. Notably `agent` and +//! `memory` are OFF: they are the two largest subsystems and the ones an +//! alternative driver would replace, so a host that wants them says so. +//! +//! Contrast the two presets: +//! +//! - [`DomainSet::harness`] — kernel + `agent` + `memory`. The embeddable agent +//! core; see `examples/embed_headless.rs`. +//! - [`DomainSet::kernel`] — the floor. Start here when you want, say, memory +//! without the agent harness, or when you intend to bind your own driver to a +//! subsystem slot. +//! +//! Because `DomainSet` is a plain struct, opting a family back in is a field +//! assignment — no builder ceremony: +//! +//! ```ignore +//! let mut domains = DomainSet::kernel(); +//! domains.memory = true; // kernel + memory, nothing else +//! ``` +//! +//! What "off" means is uniform across every axis: the family's controllers are +//! unregistered (unknown-method over `/rpc`, absent from `/schema`), its agent +//! tools are absent from the tool list rather than present-and-failing, and its +//! stores and event-bus subscribers never initialize. +//! +//! Run with: +//! +//! ```bash +//! GGML_NATIVE=OFF cargo run --example embed_kernel +//! ``` + +use openhuman_core::{CoreBuilder, DomainSet, HostKind, ServiceSet}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Kernel floor plus exactly one subsystem: memory. The agent harness stays + // out, so this process can serve memory reads/writes without ever + // constructing an agent. + let mut domains = DomainSet::kernel(); + domains.memory = true; + + let runtime = CoreBuilder::new(HostKind::Cli) + .domains(domains) + .services(ServiceSet::none()) + .build() + .await?; + + // Always available — `core.*` is kernel transport, not a domain. + let version = runtime + .invoke("core.version", serde_json::json!({})) + .await + .map_err(|e| anyhow::anyhow!("core.version failed: {e}"))?; + println!("core.version -> {version}"); + + // Enabled: memory was opted in above. + match runtime + .invoke("openhuman.memory_list_namespaces", serde_json::json!({})) + .await + { + Ok(v) => println!("memory_list_namespaces -> {v}"), + Err(e) => println!("memory_list_namespaces -> error: {e:?}"), + } + + // Disabled: `agent` is off under kernel(), so this is an UNKNOWN METHOD — + // not a registered handler returning "agent disabled". Absence is the + // contract: a registered-but-failing method teaches a model the capability + // exists and makes it retry. + match runtime + .invoke("openhuman.agent_list_definitions", serde_json::json!({})) + .await + { + Ok(v) => println!("agent_list_definitions -> unexpectedly OK: {v}"), + Err(e) => println!("agent_list_definitions -> unknown method (expected): {e:?}"), + } + + Ok(()) +} diff --git a/src/core/all.rs b/src/core/all.rs index 2535b2fa12..a8a832a525 100644 --- a/src/core/all.rs +++ b/src/core/all.rs @@ -64,6 +64,27 @@ impl RegisteredController { /// per-feature axes the child issues (#4797–#4804) additionally narrow at /// compile time. `Platform` is the catch-all for everything not in a named /// family — always on in `full()`, off in `harness()`/`none()`. +/// +/// **Groups track `src/openhuman/` family directories 1:1.** Before the domain +/// reorg (#5328) they could not: a capability lived across up to 13 sibling +/// top-level dirs, so half the controller surface was tagged `Platform` for want +/// of a family to name. That made two things wrong which are now fixed: +/// +/// - `harness()` claimed "agent + memory + threads + config + security" but +/// silently dropped `agent::{agentbox, harness_init, artifacts, learning}`, +/// `security::{credentials, devices}`, `config::{workspace, migration_helpers}`, +/// `memory::people` and `skills::webhooks`, all of which sat in `Platform`. +/// An agent harness that does not register `harness_init` is a latent bug. +/// - `embedded()` had to set `platform: true` purely to reach credentials and +/// config, which dragged in the desktop and hosted-backend surfaces it has no +/// use for. Those are now `Desktop` and `Hosted` and stay off. +/// +/// `Platform` is now what its name says: the kernel surfaces with no family of +/// their own (`platform/`, `tools/`, `http_host/`, `test_support/`). +/// +/// When adding a family directory, add the matching variant here, a field on +/// [`crate::core::runtime::DomainSet`], an arm in `allows()`, and an entry in +/// each preset — the compiler enforces all four. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DomainGroup { // Harness families — on under `DomainSet::harness()`. @@ -91,6 +112,33 @@ pub enum DomainGroup { /// `medulla` (it folds that domain's envelopes). Splitting them would add /// drift surface for no reachable configuration. Medulla, + // Families carved out of the `Platform` catch-all once the domain reorg + // (#5328) gave each one a directory to be named after. Before that, half the + // controller surface was tagged `Platform` purely because there was no + // family to point at — which made `DomainSet::embedded()` set + // `platform: true` just to reach credentials/config/cron, dragging the + // desktop and hosted-backend surfaces along with it. + /// Model inference: providers, routing, local engines, embeddings, and the + /// token-compression surface (`inference/`). + Inference, + /// External connectors reached on the user's behalf — Composio, calendar, + /// file storage, task sources (`integrations/`). + Integrations, + /// Background initiative: scheduled jobs and the subconscious tick loop + /// (`cron/`, `subconscious/`). Pairs with `ServiceSet::{cron, heartbeat}`. + Automation, + /// Code-execution substrate: the managed Node/Python runtimes, the worker + /// pool, and the sandbox/CWD-jail confinement (`runtime/`, `sandbox/`). + Runtimes, + /// Desktop-shell-facing surfaces a headless or embedded host has no use for + /// (`desktop/`). + Desktop, + /// Clients of the hosted TinyHumans backend — billing, team, referral, + /// announcements, the hosted orchestration brain (`hosted/`). A self-hosted + /// build drops these as a unit. + Hosted, + /// The multi-agent relay surface (`tinyplace/`). + Relay, // Everything not in a named family — always on in `full()`, off otherwise. Platform, } @@ -206,13 +254,13 @@ fn build_registered_controllers() -> Vec { // AgentBox marketplace adapter status push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Agent, crate::openhuman::agent::agentbox::all_agentbox_registered_controllers(), ); // Core application shell state push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Desktop, crate::openhuman::desktop::app_state::all_app_state_registered_controllers(), ); // Audio generation + podcast-style email delivery (gated with voice). @@ -225,20 +273,20 @@ fn build_registered_controllers() -> Vec { // Composio integration controllers push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Integrations, crate::openhuman::integrations::composio::all_composio_registered_controllers(), ); // Recall.ai Calendar V1 (backend-proxied) controllers push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Integrations, crate::openhuman::integrations::recall_calendar::all_recall_calendar_registered_controllers( ), ); // Scheduled job management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Automation, crate::openhuman::cron::all_cron_registered_controllers(), ); // Saved automation workflows (tinyflows graphs): create/get/list/update/delete/run @@ -252,12 +300,12 @@ fn build_registered_controllers() -> Vec { // Proactive task ingestion from external tools (github/notion/linear/clickup) push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Integrations, crate::openhuman::integrations::task_sources::all_task_sources_registered_controllers(), ); push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Desktop, crate::openhuman::desktop::dashboard::all_dashboard_registered_controllers(), ); // MCP client subsystem: Smithery registry browser, local server install/connect, tool dispatch @@ -306,7 +354,7 @@ fn build_registered_controllers() -> Vec { // One-time first-run initialization (Python/spaCy/Node provisioning) push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Agent, crate::openhuman::agent::harness_init::all_harness_init_registered_controllers(), ); // Diagnostic tools @@ -348,13 +396,13 @@ fn build_registered_controllers() -> Vec { // Agent-generated artifact storage, retrieval, and lifecycle management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Agent, crate::openhuman::agent::artifacts::all_artifacts_registered_controllers(), ); // Background heartbeat loop controls push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Automation, crate::openhuman::subconscious::heartbeat::all_heartbeat_registered_controllers(), ); // Ad-hoc static directory HTTP hosting for local file sharing / previews. @@ -409,7 +457,7 @@ fn build_registered_controllers() -> Vec { // User credentials and session management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Security, crate::openhuman::security::credentials::all_credentials_registered_controllers(), ); // Desktop service management @@ -421,43 +469,43 @@ fn build_registered_controllers() -> Vec { // Data migration utilities push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Config, crate::openhuman::config::migration_helpers::all_migration_registered_controllers(), ); // Background command monitors for agent-scoped event sources push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Automation, crate::openhuman::subconscious::monitors::all_monitor_registered_controllers(), ); // Unified inference domain: text / vision / local runtime / cloud providers. // (Formerly split across inference, local AI, and providers modules.) push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Inference, crate::openhuman::inference::all_inference_registered_controllers(), ); push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Inference, crate::openhuman::inference::all_local_inference_registered_controllers(), ); // Embedding provider configuration and embed RPC. push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Inference, crate::openhuman::inference::embeddings::all_embeddings_registered_controllers(), ); // People resolution and interaction scoring push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Memory, crate::openhuman::memory::people::all_people_registered_controllers(), ); // Sandbox execution backends (Docker, local jail, policy, cleanup) push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Runtimes, crate::openhuman::sandbox::all_sandbox_registered_controllers(), ); // Backend Socket.IO bridge + related runtime plumbing @@ -469,7 +517,7 @@ fn build_registered_controllers() -> Vec { // Managed Node.js runtime bridge (tool listing + dispatch) push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Runtimes, crate::openhuman::runtime::javascript::all_javascript_registered_controllers(), ); // Medulla integration: readiness, durable sessions, and the connected worker @@ -504,7 +552,7 @@ fn build_registered_controllers() -> Vec { // User workspace and file management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Config, crate::openhuman::config::workspace::all_workspace_registered_controllers(), ); // Workflow tool registry @@ -577,25 +625,25 @@ fn build_registered_controllers() -> Vec { // Referral and growth tracking push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Hosted, crate::openhuman::hosted::referral::all_referral_registered_controllers(), ); // Billing and subscription management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Hosted, crate::openhuman::hosted::billing::all_billing_registered_controllers(), ); // Announcements surfaced on harness init push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Hosted, crate::openhuman::hosted::announcements::all_announcements_registered_controllers(), ); // Team and role management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Hosted, crate::openhuman::hosted::team::all_team_registered_controllers(), ); // E2E test support — `openhuman.test_reset` wipes sidecar state in-place. @@ -623,7 +671,7 @@ fn build_registered_controllers() -> Vec { // Local assistive surfaces over third-party provider apps push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Desktop, crate::openhuman::desktop::provider_surfaces::all_provider_surfaces_registered_controllers( ), ); @@ -637,19 +685,19 @@ fn build_registered_controllers() -> Vec { // Background awareness and autonomous tasks push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Automation, crate::openhuman::subconscious::all_subconscious_registered_controllers(), ); push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Automation, crate::openhuman::subconscious::triggers::all_subconscious_triggers_registered_controllers( ), ); // Webhook tunnel management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Skills, crate::openhuman::skills::webhooks::all_webhooks_registered_controllers(), ); // Core binary update management @@ -667,7 +715,7 @@ fn build_registered_controllers() -> Vec { // Self-learning and user context enrichment push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Agent, crate::openhuman::agent::learning::all_learning_registered_controllers(), ); // Conversation thread and message management @@ -682,7 +730,7 @@ fn build_registered_controllers() -> Vec { // #4802 listing it under the web3 gate. Flagged for #4802 re-scope. push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Inference, crate::openhuman::inference::tokenjuice::all_tokenjuice_registered_controllers(), ); // Per-thread todo list (agent task board CRUD over RPC) @@ -694,7 +742,7 @@ fn build_registered_controllers() -> Vec { // Integration notification ingest, triage, and per-provider settings push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Desktop, crate::openhuman::desktop::notifications::all_notifications_registered_controllers(), ); // Google Meet call-join request validation (shell handles the webview). @@ -729,7 +777,7 @@ fn build_registered_controllers() -> Vec { // Mobile device pairing and management push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Security, crate::openhuman::security::devices::all_devices_registered_controllers(), ); // Durable agent session database — queryable index over transcripts, lineage, tool calls @@ -796,7 +844,7 @@ fn build_internal_only_controllers() -> Vec { // but NOT advertised to agents in tool listings or schema discovery. push( &mut controllers, - DomainGroup::Platform, + DomainGroup::Relay, crate::openhuman::tinyplace::all_tinyplace_registered_controllers(), ); // User-consented tiny.place pairing for wrapped agent sessions: UI-callable diff --git a/src/core/all_tests.rs b/src/core/all_tests.rs index 175ea38998..4d51622651 100644 --- a/src/core/all_tests.rs +++ b/src/core/all_tests.rs @@ -1258,3 +1258,161 @@ fn medulla_controllers_absent_when_feature_off() { "`medulla` must not register when the feature is off" ); } + +// ---- DomainGroup ↔ family-directory realignment ---------------------------- +// The reorg (#5328) made `src/openhuman/` one directory per family, so the +// runtime axis can finally name each one instead of sweeping half the surface +// into `Platform`. These pin that alignment in both directions. + +/// Every namespace whose family got carved out of `Platform` must now report its +/// own group. Before the realignment each of these answered `Platform`, so a +/// `DomainSet` that disabled the family still served its RPC surface. +#[test] +fn carved_out_families_report_their_own_group() { + let cases: &[(&str, DomainGroup)] = &[ + #[cfg(feature = "flows")] + ("flows", DomainGroup::Flows), + ("cron", DomainGroup::Automation), + ("heartbeat", DomainGroup::Automation), + ("composio", DomainGroup::Integrations), + ("task_sources", DomainGroup::Integrations), + ("billing", DomainGroup::Hosted), + ("team", DomainGroup::Hosted), + ("tinyplace", DomainGroup::Relay), + ("dashboard", DomainGroup::Desktop), + ("notification", DomainGroup::Desktop), + ("sandbox", DomainGroup::Runtimes), + // Mis-tagged before the realignment: these live inside a named family + // directory but answered `Platform`, so `harness()` registered nothing + // for them despite claiming to enable their family. + ("agentbox", DomainGroup::Agent), + ("harness_init", DomainGroup::Agent), + ("ai", DomainGroup::Agent), + ("auth", DomainGroup::Security), + ("devices", DomainGroup::Security), + ("workspace", DomainGroup::Config), + ("people", DomainGroup::Memory), + ]; + for (ns, want) in cases { + match group_for_namespace(ns) { + Some(got) => assert_eq!( + got, *want, + "namespace `{ns}` must be tagged {want:?}, got {got:?} — the DomainGroup \ + tag has drifted from the family directory it lives in" + ), + None => panic!("namespace `{ns}` is not registered; update this test if it moved"), + } + } +} + +/// `Platform` is now only the kernel surfaces with no family of their own. If a +/// namespace from a named family lands here, its `push(...)` tag was missed. +#[test] +fn platform_holds_only_kernel_surfaces() { + let platform: Vec<&str> = registry() + .iter() + .chain(internal_registry().iter()) + .filter(|g| g.group == DomainGroup::Platform) + .map(|g| g.controller.schema.namespace) + .collect(); + // Namespaces legitimately without a family: platform/, tools/, http_host/, + // test_support/. Anything else here is a missed tag. + for ns in &platform { + assert!( + !matches!( + *ns, + "cron" + | "heartbeat" + | "composio" + | "task_sources" + | "billing" + | "team" + | "referral" + | "announcements" + | "tinyplace" + | "dashboard" + | "notification" + | "sandbox" + | "agentbox" + | "harness_init" + | "ai" + | "auth" + | "devices" + | "workspace" + | "people" + ), + "namespace `{ns}` belongs to a named family but is still tagged Platform" + ); + } +} + +/// `harness()` claims agent + memory + threads + config + security. Before the +/// realignment it silently dropped several of their namespaces into `Platform`, +/// most damagingly `harness_init` — an agent harness that never runs harness +/// init. This asserts the claim is now true. +#[test] +fn harness_preset_registers_the_families_it_claims() { + let harness = crate::core::runtime::DomainSet::harness(); + for ns in [ + "agentbox", + "harness_init", + "ai", + "auth", + "devices", + "workspace", + "people", + ] { + let group = + group_for_namespace(ns).unwrap_or_else(|| panic!("namespace `{ns}` is not registered")); + assert!( + harness.allows(group), + "harness() must allow `{ns}` ({group:?}) — it is part of a harness family" + ); + } +} + +/// `kernel()` is the floor: threads/config/security only. It must NOT pull in +/// the two big replaceable subsystems, nor any carved-out family. +#[test] +fn kernel_preset_is_the_floor() { + let k = crate::core::runtime::DomainSet::kernel(); + assert!( + k.threads && k.config && k.security, + "kernel keeps the floor" + ); + assert!( + !k.agent && !k.memory, + "kernel() must not enable agent/memory — a host opts those in explicitly" + ); + for (name, on) in [ + ("inference", k.inference), + ("integrations", k.integrations), + ("automation", k.automation), + ("runtimes", k.runtimes), + ("desktop", k.desktop), + ("hosted", k.hosted), + ("relay", k.relay), + ("platform", k.platform), + ] { + assert!(!on, "kernel() must leave `{name}` off"); + } +} + +/// An embedded host supplies its own UI and never dials the hosted backend. +/// Before the realignment `embedded()` had to set `platform: true` to reach +/// credentials/config, which dragged both surfaces in. +#[test] +fn embedded_preset_excludes_desktop_and_hosted() { + let e = crate::core::runtime::DomainSet::embedded(); + assert!(!e.desktop, "embedded() must not enable desktop surfaces"); + assert!( + !e.hosted, + "embedded() must not enable hosted-backend clients" + ); + assert!(!e.relay, "embedded() must not enable the relay surface"); + // Still needs these: skills run on the managed runtimes, and the session + // loop is driven by cron/heartbeat. + assert!(e.runtimes, "embedded() needs the code-execution runtimes"); + assert!(e.automation, "embedded() needs cron + subconscious"); + assert!(e.inference, "embedded() needs inference"); +} diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index 43dd4ebd98..582061ce7c 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -1892,8 +1892,18 @@ async fn run_server_with_services( /// always registered as core/platform infra and intentionally absent here. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DomainSubscriberPlan { - /// webhook + notification-bridge + composio trigger + task-sources + device-tunnel. + /// Reserved for subscribers with no family of their own. Currently none: + /// the reorg gave every subscriber that used to live here a real family + /// (see the fields below), so this stays for future kernel-level ones. pub platform: bool, + /// composio trigger archive + trigger subscriber + task-sources poller. + pub integrations: bool, + /// device tunnel handshake/peer-status subscriber. + pub security: bool, + /// notification bridge (desktop-shell delivery). + pub desktop: bool, + /// webhook request subscriber (skills own inbound webhook routing). + pub skills: bool, /// channel-inbound + web-only proactive. pub channels: bool, /// flows trigger dispatch. @@ -1914,6 +1924,10 @@ impl DomainSubscriberPlan { use crate::core::all::DomainGroup; Self { platform: domains.allows(DomainGroup::Platform), + integrations: domains.allows(DomainGroup::Integrations), + security: domains.allows(DomainGroup::Security), + desktop: domains.allows(DomainGroup::Desktop), + skills: domains.allows(DomainGroup::Skills), channels: domains.allows(DomainGroup::Channels), flows: domains.allows(DomainGroup::Flows), memory: domains.allows(DomainGroup::Memory), @@ -1977,6 +1991,23 @@ fn register_domain_subscribers( .insert(group) } + /// Learning subscribers need their own idempotency token rather than + /// `group_first_time(DomainGroup::Agent)`: the Agent block below already + /// consumes that token, and whichever ran second would silently skip. + fn learning_first_time() -> bool { + static DONE: OnceLock> = OnceLock::new(); + let mut done = DONE + .get_or_init(|| Mutex::new(false)) + .lock() + .expect("learning-subscriber registry lock poisoned"); + if *done { + false + } else { + *done = true; + true + } + } + // Seed the live tool-execution timeout from the persisted `[agent]` config // so a user-configured value (Settings → Agent OS access → Action timeout) // is in effect from the first tool call. `OPENHUMAN_TOOL_TIMEOUT_SECS`, when @@ -2085,8 +2116,8 @@ fn register_domain_subscribers( // Platform: webhook + notification bridge + composio trigger + task-sources // proactive ingestion + device tunnel. - if plan.platform { - if group_first_time(DomainGroup::Platform) { + if plan.skills { + if group_first_time(DomainGroup::Skills) { if let Some(handle) = crate::core::event_bus::subscribe_global(Arc::new( crate::openhuman::skills::webhooks::bus::WebhookRequestSubscriber::new(), )) { @@ -2096,9 +2127,23 @@ fn register_domain_subscribers( "[event_bus] failed to register webhook subscriber — bus not initialized" ); } + } + } else { + log::debug!("[event_bus] webhook subscriber SKIPPED — Skills domain disabled"); + } + + if plan.desktop { + if group_first_time(DomainGroup::Desktop) { crate::openhuman::desktop::notifications::register_notification_bridge_subscriber( config.clone(), ); + } + } else { + log::debug!("[event_bus] notification bridge SKIPPED — Desktop domain disabled"); + } + + if plan.integrations { + if group_first_time(DomainGroup::Integrations) { if let Err(error) = crate::openhuman::integrations::composio::init_composio_trigger_history( workspace_dir.clone(), @@ -2108,24 +2153,39 @@ fn register_domain_subscribers( } crate::openhuman::integrations::composio::register_composio_trigger_subscriber(); crate::openhuman::integrations::task_sources::bus::register_task_sources_subscriber(); + } + } else { + log::debug!( + "[event_bus] composio + task-sources subscribers SKIPPED — Integrations domain disabled" + ); + } + + if plan.security { + if group_first_time(DomainGroup::Security) { // Device tunnel subscriber: handles tunnel:frame handshakes, // peer-status events, and register acks. Must be live before any // tunnel:frame events can arrive. crate::openhuman::security::devices::bus::register_device_tunnel_subscriber(); + } + } else { + log::debug!("[event_bus] device-tunnel subscriber SKIPPED — Security domain disabled"); + } + + if plan.agent { + if learning_first_time() { // Always-on learning subscribers (email-signature producer, rebuild // trigger + periodic loop, ProfileMdRenderer). Previously wired only // in `channels::runtime::startup::start_channels`, which is skipped // when no channel is configured — silently dropping ALL learning for - // channel-less users (#5003). Registered here on the unconditional - // Platform boot path; idempotent, so it never double-registers. + // channel-less users (#5003). `agent::learning` is an Agent-family + // domain; it sat on the Platform boot path only because `learning` + // used to be a top-level directory. Idempotent. crate::openhuman::agent::learning::startup::register_learning_subscribers( workspace_dir.clone(), ); } } else { - log::debug!( - "[event_bus] Platform subscribers (webhook/notification/composio/task-sources/device-tunnel) SKIPPED — Platform domain disabled" - ); + log::debug!("[event_bus] learning subscribers SKIPPED — Agent domain disabled"); } // Channels: inbound dispatch + web-only proactive messaging. diff --git a/src/core/jsonrpc_tests.rs b/src/core/jsonrpc_tests.rs index 99b944d13b..7caf265c66 100644 --- a/src/core/jsonrpc_tests.rs +++ b/src/core/jsonrpc_tests.rs @@ -31,6 +31,10 @@ fn domain_subscriber_plan_full_registers_every_gated_subscriber() { plan, DomainSubscriberPlan { platform: true, + integrations: true, + security: true, + desktop: true, + skills: true, channels: true, flows: true, memory: true, @@ -49,6 +53,10 @@ fn domain_subscriber_plan_none_registers_no_gated_subscriber() { plan, DomainSubscriberPlan { platform: false, + integrations: false, + security: false, + desktop: false, + skills: false, channels: false, flows: false, memory: false, diff --git a/src/core/runtime/builder.rs b/src/core/runtime/builder.rs index 931aa47abd..0f9b622c1f 100644 --- a/src/core/runtime/builder.rs +++ b/src/core/runtime/builder.rs @@ -209,6 +209,20 @@ pub struct DomainSet { /// authored harness workflows. pub medulla: bool, /// Everything not in a named family — always on in `full()`. + /// Model inference: providers, routing, local engines, embeddings. + pub inference: bool, + /// External connectors (Composio, calendar, file storage, task sources). + pub integrations: bool, + /// Background initiative: cron + the subconscious tick loop. + pub automation: bool, + /// Code-execution substrate: Node/Python runtimes, pool, sandbox. + pub runtimes: bool, + /// Desktop-shell-facing surfaces. + pub desktop: bool, + /// Clients of the hosted TinyHumans backend. + pub hosted: bool, + /// The multi-agent relay surface (tinyplace). + pub relay: bool, pub platform: bool, } @@ -231,6 +245,13 @@ impl DomainSet { voice: true, media: true, medulla: true, + inference: true, + integrations: true, + automation: true, + runtimes: true, + desktop: true, + hosted: true, + relay: true, platform: true, } } @@ -254,6 +275,13 @@ impl DomainSet { voice: false, media: false, medulla: false, + inference: false, + integrations: false, + automation: false, + runtimes: false, + desktop: false, + hosted: false, + relay: false, platform: false, } } @@ -294,10 +322,53 @@ impl DomainSet { voice: false, media: false, medulla: true, + inference: true, + integrations: false, + automation: true, + runtimes: true, + desktop: false, + hosted: false, + relay: false, platform: true, } } + /// The kernel floor: threads, config, security — and nothing else. + /// + /// Distinct from [`DomainSet::none`], which is "no domains at all". This is + /// "the minimum a host needs before opting a subsystem back in", so an + /// embedder can request kernel + exactly one family. `agent` and `memory` + /// are OFF on purpose: they are the two largest subsystems and the ones an + /// alternative driver would replace, so a host that wants them says so. + /// + /// See `examples/embed_kernel.rs`. + pub fn kernel() -> Self { + Self { + agent: false, + memory: false, + threads: true, + config: true, + security: true, + flows: false, + skills: false, + mcp: false, + meet: false, + channels: false, + web3: false, + voice: false, + media: false, + medulla: false, + inference: false, + integrations: false, + automation: false, + runtimes: false, + desktop: false, + hosted: false, + relay: false, + platform: false, + } + } + /// Nothing on — every family disabled. pub fn none() -> Self { Self { @@ -315,6 +386,13 @@ impl DomainSet { voice: false, media: false, medulla: false, + inference: false, + integrations: false, + automation: false, + runtimes: false, + desktop: false, + hosted: false, + relay: false, platform: false, } } @@ -336,6 +414,13 @@ impl DomainSet { DomainGroup::Voice => self.voice, DomainGroup::Media => self.media, DomainGroup::Medulla => self.medulla, + DomainGroup::Inference => self.inference, + DomainGroup::Integrations => self.integrations, + DomainGroup::Automation => self.automation, + DomainGroup::Runtimes => self.runtimes, + DomainGroup::Desktop => self.desktop, + DomainGroup::Hosted => self.hosted, + DomainGroup::Relay => self.relay, DomainGroup::Platform => self.platform, } } diff --git a/src/core/runtime/context.rs b/src/core/runtime/context.rs index 4da37319c3..731189732b 100644 --- a/src/core/runtime/context.rs +++ b/src/core/runtime/context.rs @@ -309,7 +309,12 @@ pub struct StoreInitPlan { pub memory: bool, /// `agent::multimodal` attachments sidecar dir — gated on [`DomainGroup::Agent`]. pub agent_attachments: bool, - /// `people::store` — gated on [`DomainGroup::Platform`]. + /// `memory::people::store` — gated on [`DomainGroup::Memory`]. + /// + /// Was `Platform` while `people` was a top-level domain. The reorg moved it + /// to `memory/people` and its controllers are tagged `Memory`; leaving the + /// store on `Platform` would register those controllers under `harness()` + /// with no store behind them. pub people: bool, /// legacy-workflow prune under `skills::registry` — gated on [`DomainGroup::Skills`]. pub skills_prune: bool, @@ -322,7 +327,7 @@ impl StoreInitPlan { Self { memory: domains.allows(DomainGroup::Memory), agent_attachments: domains.allows(DomainGroup::Agent), - people: domains.allows(DomainGroup::Platform), + people: domains.allows(DomainGroup::Memory), skills_prune: domains.allows(DomainGroup::Skills), } } @@ -482,8 +487,16 @@ mod tests { plan.agent_attachments, "harness keeps agent attachments sidecar (Agent)" ); - // Platform / Skills are NOT in harness → their stores stay off. - assert!(!plan.people, "harness must skip people::store (Platform)"); + // `people` moved to `memory/people` in the domain reorg (#5328) and its + // controllers are tagged `Memory`, so harness — which enables Memory — + // must now initialize its store too. Before the realignment it keyed on + // `Platform`, which meant harness registered the people controllers with + // no store behind them. + assert!( + plan.people, + "harness keeps memory::people::store (Memory) — it moved under memory/" + ); + // Skills is NOT in harness → its store work stays off. assert!( !plan.skills_prune, "harness must skip skills legacy-prune (Skills)" diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index 55735bc725..356e73f240 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -1359,6 +1359,14 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { "audio_generate_and_email_podcast", ]; // Threads: thread_* / todo_* handled by prefix below; these are the extras. + // Subconscious monitor + proactive-notify tools (Automation family). + const MONITORS: &[&str] = &[ + "monitor", + "monitor_list", + "monitor_read", + "monitor_stop", + "notify_user", + ]; const THREADS_EXTRA: &[&str] = &["transcript_search", "goal_get", "goal_set", "goal_complete"]; // Memory extras not covered by the `memory_`/`goals_` prefixes. const MEMORY_EXTRA: &[&str] = &[ @@ -1414,8 +1422,55 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { if name.starts_with("thread_") || name.starts_with("todo_") || THREADS_EXTRA.contains(&name) { return DomainGroup::Threads; } - // Everything else — shell/file/config/security/agent/billing/… — is - // Platform: present under full(), absent under harness()/none(). + // ── Families carved out of Platform by the DomainGroup realignment ────── + // Each of these previously fell through to Platform, which meant the tool + // stayed callable when its family was gated off under a custom DomainSet — + // the leak the #4808 review flagged for whatsapp_data. Keep these in + // lockstep with the `push(...)` tags in `core::all`. + // + // Automation: scheduled jobs (`cron_*`) plus the subconscious monitor + + // proactive-notify surface. + if name.starts_with("cron_") || MONITORS.contains(&name) { + return DomainGroup::Automation; + } + // Integrations: every external connector reached on the user's behalf. + if name.starts_with("composio") + || name.starts_with("apify_") + || name.starts_with("google_places_") + || name.starts_with("stock_") + || name.starts_with("storage_") + || name.starts_with("task_source_") + || name == "twilio_call" + { + return DomainGroup::Integrations; + } + // Hosted: clients of the TinyHumans backend. + if name.starts_with("billing_") + || name.starts_with("referral_") + || name.starts_with("team_") + || name.starts_with("orchestration_") + { + return DomainGroup::Hosted; + } + // Relay: the multi-agent relay surface. + if name.starts_with("tinyplace_") { + return DomainGroup::Relay; + } + // Desktop: shell-facing surfaces. + if name.starts_with("dashboard_") { + return DomainGroup::Desktop; + } + // Runtimes: the managed Node/Python execution tools. These live under + // `tools/impl/system/` rather than `runtime/`, so they are matched by name. + if name == "node_exec" || name == "npm_exec" || name == "python_exec" { + return DomainGroup::Runtimes; + } + // Inference: the token-compression retrieval surface. + if name.starts_with("tokenjuice_") { + return DomainGroup::Inference; + } + // Everything else — shell/file/config/security/agent/… — is Platform: + // present under full(), absent under harness()/none(). DomainGroup::Platform } From e481fd0eb58c8c1cffa89d34bc76df853ef9c033 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 14:19:37 +0300 Subject: [PATCH 02/11] fix(core): enable integrations in embedded preset and reclassify hosted controllers The embedded runtime preset now enables the integrations domain so external connectors are available in long-lived embedded hosts, and the embedded preset test asserts this. The hosted orchestration controllers are reclassified from the Agent domain group to Hosted, and the TokenJuice debug controllers are clarified as inference-gated while the content-router subscriber remains always-on core infrastructure. The people store skip log now correctly references the Memory domain instead of Platform. --- src/core/all.rs | 9 +++++---- src/core/all_tests.rs | 1 + src/core/runtime/builder.rs | 9 +++++---- src/core/runtime/context.rs | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/core/all.rs b/src/core/all.rs index a8a832a525..7ec54dabb4 100644 --- a/src/core/all.rs +++ b/src/core/all.rs @@ -725,9 +725,10 @@ fn build_registered_controllers() -> Vec { crate::openhuman::threads::all_threads_registered_controllers(), ); // TokenJuice content-router debug controllers (detect / compress / cache_stats / retrieve). - // Classified Platform (always-on): TokenJuice is the token-compression content - // router that runs on every agent tool output, not a crypto surface — despite - // #4802 listing it under the web3 gate. Flagged for #4802 re-scope. + // Classified Inference: TokenJuice is the token-compression content router, + // not a crypto surface — despite #4802 listing it under the web3 gate. + // Only these debug/inspection controllers are gated; the content-router + // subscriber used on agent tool output remains always-on core infra. push( &mut controllers, DomainGroup::Inference, @@ -859,7 +860,7 @@ fn build_internal_only_controllers() -> Vec { // Renderer-only — not advertised to agents. push( &mut controllers, - DomainGroup::Agent, + DomainGroup::Hosted, crate::openhuman::hosted::orchestration::all_registered_controllers(), ); controllers diff --git a/src/core/all_tests.rs b/src/core/all_tests.rs index 4d51622651..48ee5f0452 100644 --- a/src/core/all_tests.rs +++ b/src/core/all_tests.rs @@ -1415,4 +1415,5 @@ fn embedded_preset_excludes_desktop_and_hosted() { assert!(e.runtimes, "embedded() needs the code-execution runtimes"); assert!(e.automation, "embedded() needs cron + subconscious"); assert!(e.inference, "embedded() needs inference"); + assert!(e.integrations, "embedded() needs external integrations"); } diff --git a/src/core/runtime/builder.rs b/src/core/runtime/builder.rs index 0f9b622c1f..60ff56585c 100644 --- a/src/core/runtime/builder.rs +++ b/src/core/runtime/builder.rs @@ -208,7 +208,6 @@ pub struct DomainSet { /// Medulla integration: cloud client, session runtime, chat store, and /// authored harness workflows. pub medulla: bool, - /// Everything not in a named family — always on in `full()`. /// Model inference: providers, routing, local engines, embeddings. pub inference: bool, /// External connectors (Composio, calendar, file storage, task sources). @@ -223,6 +222,7 @@ pub struct DomainSet { pub hosted: bool, /// The multi-agent relay surface (tinyplace). pub relay: bool, + /// Everything not in a named family — always on in `full()`. pub platform: bool, } @@ -287,8 +287,8 @@ impl DomainSet { } /// A long-lived embedded host: the harness core plus the Medulla - /// integration and the workflow engine it runs on, and `platform` for the - /// credentials / config / cron / task-source domains such a session needs. + /// integration and the workflow engine it runs on, and the supporting + /// runtime, automation, integration, and platform surfaces it needs. /// /// Named for the *shape* rather than any downstream consumer — the core /// does not know which host embeds it, and a preset naming one would invert @@ -323,7 +323,7 @@ impl DomainSet { media: false, medulla: true, inference: true, - integrations: false, + integrations: true, automation: true, runtimes: true, desktop: false, @@ -839,6 +839,7 @@ mod tests { DomainGroup::Voice, DomainGroup::Media, DomainGroup::Medulla, + DomainGroup::Integrations, DomainGroup::Platform, ] { assert!(full.allows(group), "full() must allow {group:?}"); diff --git a/src/core/runtime/context.rs b/src/core/runtime/context.rs index 731189732b..5931498aa0 100644 --- a/src/core/runtime/context.rs +++ b/src/core/runtime/context.rs @@ -396,7 +396,7 @@ pub async fn init_stores( Err(e) => log::warn!("[boot] people::store init failed: {e}"), } } else { - log::debug!("[boot] people::store init SKIPPED — Platform domain disabled"); + log::debug!("[boot] people::store init SKIPPED — Memory domain disabled"); } // Prune legacy bundled skills (dev-workflow / github-issue-crusher // / pr-review-shepherd) that older builds seeded into From 3ad5ca92c30dd95256ece3631f655a3a4da04c83 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 14:19:54 +0300 Subject: [PATCH 03/11] feat(jsonrpc): separate hosted orchestration ingest from agent domain Move the hosted orchestration ingest subscriber registration out of the agent domain group into its own dedicated hosted flag, so that the tiny.place harness session DM ingestion can be enabled or disabled independently of the agent handlers and background delivery. --- src/core/jsonrpc.rs | 24 +++++++++++++++++------- src/core/jsonrpc_tests.rs | 5 ++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index 582061ce7c..61b4536879 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -1914,6 +1914,8 @@ pub struct DomainSubscriberPlan { pub meet: bool, /// agent handlers + background delivery + run-ledger finalizer + orchestration ingest. pub agent: bool, + /// hosted orchestration ingest. + pub hosted: bool, /// mcp::registry lifecycle bus init. pub mcp: bool, } @@ -1933,6 +1935,7 @@ impl DomainSubscriberPlan { memory: domains.allows(DomainGroup::Memory), meet: domains.allows(DomainGroup::Meet), agent: domains.allows(DomainGroup::Agent), + hosted: domains.allows(DomainGroup::Hosted), mcp: domains.allows(DomainGroup::Mcp), } } @@ -2114,8 +2117,8 @@ fn register_domain_subscribers( // ---- Gated domain subscribers — each group installed at most once, the // first time its owning DomainGroup is enabled. ------------------------- - // Platform: webhook + notification bridge + composio trigger + task-sources - // proactive ingestion + device tunnel. + // Carved-out families: webhook (Skills), notification bridge (Desktop), + // composio + task-sources (Integrations), and device tunnel (Security). if plan.skills { if group_first_time(DomainGroup::Skills) { if let Some(handle) = crate::core::event_bus::subscribe_global(Arc::new( @@ -2312,12 +2315,19 @@ fn register_domain_subscribers( log::debug!("[event_bus] agent_meetings subscribers SKIPPED — Meet domain disabled"); } - // Agent: orchestration ingest + native agent handlers + background-completion - // delivery + run-ledger finalizer. + // Hosted: ingest tiny.place harness session DMs off the stream bus. + if plan.hosted { + if group_first_time(DomainGroup::Hosted) { + crate::openhuman::hosted::orchestration::register_orchestration_ingest_subscriber(); + } + } else { + log::debug!("[event_bus] orchestration ingest SKIPPED — Hosted domain disabled"); + } + + // Agent: native agent handlers + background-completion delivery + + // run-ledger finalizer. if plan.agent { if group_first_time(DomainGroup::Agent) { - // Orchestration: ingest tiny.place harness session DMs off the stream bus. - crate::openhuman::hosted::orchestration::register_orchestration_ingest_subscriber(); // Native request handlers — the agent `agent.run_turn` handler is // what channel dispatch calls instead of importing // `run_tool_call_loop` directly. @@ -2336,7 +2346,7 @@ fn register_domain_subscribers( } } else { log::debug!( - "[event_bus] agent handlers + background delivery + run-ledger finalizer + orchestration ingest SKIPPED — Agent domain disabled" + "[event_bus] agent handlers + background delivery + run-ledger finalizer SKIPPED — Agent domain disabled" ); } diff --git a/src/core/jsonrpc_tests.rs b/src/core/jsonrpc_tests.rs index 7caf265c66..117855b72b 100644 --- a/src/core/jsonrpc_tests.rs +++ b/src/core/jsonrpc_tests.rs @@ -40,6 +40,7 @@ fn domain_subscriber_plan_full_registers_every_gated_subscriber() { memory: true, meet: true, agent: true, + hosted: true, mcp: true, }, "full() must register every gated domain subscriber" @@ -62,6 +63,7 @@ fn domain_subscriber_plan_none_registers_no_gated_subscriber() { memory: false, meet: false, agent: false, + hosted: false, mcp: false, }, "none() must register no gated domain subscriber (core infra still runs, ungated)" @@ -74,7 +76,7 @@ fn domain_subscriber_plan_harness_gates_by_owning_group() { // harness() = agent + memory + threads + config + security. assert!( plan.agent, - "harness keeps agent + orchestration subscribers" + "harness keeps agent subscribers" ); assert!( plan.memory, @@ -91,6 +93,7 @@ fn domain_subscriber_plan_harness_gates_by_owning_group() { ); assert!(!plan.flows, "harness must skip flows trigger dispatch"); assert!(!plan.meet, "harness must skip agent_meetings subscribers"); + assert!(!plan.hosted, "harness must skip hosted orchestration ingest"); assert!(!plan.mcp, "harness must skip mcp_registry bus init"); } From 53af86025cac3ddb7deeeff08cdcab18ec136c9b Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 14:20:13 +0300 Subject: [PATCH 04/11] fix(ops): classify agent, config, and security tools under harness Realign tool grouping so artifact, learning, subagent, config, workspace, security, and credential tools are assigned to their respective domain families instead of defaulting to Platform. This keeps them available under the harness runtime while generic Platform tools continue to drop. --- src/openhuman/tools/ops.rs | 33 ++++++++++++++++++++++++-------- src/openhuman/tools/ops_tests.rs | 12 ++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index 356e73f240..9b57d79b64 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -1271,13 +1271,9 @@ pub fn all_tools_with_runtime( /// by its `name()`, so [`all_tools_with_runtime`] can drop tools whose family is /// disabled under the ambient [`DomainSet`](crate::core::runtime::DomainSet). /// -/// Only the gate families (Web3/Mcp/Skills/Flows/Media/Voice/Meet) and the two -/// mapped harness families (Memory/Threads) are matched; **everything else -/// defaults to `Platform`**. Consequence under `harness()` (platform off): the -/// gate-family tools drop AND the generic Platform tools (shell/file/grep/edit/ -/// screen/billing/team/cron/config/security/agent-orchestration/…) drop too — -/// only memory + thread/todo tools remain. This is the strict #4796 harness -/// surface; an embedder that wants a broader tool set can widen its DomainSet. +/// Named-family tools are matched here; everything without a domain family +/// defaults to `Platform`. Under `harness()`, the Agent/Memory/Threads/Config/ +/// Security tools remain while gate-family and generic Platform tools drop. /// (Names verified against each Tool impl's `fn name()` on 2026-07-13.) fn tool_group(name: &str) -> crate::core::all::DomainGroup { use crate::core::all::DomainGroup; @@ -1422,6 +1418,27 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { if name.starts_with("thread_") || name.starts_with("todo_") || THREADS_EXTRA.contains(&name) { return DomainGroup::Threads; } + // Harness families realigned out of Platform. + if name.starts_with("artifact_") + || name.starts_with("learning_") + || name.contains("subagent") + || matches!( + name, + "agent_prepare_context" + | "delegate_graph" + | "delegate_to_personality" + | "request_plan_review" + | "plan_exit" + ) + { + return DomainGroup::Agent; + } + if name.starts_with("config_") || name.starts_with("workspace_") { + return DomainGroup::Config; + } + if name.starts_with("security_") || name.starts_with("credential_") { + return DomainGroup::Security; + } // ── Families carved out of Platform by the DomainGroup realignment ────── // Each of these previously fell through to Platform, which meant the tool // stayed callable when its family was gated off under a custom DomainSet — @@ -1469,7 +1486,7 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { if name.starts_with("tokenjuice_") { return DomainGroup::Inference; } - // Everything else — shell/file/config/security/agent/… — is Platform: + // Everything else — shell/file and other kernel utilities — is Platform: // present under full(), absent under harness()/none(). DomainGroup::Platform } diff --git a/src/openhuman/tools/ops_tests.rs b/src/openhuman/tools/ops_tests.rs index 4cc9035e32..006ff20fa7 100644 --- a/src/openhuman/tools/ops_tests.rs +++ b/src/openhuman/tools/ops_tests.rs @@ -2563,12 +2563,17 @@ fn tool_group_classifies_gate_and_harness_families() { assert_eq!(tool_group("thread_list"), DomainGroup::Threads); assert_eq!(tool_group("todo_add"), DomainGroup::Threads); assert_eq!(tool_group("goal_get"), DomainGroup::Threads); + assert_eq!(tool_group("artifact_list"), DomainGroup::Agent); + assert_eq!(tool_group("learning_list_facets"), DomainGroup::Agent); + assert_eq!(tool_group("spawn_subagent"), DomainGroup::Agent); + assert_eq!(tool_group("config_snapshot"), DomainGroup::Config); + assert_eq!(tool_group("workspace_init"), DomainGroup::Config); + assert_eq!(tool_group("security_policy_info"), DomainGroup::Security); + assert_eq!(tool_group("credential_list"), DomainGroup::Security); // Everything else → Platform (dropped under harness()). assert_eq!(tool_group("shell"), DomainGroup::Platform); assert_eq!(tool_group("file_read"), DomainGroup::Platform); - assert_eq!(tool_group("config_snapshot"), DomainGroup::Platform); - assert_eq!(tool_group("spawn_subagent"), DomainGroup::Platform); } #[test] @@ -2584,6 +2589,9 @@ fn tool_group_gate_families_dropped_under_harness_not_full() { // Harness keeps memory/threads, drops gate families AND platform. assert!(harness.allows(tool_group("memory_store"))); assert!(harness.allows(tool_group("thread_list"))); + assert!(harness.allows(tool_group("artifact_list"))); + assert!(harness.allows(tool_group("config_snapshot"))); + assert!(harness.allows(tool_group("security_policy_info"))); assert!(!harness.allows(tool_group("wallet_status"))); assert!(!harness.allows(tool_group("run_workflow"))); assert!(!harness.allows(tool_group("shell"))); From a1a08662e57f7fd54696ab785e07f9a6def9822c Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 14:26:38 +0300 Subject: [PATCH 05/11] chore(jsonrpc_tests): reformat assertions for consistency Reformatted the assertion for the harness plan's hosted subscriber to match the multi-line style used by other assertions in the test, improving readability and consistency. --- src/core/jsonrpc_tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/jsonrpc_tests.rs b/src/core/jsonrpc_tests.rs index 117855b72b..9672129ab4 100644 --- a/src/core/jsonrpc_tests.rs +++ b/src/core/jsonrpc_tests.rs @@ -74,10 +74,7 @@ fn domain_subscriber_plan_none_registers_no_gated_subscriber() { fn domain_subscriber_plan_harness_gates_by_owning_group() { let plan = DomainSubscriberPlan::for_domains(crate::core::runtime::DomainSet::harness()); // harness() = agent + memory + threads + config + security. - assert!( - plan.agent, - "harness keeps agent subscribers" - ); + assert!(plan.agent, "harness keeps agent subscribers"); assert!( plan.memory, "harness keeps memory conversation-persistence + sync bridge" @@ -93,7 +90,10 @@ fn domain_subscriber_plan_harness_gates_by_owning_group() { ); assert!(!plan.flows, "harness must skip flows trigger dispatch"); assert!(!plan.meet, "harness must skip agent_meetings subscribers"); - assert!(!plan.hosted, "harness must skip hosted orchestration ingest"); + assert!( + !plan.hosted, + "harness must skip hosted orchestration ingest" + ); assert!(!plan.mcp, "harness must skip mcp_registry bus init"); } From d3265230b72dbbe05c534122c57b1b3b1b85d4cd Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 14:43:42 +0300 Subject: [PATCH 06/11] fix(test): clarify embedded preset guard and cover integrations The test comment now explains that the embedded preset differs from harness by leaving the Platform, Channels, and Integrations families enabled, rather than listing specific dropped components. The assertions are extended to verify that Integrations is also excluded from harness and included in embedded, closing a gap in the guard against future simplification. --- src/core/runtime/builder.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/runtime/builder.rs b/src/core/runtime/builder.rs index 60ff56585c..312af93c5f 100644 --- a/src/core/runtime/builder.rs +++ b/src/core/runtime/builder.rs @@ -945,8 +945,8 @@ mod tests { #[test] fn embedded_is_not_harness_plus_medulla() { // Guards the most tempting future "simplification": deriving this - // preset from harness(). harness() sets platform:false, which drops - // credentials/config/cron/task_sources/todos. + // preset from harness(), which leaves the supporting Platform, + // Channels, and Integrations families off. let harness = DomainSet::harness(); let tui = DomainSet::embedded(); @@ -954,6 +954,8 @@ mod tests { assert!(tui.allows(DomainGroup::Platform)); assert!(!harness.allows(DomainGroup::Channels)); assert!(tui.allows(DomainGroup::Channels)); + assert!(!harness.allows(DomainGroup::Integrations)); + assert!(tui.allows(DomainGroup::Integrations)); } #[test] From be419048330af7c546890365d1d49d791d7db8d8 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 15:10:15 +0300 Subject: [PATCH 07/11] fix(boot): gate agent bootstrap and tool grouping on domain enablement Agent-related startup steps (file-state coordinator, orphaned run settlement, task reconciliation, and definition registry) now run only when the Agent domain is enabled, with debug logs when skipped. Tool-to-domain classification was also corrected so agent workflow tools (ask_user_clarification, delegate, todo, wait, etc.) map to Agent, people_* tools to Memory, and session_*/oauth_* tools to Security, with tests covering the new mappings. --- src/core/jsonrpc.rs | 41 +++++++++++++++++++++----------- src/openhuman/tools/ops.rs | 17 +++++++++++-- src/openhuman/tools/ops_tests.rs | 13 ++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index 61b4536879..9bb64ea996 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -2395,7 +2395,12 @@ pub async fn bootstrap_core_runtime( // --- Event bus bootstrap --- // Ensure the global event bus is initialized (no-op if already done by start_channels). crate::core::event_bus::init_global(crate::core::event_bus::DEFAULT_CAPACITY); - crate::openhuman::agent::file_state::init_global(); + let agent_enabled = domains.allows(crate::core::all::DomainGroup::Agent); + if agent_enabled { + crate::openhuman::agent::file_state::init_global(); + } else { + log::debug!("[boot] agent file-state coordinator SKIPPED — Agent domain disabled"); + } // Register domain subscribers for cross-module event handling. Ungated infra // runs once (INFRA: Once) and each DomainGroup installs at most once via the // per-group `group_first_time` set, so repeated calls to @@ -2430,11 +2435,12 @@ pub async fn bootstrap_core_runtime( // at boot is orphaned — its driver died without firing a terminal event, so // the finalizer never settled it. Stamp such rows `interrupted` so they stop // rendering as perpetual "running" timeline entries on thread reopen. - match crate::openhuman::agent::session_db::run_ledger::interrupt_orphaned_agent_runs(&cfg) { - Ok(0) => {} - Ok(count) => log::info!("[runtime] settled {count} orphaned agent run(s) on startup"), - Err(err) => log::warn!("[runtime] failed to settle orphaned agent runs: {err}"), - } + if agent_enabled { + match crate::openhuman::agent::session_db::run_ledger::interrupt_orphaned_agent_runs(&cfg) { + Ok(0) => {} + Ok(count) => log::info!("[runtime] settled {count} orphaned agent run(s) on startup"), + Err(err) => log::warn!("[runtime] failed to settle orphaned agent runs: {err}"), + } // --- Detached sub-agent TaskStore reconciliation ------------------- // The durable orchestration TaskStore (`/.openhuman/ @@ -2444,7 +2450,6 @@ pub async fn bootstrap_core_runtime( // re-attached. Reconcile each orphan to a terminal state and emit the typed // terminal lifecycle event so the run ledger finalizes. Best-effort and // non-fatal (issue #4249 / 07.2 steps 2 & 4). - { let reconciled = crate::openhuman::agent::orchestration::running_subagents::reconcile_orphaned_tasks_on_boot( &workspace_dir, @@ -2454,6 +2459,10 @@ pub async fn bootstrap_core_runtime( "[runtime] reconciled {reconciled} orphaned detached sub-agent task(s) on startup" ); } + } else { + log::debug!( + "[boot] agent run-ledger + orchestration task reconciliation SKIPPED — Agent domain disabled" + ); } // --- Cost dashboard tracker --- @@ -2479,13 +2488,17 @@ pub async fn bootstrap_core_runtime( // Loads built-in archetype definitions plus any custom TOML files // under `/agents/*.toml`. Idempotent — safe to call // multiple times. Uses the per-user scoped workspace_dir. - if let Err(err) = - crate::openhuman::agent::harness::AgentDefinitionRegistry::init_global(&workspace_dir) - { - log::warn!( - "[runtime] AgentDefinitionRegistry::init_global failed: {err} — \ - spawn_subagent will be unavailable until restart" - ); + if agent_enabled { + if let Err(err) = + crate::openhuman::agent::harness::AgentDefinitionRegistry::init_global(&workspace_dir) + { + log::warn!( + "[runtime] AgentDefinitionRegistry::init_global failed: {err} — \ + spawn_subagent will be unavailable until restart" + ); + } + } else { + log::debug!("[boot] agent definition registry SKIPPED — Agent domain disabled"); } // --- Agent sandbox + projects dirs --- diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index 9b57d79b64..1594b9e86c 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -1424,9 +1424,15 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { || name.contains("subagent") || matches!( name, - "agent_prepare_context" + "ask_user_clarification" + | "agent_prepare_context" + | "delegate" | "delegate_graph" | "delegate_to_personality" + | "todo" + | "update_task" + | "wait" + | "wait_loop" | "request_plan_review" | "plan_exit" ) @@ -1436,7 +1442,14 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { if name.starts_with("config_") || name.starts_with("workspace_") { return DomainGroup::Config; } - if name.starts_with("security_") || name.starts_with("credential_") { + if name.starts_with("people_") { + return DomainGroup::Memory; + } + if name.starts_with("security_") + || name.starts_with("credential_") + || name.starts_with("session_") + || name.starts_with("oauth_") + { return DomainGroup::Security; } // ── Families carved out of Platform by the DomainGroup realignment ────── diff --git a/src/openhuman/tools/ops_tests.rs b/src/openhuman/tools/ops_tests.rs index 006ff20fa7..874a228c0e 100644 --- a/src/openhuman/tools/ops_tests.rs +++ b/src/openhuman/tools/ops_tests.rs @@ -2566,10 +2566,23 @@ fn tool_group_classifies_gate_and_harness_families() { assert_eq!(tool_group("artifact_list"), DomainGroup::Agent); assert_eq!(tool_group("learning_list_facets"), DomainGroup::Agent); assert_eq!(tool_group("spawn_subagent"), DomainGroup::Agent); + for name in [ + "ask_user_clarification", + "wait", + "wait_loop", + "delegate", + "todo", + "update_task", + ] { + assert_eq!(tool_group(name), DomainGroup::Agent); + } + assert_eq!(tool_group("people_list"), DomainGroup::Memory); assert_eq!(tool_group("config_snapshot"), DomainGroup::Config); assert_eq!(tool_group("workspace_init"), DomainGroup::Config); assert_eq!(tool_group("security_policy_info"), DomainGroup::Security); assert_eq!(tool_group("credential_list"), DomainGroup::Security); + assert_eq!(tool_group("session_state"), DomainGroup::Security); + assert_eq!(tool_group("oauth_list"), DomainGroup::Security); // Everything else → Platform (dropped under harness()). assert_eq!(tool_group("shell"), DomainGroup::Platform); From c0233046dfcb7e45ce64901e4e6ea3ce8560f9b6 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 15:15:27 +0300 Subject: [PATCH 08/11] chore(jsonrpc): fix comment indentation The comment block describing detached sub-agent TaskStore reconciliation was indented inconsistently with the surrounding code. This change aligns the comment indentation with the enclosing block for readability, with no behavioral impact. --- src/core/jsonrpc.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index 9bb64ea996..770889d410 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -2442,14 +2442,14 @@ pub async fn bootstrap_core_runtime( Err(err) => log::warn!("[runtime] failed to settle orphaned agent runs: {err}"), } - // --- Detached sub-agent TaskStore reconciliation ------------------- - // The durable orchestration TaskStore (`/.openhuman/ - // orchestration_tasks.jsonl`) can hold non-terminal sub-agent records left - // by a previous process — their detached executor (abort handle + - // cooperative CancellationToken) died with that process, so they cannot be - // re-attached. Reconcile each orphan to a terminal state and emit the typed - // terminal lifecycle event so the run ledger finalizes. Best-effort and - // non-fatal (issue #4249 / 07.2 steps 2 & 4). + // --- Detached sub-agent TaskStore reconciliation ------------------- + // The durable orchestration TaskStore (`/.openhuman/ + // orchestration_tasks.jsonl`) can hold non-terminal sub-agent records left + // by a previous process — their detached executor (abort handle + + // cooperative CancellationToken) died with that process, so they cannot be + // re-attached. Reconcile each orphan to a terminal state and emit the typed + // terminal lifecycle event so the run ledger finalizes. Best-effort and + // non-fatal (issue #4249 / 07.2 steps 2 & 4). let reconciled = crate::openhuman::agent::orchestration::running_subagents::reconcile_orphaned_tasks_on_boot( &workspace_dir, From 05aacc5cfeb67ef97be3eca8be7926075bd5b08b Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 15:31:53 +0300 Subject: [PATCH 09/11] fix(tools): classify new tools into correct domain groups The tool group classifier was missing several recently added tools, causing them to fall through to the Platform group instead of their intended domains. Added spawn_parallel_agents to the Agent group, schedule to Automation, and polymarket to Integrations, with corresponding test coverage. --- src/openhuman/tools/ops.rs | 4 +++- src/openhuman/tools/ops_tests.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index 1594b9e86c..eb95ae5004 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -1435,6 +1435,7 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { | "wait_loop" | "request_plan_review" | "plan_exit" + | "spawn_parallel_agents" ) { return DomainGroup::Agent; @@ -1460,7 +1461,7 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { // // Automation: scheduled jobs (`cron_*`) plus the subconscious monitor + // proactive-notify surface. - if name.starts_with("cron_") || MONITORS.contains(&name) { + if name.starts_with("cron_") || name == "schedule" || MONITORS.contains(&name) { return DomainGroup::Automation; } // Integrations: every external connector reached on the user's behalf. @@ -1468,6 +1469,7 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { || name.starts_with("apify_") || name.starts_with("google_places_") || name.starts_with("stock_") + || name == "polymarket" || name.starts_with("storage_") || name.starts_with("task_source_") || name == "twilio_call" diff --git a/src/openhuman/tools/ops_tests.rs b/src/openhuman/tools/ops_tests.rs index 874a228c0e..cf9bf6f6be 100644 --- a/src/openhuman/tools/ops_tests.rs +++ b/src/openhuman/tools/ops_tests.rs @@ -2573,6 +2573,7 @@ fn tool_group_classifies_gate_and_harness_families() { "delegate", "todo", "update_task", + "spawn_parallel_agents", ] { assert_eq!(tool_group(name), DomainGroup::Agent); } @@ -2583,6 +2584,8 @@ fn tool_group_classifies_gate_and_harness_families() { assert_eq!(tool_group("credential_list"), DomainGroup::Security); assert_eq!(tool_group("session_state"), DomainGroup::Security); assert_eq!(tool_group("oauth_list"), DomainGroup::Security); + assert_eq!(tool_group("schedule"), DomainGroup::Automation); + assert_eq!(tool_group("polymarket"), DomainGroup::Integrations); // Everything else → Platform (dropped under harness()). assert_eq!(tool_group("shell"), DomainGroup::Platform); From 88b9b13260db823c4500a13e33276fdc2ba7cbd3 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 15:56:25 +0300 Subject: [PATCH 10/11] fix(core): classify plan review and search tools under correct groups The plan-review controller was registered under the Security domain group but is now correctly placed under Agent, matching its namespace mapping. Additional search and web tools are now classified as Integrations rather than falling through to Platform, ensuring they are properly grouped for tool routing and gating. --- src/core/all.rs | 2 +- src/core/all_tests.rs | 4 ++++ src/openhuman/tools/ops.rs | 6 ++++++ src/openhuman/tools/ops_tests.rs | 10 ++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/all.rs b/src/core/all.rs index 7ec54dabb4..e3234a67f1 100644 --- a/src/core/all.rs +++ b/src/core/all.rs @@ -390,7 +390,7 @@ fn build_registered_controllers() -> Vec { // Interactive plan-review gate — parks a live turn on a thread-scoped plan push( &mut controllers, - DomainGroup::Security, + DomainGroup::Agent, crate::openhuman::agent::plan_review::all_plan_review_registered_controllers(), ); // Agent-generated artifact storage, retrieval, and lifecycle management diff --git a/src/core/all_tests.rs b/src/core/all_tests.rs index 48ee5f0452..78ef84a3da 100644 --- a/src/core/all_tests.rs +++ b/src/core/all_tests.rs @@ -986,6 +986,10 @@ fn group_mapping_smoke() { assert_eq!(group_for_namespace("config"), Some(DomainGroup::Config)); assert_eq!(group_for_namespace("security"), Some(DomainGroup::Security)); assert_eq!(group_for_namespace("agent"), Some(DomainGroup::Agent)); + assert_eq!( + group_for_namespace("plan_review"), + Some(DomainGroup::Agent) + ); // …and a representative gated one maps to its gate group. `group_for_namespace` // reads the real controller registry, so a compile-time-gated family has no // entry to map when its feature is off. diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index eb95ae5004..b42f4adaf9 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -1466,6 +1466,12 @@ fn tool_group(name: &str) -> crate::core::all::DomainGroup { } // Integrations: every external connector reached on the user's behalf. if name.starts_with("composio") + || name == "web_search_tool" + || name.starts_with("tinyfish_") + || name.starts_with("exa_") + || name.starts_with("brave_") + || name.starts_with("parallel_") + || name.starts_with("querit_") || name.starts_with("apify_") || name.starts_with("google_places_") || name.starts_with("stock_") diff --git a/src/openhuman/tools/ops_tests.rs b/src/openhuman/tools/ops_tests.rs index cf9bf6f6be..df5ec36e2e 100644 --- a/src/openhuman/tools/ops_tests.rs +++ b/src/openhuman/tools/ops_tests.rs @@ -2586,6 +2586,16 @@ fn tool_group_classifies_gate_and_harness_families() { assert_eq!(tool_group("oauth_list"), DomainGroup::Security); assert_eq!(tool_group("schedule"), DomainGroup::Automation); assert_eq!(tool_group("polymarket"), DomainGroup::Integrations); + for name in [ + "web_search_tool", + "tinyfish_search", + "exa_get_contents", + "brave_news_search", + "parallel_search", + "querit_search", + ] { + assert_eq!(tool_group(name), DomainGroup::Integrations); + } // Everything else → Platform (dropped under harness()). assert_eq!(tool_group("shell"), DomainGroup::Platform); From ec66e67c3e373eeda748781f28451081722daf76 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 3 Aug 2026 16:00:26 +0300 Subject: [PATCH 11/11] test: simplify assertion formatting in group mapping smoke test Condense the multi-line assertion for the "plan_review" namespace into a single line, matching the style of the surrounding assertions. This is a purely cosmetic change with no effect on test behavior. --- src/core/all_tests.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/all_tests.rs b/src/core/all_tests.rs index 78ef84a3da..98a3e5c9ec 100644 --- a/src/core/all_tests.rs +++ b/src/core/all_tests.rs @@ -986,10 +986,7 @@ fn group_mapping_smoke() { assert_eq!(group_for_namespace("config"), Some(DomainGroup::Config)); assert_eq!(group_for_namespace("security"), Some(DomainGroup::Security)); assert_eq!(group_for_namespace("agent"), Some(DomainGroup::Agent)); - assert_eq!( - group_for_namespace("plan_review"), - Some(DomainGroup::Agent) - ); + assert_eq!(group_for_namespace("plan_review"), Some(DomainGroup::Agent)); // …and a representative gated one maps to its gate group. `group_for_namespace` // reads the real controller registry, so a compile-time-gated family has no // entry to map when its feature is off.