Skip to content

feat(python): stabilize SDK lifecycle contract - #26

Merged
AjayThorve merged 8 commits into
NVIDIA:mainfrom
AjayThorve:ajay/fabric-python-sdk-contract
Jun 29, 2026
Merged

feat(python): stabilize SDK lifecycle contract#26
AjayThorve merged 8 commits into
NVIDIA:mainfrom
AjayThorve:ajay/fabric-python-sdk-contract

Conversation

@AjayThorve

@AjayThorve AjayThorve commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Defines the stable native-only Python SDK contract around one source abstraction: paths use ordered profile names, while FabricConfig uses ordered FabricProfileConfig overlays.
  • Adds typed mutable config objects and read-only mapping-compatible plans, diagnostics, requests, results, artifacts, telemetry, events, capabilities, and session metadata.
  • Removes unreleased compatibility aliases (profile, *_config, start, from_text, and CLI-backed FabricClient construction) from the public SDK.
  • Makes profiles recursive overlays with object merge and scalar/list/null replacement, validates the merged config, and preserves additive fields across Python and Rust serialization.
  • Adds explicit harness family identity, ordered profiles, runtime capabilities, stable runtime defaults, typed errors, deterministic lifecycle cleanup, and a buffered stream() contract.
  • Keeps CLI output and SDK behavior aligned for equivalent config/profile stacks, and updates README, Harbor metadata projection, native smokes, and contract tests.

Notes

Test Plan

  • cargo fmt --all -- --check
  • cargo test --workspace --locked
  • cargo check -p fabric-python
  • uv run pytest -q (83 passed, 4 skipped)
  • uv run python python/tests/smoke_native_sdk.py
  • uv run python python/tests/smoke_typed_config.py
  • uv run python python/tests/smoke_readme_examples.py
  • uv run python python/tests/smoke_harbor_integration.py
  • python3 tests/smoke_cli.py

Summary by CodeRabbit

  • New Features
    • Added ordered profiles end-to-end (config/planning/results) and introduced capability-gated session/runtime update flows via start_session, RuntimeCapabilities, and structured RunRequest/RunResult.
    • Extended configuration with forward-compatible extensions and profile overlay merging.
    • Added Python SDK contract coverage with new native resolve_config and expanded public types/errors.
  • Bug Fixes
    • Improved runtime/session validation with stricter runtime handle checks and “modeled but not implemented” diagnostics.
    • Standardized harness identifiers (harness) across outputs and schemas.
  • Documentation
    • Refreshed README examples and added the Python SDK API contract document.
  • Tests
    • Added SDK contract tests and updated smoke/integration tests to the new API shapes.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Replaces singular profile handling with ordered profiles, renames harness_type to harness, adds runtime_binding to runtime handles, introduces runtime capabilities and unsupported routing, and ships a native-first Python SDK with typed models, sessions, errors, docs, and updated tests.

Changes

SDK MVP: profiles, harness, capabilities, runtime binding, and Python SDK

Layer / File(s) Summary
Docs and API guidance
README.md, POC-TO-MVP-PLAN.md, .coderabbit.yaml
The plan, README examples, and review instructions are updated to describe start_session, ordered profiles, FabricConfig.from_mapping(...), RunRequest, FabricError, and the source-tree install flow.
JSON schema contracts
schemas/*.json, tests/fixtures/hermes-shim-agent/.../fabric-adapter.json
Adapter descriptors, runtime handles, effective config, run plans/results, profile overlays, and capability targets now use required harness, ordered profiles, runtime_binding, RuntimeCapabilities, and broader additive config fields.
Rust config core
crates/fabric-core/src/config.rs
ProfileConfig becomes a recursive overlay, config structs gain flattened extensions, adapter descriptors require non-empty harness, effective/run plans use profiles, and runtime capability resolution is added.
Rust runtime, doctor, and Python binding
crates/fabric-core/src/runtime.rs, crates/fabric-core/src/doctor.rs, crates/fabric-core/src/lib.rs, crates/fabric-python/src/lib.rs, adapters/hermes-cli/fabric-adapter.json, adapters/hermes-sdk/fabric-adapter.json, crates/fabric-cli/src/main.rs
RuntimeHandle gains runtime_binding, RunResult uses profiles and harness, runtime validation checks binding/identity fields, doctor reports and warnings use the new profiles/capability model, resolve_config is exposed to Python, and adapter/CLI output switches to the new harness/profile shapes.
Python SDK types, errors, client, and session
python/src/nemo_fabric/errors.py, python/src/nemo_fabric/types.py, python/src/nemo_fabric/_config_sources.py, python/src/nemo_fabric/_native.pyi, python/src/nemo_fabric/__init__.py, python/src/nemo_fabric/client.py, python/src/nemo_fabric/session.py, python/src/nemo_fabric/integrations/harbor.py
Adds the structured error hierarchy, typed config/result models, input normalization helpers, native stub updates, expanded exports, the native-only client rewrite, session lifecycle handling, and Harbor metadata updates for profiles and harness.
SDK contract doc and README
docs/python-sdk-contract.md
Documents the MVP SDK contract and the new API shapes, lifecycle rules, error hierarchy, compatibility guarantees, and non-goals.
SDK contract and session tests
tests/test_sdk_contract.py, tests/test_session.py
Adds contract coverage for typed models, native dispatch, run/session lifecycle, concurrency, cancellation, and cleanup behavior.
Integration and smoke test updates
python/tests/smoke_*.py, tests/smoke_*.py, tests/test_hermes_cli*.py, tests/smoke_hermes_session.py
Smoke and integration tests are updated to the new profiles/input/start_session APIs and the new typed result shapes.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant FabricClient
  participant _native
  participant Session

  Caller->>FabricClient: start_session(agent, profiles, session_id)
  FabricClient->>_native: resolve / plan
  _native-->>FabricClient: RunPlan JSON
  FabricClient->>_native: start_runtime(...)
  _native-->>FabricClient: RuntimeHandle JSON
  FabricClient-->>Caller: Session

  Caller->>Session: invoke(input, overrides)
  Session->>_native: invoke_runtime(...)
  _native-->>Session: RunResult JSON
  Session-->>Caller: RunResult

  Caller->>Session: stop()
  Session->>_native: stop_runtime(...)
  _native-->>Session: ok
  Session-->>Caller: stopped
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • NVIDIA/NeMo-Fabric#25: Direct overlap on runtime_binding, handle validation, unsupported capability routing, and runtime/doctor updates.

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and accurately summarizes the PR’s main SDK lifecycle contract changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@AjayThorve
AjayThorve force-pushed the ajay/fabric-python-sdk-contract branch 5 times, most recently from 43626e4 to bb8a3b2 Compare June 27, 2026 05:09
@AjayThorve

Copy link
Copy Markdown
Collaborator Author

@coderabbitai can you review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@AjayThorve On it — I’ll review the changes now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AjayThorve AjayThorve changed the title feat(python): add typed run request contract feat(python): stabilize SDK lifecycle contract Jun 27, 2026
@AjayThorve
AjayThorve force-pushed the ajay/fabric-python-sdk-contract branch from abee70e to f48af92 Compare June 28, 2026 02:40
@AjayThorve

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fabric-core/src/config.rs`:
- Around line 1071-1089: The session capability is being enabled for all
Process/Python adapters in RuntimeCapabilities::resolve_runtime_capabilities,
but it must also be gated by the runtime transport. Update
resolve_runtime_capabilities so capabilities.session is only true when
runtime.mode is Session, the adapter kind is Process or Python, and the
transport is actually supported for sessions; keep unsupported transports like
http and native_plugin from reporting session support. Use the existing
FabricConfig.runtime and AdapterDescriptor checks in
resolve_runtime_capabilities to enforce this capability gate.

In `@python/src/nemo_fabric/client.py`:
- Around line 312-315: The exception handling in the client startup path drops
the structured stage when wrapping native session startup failures. Update the
relevant wrapper in the Fabric client startup flow so that the `start_runtime`
failure is re-raised as a `FabricRuntimeError` while preserving the normalized
structured failure metadata, especially the `stage`, consistent with the other
SDK error paths. Use the `FabricError`/`FabricRuntimeError` handling in
`client.py` to locate the fix.

In `@python/src/nemo_fabric/session.py`:
- Around line 66-67: Session is always serializing invocations by storing a
single _current_task, which breaks RunPlan.capabilities.concurrent_invocations.
Update Session.invoke and stop handling to allow multiple overlapping turns when
the capability is enabled, while still rejecting overlaps only when it is
disabled; replace the single-task bookkeeping in Session with tracking for all
active tasks so stop() can affect every in-flight invocation. Keep the
async/session behavior aligned with the native extension and review the related
Session methods around invoke and stop for the required parity.
- Around line 273-281: The _json_mapping helper is allowing non-string mapping
keys to be silently coerced by json.dumps, which mutates session override shape.
Update _json_mapping in session.py to validate that all keys are strings before
the JSON round-trip, and raise FabricConfigError for any non-string key while
keeping the existing JSON-compatible value check and error handling. Use the
existing _json_mapping symbol and FabricConfigError path so the public JSON
contract stays consistent with the native extension.

In `@python/src/nemo_fabric/types.py`:
- Around line 443-454: The _freeze helper currently returns _ConfigMapping
objects unchanged, which lets mutable config state leak into FabricMapping
snapshots and makes EffectiveConfig.config and to_mapping() non-defensive.
Update _freeze so _ConfigMapping is copied into an immutable or detached
structure just like other mutable mappings, and make sure the read-only snapshot
behavior is preserved consistently anywhere FabricMapping is built or exposed,
including the related logic around EffectiveConfig and to_mapping().
- Around line 650-657: The request normalization in the `data` builder is
treating any falsy `context` value as missing by using `context or {}`, which
silently turns invalid inputs like empty lists into empty mappings. Update the
`types.py` request assembly so only `None` falls back to `{}` and all other
values are passed through to `_mapping(context, "request context")` for
validation, matching the behavior already used for `overrides` and
`extra_fields`.

In `@python/tests/smoke_sdk_sessions.py`:
- Around line 190-195: The stopped-session assertion in the smoke test is
catching the wrong exception type, which is broader than the API contract.
Update the `session.invoke()` error check in `smoke_sdk_sessions.py` to catch
`FabricStateError` instead of `RuntimeError`, matching the behavior already
verified in `test_session.py` and ensuring `Session.invoke()` surfaces the typed
SDK error.
- Around line 211-220: The smoke test in failed_result_exposes_structured_error
only asserts the structured failed RunResult and never exercises cleanup. After
the existing failure assertions, explicitly stop the session via the session
object returned by _session/native flow so this test also covers the error-path
lifecycle cleanup and can catch regressions in stop() after a failed invoke.

In `@python/tests/smoke_typed_config.py`:
- Around line 163-175: The CLI-vs-SDK smoke parity check in the typed config
test only validates a subset of the plan shape, so it can miss drift in the
newly stabilized fields. Update the parity assertions in the helper around
client.plan and _cli_plan to compare the new public fields as well, especially
adapter_descriptor, harness_type, and top-level capabilities, in addition to the
existing config and plan sections. Keep the assertions aligned with the SDK
plan’s to_mapping output so this smoke test covers the full changed API surface.

In `@README.md`:
- Around line 200-208: The README example currently catches FabricError in the
FabricClient usage example and immediately wraps it in RuntimeError, which drops
the SDK’s structured contract. Update the example around FabricClient.run to
handle FabricError directly: inspect or log its structured fields like stage,
code, retryable, and details, then re-raise the same FabricError unchanged or
otherwise handle it without converting it. Keep the guidance aligned with the
stable Python SDK contract so readers see FabricError as the normalized failure
type.

In `@tests/test_session.py`:
- Around line 282-293: Extend the one-shot lifecycle test in
test_run_stops_runtime_after_success_and_failure to cover the stop-cleanup
failure path as well as the invoke failure path. Use the existing native_client
and mock_native setup around FabricClient.run and _run_native_lifecycle, then
make stop_runtime raise an error after a successful invoke_runtime and assert
that run still raises FabricRuntimeError with the cleanup failure message. Keep
the existing assertions that stop_runtime is called after both success and
failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2c1abed4-cb0a-4bc3-a195-93fcc67e2778

📥 Commits

Reviewing files that changed from the base of the PR and between 799d964 and f48af92.

📒 Files selected for processing (44)
  • .coderabbit.yaml
  • POC-TO-MVP-PLAN.md
  • README.md
  • adapters/hermes-cli/fabric-adapter.json
  • adapters/hermes-sdk/fabric-adapter.json
  • crates/fabric-cli/src/main.rs
  • crates/fabric-core/src/config.rs
  • crates/fabric-core/src/doctor.rs
  • crates/fabric-core/src/error.rs
  • crates/fabric-core/src/lib.rs
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-python/src/lib.rs
  • docs/python-sdk-contract.md
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/_native.pyi
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/errors.py
  • python/src/nemo_fabric/integrations/harbor.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/types.py
  • python/tests/smoke_environment_handle.py
  • python/tests/smoke_harbor_integration.py
  • python/tests/smoke_native_sdk.py
  • python/tests/smoke_readme_examples.py
  • python/tests/smoke_sdk.py
  • python/tests/smoke_sdk_concurrency.py
  • python/tests/smoke_sdk_sessions.py
  • python/tests/smoke_typed_config.py
  • schemas/adapter-descriptor.schema.json
  • schemas/adapter-invocation.schema.json
  • schemas/agent.schema.json
  • schemas/effective-config.schema.json
  • schemas/profile.schema.json
  • schemas/run-plan.schema.json
  • schemas/run-result.schema.json
  • schemas/runtime-handle.schema.json
  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json
  • tests/smoke_cli.py
  • tests/smoke_hermes_session.py
  • tests/test_hermes_cli.py
  • tests/test_hermes_cli_preflight.py
  • tests/test_sdk_contract.py
  • tests/test_session.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/test_hermes_cli_preflight.py
  • python/tests/smoke_environment_handle.py
  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json
  • tests/smoke_cli.py
  • python/tests/smoke_harbor_integration.py
  • python/tests/smoke_sdk_concurrency.py
  • tests/smoke_hermes_session.py
  • python/tests/smoke_sdk.py
  • tests/test_hermes_cli.py
  • python/tests/smoke_readme_examples.py
  • tests/test_sdk_contract.py
  • python/tests/smoke_native_sdk.py
  • python/tests/smoke_sdk_sessions.py
  • tests/test_session.py
  • python/tests/smoke_typed_config.py
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/_native.pyi
  • python/src/nemo_fabric/integrations/harbor.py
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/errors.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public Fabric contracts.

Files:

  • adapters/hermes-cli/fabric-adapter.json
  • adapters/hermes-sdk/fabric-adapter.json
crates/fabric-core/src/**/*.rs

⚙️ CodeRabbit configuration file

crates/fabric-core/src/**/*.rs: Review the Rust core for runtime lifecycle correctness, handle validation, capability routing accuracy, schema stability, and error semantics.
Public API changes should match committed schemas, tests, and documentation.

Files:

  • crates/fabric-core/src/lib.rs
  • crates/fabric-core/src/error.rs
  • crates/fabric-core/src/doctor.rs
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-core/src/config.rs
schemas/**/*

⚙️ CodeRabbit configuration file

schemas/**/*: Schemas are generated public contract snapshots. Check that schema diffs correspond to intentional Rust type changes and are covered by core tests.

Files:

  • schemas/runtime-handle.schema.json
  • schemas/adapter-descriptor.schema.json
  • schemas/run-result.schema.json
  • schemas/profile.schema.json
  • schemas/effective-config.schema.json
  • schemas/agent.schema.json
  • schemas/adapter-invocation.schema.json
  • schemas/run-plan.schema.json
crates/fabric-python/**/*

⚙️ CodeRabbit configuration file

crates/fabric-python/**/*: Treat native binding changes as public API changes. Check JSON/type conversion, error propagation, GIL/thread behavior, and parity with the Python SDK.

Files:

  • crates/fabric-python/src/lib.rs
{docs/**,README.md,AGENTS.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,AGENTS.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency with generated schemas.

Files:

  • README.md
  • docs/python-sdk-contract.md
🧬 Code graph analysis (13)
tests/test_hermes_cli_preflight.py (1)
python/src/nemo_fabric/_native.pyi (1)
  • run (25-32)
python/tests/smoke_sdk_concurrency.py (2)
python/src/nemo_fabric/_native.pyi (1)
  • run (25-32)
python/src/nemo_fabric/integrations/harbor.py (1)
  • run (70-99)
crates/fabric-core/src/error.rs (1)
crates/fabric-core/src/config.rs (1)
  • FabricError (16-16)
tests/test_hermes_cli.py (1)
python/src/nemo_fabric/_native.pyi (1)
  • run (25-32)
python/tests/smoke_readme_examples.py (1)
python/src/nemo_fabric/_native.pyi (2)
  • plan (13-13)
  • doctor (19-19)
python/src/nemo_fabric/session.py (3)
python/src/nemo_fabric/client.py (1)
  • Session (317-323)
python/src/nemo_fabric/_native.pyi (4)
  • plan (13-13)
  • start_runtime (42-42)
  • invoke_runtime (43-47)
  • stop_runtime (48-48)
python/tests/smoke_sdk_sessions.py (1)
  • stop_runtime (132-134)
python/tests/smoke_native_sdk.py (1)
python/src/nemo_fabric/_native.pyi (2)
  • plan (13-13)
  • run (25-32)
crates/fabric-core/src/doctor.rs (1)
crates/fabric-core/src/config.rs (5)
  • ResolutionStrategy (275-290)
  • AdapterKind (453-462)
  • RuntimeMode (522-529)
  • Transport (534-543)
  • resolve_run_plan (755-758)
tests/test_session.py (1)
python/src/nemo_fabric/_native.pyi (5)
  • plan (13-13)
  • plan_config (14-18)
  • run (25-32)
  • invoke_runtime (43-47)
  • stop_runtime (48-48)
python/tests/smoke_typed_config.py (2)
tests/smoke_cli.py (1)
  • run (181-193)
python/src/nemo_fabric/_native.pyi (3)
  • plan (13-13)
  • doctor (19-19)
  • run (25-32)
python/src/nemo_fabric/client.py (5)
python/src/nemo_fabric/_native.pyi (7)
  • inspect (7-7)
  • plan_config (14-18)
  • doctor_config (20-24)
  • start_runtime (42-42)
  • plan (13-13)
  • doctor (19-19)
  • run (25-25)
python/tests/smoke_native_sdk.py (2)
  • resolve (17-17)
  • FabricClient (23-23)
python/tests/smoke_sdk_sessions.py (2)
  • resolve (15-15)
  • run (233-233)
tests/smoke_cli.py (5)
  • resolve (17-17)
  • plan (36-36)
  • doctor (98-98)
  • run (172-172)
  • run (181-181)
tests/test_hermes_cli.py (1)
  • FabricClient (13-13)
python/src/nemo_fabric/types.py (1)
crates/fabric-core/src/doctor.rs (1)
  • check (456-463)
crates/fabric-core/src/config.rs (1)
crates/fabric-cli/src/main.rs (4)
  • serde_json (167-167)
  • RunPlan (20-20)
  • resolve_effective_config_with_profiles (22-22)
  • resolve_effective_config_with_profiles (135-135)
🪛 ast-grep (0.44.0)
python/src/nemo_fabric/_config_sources.py

[info] 75-75: use jsonify instead of json.dumps for JSON output
Context: json.dumps(config.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 81-81: use jsonify instead of json.dumps for JSON output
Context: json.dumps([profile.to_mapping() for profile in profiles])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_sdk_contract.py

[info] 473-473: Do not hardcode temporary file or directory names
Context: "/tmp/relay"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)


[info] 482-482: Do not hardcode temporary file or directory names
Context: "/tmp/relay"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)


[info] 287-287: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 292-292: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan()["effective_config"])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 301-301: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan()["effective_config"])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 310-310: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 314-314: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_runtime())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 323-353: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"agent_name": "demo",
"profiles": ["typed"],
"harness_type": "hermes",
"adapter_kind": "python",
"adapter_id": "test.fabric.shim",
"runtime_id": json.loads(runtime_json)["runtime_id"],
"invocation_id": "invocation-1",
"request_id": request["request_id"],
"status": "failed" if request["input"] == "fail" else "succeeded",
"output": {"received": request["input"]},
"error": {
"stage": "invoke",
"code": "adapter_failed",
"message": "adapter failed",
"retryable": False,
}
if request["input"] == "fail"
else None,
"artifacts": {"artifacts": []},
"events": [
{
"event_id": "event-1",
"timestamp_millis": 1,
"kind": "invocation_end",
"message": "completed",
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 357-357: use jsonify instead of json.dumps for JSON output
Context: json.dumps([])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/src/nemo_fabric/session.py

[info] 149-149: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 150-150: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 151-151: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 236-236: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 237-237: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 278-278: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(value))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 361-361: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(plan))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 363-363: use jsonify instead of json.dumps for JSON output
Context: json.dumps(runtime)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 369-369: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(request))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_session.py

[info] 99-99: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 101-101: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 103-103: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_runtime())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 109-129: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"agent_name": "demo",
"profiles": ["typed"],
"harness_type": "hermes",
"adapter_kind": "python",
"adapter_id": "test.fabric.shim",
"runtime_id": "runtime-1",
"invocation_id": f"invocation-{turn}",
"request_id": request["request_id"],
"status": "succeeded",
"output": {
"messages": [
{"role": "user", "content": request["input"]},
{"role": "assistant", "content": f"reply-{turn}"},
]
},
"artifacts": {"artifacts": []},
"events": [],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 132-132: use jsonify instead of json.dumps for JSON output
Context: json.dumps([])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 178-178: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan("oneshot"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/tests/smoke_typed_config.py

[error] 194-200: Use of unsanitized data to create processes
Context: subprocess.run(
args,
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)


[error] 194-200: Command coming from incoming request
Context: subprocess.run(
args,
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

python/src/nemo_fabric/client.py

[info] 309-309: use jsonify instead of json.dumps for JSON output
Context: json.dumps(plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 LanguageTool
docs/python-sdk-contract.md

[grammar] ~6-~6: Ensure spelling is correct
Context: ...ces, resolution, planning, diagnostics, oneshot runs, sessions, typed results and error...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.15.18)
python/tests/smoke_sdk.py

[warning] 53-53: Avoid specifying long messages outside the exception class

(TRY003)

python/src/nemo_fabric/_config_sources.py

[warning] 21-21: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 29-32: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 33-33: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 40-40: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 43-43: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 53-55: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 58-61: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 68-68: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 75-75: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_sdk_contract.py

[warning] 285-285: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 290-290: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 298-298: Unused method argument: profiles_json

(ARG002)


[warning] 299-299: Unused method argument: base_dir

(ARG002)


[warning] 307-307: Unused method argument: profiles_json

(ARG002)


[warning] 308-308: Unused method argument: base_dir

(ARG002)


[warning] 318-318: Unused method argument: plan_json

(ARG002)


[warning] 321-321: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 356-356: Unused method argument: plan_json

(ARG002)


[warning] 356-356: Unused method argument: runtime_json

(ARG002)


[warning] 369-369: Unused method argument: method

(ARG002)


[error] 474-474: Probable insecure usage of temporary file or directory: "/tmp/relay"

(S108)


[error] 483-483: Probable insecure usage of temporary file or directory: "/tmp/relay"

(S108)


[warning] 671-671: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)


[warning] 679-679: Unused method argument: mode

(ARG002)


[warning] 679-679: Unused method argument: exclude_none

(ARG002)


[warning] 684-684: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)


[warning] 694-694: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

python/src/nemo_fabric/session.py

[warning] 49-49: Dynamically typed expressions (typing.Any) are disallowed in client

(ANN401)


[error] 112-112: Function argument input is shadowing a Python builtin

(A002)


[warning] 112-112: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 121-121: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 123-123: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 125-125: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 158-158: Consider moving this statement to an else block

(TRY300)


[error] 169-169: Function argument input is shadowing a Python builtin

(A002)


[warning] 169-169: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 192-192: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 194-198: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 199-203: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 209-213: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 214-218: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 226-226: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 228-228: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 230-230: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 266-266: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 277-277: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 281-281: Avoid specifying long messages outside the exception class

(TRY003)


[error] 300-300: Function argument input is shadowing a Python builtin

(A002)


[warning] 300-300: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 315-317: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 320-322: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 327-327: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 339-339: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 357-357: Dynamically typed expressions (typing.Any) are disallowed in native

(ANN401)


[warning] 394-394: Dynamically typed expressions (typing.Any) are disallowed in func

(ANN401)


[warning] 394-394: Dynamically typed expressions (typing.Any) are disallowed in _call_blocking

(ANN401)


[warning] 399-402: Use contextlib.suppress(Exception) instead of try-except-pass

Replace try-except-pass with with contextlib.suppress(Exception): ...

(SIM105)


[error] 401-402: try-except-pass detected, consider logging the exception

(S110)


[warning] 401-401: Do not catch blind exception: Exception

(BLE001)


[warning] 409-413: Avoid specifying long messages outside the exception class

(TRY003)

python/tests/smoke_sdk_sessions.py

[warning] 141-141: Unused method argument: method

(ARG002)


[warning] 184-184: Assertion should be broken down into multiple parts

Break down assertion into multiple parts

(PT018)


[warning] 195-195: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 206-206: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_session.py

[warning] 100-100: Unused lambda argument: path

(ARG005)


[warning] 100-100: Unused lambda argument: profiles

(ARG005)


[warning] 102-102: Unused lambda argument: config_json

(ARG005)


[warning] 102-102: Unused lambda argument: profiles_json

(ARG005)


[warning] 102-102: Unused lambda argument: base_dir

(ARG005)


[warning] 106-106: Unused function argument: plan_json

(ARG001)


[warning] 106-106: Unused function argument: runtime_json

(ARG001)


[warning] 179-179: Unused lambda argument: path

(ARG005)


[warning] 179-179: Unused lambda argument: profiles

(ARG005)


[warning] 241-241: Missing return type annotation for private function blocking

(ANN202)


[warning] 265-265: Missing return type annotation for private function blocking

(ANN202)

python/tests/smoke_typed_config.py

[error] 195-195: subprocess call: check for execution of untrusted input

(S603)

python/src/nemo_fabric/client.py

[warning] 58-58: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 216-216: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 232-232: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[error] 247-247: Function argument input is shadowing a Python builtin

(A002)


[warning] 333-333: Dynamically typed expressions (typing.Any) are disallowed in start_service

(ANN401)


[warning] 344-344: Dynamically typed expressions (typing.Any) are disallowed in start_service

(ANN401)


[warning] 353-353: Unused method argument: overrides

(ARG002)


[warning] 358-363: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 365-365: Dynamically typed expressions (typing.Any) are disallowed in _native_module

(ANN401)


[warning] 368-368: Dynamically typed expressions (typing.Any) are disallowed in _require_native_module

(ANN401)

python/src/nemo_fabric/types.py

[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in _plain

(ANN401)


[warning] 36-36: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 42-42: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 45-45: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 48-48: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 50-50: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 54-54: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 56-56: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 60-60: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 62-62: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 71-71: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 89-91: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 98-98: Dynamically typed expressions (typing.Any) are disallowed in __getattr__

(ANN401)


[warning] 106-106: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 148-148: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 180-180: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 208-208: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 217-217: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 221-221: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 263-263: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 308-308: Dynamically typed expressions (typing.Any) are disallowed in tools

(ANN401)


[warning] 335-335: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 339-339: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 342-342: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 344-344: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 393-393: Dynamically typed expressions (typing.Any) are disallowed in tools

(ANN401)


[warning] 421-421: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 425-425: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 443-443: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 443-443: Dynamically typed expressions (typing.Any) are disallowed in _freeze

(ANN401)


[warning] 457-457: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 457-457: Dynamically typed expressions (typing.Any) are disallowed in _thaw

(ANN401)


[warning] 481-481: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 488-488: Dynamically typed expressions (typing.Any) are disallowed in __getitem__

(ANN401)


[warning] 497-497: Dynamically typed expressions (typing.Any) are disallowed in __getattr__

(ANN401)


[error] 644-644: Function argument input is shadowing a Python builtin

(A002)


[warning] 644-644: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 660-662: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 667-667: Remove quotes from type annotation

Remove quotes

(UP037)

🔇 Additional comments (19)
.coderabbit.yaml (1)

85-101: 📐 Maintainability & Code Quality

Move tools to the top level if CodeRabbit expects top-level analyzer config.

The tools block is still indented under reviews, unlike knowledge_base at the top level. If tools is only recognized there, these analyzer settings will be ignored.

crates/fabric-python/src/lib.rs (1)

10-11: LGTM!

Also applies to: 40-54, 221-221

python/src/nemo_fabric/_native.pyi (1)

7-11: LGTM!

python/src/nemo_fabric/errors.py (1)

13-49: LGTM!

python/src/nemo_fabric/_config_sources.py (1)

21-82: LGTM!

python/src/nemo_fabric/__init__.py (1)

6-75: LGTM!

python/src/nemo_fabric/client.py (1)

55-270: LGTM!

Also applies to: 294-311, 324-376

crates/fabric-core/src/error.rs (1)

112-125: LGTM!

crates/fabric-core/src/runtime.rs (2)

10-11: LGTM!

Also applies to: 26-27, 59-97, 228-246, 334-345, 426-453, 555-563, 603-697, 1147-1152, 1161-1183, 1721-1721, 1985-2205


508-553: 🩺 Stability & Availability

Remove this concern RunPlan has no HashMap/HashSet fields in the serialized graph, and this workspace’s serde_json build doesn’t enable preserve_order, so the binding hash remains deterministic.

			> Likely an incorrect or invalid review comment.
crates/fabric-core/src/doctor.rs (1)

13-16: LGTM!

Also applies to: 46-54, 58-75, 117-129, 131-170, 204-221, 378-386, 493-495, 529-541, 569-611

python/tests/smoke_native_sdk.py (1)

112-120: 📐 Maintainability & Code Quality

No action needed here; the typed FabricConfig/FabricProfileConfig calls already match FabricClient.resolve() and plan()’s public overloads.

			> Likely an incorrect or invalid review comment.
schemas/adapter-invocation.schema.json (1)

115-121: LGTM!

Also applies to: 166-170, 234-247, 256-256, 376-376, 463-463, 493-493, 522-522, 569-569, 590-590, 629-629, 708-739, 847-847, 861-861, 978-981

schemas/agent.schema.json (1)

19-19: LGTM!

Also applies to: 87-87, 117-117, 146-146, 170-170, 191-191, 230-230, 284-315, 340-340, 354-354, 416-416

schemas/effective-config.schema.json (1)

19-204: LGTM!

Also applies to: 233-317, 371-402, 427-441, 524-535

schemas/profile.schema.json (1)

3-3: LGTM!

Also applies to: 13-42, 52-57, 67-83

schemas/run-plan.schema.json (1)

4-4: LGTM!

Also applies to: 25-63, 109-109, 151-151, 226-232, 277-281, 348-358, 367-367, 487-487, 574-574, 604-604, 633-633, 680-680, 701-701, 740-740, 821-896, 921-921, 935-935, 1062-1065, 1075-1078, 1108-1111, 1142-1143

adapters/hermes-sdk/fabric-adapter.json (1)

3-3: LGTM!

tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json (1)

3-3: LGTM!

Comment thread crates/fabric-core/src/config.rs
Comment thread python/src/nemo_fabric/client.py Outdated
Comment thread python/src/nemo_fabric/session.py
Comment thread python/src/nemo_fabric/session.py
Comment thread python/src/nemo_fabric/types.py
Comment thread python/tests/smoke_sdk_sessions.py
Comment thread python/tests/smoke_sdk_sessions.py
Comment thread python/tests/smoke_typed_config.py
Comment thread README.md Outdated
Comment thread tests/test_session.py
@AjayThorve
AjayThorve force-pushed the ajay/fabric-sdk-consumer-contract branch from 799d964 to a573d8d Compare June 28, 2026 03:10
@AjayThorve
AjayThorve marked this pull request as ready for review June 29, 2026 19:00
@AjayThorve
AjayThorve changed the base branch from ajay/fabric-sdk-consumer-contract to main June 29, 2026 19:00
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
@AjayThorve
AjayThorve force-pushed the ajay/fabric-python-sdk-contract branch from 7a96c68 to 0b812fa Compare June 29, 2026 19:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/fabric-cli/src/main.rs (1)

448-452: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don't synthesize a default profile name from an empty stack.

RunPlan.profiles is now the canonical identity surface. Returning "default" here invents a profile that is not present in the plan, so the CLI can drift from SDK-visible state and mask bugs where the ordered stack is dropped upstream.

Suggested fix
 fn profile_label(plan: &RunPlan) -> String {
-    if !plan.profiles.is_empty() {
-        return plan.profiles.join(", ");
-    }
-    "default".to_string()
+    if plan.profiles.is_empty() {
+        return "[]".to_string();
+    }
+    plan.profiles.join(", ")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fabric-cli/src/main.rs` around lines 448 - 452, The profile_label
helper is inventing a fallback profile name when RunPlan.profiles is empty,
which can hide upstream state issues. Update profile_label to derive the label
only from the actual plan.profiles stack and avoid returning a synthetic
"default" value; if the stack is empty, propagate that emptiness consistently in
the CLI output. Use the profile_label function and RunPlan.profiles as the key
symbols when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fabric-core/src/config.rs`:
- Around line 376-403: The generated profile schema is missing explicit overlay
fields for metadata and profiles, so they currently fall into extensions as
arbitrary JSON instead of first-class object-or-null sections. Update
ProfileConfigSchema in config.rs to add optional metadata and profiles members
alongside harness, models, runtime, environment, tools, skills, mcp, and
telemetry, and ensure the schema generation reflects those as documented
overlays rather than flattened extras. Then verify any schema snapshots, tests,
and docs that rely on the public profile.schema.json contract are updated to
include the new explicit sections.

In `@docs/python-sdk-contract.md`:
- Around line 158-160: Clarify the defaulting behavior in the contract text so
it matches FabricConfig.from_mapping and FabricConfig: when environment is
omitted, the typed config remains environment=None and the local environment is
applied later during resolution. Update the sentence around the defaults in this
section to distinguish mutable config construction from resolution-time
defaults, and keep the wording aligned with the current SDK behavior and
generated schemas.
- Around line 451-452: The stream contract wording is backwards: in the
documented `stream()` behavior, clarify that adapters may buffer and callers
cannot assume immediate or unbuffered event emission before the terminal
`RunResult`. Update the description in the `stream()` section so it accurately
states that event delivery timing is not guaranteed, while keeping the notes
about additive event kinds and metadata consistent with the current API and
generated schemas.

In `@python/src/nemo_fabric/client.py`:
- Line 258: The async lifecycle methods are calling the synchronous native
planning path directly on the event loop, unlike doctor(). Update Client.run(),
Client.start_session(), and Client.start_service() to invoke self.plan(...) via
_call_blocking so planning is offloaded consistently. Keep the existing
plan(agent, profiles=profiles, base_dir=base_dir) call shape, but wrap the
native planning work in the same blocking helper pattern used by doctor() to
preserve parity with the native extension and avoid blocking async execution.

In `@python/src/nemo_fabric/session.py`:
- Around line 261-264: The message handling in session.py is too strict: in the
block that assigns self._messages from result.output["messages"], dict(message)
will crash if messages contains non-mapping JSON values like strings. Update the
logic in this section of the session/session result processing to only convert
mapping entries to dicts and safely ignore or preserve non-mapping items instead
of assuming every element is a mapping.
- Around line 146-164: The invoke path in Session.invoke currently re-raises
native/runtime parsing failures while leaving the session state unchanged, which
can leave a dead handle marked ACTIVE. Update the exception handling around
native.invoke_runtime and the json.loads/RunResult.from_mapping flow so
request-validation FabricError cases remain state-preserving, but native invoke
or parsing failures transition the Session to FAILED before raising
FabricRuntimeError. Use the Session.invoke method and Session.status/_absorb
behavior to locate the fix.

In `@python/src/nemo_fabric/types.py`:
- Around line 611-620: The normalizers are still masking missing or malformed
profiles by defaulting to an empty tuple, which breaks the required
ordered-profiles contract. Update the affected normalization paths in
`FabricConfig._normalize` and the related helpers at the other referenced
locations so they validate that `profiles` is present and already ordered, and
raise on missing/invalid input instead of substituting `[]`. Keep the existing
`Path` and `FabricConfig.from_mapping` handling, but remove the permissive
fallback and apply the same fail-fast behavior consistently across effective
config, run result, and session identity normalization.
- Around line 814-833: RuntimeHandle is still using the default FabricMapping
parsing path, so invalid or empty mappings can slip through and fail later; add
explicit validation in RuntimeHandle.from_mapping (or an equivalent constructor
override) to require runtime_id, runtime_binding, agent_name, harness, mode,
adapter_kind, and adapter_id shape before creating the object. Make this
rejection raise FabricConfigError, and keep the runtime_binding/native-boundary
checks aligned with the existing RuntimeHandle symbol so malformed inputs are
rejected at this boundary rather than accepted as arbitrary mappings.
- Around line 200-233: RuntimeConfig currently does not materialize the
documented stable defaults, leaving transport, input_schema, and output_schema
unset in RuntimeConfig.__init__ and RuntimeConfig.from_mapping. Update the
RuntimeConfig constructor so these fields default to "library", "text", and
"text" when not provided, while still allowing explicit overrides and preserving
extra_fields handling; use the existing RuntimeConfig symbols to keep the public
Python surface aligned with the runtime schema defaults.

In `@python/tests/smoke_native_sdk.py`:
- Around line 140-160: The smoke test in `test_smoke_native_sdk` only validates
`run()` output and session behavior, so it can miss regressions in the
stabilized `RunResult` API. Add assertions on the returned `RunResult` from
`client.run` to verify the new `profiles` and `harness` fields (and that legacy
`profile`/`harness_type` are not the expected surface), using the existing
`result` variable in this test to anchor the checks.

In `@python/tests/smoke_sdk_concurrency.py`:
- Around line 23-29: Promote the concurrency smoke scenario in
smoke_sdk_concurrency.py from the standalone main() coroutine into a
pytest-discovered async test. Replace main() with an async test function (using
the existing FabricClient and run_agent helpers) so pytest can collect and
report failures normally, and keep the async test unannotated since the async
runner detects it automatically.

In `@python/tests/smoke_sdk_sessions.py`:
- Around line 227-231: The smoke test file currently uses a single `main()`
aggregator to run `stable_runtime_across_turns`, `stream_and_lifecycle`,
`unsupported_cancel_leaves_session_active`, and
`failed_result_exposes_structured_error`, which hides independent failures from
pytest. Split each scenario into its own pytest test function (or parameterized
pytest case) so pytest can report lifecycle, cancellation, and error-path
regressions separately. Keep the existing scenario helpers if needed, but remove
the `main()` entrypoint and let pytest discover and run the tests directly.

In `@python/tests/smoke_sdk.py`:
- Around line 26-57: Move the negative-path smoke out of the standalone main()
entrypoint and into a pytest test so it runs under test discovery alongside the
other SDK smokes. Use a pytest fixture or context-managed setup in this test to
scope client_mod._native = None only within the test, then restore it afterward
so FabricClient.plan and FabricNativeUnavailableError are exercised without
leaking state into later tests. Keep the existing assertions on FabricConfig,
RunRequest, and the native-missing failure path, but make the test pytest-native
instead of relying on __main__ execution.

In `@python/tests/smoke_typed_config.py`:
- Around line 180-185: The smoke checks are currently bundled behind the main()
script runner, which makes each contract fail as one combined case. Refactor
smoke_typed_config.py to expose sdk_and_cli_profile_stacks_match,
resolves_and_diagnoses_without_a_directory, and runs_without_an_agent_package as
individual pytest tests so CI reports each parity regression separately. Keep
the existing async client usage with FabricClient, but let pytest discover and
run the async tests directly instead of calling main().

In `@schemas/adapter-descriptor.schema.json`:
- Around line 123-126: The adapter descriptor schema still allows an empty
harness string even though validate_adapter_descriptor_shape() in config.rs
rejects blank values. Update the harness property in
adapter-descriptor.schema.json to require a non-empty string by adding a
minLength constraint so the schema matches the runtime validation and the public
contract stays consistent.

In `@tests/test_sdk_contract.py`:
- Around line 305-397: Replace the handwritten native doubles in the test
contract setup with shared pytest fixtures, since NativeRecorder and
NativeClient duplicate the native harness pattern already used in
tests/test_session.py. Move the common native setup into conftest.py as reusable
fixtures, and swap the custom classes for unittest.mock.MagicMock or
AsyncMock-based mocks. Keep the FabricClient integration points (_native_module
and _require_native_module) wired through those fixtures so the contract tests
still exercise the same native paths without duplicating helper classes.
- Around line 44-53: The public contract test for FabricClient.start_service
only checks overloads, but it should verify the actual capability-gated failure
behavior. Add an async test around start_service that calls it on a client
without service support and asserts it raises FabricCapabilityError with the
documented stage, code, and details. Use the existing FabricClient and
FabricCapabilityError symbols so the test covers the promised API behavior, not
just the signature.

---

Outside diff comments:
In `@crates/fabric-cli/src/main.rs`:
- Around line 448-452: The profile_label helper is inventing a fallback profile
name when RunPlan.profiles is empty, which can hide upstream state issues.
Update profile_label to derive the label only from the actual plan.profiles
stack and avoid returning a synthetic "default" value; if the stack is empty,
propagate that emptiness consistently in the CLI output. Use the profile_label
function and RunPlan.profiles as the key symbols when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 45e5f85b-7912-400f-b284-31b68d2fbebe

📥 Commits

Reviewing files that changed from the base of the PR and between f48af92 and 0b812fa.

📒 Files selected for processing (43)
  • .coderabbit.yaml
  • POC-TO-MVP-PLAN.md
  • README.md
  • adapters/hermes-cli/fabric-adapter.json
  • adapters/hermes-sdk/fabric-adapter.json
  • crates/fabric-cli/src/main.rs
  • crates/fabric-core/src/config.rs
  • crates/fabric-core/src/doctor.rs
  • crates/fabric-core/src/lib.rs
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-python/src/lib.rs
  • docs/python-sdk-contract.md
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/_native.pyi
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/errors.py
  • python/src/nemo_fabric/integrations/harbor.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/types.py
  • python/tests/smoke_environment_handle.py
  • python/tests/smoke_harbor_integration.py
  • python/tests/smoke_native_sdk.py
  • python/tests/smoke_readme_examples.py
  • python/tests/smoke_sdk.py
  • python/tests/smoke_sdk_concurrency.py
  • python/tests/smoke_sdk_sessions.py
  • python/tests/smoke_typed_config.py
  • schemas/adapter-descriptor.schema.json
  • schemas/adapter-invocation.schema.json
  • schemas/agent.schema.json
  • schemas/effective-config.schema.json
  • schemas/profile.schema.json
  • schemas/run-plan.schema.json
  • schemas/run-result.schema.json
  • schemas/runtime-handle.schema.json
  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json
  • tests/smoke_cli.py
  • tests/smoke_hermes_session.py
  • tests/test_hermes_cli.py
  • tests/test_hermes_cli_preflight.py
  • tests/test_sdk_contract.py
  • tests/test_session.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Test
⚠️ CI failures not shown inline (2)

GitHub Actions: Rust / 0_Test.txt: feat(python): stabilize SDK lifecycle contract

Conclusion: failure

View job details

##[group]Run cargo test --workspace --locked
 �[36;1mcargo test --workspace --locked�[0m
 shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
 env:
   CARGO_INCREMENTAL: 0
   CARGO_PROFILE_DEV_DEBUG: 0
   CARGO_TERM_COLOR: always
   RUST_BACKTRACE: short
   RUSTFLAGS: -D warnings
   CARGO_UNSTABLE_SPARSE_REGISTRY: true
   CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
   CACHE_ON_FAILURE: false
 ##[endgroup]
 �[1m�[92m   Compiling�[0m fabric-core v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-core)
 �[1m�[92m   Compiling�[0m fabric-cli v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-cli)
 �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 8.28s
 �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/fabric-d6b0cd643d758f74)
 running 0 tests
 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
 �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/fabric_core-039dd9c04deba413)
 running 40 tests
 test config::tests::errors_for_unknown_manifest_profile ... ok

GitHub Actions: Rust / Test: feat(python): stabilize SDK lifecycle contract

Conclusion: failure

View job details

##[group]Run cargo test --workspace --locked
 �[36;1mcargo test --workspace --locked�[0m
 shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
 env:
   CARGO_INCREMENTAL: 0
   CARGO_PROFILE_DEV_DEBUG: 0
   CARGO_TERM_COLOR: always
   RUST_BACKTRACE: short
   RUSTFLAGS: -D warnings
   CARGO_UNSTABLE_SPARSE_REGISTRY: true
   CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
   CACHE_ON_FAILURE: false
 ##[endgroup]
 �[1m�[92m   Compiling�[0m fabric-core v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-core)
 �[1m�[92m   Compiling�[0m fabric-cli v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-cli)
 �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 8.28s
 �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/fabric-d6b0cd643d758f74)
 running 0 tests
 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
 �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/fabric_core-039dd9c04deba413)
 running 40 tests
 test config::tests::errors_for_unknown_manifest_profile ... ok
🧰 Additional context used
📓 Path-based instructions (8)
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json
  • python/tests/smoke_environment_handle.py
  • python/tests/smoke_harbor_integration.py
  • tests/test_hermes_cli_preflight.py
  • tests/test_hermes_cli.py
  • tests/smoke_cli.py
  • tests/smoke_hermes_session.py
  • python/tests/smoke_sdk_concurrency.py
  • python/tests/smoke_sdk.py
  • python/tests/smoke_readme_examples.py
  • python/tests/smoke_native_sdk.py
  • python/tests/smoke_typed_config.py
  • python/tests/smoke_sdk_sessions.py
  • tests/test_sdk_contract.py
  • tests/test_session.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run tests.
Do not add @pytest.mark.asyncio to test functions; async tests are detected and run automatically by the async runner.
Do not add a -> None return type annotation to test functions.
When mocking a class, use unittest.mock.MagicMock or unittest.mock.AsyncMock (with spec when needed) instead of defining a new class.
Name mocked classes with a mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not repeat fixtures across test files; if a fixture is needed in multiple test files, place it in conftest.py.
Define fixtures with @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and def <fixture_name>_fixture() -> <return_type>:; only specify scope when it is not Preferpytest.mark.parametrizeover creating individual tests for different input types. If a fixture is needed for a test but does not return a value or the value is unused, use@pytest.mark.usefixtures`.

Files:

  • tests/test_hermes_cli_preflight.py
  • tests/test_hermes_cli.py
  • tests/smoke_cli.py
  • tests/smoke_hermes_session.py
  • tests/test_sdk_contract.py
  • tests/test_session.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public Fabric contracts.

Files:

  • adapters/hermes-cli/fabric-adapter.json
  • adapters/hermes-sdk/fabric-adapter.json
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/integrations/harbor.py
  • python/src/nemo_fabric/errors.py
  • python/src/nemo_fabric/_native.pyi
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/types.py
schemas/**/*

⚙️ CodeRabbit configuration file

schemas/**/*: Schemas are generated public contract snapshots. Check that schema diffs correspond to intentional Rust type changes and are covered by core tests.

Files:

  • schemas/adapter-descriptor.schema.json
  • schemas/runtime-handle.schema.json
  • schemas/run-result.schema.json
  • schemas/profile.schema.json
  • schemas/agent.schema.json
  • schemas/effective-config.schema.json
  • schemas/adapter-invocation.schema.json
  • schemas/run-plan.schema.json
crates/fabric-python/**/*

⚙️ CodeRabbit configuration file

crates/fabric-python/**/*: Treat native binding changes as public API changes. Check JSON/type conversion, error propagation, GIL/thread behavior, and parity with the Python SDK.

Files:

  • crates/fabric-python/src/lib.rs
crates/fabric-core/src/**/*.rs

⚙️ CodeRabbit configuration file

crates/fabric-core/src/**/*.rs: Review the Rust core for runtime lifecycle correctness, handle validation, capability routing accuracy, schema stability, and error semantics.
Public API changes should match committed schemas, tests, and documentation.

Files:

  • crates/fabric-core/src/lib.rs
  • crates/fabric-core/src/doctor.rs
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-core/src/config.rs
{docs/**,README.md,AGENTS.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,AGENTS.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency with generated schemas.

Files:

  • README.md
  • docs/python-sdk-contract.md
🧠 Learnings (1)
📚 Learning: 2026-06-28T04:03:32.877Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 26
File: python/tests/smoke_typed_config.py:163-177
Timestamp: 2026-06-28T04:03:32.877Z
Learning: In NVIDIA NeMo Fabric Python SDK serialization of `RuntimeCapabilities` (to satisfy the “parity contract” with Rust core and the CLI), do not emit metadata keys when the corresponding metadata is absent. Instead, omit those fields entirely so the produced JSON matches the Rust/CLI output (e.g., avoid `null`, empty objects, or placeholder metadata). During review, verify the serializer/builders follow this omission rule and that Python outputs/parity tests reflect the same shape.

Applied to files:

  • python/tests/smoke_environment_handle.py
  • python/tests/smoke_harbor_integration.py
  • python/src/nemo_fabric/integrations/harbor.py
  • python/src/nemo_fabric/errors.py
  • python/tests/smoke_sdk_concurrency.py
  • python/src/nemo_fabric/__init__.py
  • python/tests/smoke_sdk.py
  • python/tests/smoke_readme_examples.py
  • python/tests/smoke_native_sdk.py
  • python/tests/smoke_typed_config.py
  • python/tests/smoke_sdk_sessions.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/types.py
🧬 Code graph analysis (2)
crates/fabric-core/src/doctor.rs (1)
crates/fabric-core/src/config.rs (4)
  • ResolutionStrategy (275-290)
  • AdapterKind (453-462)
  • RuntimeMode (522-529)
  • Transport (534-543)
python/src/nemo_fabric/client.py (1)
crates/fabric-core/src/config.rs (1)
  • EffectiveConfig (1300-1313)
🪛 ast-grep (0.44.0)
python/tests/smoke_typed_config.py

[error] 196-202: Command coming from incoming request
Context: subprocess.run(
args,
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 196-202: Use of unsanitized data to create processes
Context: subprocess.run(
args,
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)

tests/test_sdk_contract.py

[info] 534-534: Do not hardcode temporary file or directory names
Context: "/tmp/relay"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)


[info] 543-543: Do not hardcode temporary file or directory names
Context: "/tmp/relay"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)


[info] 313-313: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 318-318: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan()["effective_config"])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 327-327: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan()["effective_config"])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 336-336: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 340-340: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_runtime())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 349-379: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"agent_name": "demo",
"profiles": ["typed"],
"harness": "hermes",
"adapter_kind": "python",
"adapter_id": "test.fabric.shim",
"runtime_id": json.loads(runtime_json)["runtime_id"],
"invocation_id": "invocation-1",
"request_id": request["request_id"],
"status": "failed" if request["input"] == "fail" else "succeeded",
"output": {"received": request["input"]},
"error": {
"stage": "invoke",
"code": "adapter_failed",
"message": "adapter failed",
"retryable": False,
}
if request["input"] == "fail"
else None,
"artifacts": {"artifacts": []},
"events": [
{
"event_id": "event-1",
"timestamp_millis": 1,
"kind": "invocation_end",
"message": "completed",
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 383-383: use jsonify instead of json.dumps for JSON output
Context: json.dumps([])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/src/nemo_fabric/_config_sources.py

[info] 75-75: use jsonify instead of json.dumps for JSON output
Context: json.dumps(config.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 81-81: use jsonify instead of json.dumps for JSON output
Context: json.dumps([profile.to_mapping() for profile in profiles])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/src/nemo_fabric/client.py

[info] 311-311: use jsonify instead of json.dumps for JSON output
Context: json.dumps(plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_session.py

[info] 100-100: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 102-102: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 104-104: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_runtime())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 110-130: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"agent_name": "demo",
"profiles": ["typed"],
"harness": "hermes",
"adapter_kind": "python",
"adapter_id": "test.fabric.shim",
"runtime_id": "runtime-1",
"invocation_id": f"invocation-{turn}",
"request_id": request["request_id"],
"status": "succeeded",
"output": {
"messages": [
{"role": "user", "content": request["input"]},
{"role": "assistant", "content": f"reply-{turn}"},
]
},
"artifacts": {"artifacts": []},
"events": [],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 133-133: use jsonify instead of json.dumps for JSON output
Context: json.dumps([])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 179-179: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan("oneshot"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/src/nemo_fabric/session.py

[info] 149-149: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 150-150: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 151-151: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 236-236: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 237-237: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 293-293: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(value), allow_nan=False)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 376-376: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(plan))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 378-378: use jsonify instead of json.dumps for JSON output
Context: json.dumps(runtime)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 384-384: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(request))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 LanguageTool
docs/python-sdk-contract.md

[grammar] ~6-~6: Ensure spelling is correct
Context: ...ces, resolution, planning, diagnostics, oneshot runs, sessions, typed results and error...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.15.20)
python/tests/smoke_sdk.py

[warning] 53-53: Avoid specifying long messages outside the exception class

(TRY003)

python/tests/smoke_typed_config.py

[error] 197-197: subprocess call: check for execution of untrusted input

(S603)

python/tests/smoke_sdk_sessions.py

[warning] 142-142: Unused method argument: method

(ARG002)


[warning] 185-185: Assertion should be broken down into multiple parts

Break down assertion into multiple parts

(PT018)


[warning] 196-196: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 207-207: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_sdk_contract.py

[warning] 311-311: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 316-316: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 324-324: Unused method argument: profiles_json

(ARG002)


[warning] 325-325: Unused method argument: base_dir

(ARG002)


[warning] 333-333: Unused method argument: profiles_json

(ARG002)


[warning] 334-334: Unused method argument: base_dir

(ARG002)


[warning] 344-344: Unused method argument: plan_json

(ARG002)


[warning] 347-347: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 382-382: Unused method argument: plan_json

(ARG002)


[warning] 382-382: Unused method argument: runtime_json

(ARG002)


[warning] 395-395: Unused method argument: method

(ARG002)


[error] 535-535: Probable insecure usage of temporary file or directory: "/tmp/relay"

(S108)


[error] 544-544: Probable insecure usage of temporary file or directory: "/tmp/relay"

(S108)


[warning] 732-732: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)


[warning] 740-740: Unused method argument: mode

(ARG002)


[warning] 740-740: Unused method argument: exclude_none

(ARG002)


[warning] 745-745: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)


[warning] 755-755: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

python/src/nemo_fabric/_config_sources.py

[warning] 21-21: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 29-32: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 33-33: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 40-40: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 43-43: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 53-55: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 58-61: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 68-68: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 75-75: Avoid specifying long messages outside the exception class

(TRY003)

python/src/nemo_fabric/client.py

[warning] 59-59: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 217-217: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 233-233: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[error] 248-248: Function argument input is shadowing a Python builtin

(A002)


[warning] 335-335: Dynamically typed expressions (typing.Any) are disallowed in start_service

(ANN401)


[warning] 346-346: Dynamically typed expressions (typing.Any) are disallowed in start_service

(ANN401)


[warning] 355-355: Unused method argument: overrides

(ARG002)


[warning] 360-365: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 367-367: Dynamically typed expressions (typing.Any) are disallowed in _native_module

(ANN401)


[warning] 370-370: Dynamically typed expressions (typing.Any) are disallowed in _require_native_module

(ANN401)

tests/test_session.py

[warning] 101-101: Unused lambda argument: path

(ARG005)


[warning] 101-101: Unused lambda argument: profiles

(ARG005)


[warning] 103-103: Unused lambda argument: config_json

(ARG005)


[warning] 103-103: Unused lambda argument: profiles_json

(ARG005)


[warning] 103-103: Unused lambda argument: base_dir

(ARG005)


[warning] 107-107: Unused function argument: plan_json

(ARG001)


[warning] 107-107: Unused function argument: runtime_json

(ARG001)


[warning] 180-180: Unused lambda argument: path

(ARG005)


[warning] 180-180: Unused lambda argument: profiles

(ARG005)


[warning] 280-280: Missing return type annotation for private function blocking

(ANN202)


[warning] 304-304: Missing return type annotation for private function blocking

(ANN202)

python/src/nemo_fabric/session.py

[warning] 49-49: Dynamically typed expressions (typing.Any) are disallowed in client

(ANN401)


[error] 112-112: Function argument input is shadowing a Python builtin

(A002)


[warning] 112-112: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 121-121: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 123-123: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 125-125: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 158-158: Consider moving this statement to an else block

(TRY300)


[error] 169-169: Function argument input is shadowing a Python builtin

(A002)


[warning] 169-169: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 192-192: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 194-198: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 199-203: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 209-213: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 214-218: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 226-226: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 228-228: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 230-230: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 266-266: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 277-277: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 289-289: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 296-296: Avoid specifying long messages outside the exception class

(TRY003)


[error] 315-315: Function argument input is shadowing a Python builtin

(A002)


[warning] 315-315: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 330-332: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 335-337: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 342-342: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 354-354: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 372-372: Dynamically typed expressions (typing.Any) are disallowed in native

(ANN401)


[warning] 409-409: Dynamically typed expressions (typing.Any) are disallowed in func

(ANN401)


[warning] 409-409: Dynamically typed expressions (typing.Any) are disallowed in _call_blocking

(ANN401)


[warning] 414-417: Use contextlib.suppress(Exception) instead of try-except-pass

Replace try-except-pass with with contextlib.suppress(Exception): ...

(SIM105)


[error] 416-417: try-except-pass detected, consider logging the exception

(S110)


[warning] 416-416: Do not catch blind exception: Exception

(BLE001)


[warning] 424-428: Avoid specifying long messages outside the exception class

(TRY003)

python/src/nemo_fabric/types.py

[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 25-25: Dynamically typed expressions (typing.Any) are disallowed in _plain

(ANN401)


[warning] 36-36: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 42-42: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 45-45: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 48-48: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 50-50: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 54-54: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 56-56: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 60-60: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 62-62: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 71-71: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 89-91: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 98-98: Dynamically typed expressions (typing.Any) are disallowed in __getattr__

(ANN401)


[warning] 106-106: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 148-148: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 183-183: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 211-211: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 220-220: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 224-224: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 272-272: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 317-317: Dynamically typed expressions (typing.Any) are disallowed in tools

(ANN401)


[warning] 348-348: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 352-352: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 355-355: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 357-357: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 406-406: Dynamically typed expressions (typing.Any) are disallowed in tools

(ANN401)


[warning] 434-434: Use a dictionary comprehension instead of a for-loop

(PERF403)


[warning] 438-438: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 456-456: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 456-456: Dynamically typed expressions (typing.Any) are disallowed in _freeze

(ANN401)


[warning] 470-470: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 470-470: Dynamically typed expressions (typing.Any) are disallowed in _thaw

(ANN401)


[warning] 484-484: Dynamically typed expressions (typing.Any) are disallowed in value

(ANN401)


[warning] 484-484: Dynamically typed expressions (typing.Any) are disallowed in _snapshot_value

(ANN401)


[warning] 504-504: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 511-511: Dynamically typed expressions (typing.Any) are disallowed in __getitem__

(ANN401)


[warning] 523-523: Dynamically typed expressions (typing.Any) are disallowed in __getattr__

(ANN401)


[error] 686-686: Function argument input is shadowing a Python builtin

(A002)


[warning] 686-686: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 708-710: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 715-715: Remove quotes from type annotation

Remove quotes

(UP037)

🔇 Additional comments (23)
python/src/nemo_fabric/client.py (1)

4-209: LGTM!

Also applies to: 259-272, 306-306, 308-378

python/src/nemo_fabric/session.py (1)

1-145: LGTM!

Also applies to: 166-260, 266-429

python/src/nemo_fabric/integrations/harbor.py (1)

171-172: LGTM!

adapters/hermes-cli/fabric-adapter.json (1)

3-3: LGTM!

adapters/hermes-sdk/fabric-adapter.json (1)

3-3: LGTM!

tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json (1)

3-3: LGTM!

crates/fabric-python/src/lib.rs (1)

40-54: 🎯 Functional Correctness

No SDK leak here FabricClient.resolve() wraps _native.resolve_config() failures into FabricConfigError, so the public Python API does not expose the binding’s generic PyRuntimeError.

			> Likely an incorrect or invalid review comment.
python/tests/smoke_readme_examples.py (1)

23-41: LGTM!

Also applies to: 76-87

python/tests/smoke_harbor_integration.py (1)

96-97: LGTM!

Also applies to: 151-151

tests/smoke_cli.py (1)

60-60: LGTM!

Also applies to: 139-139

tests/smoke_hermes_session.py (1)

78-89: LGTM!

Also applies to: 105-116

tests/test_hermes_cli.py (1)

18-22: LGTM!

Also applies to: 53-59, 95-96, 108-111, 157-157

tests/test_hermes_cli_preflight.py (1)

31-35: LGTM!

schemas/adapter-invocation.schema.json (1)

115-121: LGTM!

Also applies to: 166-170, 237-247, 256-256, 376-376, 463-463, 493-493, 522-522, 569-569, 590-590, 629-629, 708-739, 847-847, 861-861, 978-981

crates/fabric-core/src/runtime.rs (1)

59-65: LGTM!

Also applies to: 228-236, 334-344, 413-558, 599-665, 766-770, 893-900, 971-975, 1104-1110, 1142-1177, 1708-1720, 1884-1891, 1978-2199

crates/fabric-core/src/lib.rs (1)

18-19: LGTM!

crates/fabric-core/src/doctor.rs (1)

46-73: LGTM!

Also applies to: 102-169, 204-220, 378-385, 493-611

schemas/agent.schema.json (1)

19-19: LGTM!

Also applies to: 87-87, 117-117, 146-146, 170-170, 191-191, 230-230, 284-315, 340-340, 354-354, 416-416

schemas/effective-config.schema.json (1)

19-204: LGTM!

Also applies to: 233-317, 371-402, 427-441, 524-535

schemas/profile.schema.json (1)

2-3: LGTM!

Also applies to: 13-42, 52-57, 67-83

schemas/run-plan.schema.json (1)

4-4: LGTM!

Also applies to: 25-25, 41-44, 63-63, 109-109, 151-151, 226-232, 277-281, 348-358, 367-367, 487-487, 574-574, 604-604, 633-633, 680-680, 701-701, 740-740, 821-865, 876-876, 885-896, 921-921, 935-935, 1062-1065, 1108-1111, 1142-1143

schemas/run-result.schema.json (1)

271-272: LGTM!

Also applies to: 288-293, 321-322

schemas/runtime-handle.schema.json (1)

153-156: LGTM!

Also applies to: 171-177

Comment thread crates/fabric-core/src/config.rs
Comment thread docs/python-sdk-contract.md Outdated
Comment thread docs/python-sdk-contract.md Outdated
Comment thread python/src/nemo_fabric/client.py Outdated
Comment thread python/src/nemo_fabric/session.py Outdated
Comment thread python/tests/smoke_sdk.py
Comment thread python/tests/smoke_typed_config.py
Comment thread schemas/adapter-descriptor.schema.json
Comment thread tests/test_sdk_contract.py
Comment thread tests/test_sdk_contract.py
Comment thread python/src/nemo_fabric/client.py Outdated
Signed-off-by: Ajay Thorve <athorve@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_sdk_contract.py (1)

520-535: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use contract-valid RunResult fixtures in these tests.

Both cases build RunResult.from_mapping(...) payloads without required identity fields such as harness, adapter_kind, runtime_id, and invocation_id. That locks in acceptance of result snapshots that the published contract does not allow. Either populate the missing fields here or flip these to rejection tests so the Python model stays aligned with the documented/native schema. As per path instructions, schemas/run-result.schema.json requires ordered profiles and a required harness, and test coverage should reflect the changed API contract.

Also applies to: 547-557

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_sdk_contract.py` around lines 520 - 535, Use contract-valid
RunResult fixtures in the affected RunResult.from_mapping tests: the current
payloads are missing required identity fields like harness, adapter_kind,
runtime_id, and invocation_id, so they should either be updated to include those
fields or rewritten as rejection cases. Update the fixtures around
test_run_result_wraps_nested_error_and_keeps_mapping_access and the other
matching test so they align with RunResult and the published run-result schema
contract.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/src/nemo_fabric/_config_sources.py`:
- Around line 37-45: Reject raw mapping inputs in path_profiles() by adding an
explicit mapping-type check before the final list(profiles) fallback, so
dict-like profile values raise FabricConfigError instead of being treated as
keys. Update the path_profiles function in _config_sources to handle the
existing None, str, and bytes cases, then reject mappings from
client.plan/profile parsing paths so mixed bare-string/raw-mapping profile
inputs do not reach native code.

In `@python/src/nemo_fabric/client.py`:
- Around line 361-364: The start_service method in client.py is receiving
overrides but never uses or validates it before the capability error path, so
wire the parameter into the service startup flow or explicitly validate/reject
unsupported overrides there; make sure the logic in start_service either applies
overrides to the request/config construction or checks them and raises a clear
error before continuing, using the existing start_service signature and related
service/client setup symbols to place the fix.

---

Outside diff comments:
In `@tests/test_sdk_contract.py`:
- Around line 520-535: Use contract-valid RunResult fixtures in the affected
RunResult.from_mapping tests: the current payloads are missing required identity
fields like harness, adapter_kind, runtime_id, and invocation_id, so they should
either be updated to include those fields or rewritten as rejection cases.
Update the fixtures around
test_run_result_wraps_nested_error_and_keeps_mapping_access and the other
matching test so they align with RunResult and the published run-result schema
contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 27fada6b-e923-48fe-8e00-15b288c73c2c

📥 Commits

Reviewing files that changed from the base of the PR and between 0b812fa and e0bdabd.

📒 Files selected for processing (12)
  • crates/fabric-core/src/config.rs
  • crates/fabric-core/src/schema.rs
  • docs/python-sdk-contract.md
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/session.py
  • python/src/nemo_fabric/types.py
  • python/tests/smoke_native_sdk.py
  • schemas/adapter-descriptor.schema.json
  • schemas/run-plan.schema.json
  • tests/test_sdk_contract.py
  • tests/test_session.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Test
⚠️ CI failures not shown inline (2)

GitHub Actions: Rust / Test: feat(python): stabilize SDK lifecycle contract

Conclusion: failure

View job details

##[group]Run cargo test --workspace --locked
 �[36;1mcargo test --workspace --locked�[0m
 shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
 env:
   CARGO_INCREMENTAL: 0
   CARGO_PROFILE_DEV_DEBUG: 0
   CARGO_TERM_COLOR: always
   RUST_BACKTRACE: short
   RUSTFLAGS: -D warnings
   CARGO_UNSTABLE_SPARSE_REGISTRY: true
   CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
   CACHE_ON_FAILURE: false
 ##[endgroup]
 �[1m�[92m   Compiling�[0m fabric-core v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-core)
 �[1m�[92m   Compiling�[0m fabric-cli v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-cli)
 �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 8.72s
 �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/fabric-d6b0cd643d758f74)
 running 0 tests
 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
 �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/fabric_core-039dd9c04deba413)
 running 41 tests
 test config::tests::errors_for_unknown_manifest_profile ... ok

GitHub Actions: Rust / 0_Test.txt: feat(python): stabilize SDK lifecycle contract

Conclusion: failure

View job details

##[group]Run cargo test --workspace --locked
 �[36;1mcargo test --workspace --locked�[0m
 shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
 env:
   CARGO_INCREMENTAL: 0
   CARGO_PROFILE_DEV_DEBUG: 0
   CARGO_TERM_COLOR: always
   RUST_BACKTRACE: short
   RUSTFLAGS: -D warnings
   CARGO_UNSTABLE_SPARSE_REGISTRY: true
   CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
   CACHE_ON_FAILURE: false
 ##[endgroup]
 �[1m�[92m   Compiling�[0m fabric-core v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-core)
 �[1m�[92m   Compiling�[0m fabric-cli v0.1.0 (/home/runner/work/NeMo-Fabric/NeMo-Fabric/crates/fabric-cli)
 �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 8.72s
 �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/fabric-d6b0cd643d758f74)
 running 0 tests
 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
 �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/fabric_core-039dd9c04deba413)
 running 41 tests
 test config::tests::errors_for_unknown_manifest_profile ... ok
🧰 Additional context used
📓 Path-based instructions (6)
crates/fabric-core/src/**/*.rs

⚙️ CodeRabbit configuration file

crates/fabric-core/src/**/*.rs: Review the Rust core for runtime lifecycle correctness, handle validation, capability routing accuracy, schema stability, and error semantics.
Public API changes should match committed schemas, tests, and documentation.

Files:

  • crates/fabric-core/src/schema.rs
  • crates/fabric-core/src/config.rs
schemas/**/*

⚙️ CodeRabbit configuration file

schemas/**/*: Schemas are generated public contract snapshots. Check that schema diffs correspond to intentional Rust type changes and are covered by core tests.

Files:

  • schemas/adapter-descriptor.schema.json
  • schemas/run-plan.schema.json
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • python/tests/smoke_native_sdk.py
  • tests/test_sdk_contract.py
  • tests/test_session.py
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
  • python/src/nemo_fabric/session.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run tests.
Do not add @pytest.mark.asyncio to test functions; async tests are detected and run automatically by the async runner.
Do not add a -> None return type annotation to test functions.
When mocking a class, use unittest.mock.MagicMock or unittest.mock.AsyncMock (with spec when needed) instead of defining a new class.
Name mocked classes with a mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not repeat fixtures across test files; if a fixture is needed in multiple test files, place it in conftest.py.
Define fixtures with @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and def <fixture_name>_fixture() -> <return_type>:; only specify scope when it is not Preferpytest.mark.parametrizeover creating individual tests for different input types. If a fixture is needed for a test but does not return a value or the value is unused, use@pytest.mark.usefixtures`.

Files:

  • tests/test_sdk_contract.py
  • tests/test_session.py
{docs/**,README.md,AGENTS.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,AGENTS.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency with generated schemas.

Files:

  • docs/python-sdk-contract.md
🧠 Learnings (1)
📚 Learning: 2026-06-28T04:03:32.877Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 26
File: python/tests/smoke_typed_config.py:163-177
Timestamp: 2026-06-28T04:03:32.877Z
Learning: In NVIDIA NeMo Fabric Python SDK serialization of `RuntimeCapabilities` (to satisfy the “parity contract” with Rust core and the CLI), do not emit metadata keys when the corresponding metadata is absent. Instead, omit those fields entirely so the produced JSON matches the Rust/CLI output (e.g., avoid `null`, empty objects, or placeholder metadata). During review, verify the serializer/builders follow this omission rule and that Python outputs/parity tests reflect the same shape.

Applied to files:

  • python/tests/smoke_native_sdk.py
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
  • python/src/nemo_fabric/session.py
🧬 Code graph analysis (5)
tests/test_sdk_contract.py (2)
python/src/nemo_fabric/types.py (2)
  • from_mapping (234-243)
  • to_mapping (552-557)
python/src/nemo_fabric/errors.py (2)
  • FabricCapabilityError (45-46)
  • FabricConfigError (33-34)
tests/test_session.py (2)
python/src/nemo_fabric/session.py (1)
  • SessionStatus (36-41)
python/src/nemo_fabric/errors.py (3)
  • FabricCapabilityError (45-46)
  • FabricStateError (41-42)
  • FabricRuntimeError (37-38)
python/src/nemo_fabric/types.py (2)
crates/fabric-core/src/config.rs (2)
  • EffectiveConfig (690-690)
  • RunPlan (757-757)
crates/fabric-core/src/schema.rs (2)
  • RuntimeHandle (141-141)
  • RunResult (144-144)
python/src/nemo_fabric/session.py (1)
python/src/nemo_fabric/errors.py (2)
  • FabricError (14-30)
  • FabricRuntimeError (37-38)
crates/fabric-core/src/config.rs (1)
crates/fabric-core/src/schema.rs (2)
  • AdapterDescriptor (60-60)
  • AdapterDescriptor (13-13)
🪛 ast-grep (0.44.0)
tests/test_sdk_contract.py

[info] 364-364: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 369-369: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_plan()["effective_config"])
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_session.py

[info] 265-265: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"input": "hello", "request_id": "request-1"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 270-270: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

python/src/nemo_fabric/session.py

[info] 150-150: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._plan.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 151-151: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 152-152: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.15.20)
python/src/nemo_fabric/_config_sources.py

[warning] 43-43: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_sdk_contract.py

[warning] 213-221: Wrong values type in pytest.mark.parametrize expected list

Use list for parameter values

(PT007)


[warning] 233-238: Wrong values type in pytest.mark.parametrize expected list of tuple

Use list of tuple for parameter values

(PT007)


[warning] 362-362: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 367-367: Dynamically typed expressions (typing.Any) are disallowed in profile

(ANN401)


[warning] 822-822: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

tests/test_session.py

[warning] 385-385: Missing return type annotation for private function record_plan

(ANN202)


[warning] 385-385: Dynamically typed expressions (typing.Any) are disallowed in *args

(ANN401)


[warning] 385-385: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)

python/src/nemo_fabric/client.py

[warning] 364-364: Unused method argument: overrides

(ARG002)

python/src/nemo_fabric/types.py

[warning] 62-62: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 65-65: Avoid specifying long messages outside the exception class

(TRY003)

python/src/nemo_fabric/session.py

[warning] 273-273: Remove quotes from type annotation

Remove quotes

(UP037)

🔇 Additional comments (8)
crates/fabric-core/src/schema.rs (1)

227-233: LGTM!

python/tests/smoke_native_sdk.py (1)

153-163: LGTM!

python/src/nemo_fabric/client.py (1)

71-88: LGTM!

Also applies to: 118-135, 165-182, 216-247, 259-263, 283-326, 340-378, 383-391

python/src/nemo_fabric/types.py (1)

60-66: LGTM!

Also applies to: 622-648, 677-677, 831-860, 902-951

python/src/nemo_fabric/session.py (1)

146-163: LGTM!

Also applies to: 271-278

schemas/run-plan.schema.json (1)

4-4: LGTM!

Also applies to: 25-65, 111-153, 350-360, 369-489, 576-742, 823-898, 923-937

crates/fabric-core/src/config.rs (1)

121-125: LGTM!

tests/test_sdk_contract.py (1)

355-435: Reuse the shared MagicMock fixtures instead of custom native doubles.

NativeRecorder and NativeClient still duplicate the native test setup already covered elsewhere. As per coding guidelines, "When mocking a class, use unittest.mock.MagicMock or unittest.mock.AsyncMock," "Prefer pytest fixtures over helper methods," and "Do not repeat fixtures across test files; if a fixture is needed in multiple test files, place it in conftest.py."

Source: Coding guidelines

Comment thread python/src/nemo_fabric/_config_sources.py
Comment thread python/src/nemo_fabric/client.py
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
@AjayThorve

Copy link
Copy Markdown
Collaborator Author

Addressed the latest outside-diff RunResult fixture finding in 92307df. The affected fixtures now include every schema-required identity field, and RunResult.from_mapping() has regression coverage that rejects missing required identities at the Python boundary. Local validation: 115 passed, 4 skipped; 41 Rust tests passed; all 14 dependency-free smoke scripts passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_sdk_contract.py`:
- Around line 347-363: The shared _run_result() helper should be converted to a
pytest fixture-backed builder to match the repo’s test conventions. Replace the
local helper with a fixture that returns a callable for constructing the run
result, and update any tests that use _run_result() to request and call that
fixture instead. Keep the existing default fields and update-overrides behavior
intact while moving the setup into pytest fixture style.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b7a419b8-17ed-4aa6-8d22-7ee548ec8d87

📥 Commits

Reviewing files that changed from the base of the PR and between e0bdabd and 92307df.

📒 Files selected for processing (4)
  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
  • tests/test_sdk_contract.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run tests.
Do not add @pytest.mark.asyncio to test functions; async tests are detected and run automatically by the async runner.
Do not add a -> None return type annotation to test functions.
When mocking a class, use unittest.mock.MagicMock or unittest.mock.AsyncMock (with spec when needed) instead of defining a new class.
Name mocked classes with a mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not repeat fixtures across test files; if a fixture is needed in multiple test files, place it in conftest.py.
Define fixtures with @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and def <fixture_name>_fixture() -> <return_type>:; only specify scope when it is not Preferpytest.mark.parametrizeover creating individual tests for different input types. If a fixture is needed for a test but does not return a value or the value is unused, use@pytest.mark.usefixtures`.

Files:

  • tests/test_sdk_contract.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/test_sdk_contract.py
🧠 Learnings (1)
📚 Learning: 2026-06-28T04:03:32.877Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 26
File: python/tests/smoke_typed_config.py:163-177
Timestamp: 2026-06-28T04:03:32.877Z
Learning: In NVIDIA NeMo Fabric Python SDK serialization of `RuntimeCapabilities` (to satisfy the “parity contract” with Rust core and the CLI), do not emit metadata keys when the corresponding metadata is absent. Instead, omit those fields entirely so the produced JSON matches the Rust/CLI output (e.g., avoid `null`, empty objects, or placeholder metadata). During review, verify the serializer/builders follow this omission rule and that Python outputs/parity tests reflect the same shape.

Applied to files:

  • python/src/nemo_fabric/_config_sources.py
  • python/src/nemo_fabric/client.py
  • python/src/nemo_fabric/types.py
🧬 Code graph analysis (4)
python/src/nemo_fabric/_config_sources.py (1)
python/src/nemo_fabric/client.py (2)
  • path_profiles (106-106)
  • path_profiles (23-23)
tests/test_sdk_contract.py (3)
python/src/nemo_fabric/errors.py (1)
  • FabricConfigError (33-34)
python/src/nemo_fabric/client.py (1)
  • start_service (337-345)
crates/fabric-core/src/runtime.rs (1)
  • RunResult (60-98)
python/src/nemo_fabric/client.py (1)
python/src/nemo_fabric/session.py (1)
  • _json_mapping (282-305)
python/src/nemo_fabric/types.py (2)
python/src/nemo_fabric/client.py (1)
  • RunResult (49-49)
tests/test_sdk_contract.py (1)
  • RunResult (35-35)
🪛 ast-grep (0.44.0)
tests/test_sdk_contract.py

[info] 594-594: Do not hardcode temporary file or directory names
Context: "/tmp/relay"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)

🪛 Ruff (0.15.20)
python/src/nemo_fabric/_config_sources.py

[warning] 45-45: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_sdk_contract.py

[warning] 347-347: Dynamically typed expressions (typing.Any) are disallowed in **updates

(ANN401)


[error] 595-595: Probable insecure usage of temporary file or directory: "/tmp/relay"

(S108)


[warning] 610-618: Wrong values type in pytest.mark.parametrize expected list

Use list for parameter values

(PT007)

🔇 Additional comments (4)
python/src/nemo_fabric/_config_sources.py (1)

37-50: LGTM!

tests/test_sdk_contract.py (1)

539-550: LGTM!

Also applies to: 565-569, 591-599, 608-625, 719-726, 874-883

python/src/nemo_fabric/client.py (1)

357-379: LGTM!

python/src/nemo_fabric/types.py (1)

900-932: LGTM!

Comment on lines +347 to +363
def _run_result(**updates: Any) -> dict[str, Any]:
result = {
"agent_name": "demo",
"profiles": [],
"harness": "hermes",
"adapter_kind": "python",
"adapter_id": "test.fabric.shim",
"runtime_id": "runtime-1",
"invocation_id": "invocation-1",
"request_id": "request-1",
"status": "succeeded",
"output": None,
"artifacts": {"artifacts": []},
"events": [],
}
result.update(updates)
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a fixture-backed result factory here.

_run_result() is shared test setup. Converting it to a pytest fixture that returns a builder keeps this file aligned with the repo’s test conventions. As per coding guidelines, "Prefer pytest fixtures over helper methods."

🧰 Tools
🪛 Ruff (0.15.20)

[warning] 347-347: Dynamically typed expressions (typing.Any) are disallowed in **updates

(ANN401)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_sdk_contract.py` around lines 347 - 363, The shared _run_result()
helper should be converted to a pytest fixture-backed builder to match the
repo’s test conventions. Replace the local helper with a fixture that returns a
callable for constructing the run result, and update any tests that use
_run_result() to request and call that fixture instead. Keep the existing
default fields and update-overrides behavior intact while moving the setup into
pytest fixture style.

Source: Coding guidelines

@AjayThorve
AjayThorve merged commit 8ba0afd into NVIDIA:main Jun 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants