Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
python/tests/smoke_typed_config.py
python/tests/smoke_consumer_neutral.py
python/tests/smoke_readme_examples.py
python/tests/smoke_sdk_sessions.py
tests/smoke_cli.py
tests/smoke_hermes_cli.py
tests/smoke_hermes_config_mapping.py
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,33 @@ plan = client.plan_config(
)
```

For multi-turn sessions, open a `Session` and invoke it repeatedly. The session
replays the accumulated transcript as conversation history so the harness has access to
prior turns:

```python
import asyncio

from nemo_fabric import FabricClient

async def chat():
async with await FabricClient().start(
"examples/code-review-agent", profile="hermes_session"
) as session:
await session.invoke("My name is Robin.")
reply = await session.invoke("What's my name?") # recalls "Robin"
print(session.id, session.status.value, len(session.messages))
print(reply["output"]["response"])

asyncio.run(chat())
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Sessions require the native binding and a session-capable (inline Python)
adapter; `start_config(...)` is the typed-config equivalent. `stream(...)` yields
events then the final result (buffered today); `cancel()` cooperatively aborts an
in-flight turn. Sessions are SDK-only — there is no `fabric` CLI equivalent (the
CLI runs one invocation per process). See `examples/session_quickstart.py`.

When installed from the repository root, `FabricClient()` uses the native Rust
binding. If the selected Python adapter descriptor provides a `runner.module`
and `runner.callable`, the SDK imports and invokes that adapter inline. The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ def settings_payload(payload: dict[str, Any]) -> dict[str, Any]:
return harness.get("settings") or payload.get("settings") or {}


def resolve_history(payload: dict[str, Any]) -> Any:
"""Conversation history for this turn.

A per-invocation request context wins over static harness settings, so the
SDK can drive multi-turn sessions by passing accumulated messages in
``request.context.history`` without mutating the agent config.
"""

context = request_payload(payload).get("context") or {}
# Check key presence so an explicit empty history ([]) clears the conversation
# rather than falling back to static harness settings.
if isinstance(context, dict) and "history" in context:
return context["history"]
return settings_payload(payload).get("history")


def models_payload(payload: dict[str, Any]) -> dict[str, Any]:
return fabric_config(payload).get("models") or payload.get("models") or {}

Expand Down Expand Up @@ -393,7 +409,7 @@ def run_hermes_sdk(payload: dict[str, Any]) -> dict[str, Any]:
conversation_kwargs = filter_supported_call_kwargs(
agent.run_conversation,
system_message=settings.get("system_prompt"),
conversation_history=settings.get("history"),
conversation_history=resolve_history(payload),
sync_honcho=False,
dont_review=True,
)
Expand Down
1 change: 0 additions & 1 deletion examples/code-review-agent/agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ harness:
adapter_id: nvidia.fabric.hermes.sdk
resolution: preinstalled
settings:
agent_profile: code_reviewer
workspace: ./repos/my-service

models:
Expand Down
1 change: 0 additions & 1 deletion examples/code-review-agent/profiles/hermes-cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ harness:
adapter_id: nvidia.fabric.hermes.cli
resolution: preinstalled
settings:
agent_profile: code_reviewer
workspace: ./repos/my-service
hermes_home: ./artifacts/hermes-cli/home
base_url: https://integrate.api.nvidia.com/v1
Expand Down
1 change: 0 additions & 1 deletion examples/code-review-agent/profiles/hermes-relay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ harness:
resolution: preinstalled
settings:
python_env: HERMES_PYTHON
agent_profile: code_reviewer
workspace: ./repos/my-service
hermes_home: ./artifacts/hermes-relay/home
base_url: https://integrate.api.nvidia.com/v1
Expand Down
1 change: 0 additions & 1 deletion examples/code-review-agent/profiles/hermes-sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ harness:
resolution: preinstalled
settings:
python_env: HERMES_PYTHON
agent_profile: code_reviewer
workspace: ./repos/my-service
hermes_home: ./artifacts/hermes-home
base_url: https://integrate.api.nvidia.com/v1
Expand Down
37 changes: 37 additions & 0 deletions examples/code-review-agent/profiles/hermes-session.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

schema_version: fabric.profile/v1alpha1
name: hermes_session
description: Drive the Hermes Python adapter as a multi-turn session (runtime mode session).

harness:
adapter_id: nvidia.fabric.hermes.sdk
resolution: preinstalled
settings:
python_env: HERMES_PYTHON
workspace: ./repos/my-service
hermes_home: ./artifacts/hermes-home
base_url: https://integrate.api.nvidia.com/v1
max_turns: 1
Comment thread
dagardner-nv marked this conversation as resolved.
max_tokens: 512
temperature: 0.0
reasoning_config:
effort: none
enabled_toolsets: []
system_prompt: You are a concise smoke test assistant.

runtime:
mode: session
transport: library
input_schema: chat
output_schema: message
artifacts: ./artifacts/hermes-session

environment:
provider: local
workspace: ./repos/my-service
artifacts: ./artifacts/hermes-session

telemetry:
enabled: false
50 changes: 50 additions & 0 deletions examples/session_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Quickstart: a multi-turn Fabric session (start -> invoke -> stream -> stop).

The session replays the accumulated transcript as conversation history on each
turn, so the harness remembers prior turns.

Run it with an interpreter that has the ``nemo_fabric`` native binding and Hermes
installed, with an API key available:

set -a; . ./.env; set +a # provides NVIDIA_API_KEY
<hermes-venv>/bin/python examples/session_quickstart.py

For a zero-setup local check of the session mechanics (no native binding, no
Hermes, no API key), run the unit smoke instead:

python3 python/tests/smoke_sdk_sessions.py
"""

from __future__ import annotations

import asyncio

from nemo_fabric import FabricClient


async def main() -> None:
async with await FabricClient().start(
"examples/code-review-agent", profile="hermes_session"
) as session:
print(f"session {session.id} [{session.status.value}]")

result = await session.invoke("My name is Robin. Please remember it for later.")
print(f"\n> remember my name\n {(result.get('output') or {}).get('response')}")

# stream() yields events as they arrive, then the final RunResult (last item).
print("\n> what is my name? (streamed)")
async for item in session.stream("What is my name? Reply with just the name."):
if "status" in item: # terminal RunResult
print(f" = {(item.get('output') or {}).get('response')}")
else: # incremental event
print(f" . {item.get('kind')}: {item.get('message')}")

print(f"\ntranscript turns accumulated: {len(session.messages)}")
print(f"\nsession [{session.status.value}] after context exit")


if __name__ == "__main__":
asyncio.run(main())
18 changes: 16 additions & 2 deletions python/src/nemo_fabric/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@

"""Python SDK surface for NeMo Fabric."""

from nemo_fabric.client import FabricCliError, FabricClient, FabricNativeUnavailableError
from nemo_fabric.client import (
FabricCliError,
FabricClient,
FabricNativeUnavailableError,
FabricSessionUnsupportedError,
Session,
SessionStatus,
)

__all__ = ["FabricCliError", "FabricClient", "FabricNativeUnavailableError"]
__all__ = [
"FabricCliError",
"FabricClient",
"FabricNativeUnavailableError",
"FabricSessionUnsupportedError",
"Session",
"SessionStatus",
]
Loading