Skip to content
Closed
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
15,499 changes: 13,625 additions & 1,874 deletions ATTRIBUTIONS-Rust.md

Large diffs are not rendered by default.

820 changes: 820 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ repository = "https://github.com/NVIDIA/nemo-fabric"
nemo-fabric-core = { path = "crates/fabric-core", version = "0.1.0" }

clap = { version = "4", default-features = false, features = ["derive", "std", "help", "usage", "error-context", "suggestions"] }
# Keep remote and local `$ref` retrieval disabled for untrusted adapter schemas.
jsonschema = { version = "0.49", default-features = false }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
schemars = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ print(result.output.response)

`HarnessConfig.adapter_id` selects the Hermes Agent adapter. To use another
supported harness, install its package extra and set the corresponding adapter
ID. Pass harness-specific options through `HarnessConfig.settings`.
ID. Pass harness-specific options through `HarnessConfig.settings` only when
the selected adapter descriptor declares them in `settings_schema`.

For a guided version of this example, refer to the
[`01_quickstart.ipynb` notebook](examples/notebooks/01_quickstart.ipynb). The
Expand Down
16 changes: 11 additions & 5 deletions adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ compare the bundled adapters and then open the linked package guide for
installation, authentication, and configuration details.

The adapter descriptor selected in `RunPlan` is authoritative for normalized
configuration and telemetry support.
configuration, its adapter-owned settings schema, and telemetry support.

## Descriptor Discovery

Expand All @@ -27,6 +27,12 @@ precedence:
NeMo Fabric resolves multi-component relative `ADAPTER_PYTHON` paths from
`<base_dir>`. It resolves bare command names through `PATH`.

The winning descriptor supplies its runner metadata and `settings_schema`
atomically. Planning validates `harness.settings` against that exact schema.
An agent-local descriptor therefore replaces an installed schema rather than
merging with it. Settings schemas must be self-contained; NeMo Fabric does not
resolve HTTP or file references from adapter descriptors.

This scan only discovers installed metadata. It is not the final registry
contract for resolving or installing third-party adapters. Installed and
agent-local descriptors both currently report `source: local`; a registry
Expand All @@ -47,16 +53,16 @@ provider should expose more precise provenance.
| --- | --- | --- | --- | --- | --- |
| [Claude](claude/README.md) | Native Anthropic or a configured Anthropic Messages-compatible provider | `tools.enabled` selects built-ins; a pre-tool hook enforces enabled and blocked names across built-in, MCP, and plugin tools | Normalized: stdio, HTTP, streamable HTTP, and SSE | Normalized `skills.paths` | Not exposed |
| [Codex](codex/README.md) | Native OpenAI or a configured Responses-compatible provider | `tools.enabled` and `tools.blocked` unsupported | Normalized: stdio, HTTP, and streamable HTTP | Normalized `SKILL.md` directories | Not exposed |
| [LangChain Deep Agents](deepagents/README.md) | LangChain model providers | Middleware enforces `tools.enabled` and `tools.blocked` across built-ins, MCP, and local subagents | Normalized through `langchain-mcp-adapters` | Normalized | Constrained declarative local delegation |
| [LangChain Deep Agents](deepagents/README.md) | LangChain model providers | Middleware enforces `tools.enabled` and `tools.blocked` across built-ins, MCP, and built-in delegation | Normalized through `langchain-mcp-adapters` | Normalized | Built-in delegation only |
| [Hermes Agent](hermes/README.md) | Configurable provider, model, and base URL | `tools.enabled` and `tools.blocked` map to Hermes native toolset selectors | Normalized | Normalized | Not exposed |

"Normalized" means that the adapter accepts the corresponding `FabricConfig`
field. "Not exposed" does not mean that the underlying harness lacks the
feature; it means that NeMo Fabric does not provide a portable configuration
surface for it. Tool values are adapter-native selectors; NeMo Fabric does not
define a cross-harness tool-name catalog. Planning fails when the selected
adapter cannot enforce a configured policy. Deep Agents subagents are limited
to declarative local subagents that inherit the parent agent's capabilities.
adapter cannot enforce a configured policy. Deep Agents delegation is limited
to its built-in subagent, which inherits the parent agent's capabilities.

`RunPlan.capability_plan.routes` records execution ownership, not network
routing. `harness_native` assigns a capability to the selected adapter,
Expand All @@ -77,7 +83,7 @@ and additive extension maps because their support does not vary by adapter:
| `schema_version` | Core | Core | Core | Core |
| `metadata.name`, `.description` | Core | Core | Core | Core |
| `harness.adapter_id`, `.resolution` | Core | Core | Core | Core |
| `harness.settings` | Adapter-owned escape hatch | Adapter-owned escape hatch | Adapter-owned escape hatch | Adapter-owned escape hatch |
| `harness.settings` | Closed Claude schema | No schema; must be empty | No schema; must be empty | No schema; must be empty |
| `models.<role>.provider` | `anthropic` uses native auth; custom names require an Anthropic Messages-compatible `base_url` and `api_key_env` | `openai` uses native auth; custom names require a Responses-compatible `base_url` and `api_key_env` | Dynamic LangChain provider; custom OpenAI-compatible endpoints require `base_url` and `api_key_env` | Dynamic Hermes provider |
| `models.<role>.model` | Yes | Yes | Yes | Yes |
| `models.<role>.api_key_env` | Yes | Yes | Yes | Yes |
Expand Down
17 changes: 13 additions & 4 deletions adapters/claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ Configure portable capabilities through the normalized `FabricConfig` fields:
- `environment.workspace` sets the Claude working directory, and
`environment.env` supplies explicit harness-visible variables.
- `tools.enabled` selects Claude built-in tools. `None` preserves the Claude
default, while an empty list disables every tool.
default, while an empty list disables every tool. With `permission_mode` set
to `dontAsk`, explicitly enabled tools are also pre-approved so headless runs
can invoke them.
- `tools.blocked` maps to Claude `disallowed_tools`. A pre-tool hook enforces
both lists across built-in, MCP, and plugin tools.
- `mcp` configures stdio, HTTP, streamable HTTP, or SSE servers. For stdio,
Expand All @@ -102,9 +104,16 @@ Configure portable capabilities through the normalized `FabricConfig` fields:

Only Claude-specific controls belong in `harness.settings`:

- `allowed_tools` and `permission_mode`
- `max_budget_usd`
- `setting_sources` (defaults to `[]` for deterministic isolation)
| Setting | Type | Required | Static default |
| --- | --- | --- | --- |
| `permission_mode` | One of `default`, `acceptEdits`, `bypassPermissions`, `plan`, `dontAsk`, or `auto` | No | No default |
| `max_budget_usd` | Number greater than `0` | No | No default |
| `setting_sources` | Array containing `user`, `project`, or `local` | No | `[]` |

Planning validates these settings against the schema in the resolved Claude
descriptor. Unknown keys and invalid values fail before the adapter starts.
Schema defaults are documentation only; planning preserves the supplied settings
without adding `setting_sources`.

The adapter filters the inherited environment before launching Claude Code.
It retains portable OS/config variables, the selected model's `api_key_env`,
Expand Down
34 changes: 34 additions & 0 deletions adapters/claude/fabric-adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@
"runner": {
"module": "nemo_fabric_adapters.claude.adapter"
},
"settings_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"setting_sources": {
"type": "array",
"items": {
"type": "string",
"enum": ["user", "project", "local"]
},
"default": [],
"description": "Claude settings scopes to load."
},
"max_budget_usd": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Maximum amount in US dollars that Claude may spend during one invocation."
},
"permission_mode": {
"type": "string",
"enum": [
"default",
"acceptEdits",
"bypassPermissions",
"plan",
"dontAsk",
"auto"
],
"description": "Claude permission handling mode."
}
},
"required": [],
"additionalProperties": false
},
"config": {
"accepts": [
"models",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,11 @@ def build_options(

system_prompt = common_utils.system_instruction(payload)
enabled_tools = common_utils.enabled_tools(payload)
allowed_tools = (
enabled_tools
if permission_mode == "dontAsk" and enabled_tools is not None
else []
)
plugins = _stage_skill_plugin(payload)
has_skill_plugin = bool(plugins)
if relay is not None:
Expand All @@ -551,7 +556,7 @@ def build_options(
model=selected_model(payload),
system_prompt=system_prompt,
tools=enabled_tools,
allowed_tools=_string_list(settings.get("allowed_tools"), name="allowed_tools"),
allowed_tools=allowed_tools,
disallowed_tools=common_utils.blocked_tools(payload),
hooks=tool_policy_hooks(payload),
permission_mode=permission_mode,
Expand Down
23 changes: 9 additions & 14 deletions adapters/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,26 +103,21 @@ deny boundary for built-in, local, MCP, and hosted tools. NeMo Fabric therefore
routes normalized blocked-tool policy as unsupported instead of applying a
partial policy.

Codex-specific controls belong in `harness.settings`:

- `sandbox`: `read-only`, `workspace-write`, or `danger-full-access`
- `approval_mode`: `auto_review` or `deny_all`
- `developer_instructions`
- `personality`, `reasoning_effort`, and `service_tier`
- `output_schema` for SDK-native structured output
- `config_overrides` as dotted Codex configuration keys applied when the SDK
runtime starts, such as Codex-only MCP timeout or required-server options
The current Codex descriptor does not declare `settings_schema`, so
`harness.settings` must remain empty. Planning rejects non-empty settings.
Codex-native controls such as sandbox mode, approval mode, reasoning effort,
and configuration overrides remain unavailable through the public NeMo Fabric
configuration until a follow-up descriptor schema declares them.

Set model selection and endpoints through `models`, system instructions through
`instructions.system`, the invocation deadline through
`runtime.timeout_seconds`, and the working directory and explicit environment
through `environment`.

For `Fabric.start_runtime(...)`, the model provider, MCP configuration, skill
roots, and `config_overrides` are fixed when the runtime starts and cannot vary
between `Runtime.invoke(...)` calls. Start a new runtime to change them.
`Fabric.run(...)` starts the same runtime, invokes it once, and stops it, so the
same settings are scoped to that single invocation.
For `Fabric.start_runtime(...)`, the model provider, MCP configuration, and
skill roots are fixed when the runtime starts and cannot vary between
`Runtime.invoke(...)` calls. Start a new runtime to change them.
`Fabric.run(...)` starts the same runtime, invokes it once, and stops it.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
The adapter filters the inherited environment. It retains portable OS and
Codex state variables, the selected model's `api_key_env`, and explicit
Expand Down
44 changes: 18 additions & 26 deletions adapters/deepagents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,35 +59,28 @@ NeMo Fabric maps the following into the harness:
- `tools.enabled` and `tools.blocked` are enforced by middleware across the full
tool surface: Deep Agents built-ins (including `task`), MCP tools, and
**delegated subagents** alike. Use Deep Agents-native tool names.
- `harness.settings.deepagents` forwards a small set of **documented,
JSON-serializable** `create_deep_agent` options (currently `subagents` and
`interrupt_on`). It is not a general Python-object escape hatch: the SDK config
round-trips through JSON and Rust planning, so `AgentMiddleware`, `BaseTool`
instances, and Python callables cannot cross the boundary. NeMo Fabric-owned
arguments (`model`, `tools`, `backend`, `skills`, `system_prompt`, `middleware`,
`checkpointer`) cannot be overridden through this passthrough, and an unknown or
unsupported key is an adapter configuration failure rather than a silently
dropped setting.
- The current Deep Agents descriptor does not declare `settings_schema`, so
`harness.settings` must remain empty. Planning rejects non-empty settings.
Caller-defined `subagents` and `interrupt_on` controls remain unavailable
through the public NeMo Fabric configuration until a follow-up descriptor
schema declares them.

### Subagents

Deep Agents can delegate to subagents through its built-in `task` tool. Subagents
**inherit** the parent run's model, tools, skills, workspace, telemetry, and
permissions. When a normalized tools policy is configured, NeMo Fabric supplies
an explicitly gated `general-purpose` subagent and gates every declarative local
subagent, so delegation cannot broaden capabilities beyond the parent. Remote
and precompiled subagents are rejected in that case because their execution
cannot be governed by the local middleware. Independently configured subagent tools, skills, models,
MCP servers, middleware, or permissions are **not** exposed through the NeMo Fabric SDK
yet; a `subagents` definition here only carries JSON-shaped fields.
Deep Agents can delegate through its built-in `task` tool. The built-in
subagent **inherits** the parent run's model, tools, skills, workspace,
telemetry, and permissions. When a normalized tools policy is configured,
NeMo Fabric supplies an explicitly gated `general-purpose` subagent so
delegation cannot broaden capabilities beyond the parent. Caller-defined,
remote, and precompiled subagents are not exposed through the public NeMo Fabric
SDK.

The normalized result includes the final response, buffered messages and
per-step events, LangGraph thread id, token usage (and cost when the provider
reports it), and errors. Usage aggregates the current turn across the main agent
and any delegated subagents (streamed with `subgraphs=True`). Configuration and
preflight failures (a missing credential, an absent `deepagents` package, an
invalid MCP server, or a passthrough option) fail runtime start before an
invocation is accepted.
preflight failures (a missing credential, an absent `deepagents` package, or an
invalid MCP server) fail runtime start before an invocation is accepted.

## Runtime Lifecycle

Expand Down Expand Up @@ -163,11 +156,10 @@ pip install "nemo-fabric-adapters-deepagents[relay]" # -> nemo-relay[deepagent
OpenTelemetry/OpenInference exporter is applied and spans export directly to
the configured collector, without writing ATOF/ATIF relay artifacts.

**Subagent boundary.** In-process, dictionary-style subagents are instrumented
with the same Relay middleware, so their model/tool calls appear under the same
trajectory. Remote and precompiled subagents (those defined with `graph_id` or
`url`) are **out of scope**: their internals execute in a separate runtime and
must be instrumented there with their own Relay integration.
**Subagent boundary.** The built-in subagent is instrumented with the same
Relay middleware, so its model and tool calls appear under the same trajectory.
Caller-defined, remote, and precompiled subagents are not currently exposed
through the public NeMo Fabric configuration.

### Typed Relay configuration

Expand Down
14 changes: 8 additions & 6 deletions adapters/hermes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ The adapter receives a normalized payload from NeMo Fabric and materializes a na
- optional NeMo Relay telemetry plugin configuration.

Tool selectors are Hermes toolset names because that is the native policy
surface Hermes exposes. Keep Hermes-specific controls such as
terminal timeout, reasoning configuration, and plugin configuration in
`harness.settings`. The adapter derives Hermes state from the NeMo Fabric
artifact root and creates a child under `runtimes/<runtime_id>`, so invocations
in one NeMo Fabric runtime share state without sharing config or the session
database with another runtime.
surface Hermes exposes. The current Hermes descriptor does not declare
`settings_schema`, so `harness.settings` must remain empty. Planning rejects
non-empty settings. Hermes-specific controls such as terminal timeout and
reasoning configuration remain unavailable through the public NeMo Fabric
configuration until a follow-up descriptor schema declares them. The adapter
derives Hermes state from the NeMo Fabric artifact root and creates a child under
`runtimes/<runtime_id>`, so invocations in one NeMo Fabric runtime share state
without sharing config or the session database with another runtime.

## Execution Model

Expand Down
34 changes: 34 additions & 0 deletions crates/fabric-cli/assets/adapters/claude/fabric-adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@
"runner": {
"module": "nemo_fabric_adapters.claude.adapter"
},
"settings_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"setting_sources": {
"type": "array",
"items": {
"type": "string",
"enum": ["user", "project", "local"]
},
"default": [],
"description": "Claude settings scopes to load."
},
"max_budget_usd": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Maximum amount in US dollars that Claude may spend during one invocation."
},
"permission_mode": {
"type": "string",
"enum": [
"default",
"acceptEdits",
"bypassPermissions",
"plan",
"dontAsk",
"auto"
],
"description": "Claude permission handling mode."
}
},
"required": [],
"additionalProperties": false
},
"config": {
"accepts": [
"models",
Expand Down
13 changes: 1 addition & 12 deletions crates/fabric-cli/src/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,18 +218,7 @@ fn codex() -> FabricConfig {
Some("NVIDIA_API_KEY"),
std::env::var(NVIDIA_FRONTIER_BASE_URL_ENV).ok().as_deref(),
)),
Map::from_iter([
("sandbox".to_string(), json!("workspace-write")),
(
"config_overrides".to_string(),
json!({
"features.apps": false,
"features.multi_agent": false,
"features.plugins": false,
"web_search": "disabled",
}),
),
]),
Map::new(),
)
}

Expand Down
1 change: 1 addition & 0 deletions crates/fabric-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ readme = "README.md"
workspace = true

[dependencies]
jsonschema.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand Down
Loading
Loading