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.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 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"] }
jsonschema = { version = "0.49", default-features = false }
schemars = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,19 @@ from nemo_fabric import (
HarnessConfig,
MetadataConfig,
ModelConfig,
RuntimeConfig,
)

config = FabricConfig(
metadata=MetadataConfig(name="quickstart-agent"),
harness=HarnessConfig(adapter_id="nvidia.fabric.hermes"),
runtime=RuntimeConfig(max_turns=1),
models={
"default": ModelConfig(
provider="nvidia",
model="nvidia/nemotron-3-nano-30b-a3b",
api_key_env="NVIDIA_API_KEY",
base_url="https://integrate.api.nvidia.com/v1",
)
},
)
Expand All @@ -113,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 All @@ -130,7 +134,7 @@ harness inside an isolated task container. Refer to the
[Harbor execution model](examples/harbor/README.md#execution-model) for details.

NeMo Fabric can also operate with the NeMo Fabric runtime and the agent harness
in separate Python environments. This setup can match existing deployment
in separate Python environments. This setup can match existing deployment
boundaries and isolate their dependencies.

Create an environment for the NeMo Fabric runtime:
Expand Down
76 changes: 66 additions & 10 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 @@ -43,19 +49,69 @@ provider should expose more precise provenance.

## Configuration Compatibility

| Agent Harness | Models | Tools / Blocked Tools | MCP | Skills | Subagents |
| Agent Harness | Models | Tool Policy | MCP | Skills | Subagents |
| --- | --- | --- | --- | --- | --- |
| [Claude](claude/README.md) | Anthropic and NVIDIA-hosted Anthropic Messages-compatible models | `allowed_tools` adapter setting / normalized `tools.blocked` | Normalized: stdio, HTTP, streamable HTTP, and SSE | Normalized `skills.paths` | Not exposed |
| [Codex](codex/README.md) | OpenAI; NVIDIA Responses-compatible models without Relay | Codex-native tools / configuring `tools.blocked` is unsupported and raises `UnsupportedToolsPolicy` | Normalized: stdio, HTTP, and streamable HTTP | Normalized `SKILL.md` directories | Not exposed |
| [LangChain Deep Agents](deepagents/README.md) | LangChain model providers | Built-ins and MCP / normalized middleware block list | Normalized through `langchain-mcp-adapters` | Normalized | Constrained declarative local delegation |
| [Hermes Agent](hermes/README.md) | Normalized provider, model, and base URL | Toolsets / normalized disabled toolsets | Normalized | Normalized | Not exposed |
| [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 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. NeMo Fabric normalizes a blocked-tool list, not a portable tool-definition
catalog. Deep Agents subagents are limited to declarative local subagents that
inherit the parent agent's capabilities.
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 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,
`fabric_managed` assigns it to NeMo Fabric, and `unsupported` means neither can
execute it. Scalar fields are validated separately against
`adapter_descriptor.config.accepts`.

### Complete FabricConfig Support

`Core` means NeMo Fabric owns the behavior and applies it uniformly before or around
adapter execution. `Yes` means the adapter translates the normalized field into
its harness. `No` means an explicitly configured value fails planning instead
of being ignored. The following table groups provider-specific Relay subfields
and additive extension maps because their support does not vary by adapter:

| `FabricConfig` Field | Claude | Codex | Deep Agents | Hermes Agent |
| --- | --- | --- | --- | --- |
| `schema_version` | Core | Core | Core | Core |
| `metadata.name`, `.description` | Core | Core | Core | Core |
| `harness.adapter_id`, `.resolution` | Core | Core | Core | Core |
| `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 |
| `models.<role>.base_url` | Yes | Yes | Yes | Yes |
| `models.<role>.temperature` | No | No | Yes | Yes |
| `models.<role>.settings.<key>` | No keys declared | No keys declared | No keys declared | No keys declared |
| `instructions.system` | Yes | Yes; base instructions | Yes | Yes |
| `runtime.input_schema`, `.output_schema` | Core | Core | Core | Core |
| `runtime.artifacts`, `.timeout_seconds` | Core | Core | Core | Core |
| `runtime.max_turns` | Yes | No | No | Yes; iteration limit |
| `environment.provider`, `.control_location`, `.ownership` | Core | Core | Core | Core |
| `environment.workspace`, `.artifacts`, `.env` | Core | Core | Core | Core |
| `environment.connection`, `.metadata`, `.settings` | Environment-provider-owned | Environment-provider-owned | Environment-provider-owned | Environment-provider-owned |
| `tools.enabled`, `.blocked` | Yes | No | Yes | Yes; native selectors are Hermes toolset names |
| `skills.paths` | Yes | Yes | Yes | Yes |
| `mcp.servers.<name>.transport`, `.url` with `harness_native` exposure | Yes | Yes | Yes | Yes |
| `mcp.servers.<name>.exposure = "fabric_managed"` | No; not implemented | No; not implemented | No; not implemented | No; not implemented |
| `telemetry.providers.relay` | Yes | Yes | Yes | Yes |
| `telemetry.providers.native` | No | Yes; OpenTelemetry | Yes; OpenTelemetry and OpenInference | No |
| `telemetry.providers.<provider>.config` | Declared-provider pass-through | Declared-provider pass-through | Declared-provider pass-through | Declared-provider pass-through |
| `relay.project`, `.output_dir`, `.observability` | Yes | Yes | Yes | Yes |
| `relay.components`, `.policy` | Yes | Yes | Yes | Yes |
| Additive `extensions` on typed config objects | Preserved; no portable adapter semantics | Preserved; no portable adapter semantics | Preserved; no portable adapter semantics | Preserved; no portable adapter semantics |

The selected model role is `default`, or the sole configured role when no
`default` exists. More than one role without `default` fails planning.
`runtime.max_turns` is optional; omitting it preserves adapter-native defaults
without creating a compatibility requirement.

## Runtime and Observability Compatibility

Expand Down
86 changes: 52 additions & 34 deletions adapters/claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ The `nvidia.fabric.claude` adapter uses the official Claude Agent SDK for
Python behind NeMo Fabric's normalized invocation contract. The SDK is an
implementation detail; consumers select the Claude harness by adapter ID.

This adapter pins `claude-agent-sdk==0.2.120`. The SDK supplies its compatible
Claude Code runtime unless `harness.settings.cli_path` explicitly selects
another executable.
This adapter pins `claude-agent-sdk==0.2.120`. The SDK supplies and selects its
compatible Claude Code runtime.

## Install

Expand All @@ -35,12 +34,13 @@ bearer credential, `ANTHROPIC_API_KEY` for a static API credential, or Anthropic
Workload Identity Federation (WIF) for production and CI workloads that should
not store a long-lived API key.

When `models.default.provider` is `nvidia`, the adapter reads the selected
model's credential from `api_key_env` (default: `NVIDIA_API_KEY`) and translates
the configured NVIDIA `/v1` endpoint into the host URL expected by Claude Code.
Set the endpoint in `models.default.settings.base_url` or
`NVIDIA_FRONTIER_BASE_URL`; the adapter does not assume a default frontier
endpoint. This request-scoped mapping does not change the parent environment.
The native `anthropic` provider can use any Claude authentication mode above
without an explicit endpoint. For another provider name, configure both
`models.<role>.api_key_env` and `models.<role>.base_url`. The endpoint must
implement the Anthropic Messages protocol; the adapter maps the named
credential and endpoint into the environment expected by Claude Code. Provider
names identify configuration; the adapter does not maintain a provider
allowlist. The runtime-scoped mapping does not change the parent environment.

The adapter forwards the Anthropic profile and federation environment variables
that Claude Code and the Claude Agent SDK consume. This includes
Expand All @@ -59,7 +59,6 @@ for mode selection, required WIF variables, and the Relay boundary. Package
installation is verified by the adapter wheel and module-entrypoint tests.

Relay-enabled runs also require the external `nemo-relay` CLI. Refer to the [NeMo Relay CLI](https://docs.nvidia.com/nemo/fabric/getting-started/install#nemo-relay-cli) install guide for instructions on installing the CLI tool.
```

The Python `nemo-relay` package does not install this executable. Refer to the
[NeMo Relay installation guide](https://docs.nvidia.com/nemo/relay/getting-started/installation)
Expand All @@ -84,33 +83,39 @@ hosting is adapter-declared; consumers do not configure a runtime strategy in

Configure portable capabilities through the normalized `FabricConfig` fields:

- `models` selects the Claude model. The adapter accepts the native `anthropic`
provider and NVIDIA-hosted Anthropic Messages-compatible models through the
`nvidia` provider.
- `environment.workspace` sets the Claude working directory.
- `tools.blocked` maps to Claude `disallowed_tools` using Claude-native tool
names.
- `models` selects the Claude model. The native `anthropic` provider retains
Claude authentication and endpoint discovery. Any other provider name must
configure an Anthropic Messages-compatible `base_url` and `api_key_env`.
- `instructions.system` supplies the Claude system instructions.
- `runtime.max_turns` sets the Claude turn limit.
- `runtime.timeout_seconds` sets the NeMo Fabric invocation deadline.
- `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.
- `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,
NeMo Fabric parses `url` as a command plus arguments.
- `skills.paths` names skill directories that contain `SKILL.md`. The adapter
stages these directories as a local Claude plugin for the runtime.

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

- `system_prompt`, `allowed_tools`, and `permission_mode`
- `max_turns`, `max_budget_usd`, and `timeout_seconds`
- `setting_sources` (defaults to `[]` for deterministic isolation)
- `cli_path` for testing or an explicitly installed Claude Code executable
- `nemo_relay_command` for an explicitly installed NeMo Relay CLI executable
- `env` for variables explicitly forwarded to Claude Code
| 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 | `[]` |

Putting `model_name`, `cwd`, `tools`, `disallowed_tools`, `mcp_servers`, or
`skills` in `harness.settings` is an error. Use the corresponding normalized
field so the same consumer configuration can compose with other adapters.
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`,
and explicitly configured `settings.env` values. Raw Claude stderr is consumed
and explicitly configured `environment.env` values. Raw Claude stderr is consumed
by the SDK and is not persisted as a NeMo Fabric artifact.

## Relay Observability
Expand All @@ -127,17 +132,17 @@ config.enable_relay(
For each Relay-enabled Claude runtime, NeMo Fabric starts one `nemo-relay` gateway,
waits for its health endpoint, and stops it with the runtime. NeMo Fabric passes the
gateway URL to the connected Claude Code process through `ANTHROPIC_BASE_URL`
and `NEMO_RELAY_GATEWAY_URL`. It also stages a runtime-scoped Claude plugin that
forwards lifecycle hooks with `nemo-relay hook-forward claude`.
and `NEMO_RELAY_GATEWAY_URL`, and passes the selected explicit model endpoint to
the gateway as its Anthropic upstream. It also stages a runtime-scoped Claude
plugin that forwards lifecycle hooks with `nemo-relay hook-forward claude`.
`Fabric.run(...)` starts the same runtime, invokes it once, and stops it, so the
gateway has the same lifecycle as that single invocation.

The NeMo Fabric result includes `relay_runtime.gateway_config_path`,
`relay_runtime.gateway_log_path`, and the collected `relay_artifacts`. Relay
startup failures return a stable adapter error and retain the gateway log for
diagnosis. The default Claude Agent SDK dependency bundles a compatible Claude
Code executable. An executable supplied with `cli_path` must support the Relay
plugin's complete hook set, including `UserPromptExpansion`.
Code executable.

## Typed Configuration

Expand All @@ -152,6 +157,8 @@ from nemo_fabric import (
Fabric,
FabricConfig,
HarnessConfig,
InstructionConfig,
InstructionsConfig,
McpConfig,
McpServerConfig,
MetadataConfig,
Expand All @@ -168,9 +175,7 @@ config = FabricConfig(
adapter_id="nvidia.fabric.claude",
resolution="preinstalled",
settings={
"system_prompt": "Review changes for correctness and regressions.",
"permission_mode": "dontAsk",
"max_turns": 8,
},
),
models={
Expand All @@ -180,9 +185,22 @@ config = FabricConfig(
api_key_env="ANTHROPIC_API_KEY",
)
},
runtime=RuntimeConfig(artifacts="./artifacts"),
instructions=InstructionsConfig(
system=InstructionConfig(
content="Review changes for correctness and regressions.",
mode="replace",
)
),
runtime=RuntimeConfig(
artifacts="./artifacts",
timeout_seconds=600,
max_turns=8,
),
environment=EnvironmentConfig(provider="local", workspace="."),
tools=ToolsConfig(blocked=["WebFetch"]),
tools=ToolsConfig(
enabled=["Read", "Edit", "Bash"],
blocked=["WebFetch"],
),
mcp=McpConfig(
servers={
"repo": McpServerConfig(
Expand Down
45 changes: 44 additions & 1 deletion adapters/claude/fabric-adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,51 @@
"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", "tools", "tools.blocked", "mcp", "skills", "telemetry"]
"accepts": [
"models",
"models.base_url",
"instructions.system",
"runtime.max_turns",
"tools.enabled",
"tools.blocked",
"mcp",
"skills"
]
},
"telemetry": {
"providers": {
Expand Down
Loading