Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6b8dd63
feat: normalize portable adapter configuration
AjayThorve Jul 24, 2026
bc614e0
docs: document normalized adapter compatibility
AjayThorve Jul 24, 2026
a8aa6ed
fix: address normalized config review feedback
AjayThorve Jul 24, 2026
c3891cd
style: format schema contract assertion
AjayThorve Jul 24, 2026
4405a5f
docs: qualify normalized config product name
AjayThorve Jul 24, 2026
b479537
refactor: simplify normalized configuration
AjayThorve Jul 24, 2026
7efee3d
fix: make adapter provider routing explicit
AjayThorve Jul 24, 2026
6e487f5
docs: use full product name in API metadata
AjayThorve Jul 24, 2026
cbf3372
fix: redact persisted adapter environment values
AjayThorve Jul 24, 2026
9ceec1c
Updating versions for v0.2.0
dagardner-nv Jul 27, 2026
2fe088b
Merge upstream/main into feat/normalize-adapter-config
AjayThorve Jul 27, 2026
9e33716
Merge pull request #122 from NVIDIA/release/0.1
GPUtester Jul 27, 2026
c49d693
fix: read normalized adapter environment
AjayThorve Jul 27, 2026
0b48413
Merge upstream/main into feat/normalize-adapter-config
AjayThorve Jul 27, 2026
1593666
fix: read normalized shim environment
AjayThorve Jul 27, 2026
4d8cf9e
Merge upstream/release/0.1 into feat/normalize-adapter-config
AjayThorve Jul 27, 2026
9eaed3c
Merge upstream/release/0.1 into feat/normalize-adapter-config
AjayThorve Jul 28, 2026
f90e425
docs: address documentation style review
AjayThorve Jul 28, 2026
5bc461a
test: make scaffold path assertion portable
AjayThorve Jul 28, 2026
f21f2b4
Revert "Updating versions for v0.2.0"
AjayThorve Jul 28, 2026
fbcc1c3
feat: normalize instructions and tool policy
AjayThorve Jul 28, 2026
bc21add
docs: document normalized adapter configuration
AjayThorve Jul 28, 2026
395978c
Merge remote-tracking branch 'upstream/release/0.1' into feat/normali…
AjayThorve Jul 28, 2026
a1bbcc6
fix: keep generated Rust scaffold warning-free
AjayThorve Jul 28, 2026
c34f3c8
Merge remote-tracking branch 'upstream/release/0.1' into feat/normali…
AjayThorve Jul 28, 2026
5d8ab72
test: align Hermes endpoint expectation
AjayThorve Jul 28, 2026
dc3cf23
fix: enforce normalized configuration boundaries
AjayThorve Jul 28, 2026
779e157
fix: validate deserialized configuration boundaries
AjayThorve Jul 28, 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
5 changes: 4 additions & 1 deletion 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 Down Expand Up @@ -130,7 +133,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
68 changes: 59 additions & 9 deletions adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,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 local subagents | Normalized through `langchain-mcp-adapters` | Normalized | Constrained declarative local delegation |
| [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 subagents are limited
to declarative local subagents that inherit 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
Comment thread
AjayThorve marked this conversation as resolved.

`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` | Adapter-owned escape hatch | Adapter-owned escape hatch | Adapter-owned escape hatch | Adapter-owned escape hatch |
| `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
79 changes: 45 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,32 @@ 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`
- `allowed_tools` and `permission_mode`
- `max_budget_usd`
- `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

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.

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 +125,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 +150,8 @@ from nemo_fabric import (
Fabric,
FabricConfig,
HarnessConfig,
InstructionConfig,
InstructionsConfig,
McpConfig,
McpServerConfig,
MetadataConfig,
Expand All @@ -168,9 +168,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 +178,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
11 changes: 10 additions & 1 deletion adapters/claude/fabric-adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
"module": "nemo_fabric_adapters.claude.adapter"
},
"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
Loading