Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f3e4d14
feat(core): runtime DomainSet seam — group-tagged registry + ambient …
oxoxDev Jul 13, 2026
752299d
feat(core): gate agent tool surface by ambient DomainSet (#4796)
oxoxDev Jul 13, 2026
9c20e59
feat(examples): embed_headless uses DomainSet::harness() (#4796)
oxoxDev Jul 13, 2026
72c59e0
test(core): DomainSet registration + tool-filter tests (#4796)
oxoxDev Jul 13, 2026
812a64d
docs(core): document DomainSet composition axis (#4796)
oxoxDev Jul 13, 2026
93f9040
test(core): prove init_stores gating via pure StoreInitPlan (#4796)
oxoxDev Jul 13, 2026
70ca2c3
test(core): prove subscriber gating via pure DomainSubscriberPlan (#4…
oxoxDev Jul 13, 2026
7230922
fix(core): gate schema_for_rpc_method in lockstep with dispatch (#4796)
oxoxDev Jul 13, 2026
9f8f558
fix(tools): classify audio_*/whatsapp_data_* tools by gate family (#4…
oxoxDev Jul 13, 2026
b40a06f
fix(core): per-group subscriber idempotency + gate x402/skills init (…
oxoxDev Jul 13, 2026
d91d6cb
docs(core): clarify none() disables domain families, not the runtime …
oxoxDev Jul 13, 2026
5243895
test(core): prove gated method is unknown-method at JSON-RPC transpor…
oxoxDev Jul 13, 2026
4a9e700
Merge remote-tracking branch 'upstream/main' into feat/4796-domainset…
oxoxDev Jul 13, 2026
49a872c
test(agent): drop stale memory_recall assertion superseded by #4764
oxoxDev Jul 13, 2026
d06e24b
fix(tools): prefix-gate Web3/Media tools + guard silent Platform defa…
oxoxDev Jul 13, 2026
32de700
docs(core): fix subscriber doc placement + stale Once/Media wording (…
oxoxDev Jul 13, 2026
46fc8f5
Merge remote-tracking branch 'upstream/main' into feat/4796-domainset…
oxoxDev Jul 13, 2026
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
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ Embedded provider webviews **must not** grow new JS injection. No new `.js` unde

Modules: `all`, `auth`, `cli`, `dispatch`, `event_bus/`, `jsonrpc`, `logging`, `observability`, `types`, etc. No business logic here.

### Runtime composition — `ServiceSet` + `DomainSet` on `CoreBuilder`

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.

### Event bus (`src/core/event_bus/`)

Typed pub/sub + native request/response. Both singletons — use module-level functions.
Expand Down
30 changes: 30 additions & 0 deletions docs/plans/pluggable-core/phase-2-corecontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,32 @@ pub enum StorageBackend { WorkspaceFs } // CoreBuilder::storage(..); only impl
| Store trait too narrow, churns later | extract traits from _observed_ handler usage per domain, not speculatively |
| Coverage gate on mechanical diffs | signature/registry PRs carry existing tests; per-domain PRs add store-trait unit tests |

## 2.d `DomainSet` — the runtime composition axis (#4796)

`DomainSet` (in `src/core/runtime/builder.rs`, sibling of `ServiceSet`) is the
**runtime** axis that selects which domain *families* exist, complementing
`ServiceSet` (which selects background services). One flag per `DomainGroup`
(`src/core/all.rs`); presets `full()` (default — byte-identical to pre-#4796),
`harness()` (agent + memory + threads + config + security only), `none()`.

Mechanism (Shape B — filter seam, **not** the full per-domain
`DomainRegistration` struct collapse, which remains phase-2-deferred):

- Every controller is tagged with its `DomainGroup` once, at the single
registration site (`build_registered_controllers` /
`build_internal_only_controllers`), via a `GroupedController { group,
controller }` wrapper — the ~109 domain modules keep returning bare
`RegisteredController` lists and never learn about groups.
- The live surface filters by the **ambient** `CoreContext::domains()`:
`all_registered_controllers` / `all_controller_schemas` (so `/schema` omits
gated namespaces), `try_invoke_registered_rpc` (gated method ⇒ `None`, i.e.
unknown-method), the agent tool surface (`tools::ops::all_tools_with_runtime`
post-filter by `tool_group`), workspace-bound store init (`init_stores`), and
domain event subscribers (`register_domain_subscribers`).
- No active context (pre-boot unit tests) ⇒ no filtering (treated as full).
- Per-gate Cargo `[features]` (children #4797–#4804) narrow the *compile-time*
surface further; `DomainSet` is the runtime axis they compose with.

## Verification

- `pnpm test:rust` + `json_rpc_e2e` green after each sub-series.
Expand All @@ -124,3 +150,7 @@ pub enum StorageBackend { WorkspaceFs } // CoreBuilder::storage(..); only impl
- `all::try_invoke_registered_rpc` installs an ambient `CoreContext::scope`
around registered handler futures, and tests verify `CoreContext::current()`
propagation through registered RPC invocation.
- `DomainSet`: `full_registration_is_byte_identical`,
`harness_excludes_gated_namespaces`, `dispatch_returns_none_for_gated_method`,
`group_mapping_smoke` (`src/core/all_tests.rs`) +
`domain_set_presets_have_expected_flags` (`builder.rs`).
28 changes: 20 additions & 8 deletions examples/embed_headless.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! Embed the OpenHuman core as a library — no HTTP, no background services.
//!
//! Demonstrates the Phase-1 pluggable-core API: build a fully-initialized core
//! with [`ServiceSet::none`] (no ports bound, no cron/channels/heartbeat) and
//! dispatch RPC methods in-process through [`CoreRuntime::invoke`] — the exact
//! same path the HTTP `/rpc` handler and the CLI use.
//! Demonstrates the pluggable-core API: build a fully-initialized core with
//! [`ServiceSet::none`] (no ports bound, no cron/channels/heartbeat) AND
//! [`DomainSet::harness`] (only the agent + memory + threads + config + security
//! domain families are live — the gate families flows/skills/mcp/meet/channels/
//! web3/voice/media and the catch-all `platform` are off, so their controllers
//! are unknown-method, their agent tools absent, and their stores/subscribers
//! never initialize). Dispatch RPC methods in-process through
//! [`CoreRuntime::invoke`] — the exact same path the HTTP `/rpc` handler and the
//! CLI use.
//!
//! Run with:
//!
Expand All @@ -16,9 +21,10 @@
//! `CancellationToken`, and call `runtime.serve(None, Some(token)).await` — it
//! binds `127.0.0.1:7788` (override with `.host(..)` / `.port(..)` on the
//! builder, or `OPENHUMAN_CORE_HOST` / `OPENHUMAN_CORE_PORT`) and serves until
//! the token is cancelled.
//! the token is cancelled. Widen the runtime surface by swapping
//! `DomainSet::harness()` for `DomainSet::full()`.

use openhuman_core::{CoreBuilder, HostKind, ServiceSet};
use openhuman_core::{CoreBuilder, DomainSet, HostKind, ServiceSet};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
Expand All @@ -27,14 +33,20 @@ async fn main() -> anyhow::Result<()> {
let _ = env_logger::builder().is_test(false).try_init();

// Initialize the core against the local workspace. `HostKind::Cli` selects
// the standalone (non-desktop) bootstrap path; `ServiceSet::none()` means no
// transport and no background services are started.
// the standalone (non-desktop) bootstrap path; `DomainSet::harness()` builds
// the embeddable agent core; `ServiceSet::none()` means no transport and no
// background services are started.
let runtime = CoreBuilder::new(HostKind::Cli)
.domains(DomainSet::harness())
.services(ServiceSet::none())
.build()
.await?;

// Dispatch a couple of RPC methods in-process — no network involved.
// `core.version` and `openhuman.ping` (a legacy alias for the built-in
// `core.ping`) are always available regardless of the DomainSet — they are
// transport built-ins, not domain controllers — so they succeed even under
// `harness()`.
let version = runtime
.invoke("core.version", serde_json::json!({}))
.await
Expand Down
Loading
Loading