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
81 changes: 79 additions & 2 deletions crates/fabric-cli/src/scaffold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,25 @@ fn language_files(language: Language, config: &FabricConfig, example: &str) -> V
}

fn rust_core_dependency() -> String {
rust_string(env!("CARGO_PKG_VERSION"))
let source_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../fabric-core");
rust_core_dependency_for_path(&source_path)
}

fn rust_core_dependency_for_path(source_path: &Path) -> String {
let version = rust_string(env!("CARGO_PKG_VERSION"));
if !source_path.join("Cargo.toml").is_file() {
return version;
}
let Ok(source_path) = source_path.canonicalize() else {
return version;
};
let Some(source_path) = source_path.to_str() else {
return version;
};
format!(
"{{ path = {}, version = {version} }}",
rust_string(source_path)
)
Comment thread
zhongxuanwang-nv marked this conversation as resolved.
}

fn write_files(destination: &Path, files: &[ScaffoldFile]) -> Result<(), String> {
Expand Down Expand Up @@ -308,12 +326,71 @@ mod tests {
assert!(
manifest.contains(&format!("nemo-fabric-core = {}", rust_core_dependency()))
);
assert!(!manifest.contains("path ="));
assert!(manifest.contains("[workspace]"));
}
fs::remove_dir_all(destination).expect("remove scaffold");
}
}

#[test]
fn source_checkout_rust_scaffold_builds_inside_the_repository() {
let destination = Path::new(env!("CARGO_MANIFEST_DIR")).join(format!(
".nemo-fabric-scaffold-test-{}-build",
std::process::id()
));
let _ = fs::remove_dir_all(&destination);
init(
examples::find("code-review").expect("example"),
None,
Language::Rust,
&destination,
)
.expect("generate scaffold");

let output = std::process::Command::new(env!("CARGO"))
.args(["check", "--offline"])
.current_dir(&destination)
.output()
.expect("run cargo check");
assert!(
output.status.success(),
"generated Rust scaffold did not build:\n{}",
String::from_utf8_lossy(&output.stderr)
);
fs::remove_dir_all(destination).expect("remove scaffold");
}

#[test]
fn rust_core_dependency_uses_local_checkout_when_available() {
let source_path = destination("core-dependency-local", Language::Rust);
let _ = fs::remove_dir_all(&source_path);
fs::create_dir_all(&source_path).expect("create source checkout");
fs::write(source_path.join("Cargo.toml"), "").expect("write Cargo manifest");
let canonical_path = source_path.canonicalize().expect("canonicalize checkout");

assert_eq!(
rust_core_dependency_for_path(&source_path),
format!(
"{{ path = {}, version = {} }}",
rust_string(canonical_path.to_str().expect("UTF-8 checkout path")),
rust_string(env!("CARGO_PKG_VERSION"))
)
);

fs::remove_dir_all(source_path).expect("remove source checkout");
}

#[test]
fn rust_core_dependency_falls_back_when_checkout_is_unavailable() {
let source_path = destination("core-dependency-missing", Language::Rust);
let _ = fs::remove_dir_all(&source_path);

assert_eq!(
rust_core_dependency_for_path(&source_path),
rust_string(env!("CARGO_PKG_VERSION"))
);
}

#[test]
fn renderers_preserve_model_settings_and_temperature() {
let mut config = presets::find("hermes")
Expand Down
2 changes: 2 additions & 0 deletions crates/fabric-cli/templates/rust/Cargo.toml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ name = "{{PACKAGE}}-rust"
version = "0.1.0"
edition = "2024"

[workspace]

[dependencies]
nemo-fabric-core = {{NEMO_FABRIC_CORE_DEPENDENCY}}
serde_json = "1"
6 changes: 5 additions & 1 deletion docs/experimentation/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ cargo run -- "Review the workspace"
```

The Rust scaffold constructs `FabricConfig` and calls `fabric-core` directly.
Neither scaffold is loaded back into the central CLI.
When the CLI is built from a source checkout, the generated manifest uses an
absolute path to that checkout's `crates/fabric-core`. Keep the checkout
available, or replace the path dependency with a compatible published version
before moving the scaffold. Neither scaffold is loaded back into the central
CLI.

## CLI Boundaries

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,5 @@ uv sync --all-groups --all-extras
Then run the build command with the `no_uv=true` flag to avoid re-installing the dependencies:

```bash
just build-all no_uv=true
just no_uv=true build-all
```
12 changes: 6 additions & 6 deletions docs/integrations/harness/codex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ Use normalized NeMo Fabric fields so the same capability configuration can be
planned before the adapter starts:

```python
from examples.code_review_agent import codex_config

config = codex_config()
config.add_skill_path("./skills/code-review")
config.add_mcp_server(
"repo",
Expand Down Expand Up @@ -83,6 +86,9 @@ To override the SDK-selected app-server intentionally, set an absolute path or
a path relative to the NeMo Fabric config root in `harness.settings.codex_bin`:

```python
from examples.code_review_agent import codex_config

config = codex_config()
config.harness.settings["codex_bin"] = "/path/to/codex"
```

Expand Down Expand Up @@ -158,12 +164,6 @@ runtime-scoped SDK configuration. Relay owns gateway transport behavior,
including decoding `Content-Encoding` before it constructs managed LLM events.
There is no NeMo Fabric compression setting.

The initial NeMo Relay `0.6.0` source tag cannot recover semantic fields from
zstd-compressed Codex SDK requests. Until a later `0.6.x` release contains the
fix, use a build that includes
[NeMo Relay PR #452](https://github.com/NVIDIA/NeMo-Relay/pull/452). NeMo Fabric's
opt-in Relay E2E rejects opaque request bodies and missing token usage.

## Compare Phoenix Trace Modes

Use native Codex OpenTelemetry when you need low-level app-server diagnostics.
Expand Down
46 changes: 26 additions & 20 deletions docs/reference/api/python-library-reference/nemo_fabric.client.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description: "Resolve, plan, diagnose, and run agents with NVIDIA NeMo Fabric."
SPDX-License-Identifier: Apache-2.0 -->

# <kbd>module</kbd> `nemo_fabric.client`

Native Python client for resolving and running NeMo Fabric agents.


Expand All @@ -15,6 +16,7 @@ Native Python client for resolving and running NeMo Fabric agents.


## <kbd>class</kbd> `Fabric`

Primary Python entrypoint for NeMo Fabric.

Every lifecycle method accepts a complete, typed ``FabricConfig`` plus an optional ``base_dir`` used to resolve relative paths. Compose variants in Python before calling the SDK. The ``doctor()``, ``plan()``, and ``run()`` results are typed, read-only mapping models. ``start_runtime()`` returns an active ``Runtime`` handle.
Expand All @@ -32,10 +34,11 @@ See the Getting Started overview for runnable single-invocation, typed-config, a
### <kbd>method</kbd> `doctor`

```python
doctor(
config: 'FabricConfig',
base_dir: 'str | PathLike[str] | None' = None
) → DoctorReport
async def doctor(
config: FabricConfig,
*,
base_dir: str | os.PathLike[str] | None = None,
) -> DoctorReport
```

Diagnose a planned agent without starting its runtime.
Expand Down Expand Up @@ -67,10 +70,11 @@ Doctor checks the resolved adapter, capability mappings, and declared environmen
### <kbd>method</kbd> `plan`

```python
plan(
config: 'FabricConfig',
base_dir: 'str | PathLike[str] | None' = None
) → RunPlan
def plan(
config: FabricConfig,
*,
base_dir: str | os.PathLike[str] | None = None,
) -> RunPlan
```

Resolve a complete typed configuration into an immutable execution plan.
Expand Down Expand Up @@ -102,12 +106,13 @@ Planning resolves the selected adapter and reports optional runtime capabilities
### <kbd>method</kbd> `run`

```python
run(
config: 'FabricConfig',
base_dir: 'str | PathLike[str] | None' = None,
input: 'Any' = None,
request: 'RunRequest | None' = None
) → RunResult
async def run(
config: FabricConfig,
*,
base_dir: str | os.PathLike[str] | None = None,
input: Any = None,
request: RunRequest | None = None,
) -> RunResult
```

Execute one complete start, invoke, and stop lifecycle.
Expand Down Expand Up @@ -142,12 +147,13 @@ Execute one complete start, invoke, and stop lifecycle.
### <kbd>method</kbd> `start_runtime`

```python
start_runtime(
config: 'FabricConfig',
base_dir: 'str | PathLike[str] | None' = None,
overrides: 'Mapping[str, Any] | None' = None,
streaming: 'bool' = False
) → Runtime
async def start_runtime(
config: FabricConfig,
*,
base_dir: str | os.PathLike[str] | None = None,
overrides: Mapping[str, Any] | None = None,
streaming: bool = False,
) -> Runtime
```

Start a stateful runtime for one or more ordered invocations.
Expand Down
Loading
Loading