Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]`)

Expand Down
15 changes: 12 additions & 3 deletions docs/specs/2026-08-02-core-kernel-domain-reorg.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
81 changes: 81 additions & 0 deletions examples/embed_kernel.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
Loading
Loading