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
12 changes: 10 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ Two columns because there are two sets (see above): **Contrib** is `[features] d
| `modules` | ON | ON | `openhuman::modules` — the dynamic module host: the loader that admits a compiled `cdylib` through tinybus's ABI descriptor, manifest, dependency and SHA-256 gates, the compiled-in registry of modules this build trusts, and the `modules` RPC namespace. Implied by `documents`. Off ⇒ `modules.*` is unknown-method and nothing can load a native module | none in the product profile (`ureq`, `flate2`, `tar`, `zip 2`, `tempfile`, `toml` are already there) — **but see the kernel-floor note**: this feature exists so `tinybus/modules` is not enabled on the dependency itself, which would put a `dlopen` loader into the kernel profile where `tinybus` is always-on |
| `skills` | ON | ON | `openhuman::skills` + `openhuman::skills::runtime` + `openhuman::skills::catalog` domains — SKILL.md discovery/parse/install, workflow execution + run logs, remote catalogs, the `skill_setup` / `skill_executor` builtin agents, and the 16 skill agent tools | none (see below) |
| `flows` | ON | ON | `openhuman::flows` (saved automation graphs — create/run/schedule, the `workflow_builder` + `flow_discovery` agents), `openhuman::flows::tinyflows` (engine seam), `openhuman::flows::rhai` (`.ragsh` language-workflow tool) | `tinyflows`, `jaq-core`, `jaq-std`, `jaq-json`, `rhai` |
| `mcp` | ON | ON | `openhuman::mcp::server` (the `openhuman mcp` stdio/HTTP server), `openhuman::mcp::registry` (dynamic Smithery installs — `mcp_clients` RPC namespace, SQLite, boot spawn, supervisor, OAuth), `openhuman::mcp::audit` (write-audit log), and the static config-declared server set in `openhuman::mcp::config_servers`. ~19 agent tools, ~20k LOC | **none** (see scope note) |
| `mcp` | ON | ON | `openhuman::mcp::server` (the `openhuman mcp` stdio/HTTP server), `openhuman::mcp::registry` (dynamic Smithery installs — `mcp_clients` RPC namespace, SQLite, boot spawn, supervisor, OAuth), `openhuman::mcp::audit` (write-audit log), and the static config-declared server set in `openhuman::mcp::config_servers`. ~19 agent tools, ~20k LOC | **none** — and the `tinymcp` module extraction does not change that either; see the scope note |
| `tui` | OFF | — | `openhuman::tui` — the tabbed ratatui/crossterm CLI UI (Logs, Chat, Config, Settings), auto-opened by bare `openhuman` on interactive non-container hosts and forced with `openhuman tui` (alias `chat`). Runs the core in-process. No controllers, no agent tools. **Intentionally NOT forwarded to the desktop shell** (allowlisted in `check-feature-forwarding.mjs`). | `ratatui`, `crossterm` |
| `channels` | ON | ON | `openhuman::channels` (external-messaging providers — Telegram/Discord/Slack/Signal/WhatsApp/iMessage/IRC/… — plus the channel runtime, controllers, host, proactive messaging + inbound dispatch) and the `channels::webview_accounts` / `webview_apis` / `webview_notifications` / `channels::whatsapp_data` webview-bridge domains (incl. the 3 `whatsapp_data_*` agent tools). **Carve-outs `channels::{traits, cli}` stay ungated.** | **28** via `tinychannels/{email,lark}` — the crate itself stays (load-bearing), its two heavy providers do not |
| `memory-git` | OFF | ON | `openhuman::memory::diff` (git-backed snapshots/checkpoints/read markers, the `memory_diff` RPC namespace + agent tool) and the git wiki mirror in `memory::store::content::wiki_git`. **Type carve-out**: `memory::diff::types` compiles in BOTH builds — the always-on memory profile renders `CrossSourceDiff`/`ChangeKind` into prompts, and tinycortex makes the matching split (its `memory::diff::{types,source}` are ungated, only the `Ledger`/`DiffEngine` half sits behind `git-diff`). Off ⇒ `memory_diff` is unknown-method, the tool is absent, the embedded driver drops `Capability::Diff` **and** `as_diff()` returns `None` in lockstep (`audit_provider` fails on either half alone), and summary nodes are still written to disk but not mirrored into git. **This crate declares no `git2`** — tinycortex owns every libgit2 call in the stack (the diff ledger, the wiki mirror, the persona git-history reader), and the gate reaches the cohort by forwarding `tinycortex/git-diff` + `tinycortex/wiki-git`; `tinymemory-core/memory-git` forwards the same pair. Do not re-add a direct `git2` dependency to this crate or to `tinymemory-core`: it would buy no crates and invite a second major pin, which `links = "git2"` makes a hard cargo error. Test code that must read a ledger back goes through the `tinycortex::git2` re-export (`tests/memory_artifacts_e2e.rs`). | **3**: `git2`, `libgit2-sys`, `libz-sys` — two of the five native C builds in the kernel profile, the largest native shed in the program |
Expand Down Expand Up @@ -667,7 +667,15 @@ Follows the voice facade+stub pattern for `mcp::server` / `mcp::registry` / `mcp
- **Type carve-out.** Inert, dependency-free type modules stay **ungated**: `mcp::registry::types`, `mcp::audit::types`, `mcp::server::tools::types` (`McpToolSpec`). They are `serde`/`serde_json`-only data consumed by always-compiled callers (the orchestrator prompt builder, `tool_registry`). Both builds therefore share the **one real type definition** — the stubs carry behaviour only, so struct fields can never drift between the enabled and disabled builds. `ConnectedServerOverview` was moved from `connections.rs` into `types.rs` for exactly this reason and is re-exported from `connections` so existing paths still resolve.
- **Split facade — the old `mcp_client` directory did not match the dependency graph, so the reorg split it three ways.** Its transport primitives went to the **ungated** `mcp::http_client` (`McpHttpClient`, `redact_endpoint`, `McpUnauthorizedError`); its static server set + stdio transport + setup agent went to the **leaf-gated** `mcp::config_servers`; and `sanitize` left the family entirely for `util::sanitize`. The `gitbooks` docs tool dials `McpHttpClient` directly (GitBook is modelled as a legacy MCP server), and the orchestrator prompt sanitizes **skill** descriptions through `util::sanitize::sanitize_for_llm` — neither has anything to do with MCP, and stubbing them would silently break a docs tool and corrupt the orchestrator prompt in slim builds. **The gate follows the real dependency graph, not the directory name.** A bonus of keeping `http_client` compiled: the `McpServerNeedsAuth` classifier coupling test in `core::observability` stays always-compiled — no `#[cfg]`, no wording-drift leak.

**Scope note — the `mcp` gate drops ZERO dependencies.** There is no MCP SDK in this crate: the dependency declarations contain no MCP-specific SDK or transport dependency; `test-mcp-stub` is the only MCP-named bin target. The entire protocol stack is hand-rolled over tokio process stdio + `reqwest` + `axum`, all of which are load-bearing for non-MCP domains. The gate is worth having for the ~20k LOC / ~19 agent tools / RPC surface it removes, but the issue-level DoD line claiming it "sheds the MCP SDK / transport stack" is superseded by this correction. The `mcp = []` feature list in `Cargo.toml` is intentionally empty — do not "fix" it by adding `dep:` entries.
**Scope note — the `mcp` gate drops ZERO dependencies, and the module extraction does not change that.** The history is worth keeping because both halves of it are counter-intuitive.

Before the extraction there was no MCP SDK in this crate at all: the entire protocol stack was hand-rolled over tokio process stdio + `reqwest` + `axum`, every one of which is load-bearing for non-MCP domains. So the gate shed nothing, and the issue-level DoD line claiming it "sheds the MCP SDK / transport stack" was superseded by that correction.

After the extraction the stack lives in `tinymcp`, and the natural expectation — recorded in the `Cargo.toml` comment and in `scripts/kernel-floor.limits`' 2026-08-22 entry — was that loading it as a TinyBus module would take `reqwest` and `rusqlite` out of the always-on graph with it. **Measured, it does not.** In the kernel profile `rusqlite` has six parents (`openhuman` itself, `tinyagents`, `tinychannels`, `tinycortex`, `tinymcp`, `tinymemory-core`) and `reqwest` has ten. `scripts/dep-sim.py --cut tinymcp` projects the whole shed at **−1 package / −1 name / 0 native**: the `tinymcp` package itself, and nothing underneath it. This is the same shape as the TinyMemory port — a module boundary buys a *compilation* boundary, not a dependency shed, whenever the module's dependencies are already shared with kernel surface.

The gate is still worth having for the ~20k LOC / ~19 agent tools / RPC surface it removes. The `mcp = []` feature list in `Cargo.toml` is intentionally empty — do not "fix" it by adding `dep:` entries.

**Step two of the extraction is registry-entered but not wired.** `src/openhuman/modules/registry.rs` pins the `tinymcp` v0.3.1 release, so the module can be downloaded, verified and loaded; the host still calls the library directly, and `Cargo.toml` still declares both `tinymcp` and `tinymcp-bus`. Cutting the path dependency needs contract additions that `tinymcp-bus` v0.3.1 does not carry — `OAuthComplete`, a connected-overview member for the already-exported `ConnectedServerOverview`, the boot-connect and reconnect-supervisor passes, the `ServerDetail` / `AuthDetection` / `AuthKind` reply types, the registry curation helpers, an error anchor for the `McpServerNeedsAuth` classifier coupling test in `src/core/observability.rs`, and `render_tool_result` / `redact_endpoint` for the ungated `gitbooks` tool. It also needs a per-`data_dir` object seam of the shape `modules::memory` already uses, because `mcp::host` keys one store per workspace and a loaded module receives one `data_dir` at load — and a desktop session moves workspace on login and again on logout. Those are upstream in `tinyhumansai/tinymcp` and must land and be released first.

**Static vs dynamic — the naming is INVERTED from intuition.** Both halves must be gated or the gate is only half-applied:

Expand Down
22 changes: 18 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,24 @@ tinymemory-core = { path = "vendor/tinymemory/crates/tinymemory-core" }
# generates a settings schema for the desktop application from those types
# rather than from a hand-kept copy.
#
# The path dependency on `tinymcp` itself is the step-one arrangement: it is
# replaced by a loadable module entry in `src/openhuman/modules/registry.rs`
# once tinymcp cuts a release, at which point only `tinymcp-bus` remains and
# reqwest/rusqlite leave this build with it.
# The path dependency on `tinymcp` itself is still the step-one arrangement. The
# registry entry step two calls for now exists — `src/openhuman/modules/registry.rs`
# pins the v0.3.1 release — but the host still calls the library directly, because
# `tinymcp-bus` v0.3.1 does not yet publish everything this host reaches for:
# `OAuthComplete`, a member returning the already-exported `ConnectedServerOverview`,
# the boot-connect and reconnect-supervisor passes, the `ServerDetail` /
# `AuthDetection` / `AuthKind` reply types, the registry curation helpers, an error
# anchor for the `McpServerNeedsAuth` classifier coupling test, and
# `render_tool_result` / `redact_endpoint` for the ungated `gitbooks` tool. It also
# needs a per-`data_dir` object seam of the shape `modules::memory` already uses,
# since `mcp::host` keys one store per workspace and a module gets one `data_dir` at
# load. Those are upstream work in tinyhumansai/tinymcp.
#
# Correcting what this comment used to promise: dropping the path dependency does
# NOT take reqwest/rusqlite with it. Measured in the kernel profile, `rusqlite` has
# six parents and `reqwest` ten, so `scripts/dep-sim.py --cut tinymcp` projects the
# entire shed at -1 package / -1 name / 0 native builds — the `tinymcp` package and
# nothing beneath it. The boundary buys compilation isolation, not a dependency shed.
#
# After cloning: `git submodule update --init vendor/tinymcp`.
# `default-features = false` drops the TinyBus adapter: this build calls the
Expand Down
31 changes: 31 additions & 0 deletions scripts/kernel-floor.limits
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,37 @@
# Simulate with: scripts/dep-sim.py --cut <crates>
#
# History
# 286/268/2 2026-08-23 tinymcp registry entry, NO CHANGE to this profile —
# and the entry exists to correct the prediction in the
# 2026-08-22 line below, which is measurably wrong.
# That line said step two of the MCP extraction would drop
# the `tinymcp` path dependency "at which point the MCP
# transport stack leaves the always-on graph entirely.
# Lower it in that PR." It does not, and the ratchet is
# therefore NOT lowered here.
# Measured, not derived: `scripts/dep-sim.py --cut tinymcp`
# projects 286/268/2 -> 285/267/2, i.e. -1 package / -1
# name / 0 native. The package that leaves is `tinymcp`
# itself and nothing beneath it, because its dependencies
# are shared with kernel surface: in this profile
# `rusqlite` has SIX parents (openhuman, tinyagents,
# tinychannels, tinycortex, tinymcp, tinymemory-core) and
# `reqwest` has TEN. Confirmed with `cargo tree
# --no-default-features --features flows -e normal -i
# rusqlite` / `-i reqwest`.
# This is the same lesson as the TinyMemory port: a module
# boundary buys a COMPILATION boundary, not a dependency
# shed, whenever the module's dependencies are already
# shared. Do not write "and its dependency tail leaves with
# it" into a History entry again without running dep-sim
# first — per-dependency intuition over-counts exactly the
# way per-dependency arithmetic does.
# What this PR does land: `TINYMCP` in
# `src/openhuman/modules/registry.rs`, pinning the v0.3.1
# release so the module can be downloaded and verified. The
# path dependency stays until `tinymcp-bus` publishes the
# members and types the host reaches for (see the Cargo.toml
# comment); the -1 lands in that PR, not this one.
# 286/268/2 2026-08-22 combined raise from two independent extractions
# landing on the same day, each additive on the 283/265/2
# base. (1) The MCP client moved out to `tinymcp` (+2
Expand Down
88 changes: 88 additions & 0 deletions src/openhuman/modules/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,93 @@ const TINYRUNTIME_PYTHON: ModuleRecord = ModuleRecord {
load: LoadPolicy::Lazy,
};

/// The `tinymcp` module: the Model Context Protocol client.
///
/// Owns both transports (Streamable HTTP and a subprocess over stdio), the
/// statically declared server set a host puts in its own configuration, the
/// dynamic registry of user-installed servers with its SQLite store, the
/// reconnect supervisor, the browser sign-in flow, and the write-audit log.
///
/// Lazy, because dialing an MCP server is something most sessions never do: a
/// host with no installed servers and no configured ones would otherwise pay a
/// download and a `dlopen` for a capability it never reaches. That differs from
/// the module's own `lazy = false` export hint, which speaks for a host whose
/// servers should be connected the moment it comes up — this host decides when
/// that moment is, and does so on the first ask.
///
/// **What stays out of the module is host policy**, and the split is the same
/// one the contract's own documentation draws: the prompt-injection scan over
/// remote tool definitions, the `mcp_clients` / `mcp_setup` RPC surface, the
/// agent-facing tools, and the proxy *scoping* decision all belong to this
/// application's threat model, not to a protocol client. `tinymcp-bus` carries
/// the vocabulary; this table says which bytes may speak it.
const TINYMCP: ModuleRecord = ModuleRecord {
id: "tinymcp",
description: "Model Context Protocol client: transports, registry, and the write-audit log",
bus_name: "ai.tinyhumans.tinymcp.Mcp",
object_path: "/ai/tinyhumans/tinymcp/Mcp",
version: "0.3.1",
release_url: "https://github.com/tinyhumansai/tinymcp/releases/tag/v0.3.1",
assets: &[
PlatformAsset {
host_key: "ubuntu-24.04-x86_64",
archive: "tinymcp-0.3.1-ubuntu-24.04-x86_64.tar.gz",
sha256: "f2ba8bfa0a74a9c234499e946936cc2de7f237e9772a85e7df0453a7c29669ab",
},
PlatformAsset {
host_key: "ubuntu-24.04-arm64",
archive: "tinymcp-0.3.1-ubuntu-24.04-arm64.tar.gz",
sha256: "a68734086449b980a7de3cf87f6b6e00f4aa43bbd1f39187ac0803b4082d62dc",
},
PlatformAsset {
host_key: "ubuntu-22.04-x86_64",
archive: "tinymcp-0.3.1-ubuntu-22.04-x86_64.tar.gz",
sha256: "da9225bbc008a3de0917da0280667e4a76a93d517f017a0962f420a0c0b311f6",
},
PlatformAsset {
host_key: "ubuntu-22.04-arm64",
archive: "tinymcp-0.3.1-ubuntu-22.04-arm64.tar.gz",
sha256: "aaefc1f2c3ae51bb1447d18b25abc5b78f37b795c172ecb6d8fa31967b8d213c",
},
PlatformAsset {
host_key: "macos-26-arm64",
archive: "tinymcp-0.3.1-macos-26-arm64.tar.gz",
sha256: "453624140df1d0df00a6e1bb1108fab086fb6b2ed3fbdced3f6765b534d4e0bc",
},
PlatformAsset {
host_key: "macos-26-x86_64",
archive: "tinymcp-0.3.1-macos-26-x86_64.tar.gz",
sha256: "bcb8c68b0744bfc82479261a60c1beb3fcc0842321bcd5ed6519aef1b6194ac6",
},
PlatformAsset {
host_key: "macos-15-arm64",
archive: "tinymcp-0.3.1-macos-15-arm64.tar.gz",
sha256: "a3e405131221a168bf35a887e1628f66e5e27a2cae65db8c260b00b4e190f4bb",
},
PlatformAsset {
host_key: "macos-15-x86_64",
archive: "tinymcp-0.3.1-macos-15-x86_64.tar.gz",
sha256: "6e501853e76ba77b7fea2f828f3ff293c15ad3e41a5b70b7f3ddb73c6efee5d1",
},
PlatformAsset {
host_key: "windows-2025-x86_64",
archive: "tinymcp-0.3.1-windows-2025-x86_64.zip",
sha256: "86d2d309e8c605c27f87bd652709683430449878843beb684842965e3fba2a41",
},
PlatformAsset {
host_key: "windows-2022-x86_64",
archive: "tinymcp-0.3.1-windows-2022-x86_64.zip",
sha256: "7b04601dafd43eddf7d218900a1e70ecfa6471aa3af34f4dcab67fce2d872a99",
},
PlatformAsset {
host_key: "windows-11-arm64",
archive: "tinymcp-0.3.1-windows-11-arm64.zip",
sha256: "db352cb7fffdbbd00a5cbd3e4fc610123e23c884c2d0cad89f1293a83f550406",
},
],
load: LoadPolicy::Lazy,
};

/// Every module this build can load.
pub const ALL: &[ModuleRecord] = &[
TINYDOCS,
Expand All @@ -505,6 +592,7 @@ pub const ALL: &[ModuleRecord] = &[
TINYRUNTIME,
TINYRUNTIME_NODEJS,
TINYRUNTIME_PYTHON,
TINYMCP,
];

/// The record for `id`, if this build knows it.
Expand Down
Loading