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
2 changes: 1 addition & 1 deletion .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Build SDK with native extension
run: |
uv venv --python 3.12 .venv
uv sync --group test --no-group dev --extra codex --extra harbor --extra hermes --extra relay --extra runtime
uv sync --group test --no-group dev --extra claude --extra codex --extra harbor --extra hermes --extra relay --extra runtime

- name: Run pytest
run: |
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ under `examples/code_review_agent/artifacts/hermes-sdk/`. Its complete base
config and clone-based variants live in
`examples/code_review_agent/config.py`.

## Claude Adapter

Build the local wheels and install Fabric with the independent Claude adapter:

```bash
just wheels
python -m pip install --find-links dist "nemo-fabric[claude]"
```

Refer to the [Claude adapter guide](adapters/claude/README.md) for
typed configuration, normalized tools, MCP and skills, multi-turn resume,
authentication, and execution details.

## Core Concepts

- **Agent source:** callers provide either an agent package path or a typed
Expand All @@ -126,7 +139,8 @@ config and clone-based variants live in
- **Adapters:** harness-specific integrations selected by `harness.adapter_id`.
The Hermes SDK and CLI adapters live under `adapters/hermes-sdk/` and
`adapters/hermes-cli/`; the Codex CLI adapter lives under
`adapters/codex-cli/`. Harness-specific extensions belong under
`adapters/codex-cli/`; the [Claude adapter](adapters/claude/README.md)
lives under `adapters/claude/`. Harness-specific extensions belong under
`harness.settings` so the normalized contract can remain stable.
- **Artifacts:** normalized output, logs, patches, and telemetry references
returned through an `ArtifactManifest`.
Expand Down
156 changes: 156 additions & 0 deletions adapters/claude/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# Claude Adapter

The `nvidia.fabric.claude` adapter uses the official Claude Agent SDK for
Python behind Fabric's normalized invocation contract. The SDK is an
implementation detail; consumers select the Claude harness by adapter ID.

## Install

```bash
just wheels
python -m pip install --find-links dist "nemo-fabric[claude]"
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Claude Code authentication can come from an existing cached login or from
`ANTHROPIC_API_KEY`. Package installation is verified by the adapter wheel and
module-entrypoint tests. Authentication is validated when Claude starts the
invocation.

## Execution Model

Each `invoke` starts a fresh adapter process. The adapter persists the terminal
Claude session ID under the Fabric artifact root, keyed by `runtime_id`, and
passes it as `ClaudeAgentOptions.resume` on the next invocation. One Fabric
runtime therefore maps to one Claude session even though no adapter process
stays resident.

## Configuration

Configure portable capabilities through the normalized `FabricConfig` fields:

- `models` selects the Claude model. A configured model must use
`provider="anthropic"`; normalized hosted/custom provider resolution is
tracked in [FABRIC-64](https://linear.app/nvidia/issue/FABRIC-64/add-normalized-model-provider-resolution-and-harness-compatibility).
- `environment.workspace` sets the Claude working directory.
- `tools` sets the base Claude tool list.
- `mcp` configures stdio, HTTP, streamable HTTP, or SSE servers. For stdio,
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 invocation.

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

- `system_prompt`, `allowed_tools`, `disallowed_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
- `env` for variables explicitly forwarded to Claude Code

Putting `model_name`, `cwd`, `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
by the SDK and is not persisted as a Fabric artifact.

## Typed Configuration

Build the agent configuration with the typed SDK models before invoking
Fabric:

```python
from pathlib import Path

from nemo_fabric import (
EnvironmentConfig,
Fabric,
FabricConfig,
HarnessConfig,
McpConfig,
McpServerConfig,
MetadataConfig,
ModelConfig,
RuntimeConfig,
SkillConfig,
)

base_dir = Path("/workspace/review-agent")
config = FabricConfig(
metadata=MetadataConfig(name="claude-review-agent"),
harness=HarnessConfig(
adapter_id="nvidia.fabric.claude",
resolution="preinstalled",
settings={
"system_prompt": "Review changes for correctness and regressions.",
"permission_mode": "dontAsk",
"max_turns": 8,
},
),
models={
"default": ModelConfig(
provider="anthropic",
model="your-claude-model",
api_key_env="ANTHROPIC_API_KEY",
)
},
runtime=RuntimeConfig(artifacts="./artifacts"),
environment=EnvironmentConfig(provider="local", workspace="."),
tools=["Read", "Glob", "Grep"],
mcp=McpConfig(
servers={
"repo": McpServerConfig(
transport="stdio",
url="repo-mcp --root .",
exposure="harness_native",
)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
),
skills=SkillConfig(paths=["./skills/code-review"]),
)

fabric = Fabric()
```

## One-Shot Run

```python
result = await fabric.run(
config,
base_dir=base_dir,
input="Inspect the repository",
)

print(result.output["response"])
print(result.output["session_id"])
```

## Multi-Turn Runtime

```python
async with await fabric.start_runtime(config, base_dir=base_dir) as runtime:
first = await runtime.invoke(input="Inspect the repository")
second = await runtime.invoke(input="Now review the latest patch")

assert first.runtime_id == second.runtime_id
assert first.output["session_id"] == second.output["session_id"]
```

Resume requires the same workspace and Claude state directory on the same host.
The Fabric-to-Claude correlation record alone is insufficient if Claude's
underlying transcript store is removed.

## Tests

The default suite uses a deterministic mock Claude Code CLI and requires no
credentials. Run the real integration only on an authenticated developer host:

```bash
RUN_FABRIC_CLAUDE_INTEGRATION=1 uv run --no-sync pytest tests/e2e/test_claude.py -q -k live
```
13 changes: 13 additions & 0 deletions adapters/claude/fabric-adapter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"contract_version": "fabric.adapter/v1alpha1",
"adapter_id": "nvidia.fabric.claude",
"harness": "claude",
"adapter_kind": "python",
"runner": {
"module": "nemo_fabric_adapters.claude.adapter",
"callable": "run"
},
"config": {
"accepts": ["models", "tools", "mcp", "skills"]
}
}
29 changes: 29 additions & 0 deletions adapters/claude/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

[build-system]
requires = [
"setuptools>=64",
]
build-backend = "setuptools.build_meta"

[project]
name = "nemo-fabric-adapters-claude"
version = "0.1.0"
description = "Claude adapter for NeMo Fabric"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"nemo-fabric-adapters-common == 0.1.0",
"claude-agent-sdk==0.2.114",
]

[tool.setuptools.packages.find]
where = ["src"]
include = ["nemo_fabric_adapters.claude*"]

[tool.setuptools.data-files]
"share/nemo-fabric/adapters/claude" = ["fabric-adapter.json"]

[tool.uv.sources]
nemo-fabric-adapters-common = { path = "../common", editable = true }
4 changes: 4 additions & 0 deletions adapters/claude/src/nemo_fabric_adapters/claude/__init__.py
Comment thread
AjayThorve marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Claude adapter for NeMo Fabric."""
Loading
Loading