diff --git a/POC-TO-MVP-PLAN.md b/POC-TO-MVP-PLAN.md deleted file mode 100644 index 9cd438cce..000000000 --- a/POC-TO-MVP-PLAN.md +++ /dev/null @@ -1,341 +0,0 @@ - - -# Fabric MVP Plan - -This plan turns the current NeMo Fabric codebase into a focused MVP. The MVP -proves the stable Fabric API surface first: configure an agent once, vary it -through profiles, map Fabric capabilities into Hermes, run through Fabric, and -return normalized results through SDK and CLI surfaces that can be consumed by -NeMo Platform and other orchestrators. - -## MVP Goal - -The MVP should prove that Fabric can be the harness-management layer between -consumer systems and agent harness runtimes. - -For the MVP: - -- Hermes is the first MVP harness target. Codex and additional priority - harnesses follow in later milestones. -- Python SDK is the primary integration surface for Platform and other - orchestrators. -- CLI is the executable surface for local debugging, CI, and integration - testing. -- Harbor is the first proof consumer, not the center of the MVP contract. -- `agent.yaml` and profile files are the portable file format. -- typed Python/Pydantic-style config is the preferred path for real consumers - that already own a top-level job or deployment config. - -## MVP Slice - -The MVP slice is: - -```text -Consumer job/deployment -> Fabric SDK or CLI -> Hermes adapter -> Hermes runtime - | - v - RunResult + ArtifactManifest + Relay refs -``` - -The consumer owns job scheduling, environment preparation, task semantics, and -domain-specific verification. Fabric owns agent config/profile resolution, -harness invocation, capability mapping, normalized results, artifact discovery, -and telemetry pass-through. - -Harbor validates this shape as a proof consumer: Harbor owns task -materialization, environment lifecycle, verifier execution, reward calculation, -and benchmark job layout, while Fabric owns the selected harness invocation. - -## Current Baseline - -The repo already contains the core shape of the MVP: - -- Rust core crate with typed config, profile resolution, adapter descriptors, - run planning, runtime handles, normalized results, artifacts, and errors. -- JSON Schema generation and committed schema snapshots. -- CLI commands for validation, inspection, planning, doctor checks, schema - generation, and running. -- Python package with native Rust bindings plus CLI fallback. -- SDK support for both agent-package paths and typed/in-memory config. -- Session-mode SDK lifecycle support with a stable `session_id` resume key for - both agent-package paths and typed/in-memory config. -- Agent package examples with `agent.yaml`, `profiles/`, `skills/`, and - workspace fixtures. -- Ordered multi-profile resolution. -- Repository-maintained Hermes SDK and Hermes CLI adapters. -- Package-local adapter descriptor discovery for custom agent packages. -- Hermes capability mapping for model, workspace, skills, MCP, tools, - telemetry hooks, and artifacts. -- Harbor proof wrapper at `nemo_fabric.integrations.harbor:FabricAgent`. -- Workspace patch/status artifact capture. -- Relay config pass-through and a Hermes Relay smoke path. - -## In Scope - -- Stable SDK and CLI behavior for Hermes-backed one-shot runs. -- Minimal session lifecycle shape where Hermes support is available. -- EffectiveConfig and RunPlan as the resolved core contract. -- Multiple profiles applied in caller-provided order. -- A reusable config-variation test matrix that proves the same agent can vary - model, runtime, skills, tools, MCP, telemetry, workspace, artifacts, and - harness adapter through profiles. -- Capability mapping for: - - skills - - tools - - MCP - - telemetry -- Harness-native config generation for supported Hermes surfaces. -- Clear validation failures for unsupported adapters, requirements, and - capability mappings. -- Relay telemetry configuration pass-through. -- ArtifactManifest entries for output, logs, patches, and telemetry references - where available. -- Consumer integration smoke with a Fabric-managed Hermes run. - -## In Scope, Deferred Until Base MVP Is Stable - -- Final adapter contract definition. -- Third-party adapter support. The base MVP supports built-in adapters only. -- Harbor SWE-Bench Verified smoke with verifier as the first evaluation proof - once the environment is available. -- Harbor integration beyond the proof already included in the POC. - -## Out Of Scope - -- Completing all priority harnesses in the MVP cut. Codex, Claude Code, Cursor, - OpenClaw, and Deep Agents are planned follow-on harnesses after the Hermes - slice is stable. -- Generic Fabric-managed MCP/tool proxy runtime. -- Full environment provisioning. Consumers provide prepared environments. -- Production Platform integration. -- External third-party adapter package registry. -- Multi-modal input/output contracts. - -## Repository Layout - -The repo layout separates Fabric-owned concepts: - -- `crates/fabric-core/`: config, schema, planning, runtime contract, and core - types. -- `crates/fabric-cli/`: `fabric` command-line surface. -- `crates/fabric-python/`: native Python bindings for the Rust core. -- `python/src/nemo_fabric/`: Python SDK and consumer integrations. -- `adapters/hermes-sdk/`: Hermes SDK adapter implementation; this is the - primary inline Python path for SDK consumers. -- `adapters/hermes-cli/`: Hermes CLI adapter implementation. -- `integrations/harbor/`: Harbor consumer integration notes. -- `examples/`: portable agent packages and config examples. -- `tests/`: CLI, adapter, Relay, local e2e, and SWE-Bench-style smokes. -- `python/tests/`: SDK and Harbor integration smokes. -- `schemas/`: committed schema snapshots. - -## Workstreams - -### 1. Core Contract - -Status: - -- Base MVP contract is mostly complete. -- Schema snapshots, profile resolution, ordered profile stacking, typed SDK - config, YAML package config, adapter descriptor validation, planning, doctor - checks, and CLI/SDK smoke coverage are already present. -- Consumers can validate and plan without running. -- The same base config can be resolved with different ordered profile stacks. -- Adapters receive EffectiveConfig/RunPlan, not raw profile files. -- The full adapter contract definition is deferred until the base MVP is - stable; the base MVP keeps only the minimal descriptor fields Fabric already - uses. -- Normalized trajectory structures and policy hooks for auditability are - deferred until Fabric owns those contracts directly. - -How to maintain: - -- Keep schema snapshots current as the contract evolves. -- Tighten error messages where review or smoke tests show ambiguity. -- Keep SDK typed-config behavior and YAML package behavior aligned when new - config fields are added. -- Add or update tests whenever the config contract changes. - -### 2. Hermes Adapter Readiness - -Status: - -- Base Hermes SDK and Hermes CLI adapter work is in place. -- `hermes-sdk` is the inline Python adapter path for SDK consumers. The Python - SDK imports the adapter callable directly and preserves the async SDK shape. -- `hermes-cli` is the process-backed path for CLI/debug and environment-backed - consumers. Fabric launches the wrapper process and captures stdout, stderr, - exit status, logs, and artifacts. -- The `hermes-cli` process path now gets a per-invocation `FABRIC_HOME` and - `FABRIC_INVOCATION` file. The launcher reads that invocation file, maps Fabric - config into Hermes-native config, and then invokes the real `hermes` CLI. -- Both paths return the normalized Fabric `RunResult` shape. -- Fabric model, workspace, skills, MCP, tools, telemetry, and artifact config - remains visible in generated Hermes-native config or launch settings. -- Unsupported Hermes MCP mappings with no target fail before invocation. -- Session-mode adapters receive Fabric's stable session key from - `runtime_context.session_id` when supplied, or `runtime_context.runtime_id` - as the default. Hermes CLI maps that Fabric key onto Hermes session id/title - for resume. -- Relay-backed Hermes CLI tests now cover ATOF/ATIF artifact references and - generated Relay config in the process-backed path. -- SDK and CLI smoke coverage asserts normalized `RunResult` parity for shared - fields across both inline Python and process-backed adapter paths. - -Next steps: - -- Review the Hermes adapter implementations for maintainability and alignment - with the minimal descriptor fields Fabric currently uses. -- Test Hermes SDK and CLI paths with more representative inputs. -- Add testing for harness-native events, artifacts, and logs. - -### 3. Config Variation Matrix - -Status: - -- Ordered profile stacking is implemented. -- `examples/code-review-agent` includes Hermes SDK, Hermes CLI, local env, MCP, - and Relay-oriented profile examples. -- CLI and SDK smoke tests cover profile resolution and multi-profile planning. -- Hermes capability mapping exists for model, workspace, skills, MCP, tools, - telemetry, and artifacts. -- Generated Hermes config checks confirm enabled skills, tools, MCP, telemetry, - workspace, and artifact settings. -- Negative tests cover unsupported mappings failing before invocation. -- Relay-enabled Hermes CLI runs now assert emitted ATOF/ATIF artifact files and - manifest visibility where the harness and adapter support it. - -Next steps: - -- Turn the example profiles into an explicit variation matrix for Hermes. -- Add missing profile variations where useful, including alternate model, - toolset, workspace, artifact, and telemetry combinations. -- Test both Hermes SDK and Hermes CLI against the applicable matrix. -- Add checks for harness-native events, artifacts, and logs. - -Config mapping and actual runtime behavior are related but not identical. -Fabric should prove that capability config is mapped into the harness-native -surface, and trajectory tests should prove whether the harness actually exposed -or used that capability during a run. - -After the Hermes matrix is stable, each new harness should reuse the same -example shape while keeping the base `agent.yaml` stable. - -### 4. SDK And CLI API - -Status: - -- Base Python SDK and CLI surfaces are in place. -- SDK supports agent-package paths and typed/in-memory config. -- CLI supports validate, inspect, plan, doctor, schema generation, and run. -- SDK session APIs cover `start_session`, `invoke`, `stream`, `cancel`, and - `stop` for `runtime.mode: session`, including caller-provided - `session_id` propagation. -- CLI includes `fabric chat` for local interactive session-mode debugging with - explicit `--session-id`, `/info`, `/verbose`, and oneshot-profile rejection. -- SDK and CLI can plan and run Hermes without callers importing - Hermes-specific code. -- CLI and SDK smoke tests cover core planning and run paths. -- README examples for plan, doctor, typed config, SDK sessions, and CLI chat are - mirrored by executable smoke tests to prevent documented API drift. -- Typed config is a first-class SDK path and is covered without requiring an - agent directory. -- The core SDK is covered as consumer-neutral and dependency-free; Harbor, - Hermes, Relay, and adapter packages stay out of a plain `import nemo_fabric`. - -Next steps: - -- Define an SDK API doc that flushes out the APIs and request/response schema - for each API. -- Keep Python SDK as the primary API for consumers. -- Keep CLI behavior aligned with SDK behavior for the same config/profile stack. - -### 5. Telemetry And Artifacts - -Status: - -- Base artifact capture is in place for output, logs, generated harness config, - workspace patch/status, and telemetry references where available. -- Relay config pass-through exists for Hermes profiles. -- Native harness outputs are preserved separately from Relay outputs. -- SDK, CLI, and Harbor-facing paths expose ArtifactManifest data. -- Relay artifact discovery is hardened for ATOF/ATIF outputs when telemetry is - enabled. -- Relay-enabled profiles have tests for inspectable telemetry outputs or clear - telemetry references. -- ArtifactManifest remains populated with output, logs, patch/status, native - harness artifacts, and telemetry references where available. -- Relay-disabled smoke coverage verifies native output and native observability - stay available without Relay. -- SDK, CLI, and Harbor-facing smoke paths cover ArtifactManifest visibility. - -### 6. Consumer Proof: Harbor - -Status: optional/stretch goal. - -Goal: validate the SDK/CLI contract through one real evaluation consumer after -the SDK/CLI and Hermes paths are stable. - -Current status: - -- Keep `nemo_fabric.integrations.harbor:FabricAgent` as the Harbor entrypoint. -- Keep Harbor-specific usage in `integrations/harbor/README.md`. -- Lightweight Harbor integration smoke coverage validates command construction, - Fabric metadata propagation, and ArtifactManifest handoff with a fake Harbor - environment. - -Next steps: - -- Run the lightweight Harbor smoke in a clean environment. -- Run one Harbor SWE-Bench Verified task through Fabric. -- Run the Harbor verifier against the Fabric-produced patch. - -Success criteria: - -- Harbor can invoke Fabric without Hermes-specific launch code. -- Fabric result metadata is copied into Harbor context metadata. -- The Fabric-produced patch is visible to Harbor's verifier. -- Harbor remains responsible for datasets, environments, verifier, and rewards. -- No Harbor-specific assumption leaks into Fabric core, SDK, or Hermes adapters. - -## Execution Order - -1. Keep core contract/schema tests green while making small contract fixes. -2. Done: SDK and CLI behavior for typed config and agent-package config. -3. In progress: finish Hermes SDK and CLI reproducibility in clean environments. -4. Run the Hermes config-variation matrix across model, runtime, skills, tools, - MCP, telemetry, workspace, artifacts, and harness adapter profiles. -5. Done: harden Relay telemetry and ArtifactManifest discovery for Hermes runs. -6. After the SDK/CLI and Hermes path are stable, split follow-up work into - adapter, consumer API, and telemetry/artifact readiness tracks. -7. Stretch: run the Harbor lightweight smoke from a clean install. -8. Stretch: run a Harbor SWE-Bench Verified smoke and verifier path as the - first evaluation proof. - -## Review Checklist - -Before calling the MVP complete: - -- `cargo test --workspace` passes. -- `cargo fmt --check` passes. -- Python SDK smoke passes. -- CLI smoke passes. -- CLI chat smoke passes for session-mode profiles. -- real Hermes SDK smoke passes in a documented clean environment. -- real Hermes CLI smoke passes in a documented clean environment. -- Hermes config-variation matrix passes for supported profile combinations. -- typed config SDK smoke passes without requiring an agent directory. -- Harbor lightweight smoke passes. -- Harbor SWE-Bench Verified smoke runs through Fabric. -- Relay-enabled run produces ATOF/ATIF outputs or telemetry references. -- ArtifactManifest includes output, logs, patches, and telemetry references - where available. -- README and integration docs describe only supported paths. - -## Open Decisions - -- What exact Platform smoke path should validate SDK consumption. -- Which SDK calls must be async in the first MVP cut versus immediately after. diff --git a/README.md b/README.md index f4961eb6a..d977bfc48 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Fabric provides: ```mermaid flowchart TB Consumer["Consumer\nCLI | Python SDK | integrations"] - Config["Agent source\nagent.yaml or FabricConfig + profiles"] + Config["Agent source\nagent.yaml or FabricConfig"] Core["Fabric Rust core\nresolve | plan | create | invoke | destroy"] Adapter["Selected Fabric adapter"] Harness["Agent harness runtime\nHermes | Codex | custom"] @@ -68,7 +68,7 @@ cargo install just --locked Refer to the [official installation guide](https://just.systems/man/en/installation.html) for more details. -Install Fabric and the `fabric` CLI from the source checkout: +Install Fabric from the source checkout: ```bash just build-all @@ -90,30 +90,29 @@ with: .tmp/hermes-venv/bin/python -m pip install -e ../hermes-agent ``` -Run one input: +Run the code-review example: ```bash export NVIDIA_API_KEY=... export HERMES_PYTHON="$PWD/.tmp/hermes-venv/bin/python" -fabric doctor examples/code-review-agent --profile hermes_sdk -fabric run examples/code-review-agent \ - --profile hermes_sdk \ +.venv/bin/python -m examples.code_review_agent \ --input "Reply with exactly: fabric works" ``` The run returns a normalized `RunResult` JSON payload and writes logs/artifacts -under `examples/code-review-agent/artifacts/hermes-sdk/`. +under `examples/code_review_agent/artifacts/hermes-sdk/`. Its complete base +config and clone-based variants live in +`examples/code_review_agent/config.py`. ## Core Concepts - **Agent source:** callers provide either an agent package path or a typed - `FabricConfig`. An agent package contains `agent.yaml` plus optional profiles, - skills, repos, and artifacts. Start with - `examples/code-review-agent/agent.yaml`. + `FabricConfig`. Start with `examples/code_review_agent/config.py` for the + application-facing Pydantic pattern. - **Typed config:** SDK consumers can construct configuration in memory without materializing an agent directory. `agent.yaml` remains the portable - representation for CLI use, examples, CI, and reproducible runs. + representation for CLI use, CI, and reproducible runs. - **Profiles:** named variations of the base config. Use profiles to vary the harness, model, MCP, tools, skills, telemetry, or environment context without editing `agent.yaml`. @@ -128,287 +127,69 @@ under `examples/code-review-agent/artifacts/hermes-sdk/`. Fabric applies profiles in caller order and validates the final effective config before planning or running. -Path sources select profiles by name. Typed `FabricConfig` sources use ordered -`FabricProfileConfig` objects; the SDK rejects mixed profile stacks. See the -[Python SDK contract](docs/python-sdk-contract.md) for the complete public API, -type definitions, lifecycle semantics, and compatibility rules. - -## Use Fabric - -Inspect the run plan before invoking a harness: - -```bash -fabric plan examples/code-review-agent --profile hermes_sdk -fabric plan examples/code-review-agent --profile env_local --profile mcp_github -``` - -Use Fabric from Python: - -```python -import asyncio -from pathlib import Path - -from nemo_fabric import FabricClient - -async def main(): - agent = Path("examples/code-review-agent") - - async with FabricClient() as client: - resolved = client.resolve(agent, profiles=["hermes_sdk"]) - plan = client.plan(agent, profiles=["hermes_sdk"]) - report = await client.doctor(agent, profiles=["hermes_sdk"]) - - print(resolved.agent_name) - print(plan.agent_name) - print(report.checks) - -asyncio.run(main()) -``` - -Consumers that already own a top-level job config can construct the Fabric slice -in code instead of materializing an agent directory: - -```python -from nemo_fabric import FabricClient, FabricConfig - -config = FabricConfig.from_mapping( - { - "schema_version": "fabric.agent/v1alpha1", - "metadata": {"name": "code-review-agent"}, - "harness": {"adapter_id": "nvidia.fabric.hermes.sdk"}, - "models": { - "default": { - "provider": "nvidia", - "model": "nvidia/nemotron-3-nano-30b-a3b", - } - }, - "runtime": { - "mode": "session", - "transport": "library", - "input_schema": "chat", - "output_schema": "message", - }, - }, -) - -client = FabricClient() -plan = client.plan( - config, - base_dir="examples/code-review-agent", -) -``` - -For runtime invocation, callers can either pass simple text or construct the -request explicitly. Results remain dict-compatible while exposing stable fields -as attributes: - -```python -from nemo_fabric import FabricClient, FabricConfig, FabricError, RunRequest - -request = RunRequest( - input="Review the workspace changes.", - request_id="job-123-turn-1", - context={"job_id": "job-123"}, - overrides={"max_iterations": 1}, -) - -async def run(raw_config): - config = FabricConfig.from_mapping(raw_config) - try: - async with FabricClient() as client: - result = await client.run( - config, - base_dir="examples/code-review-agent", - request=request, - ) - except FabricError as error: - print(error.stage, error.code, error.retryable) - raise - - print(result.status) - print(result["runtime_id"]) -``` - -`RunRequest.from_mapping(...)` accepts JSON-shaped request dictionaries when -callers load or compose requests outside the SDK. Per-request `context` is -caller-owned metadata; `overrides` are -request-scoped config changes applied only where the selected harness adapter -supports them. Failed runs expose structured `result.error.stage`, -`result.error.code`, and `result.error.retryable` when the adapter returns a -normalized failure. - -### Multi-Turn SDK Sessions - -Open a `Session` and invoke it repeatedly. The session keeps one Fabric runtime -handle active across turns; harness/adapter state is authoritative rather than -reconstructed from a Python-side transcript. - -Fabric separates runtime identity from conversation identity. Each -`start_session(...)` call creates a new `runtime_id` for that runtime lifecycle. -`session_id` is the stable conversation key used for resume: if omitted, Fabric -uses the generated `runtime_id`; if supplied, Fabric uses the caller-provided -`session_id`. - -```python -import asyncio - -from nemo_fabric import FabricClient - -async def chat(): - async with await FabricClient().start_session( - "examples/code-review-agent", - profiles=["hermes_session"], - session_id="review-session-123", - ) as session: - await session.invoke(input="My name is Robin.") - reply = await session.invoke(input="What's my name?") # recalls "Robin" - print(session.runtime_id, session.session_id, session.status.value) - print(reply["output"]["response"]) - -asyncio.run(chat()) -``` - -`start_session(...)` accepts either an agent path with named profiles or a -`FabricConfig` with typed profiles. `stream(...)` is the stable streaming API; -current adapters may buffer internally before yielding events and the final -result. Runtime updates and cancellation are capability-gated and raise -`FabricCapabilityError` when the selected runtime does not support them. -Session APIs require `runtime.mode: session`. - -Service mode is part of the forward SDK contract but is not implemented by the -current runtime. `start_service(...)` raises `FabricCapabilityError` rather than -silently emulating server or tenancy behavior outside Fabric's execution scope. - -### Interactive CLI Chat - -For local manual multi-turn testing, use `fabric chat` with a session-mode -profile. It drives the same started runtime in an interactive loop: - -```bash -fabric chat examples/code-review-agent \ - --profile hermes_cli_session \ - --session-id review-session-123 \ - --verbose -``` - -The same session flow works with an existing Codex CLI login: - -```bash -fabric chat examples/code-review-agent \ - --profile codex_cli_session \ - --session-id review-session-123 -``` - -`--session-id` is optional. Each `fabric chat` start creates a new `runtime_id`; -the session id is the stable resume key. If `--session-id` is omitted, Fabric -uses the generated `runtime_id` as the session id. If you want a later chat run -to resume the same conversation, pass that prior session id explicitly. -`fabric chat` prints a `NEMO FABRIC` session banner with the agent, profile, -harness, runtime id, and session id at startup and from `/info`, then uses a -`you[profile:session]>` prompt and `agent>` responses for the transcript. -`/help` shows commands, `/verbose on|off` toggles a fenced per-turn metadata -block after each agent response with request/invocation ids, status, artifact -count, and telemetry details, and `/clear` clears the terminal. `fabric chat` -requires `runtime.mode: session`; use `fabric run` for oneshot profiles and -machine-readable stdout. Because `chat` is an interactive terminal UI, the -transcript and metadata are written together on stderr. - -The opt-in real integration checks are `tests/e2e/test_hermes_session.py` and -`tests/e2e/test_codex_cli.py`. - -`FabricClient()` uses the native Rust binding. SDK `run(...)` and -`start_session(...)` drive the core Fabric runtime lifecycle (`start_runtime` / -`invoke_runtime` / `stop_runtime`) so one-shot and session paths use the same -adapter execution contract. The CLI is a separate interface over the same Rust -core. For source-tree development, run `just build-python` before using the -SDK. - -## Harbor Integration - -Harbor can use Fabric as one external agent while Fabric selects the execution -harness from its ordered profile stack. Harbor retains task, environment, -verification, reward, and job ownership. `FabricAgent` invokes the Fabric Python -SDK inside the Harbor task environment; it does not invoke the Fabric CLI. - -After preparing the demo build context as described in the -[Harbor multi-harness demo](integrations/harbor/demo/README.md), run the -credential-free integration example: - -```bash -DEMO_DIR="$PWD/integrations/harbor/demo" - -uv run --extra harbor harbor run \ - --path "$DEMO_DIR/task" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/smoke.yaml"]' \ - --job-name fabric-smoke \ - --jobs-dir "$DEMO_DIR/runs" \ - --n-concurrent 1 \ - --n-attempts 1 \ - --force-build -``` - -The same Harbor agent can switch between the smoke, Hermes, Hermes with Relay -telemetry, and Codex profiles. See the -[Harbor integration guide](integrations/harbor/README.md) for ownership and -installation details, and the demo guide for the complete command matrix. - -## Other Runs - -Run one isolated Codex CLI turn using Codex's existing authentication and -configuration: - -```bash -codex login status -fabric doctor examples/code-review-agent --profile codex_cli -fabric run examples/code-review-agent \ - --profile codex_cli \ - --input "Review the workspace and summarize the highest-risk issue." -``` - -Run the Hermes CLI adapter: - -```bash -export NVIDIA_API_KEY=... -export PATH="$PWD/.tmp/hermes-venv/bin:$PATH" - -fabric run examples/code-review-agent \ - --profile hermes_cli \ - --input "Reply with exactly: hermes cli ok" -``` +Path sources select profiles by name. Typed `FabricConfig` sources usually +compose the final config in Python; `FabricProfileConfig` values are available +for callers that need ordered file-style overlays. The SDK rejects raw profile +mappings and mixed profile stacks. See the +[Python SDK guide](docs/sdk/python.mdx) for the complete public API, +type definitions, lifecycle semantics, and error behavior. + +`run(...)` owns the complete start, invoke, and stop lifecycle. For typed +in-memory configuration, planning and diagnostics, explicit requests, +multi-turn runtimes, application-owned parallelism, results, and errors, see +the [Python SDK guide](docs/sdk/python.mdx). Exact signatures are in the +[generated Python API reference](docs/reference/api/python-library-reference/index.md). + +## More Workflows + +- [Python SDK guide](docs/sdk/python.mdx): typed configuration, planning, + diagnostics, requests, multi-turn runtimes, parallelism, results, and errors. +- [Getting Started overview](docs/getting-started/overview.mdx): interface + selection and the end-to-end Fabric workflow. +- [Harbor example](examples/harbor/README.md) and + [multi-harness demo](examples/harbor/demo/README.md): ownership, + installation, and complete command matrices. +- Adapter guides: [Hermes SDK](adapters/hermes-sdk/README.md), + [Hermes CLI](adapters/hermes-cli/README.md), and + [Codex CLI](adapters/codex-cli/README.md). ## Tests To run the full test suite, bootstrap a virtual environment with the optional dependencies. ```bash -uv venv --seed .venv --python 3.12' +uv venv --seed .venv --python 3.12 source .venv/bin/activate uv sync --all-groups --all-extras ``` -Build Fabric and the Python extension, since we have already bootstrapped a virtual environment, we will pass the `no_uv` flag to avoid building reinstalling depdnendencies in the virtual environment. +Build Fabric and the Python extension. Because the virtual environment is +already bootstrapped, pass `no_uv=true` to avoid reinstalling dependencies. + ```bash just no_uv=true build-all ``` Run both Rust and Python tests: + ```bash just no_uv=true test-all ``` Run just the Rust tests: + ```bash just no_uv=true test-rust ``` Run just the Python tests: + ```bash just no_uv=true test-python ``` Running `pytest` directly: + ```bash pytest -``` \ No newline at end of file +``` diff --git a/adapters/codex-cli/README.md b/adapters/codex-cli/README.md index d9352f50a..f744362e4 100644 --- a/adapters/codex-cli/README.md +++ b/adapters/codex-cli/README.md @@ -1,12 +1,16 @@ # Codex CLI Adapter -Runs an installed Codex CLI through Fabric's Python-adapter lifecycle. The -same adapter supports one-shot and session runtime modes. +Runs an installed Codex CLI through Fabric's Python-adapter lifecycle. One +Fabric runtime maps to one Codex thread. + +Keep `fabric-adapter.json` aligned with the adapter implementation. +`contract_version` must match the adapter contract supported by Fabric core; +`adapter_id` is the stable id selected by `harness.adapter_id`. Install Fabric with the adapter dependency before running it: ```bash -python3 -m pip install -e ".[codex]" +python3 -m pip install -e ".[runtime,codex]" ``` ## Authentication and Codex Config @@ -35,17 +39,13 @@ Fabric adds only explicitly configured invocation overrides: `codex_command`, `codex_state_dir`, `cwd`, `env`, and `skip_git_repo_check` are available for prepared environments and tests. -## Runtime Modes - -One-shot mode runs `codex exec --json --ephemeral` and returns the final agent -message, usage, and thread ID in the normalized Fabric result. +## Execution Paths -Session mode omits `--ephemeral`. The first invocation records Codex's generated -thread ID against Fabric's session ID; later invocations use +The first invocation records Codex's generated thread ID against the Fabric +runtime ID. Later invocations on the same runtime use `codex exec resume `. Codex owns its transcript and authentication; -Fabric owns the lifecycle and the session-to-thread correlation record. -Both modes accept text input; Codex owns conversation history for session runs. +Fabric owns the runtime lifecycle and runtime-to-thread correlation record. +Both `fabric run` and stateful runtime paths accept text input. -Use the `codex_cli` and `codex_cli_session` profiles under -`examples/code-review-agent/profiles/` for local one-shot and `fabric chat` -examples. +Use `codex_cli_config()` from `examples.code_review_agent` for local one-shot +and multi-turn examples. diff --git a/adapters/codex-cli/fabric-adapter.json b/adapters/codex-cli/fabric-adapter.json index 1a8894f93..506d92455 100644 --- a/adapters/codex-cli/fabric-adapter.json +++ b/adapters/codex-cli/fabric-adapter.json @@ -1,4 +1,5 @@ { + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "nvidia.fabric.codex.cli", "harness": "codex", "adapter_kind": "python", diff --git a/adapters/codex-cli/src/nemo_fabric_adapters/codex_cli/adapter.py b/adapters/codex-cli/src/nemo_fabric_adapters/codex_cli/adapter.py index 302c0379c..974e81084 100755 --- a/adapters/codex-cli/src/nemo_fabric_adapters/codex_cli/adapter.py +++ b/adapters/codex-cli/src/nemo_fabric_adapters/codex_cli/adapter.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Map Fabric one-shot and session invocations onto ``codex exec``.""" +"""Map Fabric runtime invocations onto ``codex exec``.""" from __future__ import annotations @@ -90,14 +90,6 @@ class CodexSettings(NamedTuple): relay_plugin_config: dict[str, Any] | None -def runtime_mode(payload: dict[str, Any]) -> str: - runtime = common_utils.fabric_config(payload).get("runtime") or {} - mode = str(runtime.get("mode") or "oneshot") - if mode not in {"oneshot", "session"}: - raise ValueError("Codex CLI adapter supports only oneshot and session modes") - return mode - - def state_dir(payload: dict[str, Any]) -> Path: settings = common_utils.settings_payload(payload) config_root = Path(common_utils.config_root(payload)).resolve() @@ -112,28 +104,33 @@ def state_dir(payload: dict[str, Any]) -> Path: return config_root / "artifacts" / "codex-cli" / ".fabric" -def session_state_path(payload: dict[str, Any], session_id: str) -> Path: - key = hashlib.sha256(session_id.encode("utf-8")).hexdigest() - return state_dir(payload) / "sessions" / f"{key}.json" +def runtime_state_path(payload: dict[str, Any], runtime_id: str) -> Path: + key = hashlib.sha256(runtime_id.encode("utf-8")).hexdigest() + return state_dir(payload) / "runtimes" / f"{key}.json" -def load_thread_id(payload: dict[str, Any], session_id: str) -> str | None: - path = session_state_path(payload, session_id) +def load_thread_id(payload: dict[str, Any], runtime_id: str) -> str | None: + path = runtime_state_path(payload, runtime_id) if not path.is_file(): return None - value = json.loads(path.read_text(encoding="utf-8")) - if value.get("session_id") != session_id or not value.get("thread_id"): - raise RuntimeError(f"invalid Codex session state in {path}") + try: + value = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as error: + raise RuntimeError(f"invalid Codex runtime state in {path}") from error + if not isinstance(value, dict) or value.get("runtime_id") != runtime_id or not value.get( + "thread_id" + ): + raise RuntimeError(f"invalid Codex runtime state in {path}") return str(value["thread_id"]) -def save_thread_id(payload: dict[str, Any], session_id: str, thread_id: str) -> None: - path = session_state_path(payload, session_id) +def save_thread_id(payload: dict[str, Any], runtime_id: str, thread_id: str) -> None: + path = runtime_state_path(payload, runtime_id) path.parent.mkdir(parents=True, exist_ok=True) invocation_id = common_utils.runtime_context(payload).get("invocation_id") or "pending" temporary = path.with_suffix(f".{invocation_id}.tmp") temporary.write_text( - json.dumps({"session_id": session_id, "thread_id": thread_id}, indent=2), + json.dumps({"runtime_id": runtime_id, "thread_id": thread_id}, indent=2), encoding="utf-8", ) os.replace(temporary, path) @@ -160,7 +157,6 @@ def build_command( ) -> list[str]: settings = common_utils.settings_payload(payload) command = resolve_command(payload, settings.get("codex_command") or "codex") - mode = runtime_mode(payload) sandbox = str(settings.get("sandbox") or "read-only") if sandbox not in SANDBOXES: raise ValueError( @@ -169,8 +165,6 @@ def build_command( args = [command, "exec", "--json"] - if mode == "oneshot": - args.append("--ephemeral") args.extend(["--sandbox", sandbox]) if codex_settings.codex_profile_name is not None: @@ -600,11 +594,8 @@ def exception_output(value: str | bytes | None) -> str: def run_codex(payload: dict[str, Any]) -> dict[str, Any]: - mode = runtime_mode(payload) - session_id = common_utils.runtime_session_id(payload) if mode == "session" else None - if mode == "session" and not session_id: - raise RuntimeError("runtime.mode=session requires a session_id or runtime_id") - prior_thread_id = load_thread_id(payload, session_id) if session_id else None + runtime_id = common_utils.runtime_id(payload) + prior_thread_id = load_thread_id(payload, runtime_id) cwd = resolve_cwd(payload) codex_settings = write_config_files(payload) relay_gateway_process = None @@ -667,23 +658,22 @@ def run_codex(payload: dict[str, Any]) -> dict[str, Any]: error = error or completed.stderr.strip() or "Codex CLI exited with a non-zero status" if parsed["response"] is None: error = error or "Codex invocation did not return a final agent message" - if session_id and not thread_id: - error = error or "Codex session invocation did not return a thread identity" - if session_id and prior_thread_id and thread_id != prior_thread_id: - error = ( + if not thread_id: + error = error or "Codex runtime invocation did not return a thread identity" + if prior_thread_id and thread_id != prior_thread_id: + error = error or ( f"Codex resumed thread {thread_id}, expected persisted thread {prior_thread_id}" ) - if session_id and thread_id and not error: - save_thread_id(payload, session_id, thread_id) + if thread_id and not error: + save_thread_id(payload, runtime_id, thread_id) output = { "harness": "codex", "adapter": "cli", - "mode": f"codex_cli_{mode}", + "mode": "codex_cli_runtime", "command": redact_command(command), "cwd": str(cwd), "model": selected_model(payload), - "session_id": session_id, "thread_id": thread_id, "response": parsed["response"], "usage": parsed["usage"], diff --git a/adapters/common/src/nemo_fabric_adapters/common/hermes.py b/adapters/common/src/nemo_fabric_adapters/common/hermes.py index 61f5e2220..a31b9c54c 100644 --- a/adapters/common/src/nemo_fabric_adapters/common/hermes.py +++ b/adapters/common/src/nemo_fabric_adapters/common/hermes.py @@ -197,42 +197,41 @@ def relay_model_name(payload: dict[str, Any]) -> str: return settings.get("model_name") or model_config.get("model") or "unknown" -def ensure_hermes_session( - fabric_session_id: str, +def ensure_hermes_runtime_session( + fabric_runtime_id: str, model_name: str, model_config: dict[str, Any], hermes_home: Path, ) -> dict[str, Any]: """ - Ensure that Hermes has a session mapped from Fabric's session key. + Ensure that Hermes has a native session mapped from a Fabric runtime. - Fabric chooses this key from runtime_context.session_id when the caller - supplies one, otherwise from runtime_context.runtime_id. The adapter maps - that Fabric-owned key onto Hermes' session id/title. + The adapter maps ``runtime_context.runtime_id`` onto Hermes' native session + id/title. Fabric neither exposes nor interprets the Hermes session id. If the session does not exist, it will be created. When creating a new session, Hermes allows us to provide our own session_id (as long as it's unique), which for - convenience will be set to the Fabric session key. + convenience will be set to the Fabric runtime id. However when Hermes compresses a session, it will return a new session_id, so we can't depend on the - Fabric session key being the same as the session_id after a session has been compressed. + Fabric runtime id being the same as the session_id after a session has been compressed. However looking up a session by title will always return the most recent session, so after creating the session - we will set the title to the Fabric session key, and then we can always look up the session by title. + we set the title to the Fabric runtime id and can always look up the session by title. """ from hermes_state import SessionDB session_db = SessionDB(db_path=hermes_home / "state.db") - session = session_db.get_session_by_title(fabric_session_id) + session = session_db.get_session_by_title(fabric_runtime_id) if session is None: session_db.ensure_session( - fabric_session_id, + fabric_runtime_id, source="fabric", model=model_name, model_config=model_config, ) - session_db.set_session_title(session_id=fabric_session_id, title=fabric_session_id) - session = session_db.get_session_by_title(fabric_session_id) + session_db.set_session_title(session_id=fabric_runtime_id, title=fabric_runtime_id) + session = session_db.get_session_by_title(fabric_runtime_id) return session diff --git a/adapters/common/src/nemo_fabric_adapters/common/utils.py b/adapters/common/src/nemo_fabric_adapters/common/utils.py index c5fa42243..d7b760737 100644 --- a/adapters/common/src/nemo_fabric_adapters/common/utils.py +++ b/adapters/common/src/nemo_fabric_adapters/common/utils.py @@ -51,17 +51,19 @@ def runtime_context(payload: dict[str, Any]) -> dict[str, Any]: return payload.get("runtime_context") or {} -def runtime_session_id(payload: dict[str, Any]) -> str | None: - """Return Fabric's session key for adapter-owned harness session mapping.""" - - context = runtime_context(payload) - session_id = context.get("session_id") - if session_id: - return str(session_id) - runtime_id = context.get("runtime_id") - if runtime_id: - return str(runtime_id) - return None +def runtime_id(payload: dict[str, Any]) -> str: + """Return the Fabric runtime id used to key adapter-owned state.""" + + value = runtime_context(payload).get("runtime_id") + if not value: + raise ValueError("runtime_context.runtime_id is required") + return str(value) + + +def runtime_state_directory(base: str | Path, payload: dict[str, Any]) -> Path: + """Return a harness-owned state directory isolated to one Fabric runtime.""" + + return Path(base).joinpath("runtimes", runtime_id(payload)) def environment_payload(payload: dict[str, Any]) -> dict[str, Any]: diff --git a/adapters/hermes-cli/README.md b/adapters/hermes-cli/README.md index 376fd35d5..43d270ee2 100644 --- a/adapters/hermes-cli/README.md +++ b/adapters/hermes-cli/README.md @@ -26,10 +26,15 @@ before calling the CLI. It maps: - Fabric MCP servers as Hermes MCP server config; - selected CLI flags and environment variables from harness settings. +`hermes_home` configures a base directory. The adapter creates a child under +`runtimes/` so invocations in one Fabric runtime share Hermes state +without sharing config or the session database with another runtime. + ## Maintaining The Adapter Keep `fabric-adapter.json` aligned with the Python implementation: +- `contract_version` must match the adapter contract supported by Fabric core. - `adapter_id` is the stable id selected by `harness.adapter_id`. - `adapter_kind` is `python` because Fabric invokes the adapter with Python. - `runner.module` names the module that Fabric invokes with `python -m`. diff --git a/adapters/hermes-cli/fabric-adapter.json b/adapters/hermes-cli/fabric-adapter.json index 25d28ec94..7fb247045 100644 --- a/adapters/hermes-cli/fabric-adapter.json +++ b/adapters/hermes-cli/fabric-adapter.json @@ -1,4 +1,5 @@ { + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "nvidia.fabric.hermes.cli", "harness": "hermes", "adapter_kind": "python", diff --git a/adapters/hermes-cli/src/nemo_fabric_adapters/hermes_cli/adapter.py b/adapters/hermes-cli/src/nemo_fabric_adapters/hermes_cli/adapter.py index 0a6df4bcd..985f87df4 100755 --- a/adapters/hermes-cli/src/nemo_fabric_adapters/hermes_cli/adapter.py +++ b/adapters/hermes-cli/src/nemo_fabric_adapters/hermes_cli/adapter.py @@ -40,11 +40,6 @@ def _api_key_preflight_check(settings: dict[str, Any], model_config: dict[str, A ) from exc -def get_runtime_mode(payload: dict[str, Any]) -> str: - runtime = common_utils.fabric_config(payload).get("runtime") or {} - return runtime.get("mode", "oneshot") - - def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: hermes_common.validate_hermes_telemetry_provider(payload) settings = common_utils.settings_payload(payload) @@ -53,18 +48,17 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: environment = common_utils.environment_payload(payload) model_config = hermes_common.selected_model_config(payload) model_name = settings.get("model_name") or model_config.get("model") - runtime_mode = get_runtime_mode(payload) - use_session = runtime_mode == "session" - fabric_session_id = common_utils.runtime_session_id(payload) + fabric_runtime_id = common_utils.runtime_id(payload) relay_plugin_config = hermes_common.configure_hermes_relay(payload) _api_key_preflight_check(settings, model_config) - hermes_home = resolve_path( + hermes_home_base = resolve_path( config_root, settings.get("hermes_home", "./artifacts/hermes-cli/home"), ) + hermes_home = common_utils.runtime_state_directory(hermes_home_base, payload) hermes_home.mkdir(parents=True, exist_ok=True) hermes_config_path, hermes_config = hermes_common.write_hermes_config( payload, @@ -72,14 +66,9 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: relay_enabled=relay_plugin_config is not None, ) - if use_session: - if fabric_session_id is None: - raise RuntimeError( - "runtime.mode=session is set, but no session_id or runtime_id was provided " - "in the payload. Please provide an id to resume an existing session." - ) - hermes_common.ensure_hermes_session( - fabric_session_id, + if settings.get("prepare_runtime_state", True): + hermes_common.ensure_hermes_runtime_session( + fabric_runtime_id, model_name, model_config, hermes_home, @@ -95,8 +84,8 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: model_name, prompt, toolsets=toolsets, - use_session=use_session, - fabric_session_id=fabric_session_id, + use_native_session=True, + fabric_runtime_id=fabric_runtime_id, ) cwd = resolve_path( config_root, @@ -113,7 +102,7 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: check=False, ) - # When use_session is True, session_id will be printed to stderr + # Hermes prints its native session id to stderr when --continue is used. response = completed.stdout.strip() stderr_output = completed.stderr.strip() return_code = completed.returncode @@ -126,7 +115,7 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: "harness": "hermes", "adapter": "cli", "base_url": hermes_common.get_base_url(settings, model_config), - "mode": f"hermes_cli_{runtime_mode}", + "mode": "hermes_cli_runtime", "command": redact_command(command), "cwd": str(cwd), "enabled_toolsets": toolsets, @@ -136,7 +125,6 @@ def run_hermes_cli(payload: dict[str, Any]) -> dict[str, Any]: "model": model_name, "returncode": return_code, "response": response, - "session_id": fabric_session_id, "stdout": completed.stdout, "stderr": completed.stderr, "failed": return_code != 0, @@ -164,8 +152,8 @@ def build_command( model_name: str | None, prompt: str, toolsets: list[str] | None = None, - use_session: bool = False, - fabric_session_id: str | None = None, + use_native_session: bool = False, + fabric_runtime_id: str | None = None, ) -> list[str]: command = resolve_command( config_root, @@ -175,12 +163,12 @@ def build_command( provider = settings.get("provider") or model_config.get("provider") args = [command, *command_args, "chat", "--quiet", "--query", prompt] - if use_session: - if not fabric_session_id: - raise RuntimeError("session mode requires a session_id or runtime_id") - # Fabric's session key is explicitly mapped onto Hermes' session id/title. + if use_native_session: + if not fabric_runtime_id: + raise RuntimeError("Hermes native session mode requires a Fabric runtime_id") + # The Fabric runtime id is mapped onto Hermes' native session id/title. # On the first invocation, this resumes an empty session created up front. - args.extend([ "--continue", fabric_session_id, ]) + args.extend(["--continue", fabric_runtime_id]) if model_name: args.extend(["--model", str(model_name)]) diff --git a/adapters/hermes-sdk/README.md b/adapters/hermes-sdk/README.md index c0d976df2..9854d756c 100644 --- a/adapters/hermes-sdk/README.md +++ b/adapters/hermes-sdk/README.md @@ -24,10 +24,15 @@ configuration for: - Fabric MCP servers as Hermes MCP server config; - optional NeMo Relay telemetry plugin configuration. +`hermes_home` configures a base directory. The adapter creates a child under +`runtimes/` so invocations in one Fabric runtime share Hermes state +without sharing config or the session database with another runtime. + ## Maintaining The Adapter Keep `fabric-adapter.json` aligned with the Python implementation: +- `contract_version` must match the adapter contract supported by Fabric core. - `adapter_id` is the stable id selected by `harness.adapter_id`. - `adapter_kind` is `python` because Fabric can invoke it through Python. - `runner.module` names the module that Fabric invokes with `python -m`. diff --git a/adapters/hermes-sdk/fabric-adapter.json b/adapters/hermes-sdk/fabric-adapter.json index 761d6d096..78661afc1 100644 --- a/adapters/hermes-sdk/fabric-adapter.json +++ b/adapters/hermes-sdk/fabric-adapter.json @@ -1,4 +1,5 @@ { + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "nvidia.fabric.hermes.sdk", "harness": "hermes", "adapter_kind": "python", diff --git a/adapters/hermes-sdk/src/nemo_fabric_adapters/hermes_sdk/adapter.py b/adapters/hermes-sdk/src/nemo_fabric_adapters/hermes_sdk/adapter.py index 7a6a79b69..cdc614ac1 100755 --- a/adapters/hermes-sdk/src/nemo_fabric_adapters/hermes_sdk/adapter.py +++ b/adapters/hermes-sdk/src/nemo_fabric_adapters/hermes_sdk/adapter.py @@ -69,9 +69,10 @@ async def run_hermes_sdk(payload: dict[str, Any]) -> dict[str, Any]: settings = common_utils.settings_payload(payload) request = hermes_common.request_payload(payload) model_config = hermes_common.selected_model_config(payload) - hermes_home = Path(common_utils.config_root(payload)).joinpath( + hermes_home_base = Path(common_utils.config_root(payload)).joinpath( settings.get("hermes_home", "./artifacts/hermes-home") ) + hermes_home = common_utils.runtime_state_directory(hermes_home_base, payload) hermes_home.mkdir(parents=True, exist_ok=True) os.environ["HOME"] = str(hermes_home) os.environ["HERMES_HOME"] = str(hermes_home) @@ -179,7 +180,7 @@ def _invoke_hermes( discover_plugins(force=True) loaded_hermes_config = load_config() enabled_toolsets = resolve_hermes_toolsets(settings, loaded_hermes_config) - session_id = common_utils.runtime_session_id(payload) + session_id = common_utils.runtime_id(payload) session_db = SessionDB() conversation_history = load_runtime_history(session_db, session_id) agent = None diff --git a/crates/fabric-cli/src/main.rs b/crates/fabric-cli/src/main.rs index 203365025..7aa6c74b1 100644 --- a/crates/fabric-cli/src/main.rs +++ b/crates/fabric-cli/src/main.rs @@ -16,8 +16,8 @@ use std::time::Duration; use clap::{Parser, Subcommand}; use fabric_core::{ - AdapterKind, RunPlan, RunRequest, RunResult, RunStatus, RuntimeHandle, RuntimeMode, SchemaName, - doctor_plan, generate_all_schemas, generate_schema_json, invoke_runtime, + AdapterKind, RunPlan, RunRequest, RunResult, RunStatus, RuntimeHandle, SchemaName, doctor_plan, + generate_all_schemas, generate_schema_json, invoke_runtime, resolve_effective_config_with_profiles, resolve_run_plan_with_profiles, run_plan, start_runtime, stop_runtime, validate_agent_directory, write_schema_snapshots, }; @@ -63,16 +63,13 @@ enum Command { #[arg(long = "profile")] profile: Vec, }, - /// Start an interactive multi-turn session. + /// Start an interactive multi-turn runtime. Chat { /// Path to an agent directory or YAML config. path: PathBuf, /// Profile name from configured profile directories, or a YAML profile path. #[arg(long = "profile")] profile: Vec, - /// Caller-provided harness conversation id. - #[arg(long = "session-id")] - session_id: Option, /// Show per-turn runtime, invocation, artifact, and telemetry details. #[arg(long)] verbose: bool, @@ -146,10 +143,9 @@ fn run() -> Result<(), Box> { Some(Command::Chat { path, profile, - session_id, verbose, }) => { - run_chat(path, &profile, session_id, verbose)?; + run_chat(path, &profile, verbose)?; } Some(Command::Run { path, @@ -234,18 +230,11 @@ fn run() -> Result<(), Box> { fn run_chat( path: PathBuf, profile: &[String], - session_id: Option, verbose: bool, ) -> Result<(), Box> { let plan = resolve_run_plan_with_profiles(path, profile)?; - if plan.config.runtime.mode != RuntimeMode::Session { - return Err( - "fabric chat requires runtime.mode=session; use `fabric run` for oneshot profiles" - .into(), - ); - } let runtime = start_runtime(&plan)?; - let chat_result = chat_loop(&plan, &runtime, session_id.as_deref(), verbose); + let chat_result = chat_loop(&plan, &runtime, verbose); let stop_result = stop_runtime(&plan, &runtime); if let Err(error) = chat_result { return Err(error); @@ -257,14 +246,9 @@ fn run_chat( fn chat_loop( plan: &RunPlan, runtime: &RuntimeHandle, - session_id: Option<&str>, mut verbose: bool, ) -> Result<(), Box> { - let harness_session_id = session_id - .unwrap_or(runtime.runtime_id.as_str()) - .to_string(); - let session_provided = session_id.is_some(); - let prompt = chat_prompt(plan, &harness_session_id); + let prompt = chat_prompt(plan, &runtime.runtime_id); let interrupted = Arc::new(AtomicBool::new(false)); { let interrupted = Arc::clone(&interrupted); @@ -274,7 +258,7 @@ fn chat_loop( } let input_is_terminal = io::stdin().is_terminal(); let lines = stdin_lines(); - print_chat_info(plan, runtime, &harness_session_id, session_provided); + print_chat_info(plan, runtime); eprintln!(); let mut turn_count = 0_u64; @@ -294,7 +278,7 @@ fn chat_loop( continue; } "/info" => { - print_chat_info(plan, runtime, &harness_session_id, session_provided); + print_chat_info(plan, runtime); continue; } "/clear" => { @@ -333,11 +317,7 @@ fn chat_loop( continue; } - let mut request = RunRequest::text(input); - request.context.insert( - "session_id".to_string(), - Value::String(harness_session_id.clone()), - ); + let request = RunRequest::text(input); let result = invoke_runtime(plan, runtime, request)?; turn_count += 1; if !input_is_terminal { @@ -366,30 +346,16 @@ fn chat_loop( Ok(()) } -fn print_chat_info( - plan: &RunPlan, - runtime: &RuntimeHandle, - session_id: &str, - session_provided: bool, -) { +fn print_chat_info(plan: &RunPlan, runtime: &RuntimeHandle) { eprintln!("+================================================================+"); eprintln!("| NEMO FABRIC |"); - eprintln!("| interactive runtime session |"); + eprintln!("| interactive runtime |"); eprintln!("+----------------------------------------------------------------+"); eprintln!("| agent: {}", plan.agent_name); eprintln!("| profile: {}", profile_label(plan)); eprintln!("| harness: {}", runtime.harness); eprintln!("| adapter: {}", adapter_kind_label(runtime.adapter_kind)); eprintln!("| runtime_id: {}", runtime.runtime_id); - eprintln!( - "| session_id: {} ({})", - session_id, - if session_provided { - "provided" - } else { - "runtime_id default" - } - ); eprintln!("| commands: /help, /info, /verbose on|off, /clear, /exit, /quit"); eprintln!("+----------------------------------------------------------------"); } @@ -397,11 +363,11 @@ fn print_chat_info( fn print_chat_help() { eprintln!("Commands:"); eprintln!(" /help show this help"); - eprintln!(" /info show session/runtime info"); + eprintln!(" /info show runtime info"); eprintln!(" /verbose on|off toggle per-turn metadata"); eprintln!(" /clear clear the terminal"); eprintln!(" /exit, /quit stop the runtime and exit"); - eprintln!("Type a non-empty message to invoke the same runtime session."); + eprintln!("Type a non-empty message to invoke the same runtime."); } fn print_turn_verbose(turn: u64, result: &RunResult) { @@ -427,11 +393,11 @@ fn print_turn_verbose(turn: u64, result: &RunResult) { eprintln!("+----------------------------------------------------------------"); } -fn chat_prompt(plan: &RunPlan, session_id: &str) -> String { +fn chat_prompt(plan: &RunPlan, runtime_id: &str) -> String { format!( "you[{}:{}]", profile_label(plan), - short_prompt_label(session_id) + short_prompt_label(runtime_id) ) } diff --git a/crates/fabric-core/src/config.rs b/crates/fabric-core/src/config.rs index ba4a643f7..1c9efb9ee 100644 --- a/crates/fabric-core/src/config.rs +++ b/crates/fabric-core/src/config.rs @@ -15,6 +15,8 @@ use serde_json::Value; use crate::error::{FabricError, Result}; const AGENT_YAML: &str = "agent.yaml"; +/// Adapter descriptor contract version supported by this core. +pub const ADAPTER_CONTRACT_VERSION: &str = "fabric.adapter/v1alpha1"; /// A loaded Fabric document with resolved source path and agent root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] @@ -43,7 +45,7 @@ pub struct FabricConfig { /// Model aliases. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub models: BTreeMap, - /// Runtime mode and input/output contract. + /// Runtime input/output contract. pub runtime: RuntimeConfig, /// Environment where the harness or its tools execute. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -117,6 +119,9 @@ pub struct HarnessConfig { /// Language-neutral adapter descriptor for a harness integration. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AdapterDescriptor { + /// Adapter descriptor contract version. + #[schemars(length(min = 1))] + pub contract_version: String, /// Unique id for this adapter implementation. #[schemars(length(min = 1))] pub adapter_id: String, @@ -137,6 +142,9 @@ pub struct AdapterDescriptor { /// Telemetry support declared by this adapter. #[serde(default)] pub telemetry: AdapterTelemetrySupport, + /// Runtime lifecycle operations supported by this adapter. + #[serde(default)] + pub capabilities: RuntimeCapabilities, /// Additive adapter descriptor fields. #[serde(default, flatten)] pub extensions: BTreeMap, @@ -483,14 +491,9 @@ pub struct ModelConfig { pub extensions: BTreeMap, } -/// Runtime mode and input/output contract. +/// Runtime input/output contract. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct RuntimeConfig { - /// Runtime mode. - pub mode: RuntimeMode, - /// Transport used to operate the harness. - #[serde(default = "default_runtime_transport")] - pub transport: Transport, /// Input schema label. #[serde(default = "default_input_schema")] pub input_schema: String, @@ -505,10 +508,6 @@ pub struct RuntimeConfig { pub extensions: BTreeMap, } -fn default_runtime_transport() -> Transport { - Transport::Library -} - fn default_input_schema() -> String { "text".to_string() } @@ -517,32 +516,6 @@ fn default_output_schema() -> String { "text".to_string() } -/// Runtime lifecycle mode. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum RuntimeMode { - /// Request is the lifecycle boundary. - Oneshot, - /// Long-running process or service is the lifecycle boundary. - Service, - /// Session is the lifecycle boundary. - Session, -} - -/// Runtime transport. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Transport { - /// In-process library/SDK call. - Library, - /// CLI process. - Cli, - /// HTTP service. - Http, - /// Harness-native plugin surface. - NativePlugin, -} - /// Execution environment configuration. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct EnvironmentConfig { @@ -560,7 +533,7 @@ pub struct EnvironmentConfig { /// Artifact path inside or outside the provider. #[serde(default, skip_serializing_if = "Option::is_none")] pub artifacts: Option, - /// Provider connection metadata, such as server URL, session id, or namespace. + /// Provider connection metadata, such as server URL, credential reference, or namespace. #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")] pub connection: serde_json::Map, /// Consumer-provided environment metadata. @@ -637,9 +610,6 @@ pub struct TelemetryConfig { /// Telemetry provider responsible for runtime integration. #[serde(default)] pub provider: TelemetryProvider, - /// Telemetry mode, for example `sdk`, `gateway`, or `external`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mode: Option, /// Optional project name for telemetry backends. #[serde(default, skip_serializing_if = "Option::is_none")] pub project: Option, @@ -1071,6 +1041,16 @@ fn validate_adapter_descriptor( } fn validate_adapter_descriptor_shape(descriptor: &AdapterDescriptor, path: &Path) -> Result<()> { + if descriptor.contract_version.trim().is_empty() { + return invalid_adapter_descriptor(path, "contract_version must not be empty"); + } + if descriptor.contract_version != ADAPTER_CONTRACT_VERSION { + return Err(FabricError::AdapterDescriptorUnsupported { + adapter_id: descriptor.adapter_id.clone(), + field: "contract_version", + value: descriptor.contract_version.clone(), + }); + } if descriptor.adapter_id.trim().is_empty() { return invalid_adapter_descriptor(path, "adapter_id must not be empty"); } @@ -1095,7 +1075,7 @@ fn validate_control_location( } fn resolve_runtime_capabilities( - config: &FabricConfig, + _config: &FabricConfig, descriptor: Option<&AdapterDescriptor>, ) -> RuntimeCapabilities { let implemented_runtime = descriptor.is_some_and(|descriptor| { @@ -1103,18 +1083,16 @@ fn resolve_runtime_capabilities( descriptor.adapter_kind, AdapterKind::Process | AdapterKind::Python ) - }) && matches!( - config.runtime.transport, - Transport::Library | Transport::Cli - ); + }); + let descriptor_capabilities = descriptor + .map(|descriptor| descriptor.capabilities.clone()) + .unwrap_or_default(); RuntimeCapabilities { - session: implemented_runtime && config.runtime.mode == RuntimeMode::Session, - service: false, - streaming: false, - updates: false, - cancellation: false, - concurrent_invocations: false, - metadata: BTreeMap::new(), + service: implemented_runtime && descriptor_capabilities.service, + streaming: implemented_runtime && descriptor_capabilities.streaming, + updates: implemented_runtime && descriptor_capabilities.updates, + cancellation: implemented_runtime && descriptor_capabilities.cancellation, + metadata: descriptor_capabilities.metadata, } } @@ -1296,7 +1274,6 @@ fn resolve_telemetry_plan( Some(TelemetryPlan { provider: telemetry.provider, relay_enabled: telemetry.enabled && telemetry.provider == TelemetryProvider::Relay, - relay_mode: telemetry.mode.clone(), relay_project: telemetry.project.clone(), relay_output_dir: telemetry.output_dir.clone(), relay_config: telemetry.config.clone(), @@ -1377,18 +1354,18 @@ pub struct RunPlan { /// Lifecycle behavior implemented by a resolved runtime path. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct RuntimeCapabilities { - /// Whether the selected runtime supports session lifecycle operations. - pub session: bool, /// Whether the selected runtime supports service lifecycle operations. + #[serde(default)] pub service: bool, /// Whether invocations can emit progressive output. + #[serde(default)] pub streaming: bool, /// Whether a running runtime can accept config updates. + #[serde(default)] pub updates: bool, /// Whether an in-flight invocation can be cancelled. + #[serde(default)] pub cancellation: bool, - /// Whether the runtime accepts concurrent invocations. - pub concurrent_invocations: bool, /// Additional adapter-specific capability metadata. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub metadata: BTreeMap, @@ -1515,9 +1492,6 @@ pub struct TelemetryPlan { pub provider: TelemetryProvider, /// Whether Relay is enabled. pub relay_enabled: bool, - /// Relay mode, when configured. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub relay_mode: Option, /// Relay project, when configured. #[serde(default, skip_serializing_if = "Option::is_none")] pub relay_project: Option, @@ -1536,8 +1510,8 @@ pub struct TelemetryPlan { mod tests { use super::*; - fn example_agent_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/code-review-agent") + fn file_config_agent_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/file-config-agent") } fn example_adapter_descriptor_path() -> PathBuf { @@ -1556,8 +1530,6 @@ harness: settings: workspace: ./workspace runtime: - mode: oneshot - transport: library input_schema: chat output_schema: message tools: @@ -1574,12 +1546,12 @@ future_top_level: let profile: ProfileConfig = serde_yaml::from_str( r#" schema_version: fabric.profile/v1alpha1 -name: session +name: overlay harness: settings: timeout_seconds: 30 runtime: - mode: session + input_schema: prompt tools: enabled: [profile] cleared: null @@ -1599,9 +1571,8 @@ future_top_level: .expect("effective config"); let value = serde_json::to_value(&effective.config).expect("config json"); - assert_eq!(effective.profiles, ["session"]); - assert_eq!(value["runtime"]["mode"], "session"); - assert_eq!(value["runtime"]["transport"], "library"); + assert_eq!(effective.profiles, ["overlay"]); + assert_eq!(value["runtime"]["input_schema"], "prompt"); assert_eq!(value["harness"]["settings"]["workspace"], "./workspace"); assert_eq!(value["harness"]["settings"]["timeout_seconds"], 30); assert_eq!(value["tools"]["enabled"], serde_json::json!(["profile"])); @@ -1622,12 +1593,10 @@ metadata: harness: adapter_id: nvidia.fabric.hermes.sdk runtime: - mode: oneshot "#, ) .expect("minimal config"); - assert_eq!(config.runtime.transport, Transport::Library); assert_eq!(config.runtime.input_schema, "text"); assert_eq!(config.runtime.output_schema, "text"); } @@ -1642,7 +1611,6 @@ metadata: harness: adapter_id: nvidia.fabric.hermes.sdk runtime: - mode: oneshot telemetry: enabled: true config: @@ -1673,7 +1641,6 @@ metadata: harness: adapter_id: nvidia.fabric.hermes.sdk runtime: - mode: oneshot telemetry: enabled: true provider: native @@ -1705,35 +1672,12 @@ provider: unsupported assert!(result.is_err()); } - #[test] - fn unsupported_transports_do_not_claim_session_capability() { - let mut config: FabricConfig = serde_yaml::from_str( - r#" -schema_version: fabric.agent/v1alpha1 -metadata: - name: demo -harness: - adapter_id: nvidia.fabric.hermes.sdk -runtime: - mode: session -"#, - ) - .expect("session config"); - let descriptor = - load_adapter_descriptor(example_adapter_descriptor_path()).expect("adapter descriptor"); - - assert!(resolve_runtime_capabilities(&config, Some(&descriptor)).session); - for transport in [Transport::Http, Transport::NativePlugin] { - config.runtime.transport = transport; - assert!(!resolve_runtime_capabilities(&config, Some(&descriptor)).session); - } - } - #[test] fn loads_adapter_descriptor() { let descriptor = load_adapter_descriptor(example_adapter_descriptor_path()).expect("adapter descriptor"); + assert_eq!(descriptor.contract_version, ADAPTER_CONTRACT_VERSION); assert_eq!(descriptor.adapter_id, "nvidia.fabric.hermes.sdk"); assert_eq!(descriptor.harness, "hermes"); assert_eq!(descriptor.adapter_kind, AdapterKind::Python); @@ -1757,7 +1701,7 @@ runtime: #[test] fn resolves_base_config_from_agent_directory() { - let plan = resolve_run_plan(example_agent_dir(), None).expect("run plan"); + let plan = resolve_run_plan(file_config_agent_dir(), None).expect("run plan"); assert_eq!(plan.agent_name, "code-review-agent"); assert!(plan.profiles.is_empty()); @@ -1766,12 +1710,10 @@ runtime: assert_eq!( plan_json["capabilities"], serde_json::json!({ - "session": true, "service": false, "streaming": false, "updates": false, - "cancellation": false, - "concurrent_invocations": false + "cancellation": false }) ); assert_eq!(plan.config.harness.adapter_id, "nvidia.fabric.hermes.sdk"); @@ -1823,7 +1765,7 @@ runtime: #[test] fn resolves_hermes_sdk_adapter_descriptor() { - let plan = resolve_run_plan(example_agent_dir(), Some("hermes_sdk")).expect("run plan"); + let plan = resolve_run_plan(file_config_agent_dir(), Some("hermes_sdk")).expect("run plan"); let adapter = plan .adapter_descriptor .as_ref() @@ -1853,8 +1795,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: message environment: @@ -1865,6 +1805,7 @@ environment: std::fs::write( root.join("adapters/reviewer-process/fabric-adapter.json"), r#"{ + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "acme.fabric.reviewer.process", "harness": "reviewer", "adapter_kind": "process" @@ -1891,7 +1832,7 @@ environment: #[test] fn resolves_env_profile_from_agent_directory() { let plan = - resolve_run_plan(example_agent_dir(), Some("env_opensandbox")).expect("run plan"); + resolve_run_plan(file_config_agent_dir(), Some("env_opensandbox")).expect("run plan"); assert_eq!(plan.profiles, vec!["env_opensandbox"]); assert!(plan.config_path.ends_with("agent.yaml")); @@ -1906,7 +1847,7 @@ environment: #[test] fn resolves_mcp_profile_from_agent_directory() { - let plan = resolve_run_plan(example_agent_dir(), Some("mcp_github")).expect("run plan"); + let plan = resolve_run_plan(file_config_agent_dir(), Some("mcp_github")).expect("run plan"); assert_eq!(plan.profiles, vec!["mcp_github"]); let plan_json = serde_json::to_value(&plan).expect("plan json"); @@ -1934,7 +1875,7 @@ environment: fn resolves_ordered_profiles_from_agent_directory() { let profiles = vec!["env_local".to_string(), "mcp_github".to_string()]; let plan = - resolve_run_plan_with_profiles(example_agent_dir(), &profiles).expect("run plan"); + resolve_run_plan_with_profiles(file_config_agent_dir(), &profiles).expect("run plan"); assert_eq!(plan.profiles, profiles); assert_eq!( @@ -1961,7 +1902,7 @@ environment: #[test] fn resolves_in_memory_config_with_typed_profiles() { let FabricDocument::FabricConfig { config, root, .. } = - load_fabric_document(example_agent_dir()).expect("agent config"); + load_fabric_document(file_config_agent_dir()).expect("agent config"); let profile = read_yaml::(&root.join("profiles/mcp-github.yaml")) .expect("profile config"); @@ -2006,8 +1947,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: text tools: @@ -2027,6 +1966,7 @@ mcp: std::fs::write( root.join("adapters/minimal/fabric-adapter.json"), r#"{ + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "acme.fabric.minimal", "harness": "minimal", "adapter_kind": "process" @@ -2063,7 +2003,7 @@ mcp: fn later_profiles_override_earlier_profiles() { let profiles = vec!["env_opensandbox".to_string(), "env_local".to_string()]; let plan = - resolve_run_plan_with_profiles(example_agent_dir(), &profiles).expect("run plan"); + resolve_run_plan_with_profiles(file_config_agent_dir(), &profiles).expect("run plan"); assert_eq!(plan.profiles, profiles); assert_eq!( @@ -2088,7 +2028,7 @@ mcp: let profiles = vec!["env_local".to_string(), "env_opensandbox".to_string()]; let plan = - resolve_run_plan_with_profiles(example_agent_dir(), &profiles).expect("run plan"); + resolve_run_plan_with_profiles(file_config_agent_dir(), &profiles).expect("run plan"); assert_eq!( plan.environment_plan @@ -2106,7 +2046,7 @@ mcp: #[test] fn resolves_hermes_sdk_profile_from_agent_directory() { - let plan = resolve_run_plan(example_agent_dir(), Some("hermes_sdk")).expect("run plan"); + let plan = resolve_run_plan(file_config_agent_dir(), Some("hermes_sdk")).expect("run plan"); assert_eq!(plan.profiles, vec!["hermes_sdk"]); assert_eq!(plan.config.harness.adapter_id, "nvidia.fabric.hermes.sdk"); @@ -2148,7 +2088,7 @@ mcp: #[test] fn resolves_direct_profile_path_from_agent_directory() { - let plan = resolve_run_plan(example_agent_dir(), Some("./profiles/hermes-sdk.yaml")) + let plan = resolve_run_plan(file_config_agent_dir(), Some("./profiles/hermes-sdk.yaml")) .expect("run plan"); assert_eq!(plan.profiles, vec!["./profiles/hermes-sdk.yaml"]); @@ -2163,7 +2103,7 @@ mcp: #[test] fn errors_for_unknown_manifest_profile() { - let error = resolve_run_plan(example_agent_dir(), Some("missing")).expect_err("error"); + let error = resolve_run_plan(file_config_agent_dir(), Some("missing")).expect_err("error"); assert!(matches!(error, FabricError::UnknownProfile { .. })); } @@ -2188,8 +2128,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: message environment: @@ -2200,6 +2138,7 @@ environment: std::fs::write( root.join("adapters/invalid-process/fabric-adapter.json"), r#"{ + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": " ", "harness": "invalid", "adapter_kind": "process" @@ -2229,6 +2168,7 @@ environment: std::fs::write( &descriptor_path, r#"{ + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "", "harness": "invalid", "adapter_kind": "process" @@ -2245,4 +2185,37 @@ environment: let _ = std::fs::remove_dir_all(root); } + + #[test] + fn rejects_unsupported_adapter_contract_version() { + let root = std::env::temp_dir().join(format!( + "fabric-invalid-adapter-contract-test-{}", + std::process::id() + )); + let _ = std::fs::remove_dir_all(&root); + std::fs::create_dir_all(&root).expect("create temp root"); + let descriptor_path = root.join("fabric-adapter.json"); + std::fs::write( + &descriptor_path, + r#"{ + "contract_version": "fabric.adapter/v9", + "adapter_id": "acme.fabric.future", + "harness": "future", + "adapter_kind": "process" +}"#, + ) + .expect("write adapter descriptor"); + + let error = load_adapter_descriptor(&descriptor_path).expect_err("invalid descriptor"); + assert!(matches!( + error, + FabricError::AdapterDescriptorUnsupported { + field, + value, + .. + } if field == "contract_version" && value == "fabric.adapter/v9" + )); + + let _ = std::fs::remove_dir_all(root); + } } diff --git a/crates/fabric-core/src/doctor.rs b/crates/fabric-core/src/doctor.rs index 5da804fe5..ab5c5274e 100644 --- a/crates/fabric-core/src/doctor.rs +++ b/crates/fabric-core/src/doctor.rs @@ -12,7 +12,7 @@ use serde_json::Value; use crate::config::{ AdapterKind, CapabilityTarget, ControlLocation, EnvironmentOwnership, ResolutionStrategy, - RunPlan, RuntimeMode, Transport, + RunPlan, }; /// Diagnostic status. @@ -130,27 +130,6 @@ fn check_resolution(plan: &RunPlan) -> DoctorCheck { fn check_runtime_execution_surface(plan: &RunPlan) -> Vec { let mut checks = Vec::new(); - match plan.config.runtime.mode { - RuntimeMode::Service => checks.push(check( - "runtime.mode", - DoctorStatus::Warn, - "runtime mode `service` is modeled but not implemented by Fabric runtime dispatch", - )), - RuntimeMode::Oneshot | RuntimeMode::Session => {} - } - match plan.config.runtime.transport { - Transport::Http => checks.push(check( - "runtime.transport", - DoctorStatus::Warn, - "runtime transport `http` is modeled but not implemented by Fabric runtime dispatch", - )), - Transport::NativePlugin => checks.push(check( - "runtime.transport", - DoctorStatus::Warn, - "runtime transport `native_plugin` is modeled but not implemented by Fabric runtime dispatch", - )), - Transport::Library | Transport::Cli => {} - } let Some(adapter) = &plan.adapter_descriptor else { return checks; }; @@ -490,17 +469,15 @@ mod tests { use serde_json::Value; use super::*; - use crate::config::{ - AdapterKind, ResolutionStrategy, RuntimeMode, Transport, resolve_run_plan, - }; + use crate::config::{AdapterKind, ResolutionStrategy, resolve_run_plan}; - fn example_agent_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/code-review-agent") + fn file_config_agent_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/file-config-agent") } #[test] fn image_provided_uses_environment_image_instead_of_host_requirements() { - let mut plan = resolve_run_plan(example_agent_dir(), None).expect("run plan"); + let mut plan = resolve_run_plan(file_config_agent_dir(), None).expect("run plan"); plan.resolution = Some(ResolutionStrategy::ImageProvided); plan.environment_plan .as_mut() @@ -528,7 +505,7 @@ mod tests { #[test] fn preinstalled_non_local_environment_does_not_probe_host_requirements() { let plan = - resolve_run_plan(example_agent_dir(), Some("env_opensandbox")).expect("run plan"); + resolve_run_plan(file_config_agent_dir(), Some("env_opensandbox")).expect("run plan"); let report = doctor_plan(&plan); @@ -568,10 +545,8 @@ mod tests { } #[test] - fn doctor_reports_service_and_http_execution_as_modeled_not_implemented() { - let mut plan = resolve_run_plan(example_agent_dir(), None).expect("run plan"); - plan.config.runtime.mode = RuntimeMode::Service; - plan.config.runtime.transport = Transport::Http; + fn doctor_reports_http_execution_as_modeled_not_implemented() { + let mut plan = resolve_run_plan(file_config_agent_dir(), None).expect("run plan"); plan.resolution = Some(ResolutionStrategy::Service); plan.adapter_descriptor .as_mut() @@ -582,18 +557,6 @@ mod tests { let report = doctor_plan(&plan); assert_eq!(report.status, DoctorStatus::Warn); - assert!(report.checks.iter().any(|check| { - check.name == "runtime.mode" - && check.status == DoctorStatus::Warn - && check.message.contains("modeled but not implemented") - && check.message.contains("service") - })); - assert!(report.checks.iter().any(|check| { - check.name == "runtime.transport" - && check.status == DoctorStatus::Warn - && check.message.contains("modeled but not implemented") - && check.message.contains("http") - })); assert!(report.checks.iter().any(|check| { check.name == "runtime.adapter" && check.status == DoctorStatus::Warn diff --git a/crates/fabric-core/src/lib.rs b/crates/fabric-core/src/lib.rs index 7bd4ee58a..d24ab5dcb 100644 --- a/crates/fabric-core/src/lib.rs +++ b/crates/fabric-core/src/lib.rs @@ -10,17 +10,16 @@ pub mod runtime; pub mod schema; pub use config::{ - AdapterConfigSupport, AdapterDescriptor, AdapterDescriptorSource, AdapterKind, - AdapterRequirements, AdapterTelemetrySupport, CapabilityPlan, ControlLocation, EffectiveConfig, - EnvironmentConfig, EnvironmentOwnership, EnvironmentPlan, FabricConfig, FabricDocument, - HarnessConfig, McpConfig, McpExposure, McpServerPlan, MetadataConfig, ModelConfig, - ProfileConfig, ResolutionStrategy, ResolveContext, ResolvedAdapterDescriptor, RunPlan, - RuntimeCapabilities, RuntimeConfig, RuntimeMode, SkillConfig, TelemetryConfig, TelemetryPlan, - TelemetryProvider, Transport, load_adapter_descriptor, load_fabric_document, - resolve_effective_config, resolve_effective_config_from_config, - resolve_effective_config_with_profiles, resolve_run_plan, resolve_run_plan_from_config, - resolve_run_plan_from_effective_config, resolve_run_plan_with_profiles, - validate_agent_directory, + ADAPTER_CONTRACT_VERSION, AdapterConfigSupport, AdapterDescriptor, AdapterDescriptorSource, + AdapterKind, AdapterRequirements, AdapterTelemetrySupport, CapabilityPlan, ControlLocation, + EffectiveConfig, EnvironmentConfig, EnvironmentOwnership, EnvironmentPlan, FabricConfig, + FabricDocument, HarnessConfig, McpConfig, McpExposure, McpServerPlan, MetadataConfig, + ModelConfig, ProfileConfig, ResolutionStrategy, ResolveContext, ResolvedAdapterDescriptor, + RunPlan, RuntimeCapabilities, RuntimeConfig, SkillConfig, TelemetryConfig, TelemetryPlan, + TelemetryProvider, load_adapter_descriptor, load_fabric_document, resolve_effective_config, + resolve_effective_config_from_config, resolve_effective_config_with_profiles, resolve_run_plan, + resolve_run_plan_from_config, resolve_run_plan_from_effective_config, + resolve_run_plan_with_profiles, validate_agent_directory, }; pub use doctor::{DoctorCheck, DoctorReport, DoctorStatus, doctor_plan}; pub use error::{FabricError, Result}; diff --git a/crates/fabric-core/src/runtime.rs b/crates/fabric-core/src/runtime.rs index e59c7a566..f5b06f2d5 100644 --- a/crates/fabric-core/src/runtime.rs +++ b/crates/fabric-core/src/runtime.rs @@ -18,11 +18,12 @@ use serde_json::{Map, Value}; use crate::config::{ AdapterKind, CapabilityPlan, ControlLocation, EffectiveConfig, EnvironmentOwnership, RunPlan, - RuntimeMode, TelemetryPlan, + TelemetryPlan, }; use crate::error::{FabricError, Result}; static NEXT_ID: AtomicU64 = AtomicU64::new(1); +const RELAY_RUNTIME_MODE: &str = "sdk"; #[cfg(test)] static TEST_STOPPED_AGENTS: Mutex> = Mutex::new(Vec::new()); @@ -34,7 +35,7 @@ pub struct RunRequest { /// Request payload for the harness. #[serde(default)] pub input: Value, - /// Runtime context such as task, rollout, session, or caller metadata. + /// Runtime context such as task, rollout, workflow, or caller metadata. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub context: BTreeMap, /// Per-invocation overrides allowed by the resolved profile. @@ -234,8 +235,6 @@ pub struct RuntimeHandle { pub agent_name: String, /// Stable machine-readable harness identifier. pub harness: String, - /// Runtime mode. - pub mode: RuntimeMode, /// Adapter kind. pub adapter_kind: AdapterKind, /// Adapter implementation id. @@ -261,9 +260,6 @@ pub struct InvocationHandle { pub struct RuntimeContext { /// Runtime handle id. pub runtime_id: String, - /// Optional caller-provided harness conversation id. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub session_id: Option, /// Invocation handle id. pub invocation_id: String, /// Request id. @@ -462,12 +458,6 @@ fn validate_runtime_handle(plan: &RunPlan, runtime: &RuntimeHandle) -> Result<() )?; expect_runtime_field(runtime, "agent_name", &plan.agent_name, &runtime.agent_name)?; expect_runtime_field(runtime, "harness", &harness(plan), &runtime.harness)?; - expect_runtime_field( - runtime, - "runtime.mode", - &runtime_mode_name(plan.config.runtime.mode), - &runtime_mode_name(runtime.mode), - )?; expect_runtime_field( runtime, "adapter_kind", @@ -610,7 +600,6 @@ impl RuntimeAdapter for ProcessAdapter { runtime_binding, agent_name: plan.agent_name.clone(), harness: harness(plan), - mode: plan.config.runtime.mode, adapter_kind: adapter_kind(plan), adapter_id: adapter_id(plan), environment, @@ -658,7 +647,6 @@ impl RuntimeAdapter for PythonAdapter { runtime_binding, agent_name: plan.agent_name.clone(), harness: harness(plan), - mode: plan.config.runtime.mode, adapter_kind: adapter_kind(plan), adapter_id: adapter_id(plan), environment, @@ -718,8 +706,15 @@ fn run_process_adapter( .or_else(|| runtime.environment.workspace.clone()) .unwrap_or_else(|| plan.agent_root.clone()); let mut artifacts = artifact_manifest(plan)?; - let relay_config = - prepare_relay_runtime_config(plan, runtime, &invocation, &request, &mut artifacts)?; + let fabric_home = prepare_fabric_home(&artifacts, runtime, &invocation)?; + let relay_config = prepare_relay_runtime_config( + plan, + runtime, + &invocation, + &request, + &fabric_home, + &mut artifacts, + )?; let adapter_payload = fabric_adapter_payload( plan, runtime, @@ -728,7 +723,6 @@ fn run_process_adapter( &artifacts, relay_config.as_ref(), )?; - let fabric_home = prepare_fabric_home(&artifacts, runtime, &invocation)?; let fabric_invocation = write_fabric_invocation(&fabric_home, &adapter_payload)?; let mut command = Command::new(&command_path); @@ -828,6 +822,7 @@ fn run_process_adapter( if !stdout.is_empty() { write_artifact( &mut artifacts, + &fabric_home, "stdout", "log", "stdout.txt", @@ -838,6 +833,7 @@ fn run_process_adapter( if !stderr.is_empty() { write_artifact( &mut artifacts, + &fabric_home, "stderr", "log", "stderr.txt", @@ -845,7 +841,7 @@ fn run_process_adapter( "text/plain", )?; } - collect_workspace_artifacts(&mut artifacts, runtime, &mut events)?; + collect_workspace_artifacts(&mut artifacts, &fabric_home, runtime, &mut events)?; let mut metadata = BTreeMap::new(); metadata.insert( @@ -933,8 +929,15 @@ fn run_python_adapter( let python = resolve_python_command(&plan.config_root, &settings); let mut artifacts = artifact_manifest(plan)?; - let relay_config = - prepare_relay_runtime_config(plan, runtime, &invocation, &request, &mut artifacts)?; + let fabric_home = prepare_fabric_home(&artifacts, runtime, &invocation)?; + let relay_config = prepare_relay_runtime_config( + plan, + runtime, + &invocation, + &request, + &fabric_home, + &mut artifacts, + )?; let mut command = Command::new(&python); command @@ -1034,6 +1037,7 @@ fn run_python_adapter( if !stdout.is_empty() { write_artifact( &mut artifacts, + &fabric_home, "stdout", "log", "stdout.txt", @@ -1044,6 +1048,7 @@ fn run_python_adapter( if !stderr.is_empty() { write_artifact( &mut artifacts, + &fabric_home, "stderr", "log", "stderr.txt", @@ -1051,7 +1056,7 @@ fn run_python_adapter( "text/plain", )?; } - collect_workspace_artifacts(&mut artifacts, runtime, &mut events)?; + collect_workspace_artifacts(&mut artifacts, &fabric_home, runtime, &mut events)?; let mut metadata = BTreeMap::new(); metadata.insert( @@ -1154,15 +1159,6 @@ fn adapter_kind_name(adapter_kind: AdapterKind) -> String { .to_string() } -fn runtime_mode_name(mode: RuntimeMode) -> String { - match mode { - RuntimeMode::Oneshot => "oneshot", - RuntimeMode::Service => "service", - RuntimeMode::Session => "session", - } - .to_string() -} - fn optional_runtime_value(value: Option<&str>) -> String { value.unwrap_or("").to_string() } @@ -1257,7 +1253,6 @@ fn adapter_invocation( effective_config, runtime_context: RuntimeContext { runtime_id: runtime.runtime_id.clone(), - session_id: request_session_id(request), invocation_id: invocation.invocation_id.clone(), request_id: request.request_id.clone(), environment: runtime.environment.clone(), @@ -1270,15 +1265,6 @@ fn adapter_invocation( }) } -fn request_session_id(request: &RunRequest) -> Option { - request - .context - .get("session_id") - .and_then(Value::as_str) - .filter(|session_id| !session_id.is_empty()) - .map(ToOwned::to_owned) -} - fn runtime_telemetry_context( plan: &RunPlan, relay_config: Option<&RelayRuntimeConfig>, @@ -1289,9 +1275,6 @@ fn runtime_telemetry_context( "telemetry_provider".to_string(), Value::String(telemetry.provider.as_str().to_string()), ); - if let Some(mode) = &telemetry.relay_mode { - metadata.insert("relay_mode".to_string(), Value::String(mode.clone())); - } if let Some(project) = &telemetry.relay_project { metadata.insert("relay_project".to_string(), Value::String(project.clone())); } @@ -1528,16 +1511,17 @@ fn write_fabric_invocation(fabric_home: &Path, payload: &str) -> Result fn write_artifact( manifest: &mut ArtifactManifest, + directory: &Path, name: &str, kind: &str, filename: &str, contents: &str, media_type: &str, ) -> Result<()> { - let Some(root) = &manifest.root else { + if manifest.root.is_none() { return Ok(()); - }; - let path = root.join(filename); + } + let path = directory.join(filename); std::fs::write(&path, contents).map_err(|source| FabricError::Write { path: path.clone(), source, @@ -1553,6 +1537,7 @@ fn write_artifact( fn collect_workspace_artifacts( manifest: &mut ArtifactManifest, + artifact_directory: &Path, runtime: &RuntimeHandle, events: &mut Vec, ) -> Result<()> { @@ -1600,6 +1585,7 @@ fn collect_workspace_artifacts( } write_artifact( manifest, + artifact_directory, "workspace_patch", "patch", "workspace.patch", @@ -1608,6 +1594,7 @@ fn collect_workspace_artifacts( )?; write_artifact( manifest, + artifact_directory, "workspace_status", "log", "workspace-status.txt", @@ -1680,6 +1667,7 @@ fn prepare_relay_runtime_config( runtime: &RuntimeHandle, invocation: &InvocationHandle, request: &RunRequest, + artifact_directory: &Path, artifacts: &mut ArtifactManifest, ) -> Result> { let Some(telemetry) = plan.telemetry_plan.as_ref() else { @@ -1688,14 +1676,14 @@ fn prepare_relay_runtime_config( if !telemetry.relay_enabled { return Ok(None); } - let Some(root) = artifacts.root.clone() else { + if artifacts.root.is_none() { return Ok(None); - }; + } let relay_config = serde_json::json!({ "schema_version": "fabric.relay/v1alpha1", "relay": { "enabled": true, - "mode": telemetry.relay_mode.as_deref().unwrap_or("sdk"), + "mode": RELAY_RUNTIME_MODE, "project": telemetry.relay_project.clone(), "output_dir": telemetry .relay_output_dir @@ -1721,22 +1709,22 @@ fn prepare_relay_runtime_config( serde_json::to_string_pretty(&relay_config).map_err(FabricError::SerializeJson)?; write_artifact( artifacts, + artifact_directory, "relay_config", "telemetry_config", "relay-config.json", &contents, "application/json", )?; - let path = absolute_path(root.join("relay-config.json"))?; - let mode = telemetry - .relay_mode - .clone() - .unwrap_or_else(|| "sdk".to_string()); + let path = absolute_path(artifact_directory.join("relay-config.json"))?; Ok(Some(RelayRuntimeConfig { path: path.clone(), env: BTreeMap::from([ ("FABRIC_RELAY_ENABLED".to_string(), "true".to_string()), - ("FABRIC_RELAY_MODE".to_string(), mode), + ( + "FABRIC_RELAY_MODE".to_string(), + RELAY_RUNTIME_MODE.to_string(), + ), ( "FABRIC_RELAY_CONFIG_PATH".to_string(), path.to_string_lossy().into_owned(), @@ -1762,9 +1750,6 @@ fn telemetry_ref( "telemetry_provider".to_string(), Value::String(telemetry.provider.as_str().to_string()), ); - if let Some(mode) = &telemetry.relay_mode { - metadata.insert("relay_mode".to_string(), Value::String(mode.clone())); - } if let Some(project) = &telemetry.relay_project { metadata.insert("relay_project".to_string(), Value::String(project.clone())); } @@ -1865,8 +1850,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: text artifacts: ./artifacts @@ -1883,6 +1866,7 @@ runtime: fn process_adapter_descriptor() -> &'static str { r#"{ + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "acme.fabric.process", "harness": "process", "adapter_kind": "process" @@ -1913,8 +1897,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: text artifacts: ./artifacts @@ -1994,6 +1976,54 @@ environment: let _ = fs::remove_dir_all(root); } + #[test] + fn independent_runtimes_use_distinct_artifact_paths() { + let root = temp_process_agent_dir(); + let plan = resolve_run_plan(&root, None).expect("run plan"); + let first_runtime = start_runtime(&plan).expect("first runtime"); + let second_runtime = start_runtime(&plan).expect("second runtime"); + + let first = invoke_runtime(&plan, &first_runtime, RunRequest::text("first runtime")) + .expect("first invocation"); + let second = invoke_runtime(&plan, &second_runtime, RunRequest::text("second runtime")) + .expect("second invocation"); + let first_stdout = first + .artifacts + .artifacts + .iter() + .find(|artifact| artifact.name == "stdout") + .expect("first stdout artifact"); + let second_stdout = second + .artifacts + .artifacts + .iter() + .find(|artifact| artifact.name == "stdout") + .expect("second stdout artifact"); + + assert_eq!(first.artifacts.root, second.artifacts.root); + assert_ne!(first_stdout.path, second_stdout.path); + assert!( + first_stdout.path.starts_with( + root.join("artifacts") + .join(".fabric") + .join(&first_runtime.runtime_id) + .join(&first.invocation_id) + ) + ); + assert!( + second_stdout.path.starts_with( + root.join("artifacts") + .join(".fabric") + .join(&second_runtime.runtime_id) + .join(&second.invocation_id) + ) + ); + assert_eq!(artifact_content(&first, "stdout"), "first runtime"); + assert_eq!(artifact_content(&second, "stdout"), "second runtime"); + + let _ = fs::remove_dir_all(root); + } + #[test] fn native_telemetry_skips_relay_and_reaches_adapter_payload() { let root = temp_process_agent_dir(); @@ -2018,10 +2048,18 @@ telemetry: runtime_id: runtime.runtime_id.clone(), }; let mut artifacts = artifact_manifest(&plan).expect("artifact manifest"); + let artifact_directory = + prepare_fabric_home(&artifacts, &runtime, &invocation).expect("fabric home"); - let relay = - prepare_relay_runtime_config(&plan, &runtime, &invocation, &request, &mut artifacts) - .expect("prepare telemetry"); + let relay = prepare_relay_runtime_config( + &plan, + &runtime, + &invocation, + &request, + &artifact_directory, + &mut artifacts, + ) + .expect("prepare telemetry"); let payload = adapter_invocation( &plan, &runtime, @@ -2305,8 +2343,6 @@ print(json.dumps({ }, }, "runtime": { - "mode": "oneshot", - "transport": "cli", "input_schema": "text", "output_schema": "text", "artifacts": "./artifacts", @@ -2356,9 +2392,9 @@ print(json.dumps({ } #[test] - fn adapter_runtime_context_includes_caller_session_id() { + fn adapter_runtime_context_contains_runtime_and_invocation_ids() { let root = std::env::temp_dir().join(format!( - "fabric-session-context-test-{}", + "fabric-runtime-context-test-{}", std::process::id() )); let _ = fs::remove_dir_all(&root); @@ -2367,7 +2403,7 @@ print(json.dumps({ root.join("agent.yaml"), r#"schema_version: fabric.agent/v1alpha1 metadata: - name: session-context-agent + name: runtime-context-agent harness: adapter_id: acme.fabric.process settings: @@ -2385,8 +2421,6 @@ models: provider: test model: test-model runtime: - mode: session - transport: cli input_schema: text output_schema: text artifacts: ./artifacts @@ -2400,22 +2434,19 @@ runtime: .expect("write adapter descriptor"); let plan = resolve_run_plan(&root, None).expect("run plan"); - let mut request = RunRequest::text("hello fabric"); - request.context.insert( - "session_id".to_string(), - Value::String("caller-session-123".to_string()), - ); + let request = RunRequest::text("hello fabric"); let result = run_plan(&plan, request).expect("run result"); assert_eq!(result.status, RunStatus::Succeeded); - assert_eq!( - result.output["session_id"], - Value::String("caller-session-123".to_string()) - ); + assert!(result.output.get("session_id").is_none()); assert_eq!( result.output["runtime_id"], Value::String(result.runtime_id.clone()) ); + assert_eq!( + result.output["invocation_id"], + Value::String(result.invocation_id.clone()) + ); let _ = fs::remove_dir_all(root); } @@ -2447,8 +2478,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: text artifacts: ./artifacts @@ -2539,8 +2568,6 @@ models: provider: test model: test-model runtime: - mode: oneshot - transport: cli input_schema: text output_schema: text artifacts: ./artifacts diff --git a/crates/fabric-core/src/schema.rs b/crates/fabric-core/src/schema.rs index 316d9c99f..b5ea09354 100644 --- a/crates/fabric-core/src/schema.rs +++ b/crates/fabric-core/src/schema.rs @@ -228,6 +228,7 @@ mod tests { fn adapter_descriptor_schema_rejects_empty_identifiers() { let schema = generate_schema(SchemaName::AdapterDescriptor).expect("schema generation"); + assert_eq!(schema["properties"]["contract_version"]["minLength"], 1); assert_eq!(schema["properties"]["adapter_id"]["minLength"], 1); assert_eq!(schema["properties"]["harness"]["minLength"], 1); } diff --git a/docs/getting-started/overview.mdx b/docs/getting-started/overview.mdx index 403be1a51..b5ef20da6 100644 --- a/docs/getting-started/overview.mdx +++ b/docs/getting-started/overview.mdx @@ -13,7 +13,7 @@ versioned config, lifecycle, result, artifact, and telemetry contracts whether the selected harness is Hermes SDK, Hermes CLI, Codex CLI, or a custom adapter. Fabric owns the seam between an application and its harness. It resolves -configuration and profiles, selects an adapter, drives the runtime lifecycle, +configuration, selects an adapter, drives the runtime lifecycle, and returns normalized evidence without leaking harness-specific control code into the caller. @@ -22,8 +22,8 @@ into the caller. Use a versioned `agent.yaml` package or construct the same typed - `FabricConfig` in Python. Ordered profiles make evaluation and ablation - variants explicit and reproducible. + `FabricConfig` in Python. Applications create variants from typed copies; + portable file packages can use ordered profiles. Plan and invoke different harnesses through one Rust core, CLI, and Python @@ -31,7 +31,7 @@ into the caller. Resolve configs, inspect capabilities, run one-shot jobs, and hold - multi-turn sessions with typed requests, plans, handles, and results. + multi-turn runtimes with typed requests, plans, handles, and results. Collect output, errors, lifecycle events, artifact manifests, and telemetry @@ -47,7 +47,7 @@ Application or evaluation harness | Python SDK or fabric CLI v NeMo Fabric Rust core - config -> profiles -> plan -> lifecycle + config -> plan -> lifecycle | | resolved adapter contract v @@ -81,37 +81,24 @@ export PATH="$HOME/.cargo/bin:$PATH" just build-all ``` -Inspect and diagnose an agent package before running it: - -```bash -fabric plan examples/code-review-agent --profile hermes_sdk -fabric doctor examples/code-review-agent --profile hermes_sdk -fabric run examples/code-review-agent \ - --profile hermes_sdk \ - --input "Reply with exactly: fabric works" -``` - -Use the same package through the typed Python SDK: +Run the example through the Python SDK: ```python import asyncio -from pathlib import Path - -from nemo_fabric import FabricClient +from examples.code_review_agent import BASE_DIR, hermes_sdk_config +from nemo_fabric import Fabric async def main() -> None: - agent = Path("examples/code-review-agent") - async with FabricClient() as client: - plan = client.plan(agent, profiles=["hermes_sdk"]) - report = await client.doctor(agent, profiles=["hermes_sdk"]) - result = await client.run( - agent, - profiles=["hermes_sdk"], - input="Reply with exactly: fabric works", - ) + config = hermes_sdk_config() + client = Fabric() + result = await client.run( + config, + base_dir=BASE_DIR, + input="Reply with exactly: fabric works", + ) - print(plan.adapter.harness, report.status, result.status) + print(result.status) asyncio.run(main()) @@ -121,12 +108,15 @@ Harness installation and credential requirements differ by adapter. The [repository quick start](https://github.com/NVIDIA/NeMo-Fabric#quick-start-hermes-sdk) contains the complete Hermes environment recipe. +See the [Python SDK guide](/sdk/python) for planning, diagnostics, typed +requests, and multi-turn runtime examples. + ## Choose your interface | Interface | Use it when | Start with | | --- | --- | --- | | Python SDK | Your application owns job config, runtime lifecycle, or multi-turn state | [Client API](/reference/api/python-library-reference/client) | -| Session API | You need multiple ordered turns over one live harness runtime | [Sessions](/reference/api/python-library-reference/sessions) | +| Runtime API | You need multiple ordered turns over one live harness runtime | [Runtime](/reference/api/python-library-reference/runtime) | | `fabric` CLI | You are validating packages, debugging profiles, or running reproducible local/CI jobs | `fabric validate`, `plan`, `doctor`, `run`, and `chat` | | JSON Schema | You are building editors, validation, code generation, or another language binding | Committed schemas in the [repository](https://github.com/NVIDIA/NeMo-Fabric/tree/main/schemas) | @@ -137,12 +127,13 @@ and wants to pass only its Fabric slice in memory. ## Core workflow 1. **Configure** an agent package or typed `FabricConfig` with a harness adapter, - runtime mode, environment, models, tools, skills, MCP, and telemetry. -2. **Apply profiles** in caller order to vary harness, model, environment, or - observability settings without mutating the base config. + environment, models, tools, skills, MCP, and telemetry. +2. **Create variants** from deep copies to vary harness, model, environment, or + observability settings without mutating the base config. File-backed + packages may instead apply ordered profiles. 3. **Plan and diagnose** to resolve the adapter and check capabilities and requirements before spending work on a runtime. -4. **Run or start a session** through the shared start, invoke, and stop +4. **Run or start a runtime** through the shared start, invoke, and stop lifecycle contract. 5. **Consume evidence** from `RunResult`: output, structured failure details, artifacts, events, and telemetry references. @@ -157,11 +148,10 @@ and wants to pass only its Fabric slice in memory. Resolve, plan, diagnose, run, and start stateful runtimes. - Invoke multiple turns, inspect identity and history, stream events, and - stop runtime handles safely. + Invoke multiple ordered turns and stop runtime handles safely. FabricAgent -> Fabric SDK -> selected adapter -> agent harness - | | - +----- verifier, reward, and run layout <- RunResult -----+ -``` - -## Ownership boundary - -| Harbor owns | Fabric owns | -| --- | --- | -| Task and dataset materialization | Fabric config and ordered profile resolution | -| Environment and container lifecycle | Harness adapter selection and invocation | -| Verifier execution and reward calculation | Normalized requests, results, and artifacts | -| Job, trial, log, and artifact layout | Telemetry configuration and references | - -Fabric runs inside the Harbor task environment. It does not replace Harbor's -container management, verifier, or evaluation semantics. - -## Install the integration - -Install Fabric with the optional Harbor dependency: - -```bash -python3 -m pip install "nemo-fabric[harbor]" -``` - -For a source checkout, `uv run --extra harbor` installs the same optional -dependency before invoking Harbor. - -## Run a Fabric-backed Harbor task - -Point Harbor at the Fabric agent class, then pass the base config and ordered -profile paths as agent constructor arguments: - -```bash -uv run --extra harbor harbor run \ - --path "$TASK_DIR" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/hermes.yaml"]' \ - --model nvidia/nemotron-3-nano-30b-a3b \ - --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" -``` - -The config and profile paths are paths inside the Harbor task container. -`--ak` passes constructor arguments to `FabricAgent`; these are not Fabric CLI -flags. The integration loads the YAML into typed SDK config objects and calls -`FabricClient.run()` inside the task environment. - -## Switch harnesses and telemetry with profiles - -The runnable demo keeps the Harbor agent and task fixed. Only the Fabric profile -stack, model, and required credentials change: - -| Profile stack | Execution path | What it demonstrates | -| --- | --- | --- | -| `smoke.yaml` | Deterministic scripted adapter | Credential-free Harbor, Fabric, workspace, and verifier pipeline | -| `hermes.yaml` | Hermes CLI | A real model-backed harness selected through Fabric | -| `hermes.yaml`, `telemetry.yaml` | Hermes CLI with NeMo Relay | Phoenix OpenInference traces plus ATOF events and an ATIF trajectory | -| `codex.yaml` | Codex CLI | A second real harness using an existing Codex login mounted by Harbor | - -Profiles are applied in caller order. The telemetry profile composes with the -Hermes profile without requiring another Harbor agent implementation. - -## Inspect results and evidence - -Each trial stores Fabric's normalized result in the Harbor agent logs as -`fabric-result.json`. It includes status, selected profiles, harness and adapter -identity, runtime and invocation IDs, artifacts, telemetry references, and any -structured error. - -Open the Harbor viewer for rewards, exceptions, and trial logs: - -```bash -uv run --extra harbor harbor view "$RUNS_DIR" -``` - -For Relay-enabled runs, inspect the same Harbor run directory for portable ATOF -and ATIF records: - -```bash -find "$RUNS_DIR" -path '*/agent/fabric-artifacts/*/relay/events.atof.jsonl' -find "$RUNS_DIR" -path '*/agent/fabric-artifacts/*/relay/*.atif.json' -``` - -Open Phoenix separately to inspect the corresponding OpenInference trace. - -## Run the complete demo - -The repository demo contains the task image, Fabric config, profile matrix, -portable Phoenix routing, exact commands for every variant, expected results, -and a recording flow: - -[Run the Harbor multi-harness demo](https://github.com/NVIDIA/NeMo-Fabric/tree/main/integrations/harbor/demo) diff --git a/docs/index.yml b/docs/index.yml index fb5da445f..1798f0084 100644 --- a/docs/index.yml +++ b/docs/index.yml @@ -6,10 +6,14 @@ navigation: contents: - page: Overview path: ./getting-started/overview.mdx - - section: Guides + - section: SDK contents: - - page: Evaluate agents with Harbor - path: ./guides/harbor-evaluation.mdx + - page: Python SDK + path: ./sdk/python.mdx + - section: Integrations + contents: + - page: Harbor + path: ./integrations/harbor.mdx - section: Reference contents: - section: API @@ -20,8 +24,10 @@ navigation: path: ./reference/api/python-library-reference/index.md - page: Client path: ./reference/api/python-library-reference/nemo_fabric.client.md - - page: Sessions - path: ./reference/api/python-library-reference/nemo_fabric.session.md + - page: Runtime + path: ./reference/api/python-library-reference/nemo_fabric.runtime.md + - page: Models + path: ./reference/api/python-library-reference/nemo_fabric.models.md - page: Types path: ./reference/api/python-library-reference/nemo_fabric.types.md - page: Errors diff --git a/docs/integrations/harbor.mdx b/docs/integrations/harbor.mdx new file mode 100644 index 000000000..bf5a04a39 --- /dev/null +++ b/docs/integrations/harbor.mdx @@ -0,0 +1,100 @@ +--- +title: "Evaluate agents with Harbor" +description: "Run multiple agent harnesses through one Harbor integration and inspect normalized results, artifacts, and telemetry." +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +# Evaluate multiple harnesses through one Harbor agent + +Use `nemo_fabric.integrations.harbor:FabricAgent` when Harbor owns the +evaluation workflow and Fabric owns harness execution. The Harbor agent class +stays fixed while each complete Fabric config selects Hermes CLI, Codex CLI, or +another adapter. + +```text +Harbor task -> FabricAgent -> Fabric SDK -> selected adapter -> agent harness + | | + +----- verifier, reward, and run layout <- RunResult -----+ +``` + +## Ownership boundary + +| Harbor owns | Fabric owns | +| --- | --- | +| Task and dataset materialization | Fabric config validation | +| Environment and container lifecycle | Harness adapter lifecycle and invocation | +| Verifier execution and reward calculation | Normalized requests, results, and artifacts | +| Jobs, retries, concurrency, and run layout | Telemetry configuration and references | + +One Harbor agent run creates one independent Fabric runtime. Fabric does not +interpret Harbor job IDs or concurrency settings. + +## Install the integration + +Harbor requires Python 3.12 or later. + +```bash +python3 -m pip install "nemo-fabric[runtime,harbor]" +``` + +For a source checkout, use both the runtime and Harbor extras. + +## Run a Fabric-backed Harbor task + +Pass one complete config path through Harbor's agent arguments: + +```bash +uv run --extra runtime --extra harbor harbor run \ + --path "$TASK_DIR" \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --ak fabric_config_path=/opt/fabric-demo/configs/hermes.yaml \ + --model nvidia/nemotron-3-nano-30b-a3b \ + --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" +``` + +The config path is inside the Harbor task container. `--ak` passes constructor +arguments to `FabricAgent`; it is not a Fabric CLI flag. + +The runner loads the YAML as `FabricConfig`, makes a deep copy, applies Harbor's +model, MCP servers, and skill directory through SDK models and helpers, and +calls `Fabric.run()` inside the task environment. + +## Choose a complete config + +The demo keeps the Harbor agent and task fixed while selecting one complete +config per execution path: + +| Config | Execution path | What it demonstrates | +| --- | --- | --- | +| `smoke.yaml` | Deterministic scripted adapter | Credential-free Harbor, Fabric, workspace, and verifier pipeline | +| `hermes.yaml` | Hermes CLI | A model-backed harness selected through Fabric | +| `hermes-relay.yaml` | Hermes CLI with NeMo Relay | Phoenix traces plus ATOF and ATIF records | +| `codex.yaml` | Codex CLI | An existing Codex login mounted by Harbor | + +Harbor's `--model`, MCP servers, and skill directory are applied to an +independent copy of the selected config for each run. Config-owned MCP servers +and skills remain unchanged when Harbor does not provide replacements. + +## Inspect results and evidence + +Each trial stores a normalized Fabric result in the Harbor agent logs. It +includes status, harness and adapter identity, runtime and invocation IDs, +artifacts, telemetry references, and structured errors. + +```bash +uv run --extra runtime --extra harbor harbor view "$RUNS_DIR" +``` + +For Relay-enabled runs, inspect the same directory for portable ATOF and ATIF +records: + +```bash +find "$RUNS_DIR" -path '*/agent/fabric-artifacts/*/relay/events.atof.jsonl' +find "$RUNS_DIR" -path '*/agent/fabric-artifacts/*/relay/*.atif.json' +``` + +The repository demo contains the task image, complete config matrix, Phoenix +routing, exact commands, expected results, and recording flow: + +[Run the Harbor multi-harness demo](https://github.com/NVIDIA/NeMo-Fabric/tree/main/examples/harbor/demo) diff --git a/docs/python-sdk-contract.md b/docs/python-sdk-contract.md deleted file mode 100644 index b544b0fcd..000000000 --- a/docs/python-sdk-contract.md +++ /dev/null @@ -1,562 +0,0 @@ -# Python SDK Contract - -## Scope and Status - -This is the target public API. MVP includes typed sources, resolution, planning, -diagnostics, oneshot runs, sessions, typed results and errors, capability checks, -and a stable buffered `stream()` shape. Runtime updates, progressive streaming, -and service mode may follow MVP. Unsupported operations raise -`FabricCapabilityError`. - -## Design - -Fabric owns runtime execution; callers own orchestration, servers, tenancy, -persistence, and product workflows. The SDK uses one source abstraction, one -ordered `profiles` argument, and one method per lifecycle operation. - -## Common Types - -All values crossing the Python/native boundary are JSON-shaped. - -```python -from __future__ import annotations - -import asyncio -import os -from collections.abc import AsyncIterator, Mapping, Sequence -from pathlib import Path -from typing import Literal, overload - -JSONScalar = str | int | float | bool | None -JSONValue = JSONScalar | list["JSONValue"] | dict[str, "JSONValue"] -PathSource = str | os.PathLike[str] -AgentSource = PathSource | FabricConfig -``` - -Invalid JSON values raise `FabricConfigError` before native execution. - -## Client and CLI - -```python -class FabricClient: - def __init__(self) -> None: ... -``` - -`FabricClient` is native-only. The CLI is a separate surface over the same core; -the same file-backed config and profiles produce equivalent contract data. - -## Agent Sources and Profiles - -Profile types follow the agent source: - -```python -@overload -def plan( - agent: PathSource, - *, - profiles: str | Sequence[str] | None = None, -) -> RunPlan: ... - -@overload -def plan( - agent: FabricConfig, - *, - profiles: Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, -) -> RunPlan: ... -``` - -The same overload pattern applies to `resolve`, `doctor`, `run`, -`start_session`, and `start_service`. - -- Agent strings are paths, never raw config, adapter IDs, or agent names. -- Paths accept one profile name or an ordered sequence of names. `FabricConfig` - uses ordered `FabricProfileConfig` objects; mixed stacks are rejected. -- `base_dir` applies only to `FabricConfig`. -- Raw mappings require explicit `from_mapping(...)` conversion. -- Equivalent file and typed sources produce equivalent configs and plans. - -There is no public singular `profile` alias or public `plan_config`, -`run_config`, `doctor_config`, `start`, or `start_config` family. - -## Typed Config - -Typed config uses the same schema as `agent.yaml`. - -```python -class MetadataConfig: - name: str - description: str | None - extra_fields: Mapping[str, JSONValue] - -class HarnessConfig: - adapter_id: str - resolution: str | None - settings: Mapping[str, JSONValue] - extra_fields: Mapping[str, JSONValue] - -class RuntimeConfig: - mode: Literal["oneshot", "session", "service"] - transport: str | None - input_schema: str | None - output_schema: str | None - artifacts: str | Path | None - extra_fields: Mapping[str, JSONValue] - -class EnvironmentConfig: - provider: str - workspace: str | Path | None - artifacts: str | Path | None - settings: Mapping[str, JSONValue] - metadata: Mapping[str, JSONValue] - extra_fields: Mapping[str, JSONValue] - -class FabricConfig: - schema_version: str - metadata: MetadataConfig - harness: HarnessConfig - runtime: RuntimeConfig - environment: EnvironmentConfig | None - models: Mapping[str, Mapping[str, JSONValue]] - mcp: Mapping[str, JSONValue] | None - skills: Mapping[str, JSONValue] | None - telemetry: Mapping[str, JSONValue] | None - profiles: Mapping[str, JSONValue] | None - tools: JSONValue - extra_fields: Mapping[str, JSONValue] - - @classmethod - def from_mapping(cls, value: Mapping[str, JSONValue]) -> FabricConfig: ... - - def to_mapping(self) -> dict[str, JSONValue]: ... - -class FabricProfileConfig: - schema_version: str - name: str - description: str | None - harness: HarnessConfig | Mapping[str, JSONValue] | None - runtime: RuntimeConfig | Mapping[str, JSONValue] | None - environment: EnvironmentConfig | Mapping[str, JSONValue] | None - models: Mapping[str, Mapping[str, JSONValue]] | None - mcp: Mapping[str, JSONValue] | None - skills: Mapping[str, JSONValue] | None - telemetry: Mapping[str, JSONValue] | None - tools: JSONValue - extra_fields: Mapping[str, JSONValue] - - @classmethod - def from_mapping( - cls, - value: Mapping[str, JSONValue], - ) -> FabricProfileConfig: ... - - def to_mapping(self) -> dict[str, JSONValue]: ... -``` - -- `metadata` and `harness` are required; names, adapter IDs, and runtime mode - are validated. -- Mutable configs default to the v1alpha1 schemas and `oneshot`; omitted - environment, runtime transport, and schemas remain unset until resolution, - which applies local, `library`, `text`, and `text` defaults. -- Constructors reject unknown keywords. Mapping conversion preserves unknown - fields through `extra_fields` and returns deep copies. -- Profile sections are partial recursive overlays. They are validated as a - complete config after merging with the base and earlier profiles. -- Config is mutable before resolution; plans and runtimes are snapshots. -- Unstable model, MCP, skill, telemetry, and tool shapes remain JSON mappings. -- `FabricConfig.profiles` controls discovery; lifecycle `profiles` selects - overlays. - -## Config Extension - -Normalized fields represent cross-harness concepts. Adapter-only fields belong -in `HarnessConfig.settings`. Unknown fields are preserved but are not supported -until the SDK recognizes them. - -## Inspection Types - -Inspection and result models are typed, read-only mappings that preserve unknown -fields. - -```python -class AdapterInfo: - adapter_id: str - harness: str - adapter_kind: str - metadata: Mapping[str, JSONValue] - -class RuntimeCapabilities: - session: bool - service: bool - streaming: bool - updates: bool - cancellation: bool - concurrent_invocations: bool - metadata: Mapping[str, JSONValue] - -class EffectiveConfig: - agent_name: str - profiles: Sequence[str] - agent_root: Path - config_path: Path | None - config_root: Path - config: FabricConfig - -class RunPlan: - effective_config: EffectiveConfig - agent_name: str - profiles: Sequence[str] - adapter: AdapterInfo - capabilities: RuntimeCapabilities - -class DoctorCheck: - name: str - status: Literal["pass", "warn", "fail"] - message: str - metadata: Mapping[str, JSONValue] - -class DoctorReport: - agent_name: str - profiles: Sequence[str] - status: Literal["pass", "warn", "fail"] - checks: Sequence[DoctorCheck] -``` - -`harness` is the stable machine-readable harness identifier. `adapter_id` -identifies its Fabric adapter implementation, while `adapter_kind` identifies -the execution mechanism. - -## Client API - -These compact signatures use the source-specific overloads above. - -```python -class FabricClient: - def resolve( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - ) -> EffectiveConfig: ... - - def plan( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - ) -> RunPlan: ... - - async def doctor( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - ) -> DoctorReport: ... - - async def run( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - input: JSONValue = None, - input_file: str | Path | None = None, - request: RunRequest | Mapping[str, JSONValue] | None = None, - request_file: str | Path | None = None, - request_id: str | None = None, - context: Mapping[str, JSONValue] | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> RunResult: ... - - async def start_session( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - session_id: str | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> Session: ... - - async def start_service( - self, - agent: AgentSource, - *, - profiles: str | Sequence[str] | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - service_id: str | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> RuntimeService: ... -``` - -`resolve()` resolves config only; `plan()` resolves adapters and capabilities. - -## Requests and Overrides - -```python -class RunRequest: - input: JSONValue - request_id: str - context: Mapping[str, JSONValue] - overrides: Mapping[str, JSONValue] | None - extra_fields: Mapping[str, JSONValue] - - @classmethod - def from_mapping( - cls, - value: Mapping[str, JSONValue], - ) -> RunRequest: ... - - def to_mapping(self) -> dict[str, JSONValue]: ... -``` - -At most one input source is accepted; none means empty text. File inputs apply -only to `run()`. Request IDs default automatically, context is caller-owned, and -unknown fields are preserved. Complete requests reject separate request fields. -There is no `from_text()` or `input_text` alias. - -Merge precedence is: - -```text -base config < ordered profiles < service < session < invocation -``` - -Objects merge recursively; later scalars, arrays, and `null` replace earlier -values. Lists are not concatenated. Runtime changes are capability-gated. - -## Oneshot Runs - -`run()` resolves, plans, creates, invokes, collects, and destroys one runtime. -Cleanup failure raises `FabricRuntimeError` even after a successful invocation. - -## Sessions - -A `Session` owns one runtime and orders turns unless concurrency is declared. - -```python -class SessionInfo: - session_id: str - runtime_id: str - agent_name: str - profiles: Sequence[str] - harness: str - adapter_id: str - adapter_kind: str - status: Literal["active", "stopped", "failed"] - capabilities: RuntimeCapabilities - -class Session: - session_id: str - runtime_id: str - info: SessionInfo - - async def invoke( - self, - *, - input: JSONValue = None, - request: RunRequest | Mapping[str, JSONValue] | None = None, - request_id: str | None = None, - context: Mapping[str, JSONValue] | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> RunResult: ... - - async def stream( - self, - *, - input: JSONValue = None, - request: RunRequest | Mapping[str, JSONValue] | None = None, - request_id: str | None = None, - context: Mapping[str, JSONValue] | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> AsyncIterator[FabricEvent | RunResult]: ... - - async def update(self, update: RuntimeUpdate) -> RuntimeUpdateResult: ... - async def cancel(self) -> None: ... - async def stop(self) -> None: ... -``` - -- `Session.info` copies plan and runtime identity; it never derives one identity - field from another. -- `cancel()` targets the current invocation, leaves a supported runtime active, - and raises `FabricCapabilityError` when unsupported. -- `stop()` rejects active work and destroys an idle runtime exactly once. - Invoke, cancel, and stop transitions are serialized. - -## Services - -Service mode reuses one runtime. `RuntimeService` owns it; `ServiceSession` owns -only logical state. Callers retain serving, authentication, tenancy, persistence, -and scheduling. - -```python -class ServiceInfo: - service_id: str - runtime_id: str - agent_name: str - profiles: Sequence[str] - harness: str - adapter_id: str - adapter_kind: str - status: Literal["active", "stopped", "failed"] - capabilities: RuntimeCapabilities - -class ServiceSessionInfo: - service_id: str - session_id: str - runtime_id: str - status: Literal["active", "closed", "failed"] - -class ServiceSession: - service_id: str - session_id: str - info: ServiceSessionInfo - - async def invoke(...) -> RunResult: ... - async def stream(...) -> AsyncIterator[FabricEvent | RunResult]: ... - async def update(self, update: RuntimeUpdate) -> RuntimeUpdateResult: ... - async def cancel(self) -> None: ... - async def close(self) -> None: ... - -class RuntimeService: - service_id: str - runtime_id: str - info: ServiceInfo - - async def create_session( - self, - *, - session_id: str | None = None, - context: Mapping[str, JSONValue] | None = None, - overrides: Mapping[str, JSONValue] | None = None, - ) -> ServiceSession: ... - - async def get_session(self, session_id: str) -> ServiceSession: ... - async def invoke(...) -> RunResult: ... - async def stream(...) -> AsyncIterator[FabricEvent | RunResult]: ... - async def cancel(self, request_id: str) -> None: ... - async def update(self, update: RuntimeUpdate) -> RuntimeUpdateResult: ... - async def close_session(self, session_id: str) -> None: ... - async def stop(self) -> None: ... -``` - -Abbreviated invocation methods match `Session`. `ServiceSession.close()` releases -logical state; `RuntimeService.stop()` closes idle sessions and the runtime. -Direct service calls are stateless. IDs are correlation, not authorization. - -## Streaming and Updates - -`stream()` yields events and one terminal result. Adapters may buffer, so callers -must not assume immediate event delivery. Event kinds and metadata are additive. - -```python -class RuntimeUpdate: - overrides: Mapping[str, JSONValue] - metadata: Mapping[str, JSONValue] - -class RuntimeUpdateResult: - status: Literal["applied", "partially_applied", "rejected"] - applied: Mapping[str, JSONValue] - rejected: Mapping[str, JSONValue] - reason: str | None -``` - -The target determines update scope. Unsupported updates raise -`FabricCapabilityError`; supported updates report applied and rejected fields. - -## Results and Identity - -```python -class ErrorInfo: - stage: str - code: str - message: str - retryable: bool - metadata: Mapping[str, JSONValue] - -class ArtifactRef: - name: str - kind: str - path: Path - media_type: str | None - metadata: Mapping[str, JSONValue] - -class ArtifactManifest: - root: Path | None - artifacts: Sequence[ArtifactRef] - -class TelemetryRef: - provider: str - kind: str - uri: str | None - trace_id: str | None - metadata: Mapping[str, JSONValue] - -class FabricEvent: - event_id: str - timestamp_millis: int - kind: str - message: str - metadata: Mapping[str, JSONValue] - -class RunResult: - agent_name: str - profiles: Sequence[str] - harness: str - adapter_kind: str - adapter_id: str - runtime_id: str - invocation_id: str - request_id: str - status: Literal["succeeded", "failed", "cancelled"] - output: JSONValue - error: ErrorInfo | None - artifacts: ArtifactManifest - telemetry: Sequence[TelemetryRef] - events: Sequence[FabricEvent] - metadata: Mapping[str, JSONValue] - extra_fields: Mapping[str, JSONValue] -``` - -`profiles` is the full ordered stack; no singular field exists. Harness, adapter, -and runtime identities stay distinct. Normalized harness failure returns a -failed result; lifecycle failure raises a typed exception. - -## Errors - -```python -class FabricError(RuntimeError): - stage: str | None - code: str | None - retryable: bool - details: Mapping[str, JSONValue] - -class FabricConfigError(FabricError): ... -class FabricRuntimeError(FabricError): ... -class FabricStateError(FabricRuntimeError): ... -class FabricCapabilityError(FabricRuntimeError): ... -class FabricNativeUnavailableError(FabricRuntimeError): ... -``` - -Invalid input, unsupported operations, bad handle state, and lifecycle failure -map to the four specific errors above. Native exceptions never leak. Python task -cancellation remains `asyncio.CancelledError` with deterministic cleanup. - -## Compatibility - -- Unknown fields survive Python, native, adapter, and serialization boundaries. -- New optional fields and event kinds are additive. -- New required fields require a schema-version change. -- Capabilities declare support for session, service, streaming, updates, - cancellation, and concurrency. -- Public symbols and signatures are covered by static type and API contract - tests. -- Aliases are added only for migration from an actually released API. - -## Non-Goals - -The SDK does not own external server lifecycle, authentication, tenancy policy, -durable job persistence, UI state, evaluation scoring, or caller-specific -orchestration. diff --git a/docs/reference/api/python-library-reference/index.md b/docs/reference/api/python-library-reference/index.md index 52319be1c..81fd40d4b 100644 --- a/docs/reference/api/python-library-reference/index.md +++ b/docs/reference/api/python-library-reference/index.md @@ -11,44 +11,49 @@ SPDX-License-Identifier: Apache-2.0 */} ## Modules - [`nemo_fabric.client`](./nemo_fabric.client.md#module-nemo_fabricclient): Native Python client for resolving and running NeMo Fabric agents. -- [`nemo_fabric.session`](./nemo_fabric.session.md#module-nemo_fabricsession): Session lifecycle support for the Fabric Python SDK. +- [`nemo_fabric.runtime`](./nemo_fabric.runtime.md#module-nemo_fabricruntime): Runtime lifecycle support for the Fabric Python SDK. +- [`nemo_fabric.models`](./nemo_fabric.models.md#module-nemo_fabricmodels): Pydantic SDK models for NeMo Fabric configuration and requests. - [`nemo_fabric.types`](./nemo_fabric.types.md#module-nemo_fabrictypes): Public data contracts for the NeMo Fabric Python SDK. - [`nemo_fabric.errors`](./nemo_fabric.errors.md#module-nemo_fabricerrors): Public exception hierarchy for the NeMo Fabric Python SDK. ## Classes -- [`client.FabricClient`](./nemo_fabric.client.md#class-fabricclient): Primary Python entrypoint for NeMo Fabric. -- [`session.Session`](./nemo_fabric.session.md#class-session): One ordered multi-turn conversation over a Fabric runtime. -- [`session.SessionStatus`](./nemo_fabric.session.md#class-sessionstatus): Lifecycle state of a session runtime. +- [`client.Fabric`](./nemo_fabric.client.md#class-fabric): Primary Python entrypoint for NeMo Fabric. +- [`runtime.Runtime`](./nemo_fabric.runtime.md#class-runtime): One logical, stateful harness execution. +- [`runtime.RuntimeStatus`](./nemo_fabric.runtime.md#class-runtimestatus): Lifecycle state of a runtime. +- [`models.EnvironmentConfig`](./nemo_fabric.models.md#class-environmentconfig): Execution environment configuration supplied by the consumer. +- [`models.FabricBaseModel`](./nemo_fabric.models.md#class-fabricbasemodel): Base class for SDK-facing Pydantic models. +- [`models.FabricConfig`](./nemo_fabric.models.md#class-fabricconfig): SDK-facing typed Fabric agent configuration. +- [`models.FabricProfileConfig`](./nemo_fabric.models.md#class-fabricprofileconfig): Typed profile overlay used when a Python caller wants file-style overlays. +- [`models.HarnessConfig`](./nemo_fabric.models.md#class-harnessconfig): Harness adapter selection plus adapter-owned settings. +- [`models.McpConfig`](./nemo_fabric.models.md#class-mcpconfig): MCP capability configuration. +- [`models.McpServerConfig`](./nemo_fabric.models.md#class-mcpserverconfig): MCP server configuration. +- [`models.MetadataConfig`](./nemo_fabric.models.md#class-metadataconfig): Human-readable agent identity. +- [`models.ModelConfig`](./nemo_fabric.models.md#class-modelconfig): Model alias configuration. +- [`models.ProfileRegistryConfig`](./nemo_fabric.models.md#class-profileregistryconfig): Profile discovery config for portable file-backed agent packages. +- [`models.RunRequest`](./nemo_fabric.models.md#class-runrequest): One validated Fabric invocation request. +- [`models.RuntimeConfig`](./nemo_fabric.models.md#class-runtimeconfig): Runtime input/output contract. +- [`models.SkillConfig`](./nemo_fabric.models.md#class-skillconfig): Skill capability configuration. +- [`models.TelemetryConfig`](./nemo_fabric.models.md#class-telemetryconfig): Telemetry configuration. - [`types.AdapterInfo`](./nemo_fabric.types.md#class-adapterinfo): Resolved adapter identity attached to a run plan. - [`types.ArtifactManifest`](./nemo_fabric.types.md#class-artifactmanifest): Normalized collection of artifacts produced by a run. - [`types.ArtifactRef`](./nemo_fabric.types.md#class-artifactref): Reference to one artifact produced by a run. - [`types.DoctorCheck`](./nemo_fabric.types.md#class-doctorcheck): One diagnostic check in a ``DoctorReport``. - [`types.DoctorReport`](./nemo_fabric.types.md#class-doctorreport): Aggregate preflight diagnostics for a resolved run plan. - [`types.EffectiveConfig`](./nemo_fabric.types.md#class-effectiveconfig): Immutable result of config loading and ordered profile application. -- [`types.EnvironmentConfig`](./nemo_fabric.types.md#class-environmentconfig): Execution environment configuration. - [`types.ErrorInfo`](./nemo_fabric.types.md#class-errorinfo): Structured failure returned inside a normalized ``RunResult``. -- [`types.FabricConfig`](./nemo_fabric.types.md#class-fabricconfig): Mutable typed representation of a Fabric agent configuration. - [`types.FabricEvent`](./nemo_fabric.types.md#class-fabricevent): One normalized lifecycle or invocation event. -- [`types.FabricProfileConfig`](./nemo_fabric.types.md#class-fabricprofileconfig): Mutable, partial overlay applied to a typed ``FabricConfig``. -- [`types.HarnessConfig`](./nemo_fabric.types.md#class-harnessconfig): Harness adapter selection and adapter-owned settings. -- [`types.MetadataConfig`](./nemo_fabric.types.md#class-metadataconfig): Agent identity and human-readable metadata. - [`types.RunPlan`](./nemo_fabric.types.md#class-runplan): Immutable execution plan produced before a runtime is started. -- [`types.RunRequest`](./nemo_fabric.types.md#class-runrequest): One normalized invocation request. - [`types.RunResult`](./nemo_fabric.types.md#class-runresult): Normalized terminal result from one Fabric invocation. - [`types.RuntimeCapabilities`](./nemo_fabric.types.md#class-runtimecapabilities): Operations declared by the resolved runtime and adapter. -- [`types.RuntimeConfig`](./nemo_fabric.types.md#class-runtimeconfig): Runtime lifecycle mode and input/output contract. - [`types.RuntimeHandle`](./nemo_fabric.types.md#class-runtimehandle): Opaque identity and binding for one started runtime. -- [`types.RuntimeUpdate`](./nemo_fabric.types.md#class-runtimeupdate): Capability-gated update requested for a running session. -- [`types.RuntimeUpdateResult`](./nemo_fabric.types.md#class-runtimeupdateresult): Normalized outcome of a runtime update request. -- [`types.SessionInfo`](./nemo_fabric.types.md#class-sessioninfo): Read-only metadata snapshot for an active or stopped session. - [`types.TelemetryRef`](./nemo_fabric.types.md#class-telemetryref): Reference to external or persisted telemetry for a run. - [`errors.FabricCapabilityError`](./nemo_fabric.errors.md#class-fabriccapabilityerror): Operation rejected by resolved runtime capabilities or implementation status. - [`errors.FabricConfigError`](./nemo_fabric.errors.md#class-fabricconfigerror): Invalid SDK input, request shape, profile stack, or resolved config. - [`errors.FabricError`](./nemo_fabric.errors.md#class-fabricerror): Base class for structured SDK-level Fabric errors. - [`errors.FabricNativeUnavailableError`](./nemo_fabric.errors.md#class-fabricnativeunavailableerror): SDK call requires the PyO3 extension, but it is not installed or importable. - [`errors.FabricRuntimeError`](./nemo_fabric.errors.md#class-fabricruntimeerror): Failure while starting, invoking, stopping, or otherwise driving a runtime. -- [`errors.FabricStateError`](./nemo_fabric.errors.md#class-fabricstateerror): Operation rejected because a local session handle is in the wrong state. +- [`errors.FabricStateError`](./nemo_fabric.errors.md#class-fabricstateerror): Operation rejected because a local runtime is in the wrong state. ## Functions diff --git a/docs/reference/api/python-library-reference/nemo_fabric.client.md b/docs/reference/api/python-library-reference/nemo_fabric.client.md index f314a4228..8ea962d20 100644 --- a/docs/reference/api/python-library-reference/nemo_fabric.client.md +++ b/docs/reference/api/python-library-reference/nemo_fabric.client.md @@ -1,7 +1,7 @@ --- title: "Client" slug: "/reference/api/python-library-reference/client" -description: "Resolve, plan, diagnose, and run agents with FabricClient." +description: "Resolve, plan, diagnose, and run agents with Fabric." --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -14,14 +14,12 @@ Native Python client for resolving and running NeMo Fabric agents. --- -## class `FabricClient` +## class `Fabric` Primary Python entrypoint for NeMo Fabric. The client accepts either a path-backed agent package or a typed ``FabricConfig``. Path-backed sources select profiles by name; typed sources accept ordered ``FabricProfileConfig`` values and may use ``base_dir`` to resolve relative paths. All inspection and execution APIs return typed, read-only mapping models. -``FabricClient`` is native-only. The ``fabric`` CLI is a separate public surface over the same Rust core; SDK calls raise ``FabricNativeUnavailableError`` when the native extension is not installed. - -The client is also an asynchronous context manager. Leaving the context does not stop independently created sessions; use each ``Session`` as an asynchronous context manager or call ``Session.stop()`` explicitly. +``Fabric`` is native-only. The ``fabric`` CLI is a separate public surface over the same Rust core; SDK calls raise ``FabricNativeUnavailableError`` when the native extension is not installed. See the Getting Started overview for runnable one-shot, typed-config, and multi-turn examples. @@ -36,14 +34,14 @@ See the Getting Started overview for runnable one-shot, typed-config, and multi- ```python doctor( agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, + profiles: 'PathProfiles | TypedProfiles | None' = None, base_dir: 'PathSource | None' = None ) → DoctorReport ``` Diagnose a planned agent without starting its runtime. -Doctor checks the resolved adapter, capability mappings, and declared environment requirements. Blocking native work runs off the event loop. +Doctor checks the resolved adapter, capability mappings, and declared environment requirements using the native Fabric core. @@ -51,7 +49,7 @@ Doctor checks the resolved adapter, capability mappings, and declared environmen - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. + - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a typed config source. @@ -73,22 +71,22 @@ Doctor checks the resolved adapter, capability mappings, and declared environmen ```python plan( agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, + profiles: 'PathProfiles | TypedProfiles | None' = None, base_dir: 'PathSource | None' = None ) → RunPlan ``` Resolve an agent source into an immutable execution plan. -Planning applies profiles, resolves the selected adapter, and reports the runtime capabilities that gate session, service, streaming, update, cancellation, and concurrency APIs. It does not start the runtime. +Planning applies profiles, resolves the selected adapter, and reports optional runtime capabilities such as streaming, updates, and cancellation. It does not start the runtime. **Args:** - - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. Raw mappings are not accepted. + - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. Raw mappings are not accepted. - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. + - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a typed config source. @@ -110,7 +108,7 @@ Planning applies profiles, resolves the selected adapter, and reports the runtim ```python resolve( agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, + profiles: 'PathProfiles | TypedProfiles | None' = None, base_dir: 'PathSource | None' = None ) → EffectiveConfig ``` @@ -123,9 +121,9 @@ Resolution validates and normalizes configuration but does not resolve an adapte **Args:** - - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. Raw mappings are not accepted; convert them with ``FabricConfig.from_mapping()``. + - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. Raw mappings are not accepted; convert them with ``FabricConfig.from_mapping()``. - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. + - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a typed config source. @@ -147,21 +145,16 @@ Resolution validates and normalizes configuration but does not resolve an adapte ```python run( agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, + profiles: 'PathProfiles | TypedProfiles | None' = None, base_dir: 'PathSource | None' = None, input: 'Any' = None, - input_file: 'str | Path | None' = None, - request: 'RunRequest | Mapping[str, Any] | None' = None, - request_file: 'str | Path | None' = None, - request_id: 'str | None' = None, - context: 'Mapping[str, Any] | None' = None, - overrides: 'Mapping[str, Any] | None' = None + request: 'RunRequest | None' = None ) → RunResult ``` Execute one complete start, invoke, and stop lifecycle. -Exactly zero or one of ``input``, ``input_file``, ``request``, and ``request_file`` may be supplied. Omitting all four produces an empty text input. A complete ``request`` or ``request_file`` cannot be mixed with separate ``request_id``, ``context``, or ``overrides`` fields. Blocking native lifecycle calls run off the event loop, and Fabric attempts to stop a started runtime even when invocation fails. +``input`` and ``request`` are mutually exclusive. Omitting both produces an empty text input. Use ``RunRequest`` when the invocation needs a caller-owned request ID, context, or overrides. Fabric attempts to stop a started runtime even when invocation fails. @@ -169,14 +162,9 @@ Exactly zero or one of ``input``, ``input_file``, ``request``, and ``request_fil - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. + - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a typed config source. - `input`: JSON-compatible invocation input. - - `input_file`: UTF-8 file whose contents become the invocation input. - - `request`: Complete ``RunRequest`` or compatible mapping. - - `request_file`: UTF-8 JSON file containing a complete request. - - `request_id`: Caller-owned request identifier. Fabric generates one when omitted. - - `context`: Caller-owned, JSON-compatible request metadata. - - `overrides`: JSON-compatible invocation-scoped config overrides. + - `request`: Complete validated ``RunRequest``. @@ -187,65 +175,27 @@ Exactly zero or one of ``input``, ``input_file``, ``request``, and ``request_fil **Raises:** - - `FabricConfigError`: If sources are combined, request data is not JSON-compatible, or config resolution fails. + - `FabricConfigError`: If input and request are combined, request data is not JSON-compatible, or config resolution fails. - `FabricNativeUnavailableError`: If the native extension is not installed. - `FabricRuntimeError`: If the native runtime lifecycle fails before a normalized result can be returned. --- -### method `start_service` - -```python -start_service( - agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, - base_dir: 'PathSource | None' = None, - service_id: 'str | None' = None, - overrides: 'Mapping[str, Any] | None' = None -) → Any -``` - -Validate a service request and report the unsupported operation. - -Service handles are part of the reserved SDK contract, but the current Fabric runtime does not implement service creation. This method validates inputs and resolves the plan before raising ``FabricCapabilityError`` with code ``service_not_supported``. - - - -**Args:** - - - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. - - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. - - `service_id`: Reserved caller-owned service identifier. - - `overrides`: JSON-compatible service-scoped config overrides. - - - -**Raises:** - - - `FabricConfigError`: If inputs or overrides are invalid. - - `FabricNativeUnavailableError`: If the native extension is not installed. - - `FabricCapabilityError`: Always, because service creation is not yet implemented. - ---- - - -### method `start_session` +### method `start_runtime` ```python -start_session( +start_runtime( agent: 'AgentSource', - profiles: 'PathProfiles | Sequence[FabricProfileConfig] | None' = None, + profiles: 'PathProfiles | TypedProfiles | None' = None, base_dir: 'PathSource | None' = None, - session_id: 'str | None' = None, overrides: 'Mapping[str, Any] | None' = None -) → Session +) → Runtime ``` -Start a stateful, multi-turn session runtime. +Start a stateful runtime for one or more ordered invocations. -The resolved plan must declare the session capability. Each call starts a new runtime. ``session_id`` is the stable conversation identifier; if omitted, the new runtime identifier is used. Session-scoped overrides are recursively merged below invocation-scoped overrides. +Each call starts a new logical runtime. Runtime-scoped overrides are recursively merged below invocation-scoped overrides. @@ -253,14 +203,13 @@ The resolved plan must declare the session capability. Each call starts a new ru - `agent`: Agent-package directory or config-file path, or a typed ``FabricConfig``. - `profiles`: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. - - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a ``FabricConfig``. - - `session_id`: Stable caller-owned conversation identifier. Defaults to the generated runtime identifier. - - `overrides`: JSON-compatible overrides applied to every invocation in the session unless superseded by invocation overrides. + - `base_dir`: Base directory for resolving relative paths in a typed config. Valid only when ``agent`` is a typed config source. + - `overrides`: JSON-compatible overrides applied to every invocation in the runtime unless superseded by invocation overrides. **Returns:** - An active ``Session``. Use it as an asynchronous context manager to guarantee runtime shutdown. + An active ``Runtime``. Use it as an asynchronous context manager to guarantee runtime shutdown. @@ -268,7 +217,6 @@ The resolved plan must declare the session capability. Each call starts a new ru - `FabricConfigError`: If inputs or overrides are invalid. - `FabricNativeUnavailableError`: If the native extension is not installed. - - `FabricCapabilityError`: If the resolved runtime does not support sessions. - `FabricRuntimeError`: If runtime startup fails. diff --git a/docs/reference/api/python-library-reference/nemo_fabric.errors.md b/docs/reference/api/python-library-reference/nemo_fabric.errors.md index 122445977..61fbf73e9 100644 --- a/docs/reference/api/python-library-reference/nemo_fabric.errors.md +++ b/docs/reference/api/python-library-reference/nemo_fabric.errors.md @@ -131,7 +131,7 @@ Initialize a structured Fabric exception. ## class `FabricStateError` -Operation rejected because a local session handle is in the wrong state. +Operation rejected because a local runtime is in the wrong state. ### method `__init__` diff --git a/docs/reference/api/python-library-reference/nemo_fabric.models.md b/docs/reference/api/python-library-reference/nemo_fabric.models.md new file mode 100644 index 000000000..763c606f3 --- /dev/null +++ b/docs/reference/api/python-library-reference/nemo_fabric.models.md @@ -0,0 +1,1014 @@ +--- +title: "Models" +slug: "/reference/api/python-library-reference/models" +description: "Pydantic authoring models for Fabric config and request inputs." +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +# module `nemo_fabric.models` +Pydantic SDK models for NeMo Fabric configuration and requests. + +The Rust core remains the source of truth for persisted schema snapshots. These models provide the Python SDK's typed authoring surface and intentionally keep extension fields so consumers can carry adapter- or application-owned data without waiting for a schema release. + + + +--- + + +## class `FabricBaseModel` +Base class for SDK-facing Pydantic models. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `MetadataConfig` +Human-readable agent identity. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `HarnessConfig` +Harness adapter selection plus adapter-owned settings. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `RuntimeConfig` +Runtime input/output contract. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `EnvironmentConfig` +Execution environment configuration supplied by the consumer. + +``provider`` selects the environment implementation. ``workspace`` is the path visible to the harness, while ``artifacts`` is the provider-specific output location. ``settings`` configures the selected provider; ``connection`` describes how Fabric reaches an existing environment; and ``metadata`` carries consumer-owned values that Fabric does not interpret. ``ownership`` identifies who tears the environment down, and ``control_location`` identifies whether Fabric control code runs inside or outside it. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `ModelConfig` +Model alias configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `SkillConfig` +Skill capability configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### method `add_path` + +```python +add_path(path: 'str | Path') → Self +``` + +Add a skill path if absent. + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `remove_path` + +```python +remove_path(path: 'str | Path') → Self +``` + +Remove a skill path if present. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `McpServerConfig` +MCP server configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `McpConfig` +MCP capability configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### method `add_server` + +```python +add_server( + name: 'str', + transport: 'str', + url: 'str', + exposure: "Literal['harness_native', 'fabric_managed']" = 'harness_native', + extra_fields: 'Mapping[str, Any] | None' = None +) → Self +``` + +Add or replace a named MCP server. + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `remove_server` + +```python +remove_server(name: 'str') → Self +``` + +Remove a named MCP server if present. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `TelemetryConfig` +Telemetry configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### method `disable` + +```python +disable() → Self +``` + +Disable telemetry. + +--- + + +### method `enable_native` + +```python +enable_native() → Self +``` + +Let the selected adapter handle telemetry natively. + +--- + + +### method `enable_relay` + +```python +enable_relay( + project: 'str | None' = None, + output_dir: 'str | Path | None' = None, + config: 'Mapping[str, Any] | None' = None +) → Self +``` + +Enable NeMo Relay telemetry for subsequently started runtimes. + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `ProfileRegistryConfig` +Profile discovery config for portable file-backed agent packages. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `FabricConfig` +SDK-facing typed Fabric agent configuration. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### method `add_mcp_server` + +```python +add_mcp_server( + name: 'str', + transport: 'str', + url: 'str', + exposure: "Literal['harness_native', 'fabric_managed']" = 'harness_native', + extra_fields: 'Mapping[str, Any] | None' = None +) → Self +``` + +Add or replace a named MCP server and return this config. + +--- + + +### method `add_skill_path` + +```python +add_skill_path(path: 'str | Path') → Self +``` + +Add a skill path and return this config. + +--- + + +### method `enable_relay` + +```python +enable_relay( + project: 'str | None' = None, + output_dir: 'str | Path | None' = None, + config: 'Mapping[str, Any] | None' = None +) → Self +``` + +Enable NeMo Relay telemetry and return this config. + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate the public agent config mapping shape. + +--- + + +### method `remove_mcp_server` + +```python +remove_mcp_server(name: 'str') → Self +``` + +Remove a named MCP server and return this config. + +--- + + +### method `remove_skill_path` + +```python +remove_skill_path(path: 'str | Path') → Self +``` + +Remove a skill path and return this config. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached mapping matching the Rust ``FabricConfig`` schema. + + +--- + + +## class `FabricProfileConfig` +Typed profile overlay used when a Python caller wants file-style overlays. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached JSON-compatible mapping for Rust/core calls. + + +--- + + +## class `RunRequest` +One validated Fabric invocation request. + + +--- + +### property extra_fields + +Return fields preserved by the extension point for this model. + +--- + +### property model_extra + +Get extra fields set during validation. + + + +**Returns:** + A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`. + +--- + +### property model_fields_set + +Returns the set of fields that have been explicitly set on this model instance. + + + +**Returns:** + A set of strings representing the fields that have been set, i.e. that were not filled from defaults. + + + +--- + + +### classmethod `from_mapping` + +```python +from_mapping(value: 'Mapping[str, Any]') → Self +``` + +Validate a mapping using this Pydantic model. + +--- + + +### method `to_mapping` + +```python +to_mapping() → dict[str, Any] +``` + +Return a detached request mapping for the Rust runtime. + + + + +--- + +_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/docs/reference/api/python-library-reference/nemo_fabric.runtime.md b/docs/reference/api/python-library-reference/nemo_fabric.runtime.md new file mode 100644 index 000000000..e0e1474f2 --- /dev/null +++ b/docs/reference/api/python-library-reference/nemo_fabric.runtime.md @@ -0,0 +1,129 @@ +--- +title: "Runtime" +slug: "/reference/api/python-library-reference/runtime" +description: "Drive stateful multi-turn execution through the Runtime API." +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +# module `nemo_fabric.runtime` +Runtime lifecycle support for the Fabric Python SDK. + + + +--- + + +## class `RuntimeStatus` +Lifecycle state of a runtime. + +``ACTIVE`` accepts invocations, ``STOPPED`` has released its runtime, and ``FAILED`` records a lifecycle failure that prevents further invocations but still permits cleanup. + + + + + +--- + + +## class `Runtime` +One logical, stateful harness execution. + +Create runtimes with ``Fabric.start_runtime()`` rather than calling the constructor. A runtime serializes invocations and preserves adapter-owned harness state across turns. Use it as an asynchronous context manager to stop the runtime on exit. + +Runtime-scoped overrides are recursively merged with invocation overrides; invocation values win. + + +--- + +### property handle + +Return a detached snapshot of the runtime handle. + +--- + +### property invocations + +Return copied request, runtime, and invocation IDs for completed turns. + +--- + +### property messages + +Return a deep copy of the latest harness-provided message history. + +--- + +### property runtime_id + +Return the unique identifier for this started runtime lifecycle. + +--- + +### property status + +Return the current ``ACTIVE``, ``STOPPED``, or ``FAILED`` state. + + + +--- + + +### method `invoke` + +```python +invoke(input: 'Any' = None, request: 'RunRequest | None' = None) → RunResult +``` + +Run one turn on this runtime. + +``input`` and ``request`` are mutually exclusive. Runtime overrides are merged below invocation overrides from ``RunRequest``. Concurrent turns on the same runtime are rejected. + + + +**Args:** + + - `input`: JSON-compatible turn input. + - `request`: Complete validated ``RunRequest``. + + + +**Returns:** + The normalized ``RunResult`` for this turn. + + + +**Raises:** + + - `FabricConfigError`: If request fields conflict or are not JSON-compatible. + - `FabricStateError`: If the runtime is not active, is stopping, or is already running a turn. + - `FabricNativeUnavailableError`: If the native extension is missing. + - `FabricRuntimeError`: If native invocation fails before returning a normalized result. + +--- + + +### method `stop` + +```python +stop() → None +``` + +Destroy an idle runtime exactly once. + +Repeated calls after a successful stop are no-ops. A failed runtime may still be stopped so its resources are released. + + + +**Raises:** + + - `FabricStateError`: If the runtime is already stopping or has an invocation in flight. + - `FabricNativeUnavailableError`: If the native extension is missing. + - `FabricRuntimeError`: If native runtime shutdown fails. + + + + +--- + +_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/docs/reference/api/python-library-reference/nemo_fabric.session.md b/docs/reference/api/python-library-reference/nemo_fabric.session.md deleted file mode 100644 index add56ccdc..000000000 --- a/docs/reference/api/python-library-reference/nemo_fabric.session.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: "Sessions" -slug: "/reference/api/python-library-reference/sessions" -description: "Drive stateful multi-turn runtimes through the Session API." ---- -{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -SPDX-License-Identifier: Apache-2.0 */} - -# module `nemo_fabric.session` -Session lifecycle support for the Fabric Python SDK. - - - ---- - - -## class `SessionStatus` -Lifecycle state of a session runtime. - -``ACTIVE`` accepts invocations, ``STOPPED`` has released its runtime, and ``FAILED`` records a lifecycle failure that prevents further use. - - - - - ---- - - -## class `Session` -One ordered multi-turn conversation over a Fabric runtime. - -Create sessions with ``FabricClient.start_session()`` rather than calling the constructor. A session owns one started runtime, serializes invocations, and preserves harness state across turns. Use it as an asynchronous context manager to stop the runtime on exit. - -Session-scoped overrides are recursively merged with invocation overrides; invocation values win. Runtime identity and conversation identity are distinct: ``runtime_id`` identifies this lifecycle, while ``session_id`` is the stable caller-owned resume key. - - ---- - -#### property info - -Return a typed snapshot of session identity, status, and capabilities. - ---- - -#### property invocations - -Return copied request, runtime, and invocation IDs for completed turns. - ---- - -#### property messages - -Return a deep copy of the latest harness-provided message history. - ---- - -#### property runtime - -Return a detached snapshot of the underlying runtime handle. - ---- - -#### property runtime_id - -Return the unique identifier for this started runtime lifecycle. - ---- - -#### property session_id - -Return the stable conversation ID, defaulting to ``runtime_id``. - ---- - -#### property status - -Return the current ``ACTIVE``, ``STOPPED``, or ``FAILED`` state. - - - ---- - - -### method `cancel` - -```python -cancel() → None -``` - -Report whether runtime cancellation is available. - - - -**Raises:** - - - `FabricCapabilityError`: If cancellation is unsupported or the cancellation transport is not yet implemented. - ---- - - -### method `invoke` - -```python -invoke( - input: 'Any' = None, - request: 'RunRequest | Mapping[str, Any] | None' = None, - request_id: 'str | None' = None, - context: 'Mapping[str, Any] | None' = None, - overrides: 'Mapping[str, Any] | None' = None -) → RunResult -``` - -Run one turn on the session's existing runtime. - -A complete ``request`` cannot be combined with separate ``request_id``, ``context``, or ``overrides`` fields. The session identifier is injected into request context, and session overrides are merged below invocation overrides. Concurrent turns on the same handle are rejected. - - - -**Args:** - - - `input`: JSON-compatible turn input. - - `request`: Complete ``RunRequest`` or compatible mapping. - - `request_id`: Caller-owned request identifier; generated when omitted. - - `context`: Caller-owned, JSON-compatible request metadata. - - `overrides`: JSON-compatible invocation-scoped config overrides. - - - -**Returns:** - The normalized ``RunResult`` for this turn. - - - -**Raises:** - - - `FabricConfigError`: If request fields conflict or are not JSON-compatible. - - `FabricStateError`: If the session is not active, is stopping, or is already running a turn. - - `FabricNativeUnavailableError`: If the native extension is missing. - - `FabricRuntimeError`: If native invocation fails before returning a normalized result. - ---- - - -### method `stop` - -```python -stop() → None -``` - -Destroy an idle runtime exactly once. - -Repeated calls after a successful stop are no-ops. A failed session or an in-flight invocation must reach a terminal state before cleanup can proceed. - - - -**Raises:** - - - `FabricStateError`: If the session failed, is already stopping, or has an invocation in flight. - - `FabricNativeUnavailableError`: If the native extension is missing. - - `FabricRuntimeError`: If native runtime shutdown fails. - ---- - - -### method `stream` - -```python -stream( - input: 'Any' = None, - request: 'RunRequest | Mapping[str, Any] | None' = None, - request_id: 'str | None' = None, - context: 'Mapping[str, Any] | None' = None, - overrides: 'Mapping[str, Any] | None' = None -) → AsyncIterator[FabricEvent | RunResult] -``` - -Yield buffered events followed by one terminal result. - -Current adapters may buffer internally; this API does not promise that events arrive in real time. Request validation and failure behavior are identical to ``invoke()``. - - - -**Args:** - - - `input`: JSON-compatible turn input. - - `request`: Complete ``RunRequest`` or compatible mapping. - - `request_id`: Caller-owned request identifier; generated when omitted. - - `context`: Caller-owned, JSON-compatible request metadata. - - `overrides`: JSON-compatible invocation-scoped config overrides. - - - -**Yields:** - Each normalized ``FabricEvent``, then the terminal ``RunResult``. - ---- - - -### method `update` - -```python -update(update: 'RuntimeUpdate') → RuntimeUpdateResult -``` - -Validate a runtime update and report transport availability. - - - -**Args:** - - - `update`: Typed update containing overrides and caller metadata. - - - -**Raises:** - - - `FabricConfigError`: If ``update`` is not a ``RuntimeUpdate``. - - `FabricCapabilityError`: If updates are unsupported or the update transport is not yet implemented. - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/docs/reference/api/python-library-reference/nemo_fabric.types.md b/docs/reference/api/python-library-reference/nemo_fabric.types.md index fdcab418c..e6f646b23 100644 --- a/docs/reference/api/python-library-reference/nemo_fabric.types.md +++ b/docs/reference/api/python-library-reference/nemo_fabric.types.md @@ -11,426 +11,6 @@ Public data contracts for the NeMo Fabric Python SDK. ---- - - -## class `MetadataConfig` -Agent identity and human-readable metadata. - - - -**Attributes:** - - - `name`: Stable, non-empty agent name. - - `description`: Optional human-readable description. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - name: 'str', - description: 'str | None' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'MetadataConfig' -``` - -Validate a metadata mapping and preserve unknown extension fields. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `HarnessConfig` -Harness adapter selection and adapter-owned settings. - - - -**Attributes:** - - - `adapter_id`: Stable identifier of the Fabric adapter to resolve. - - `resolution`: Optional adapter resolution strategy. - - `settings`: JSON-compatible settings owned by the selected adapter. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - adapter_id: 'str', - resolution: 'str | None' = None, - settings: 'Mapping[str, Any] | None' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'HarnessConfig' -``` - -Validate a harness mapping and preserve unknown extension fields. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `RuntimeConfig` -Runtime lifecycle mode and input/output contract. - - - -**Attributes:** - - - `mode`: Lifecycle mode: ``oneshot``, ``session``, or ``service``. - - `transport`: Optional adapter transport such as ``library`` or ``stdio``. - - `input_schema`: Optional logical input contract identifier. - - `output_schema`: Optional logical output contract identifier. - - `artifacts`: Optional artifact-root path. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - mode: 'str' = 'oneshot', - transport: 'str | None' = None, - input_schema: 'str | None' = None, - output_schema: 'str | None' = None, - artifacts: 'str | Path | None' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'RuntimeConfig' -``` - -Validate a runtime mapping and apply stable constructor defaults. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `EnvironmentConfig` -Execution environment configuration. - - - -**Attributes:** - - - `provider`: Environment provider identifier; defaults to ``local``. - - `workspace`: Optional workspace path visible to the harness. - - `artifacts`: Optional environment-specific artifact path. - - `settings`: JSON-compatible provider settings. - - `metadata`: JSON-compatible caller metadata. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - provider: 'str' = 'local', - workspace: 'str | Path | None' = None, - artifacts: 'str | Path | None' = None, - settings: 'Mapping[str, Any] | None' = None, - metadata: 'Mapping[str, Any] | None' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'EnvironmentConfig' -``` - -Validate an environment mapping and preserve extension fields. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `FabricConfig` -Mutable typed representation of a Fabric agent configuration. - -The object follows the same schema as ``agent.yaml``. It is mutable while callers compose a job, then copied into immutable resolution and plan snapshots. Unknown fields survive round trips through ``extra_fields``. - - - -**Attributes:** - - - `schema_version`: Agent schema identifier. - - `metadata`: Required ``MetadataConfig`` agent identity. - - `harness`: Required ``HarnessConfig`` adapter selection. - - `runtime`: Runtime lifecycle configuration; defaults to oneshot. - - `environment`: Optional execution environment configuration. - - `models`: Named, JSON-compatible model configurations. - - `mcp`: Optional MCP configuration. - - `skills`: Optional skill configuration. - - `telemetry`: Optional telemetry configuration. - - `profiles`: Optional profile-discovery configuration. - - `tools`: Optional harness-neutral tool configuration. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - metadata: 'MetadataConfig | Mapping[str, Any]', - harness: 'HarnessConfig | Mapping[str, Any]', - runtime: 'RuntimeConfig | Mapping[str, Any] | None' = None, - schema_version: 'str' = 'fabric.agent/v1alpha1', - environment: 'EnvironmentConfig | Mapping[str, Any] | None' = None, - models: 'Mapping[str, Any] | None' = None, - mcp: 'Mapping[str, Any] | None' = None, - skills: 'Mapping[str, Any] | None' = None, - telemetry: 'Mapping[str, Any] | None' = None, - profiles: 'Mapping[str, Any] | None' = None, - tools: 'Any' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'FabricConfig' -``` - -Build a typed agent config from the ``agent.yaml`` mapping shape. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `FabricProfileConfig` -Mutable, partial overlay applied to a typed ``FabricConfig``. - -Profile sections recursively overlay the base config in caller order. A profile may omit fields required by a complete agent config because Fabric validates only after all overlays have been applied. - - - -**Attributes:** - - - `schema_version`: Profile schema identifier. - - `name`: Stable, non-empty profile name. - - `description`: Optional human-readable description. - - `harness`: Optional partial harness overlay. - - `runtime`: Optional partial runtime overlay. - - `environment`: Optional partial environment overlay. - - `models`: Optional partial model overlay. - - `mcp`: Optional partial MCP overlay. - - `skills`: Optional partial skill overlay. - - `telemetry`: Optional partial telemetry overlay. - - `tools`: Optional tool overlay. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - name: 'str', - schema_version: 'str' = 'fabric.profile/v1alpha1', - description: 'str | None' = None, - harness: 'HarnessConfig | Mapping[str, Any] | None' = None, - runtime: 'RuntimeConfig | Mapping[str, Any] | None' = None, - environment: 'EnvironmentConfig | Mapping[str, Any] | None' = None, - models: 'Mapping[str, Any] | None' = None, - mcp: 'Mapping[str, Any] | None' = None, - skills: 'Mapping[str, Any] | None' = None, - telemetry: 'Mapping[str, Any] | None' = None, - tools: 'Any' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return preserved schema-extension fields as a deep copy. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'FabricProfileConfig' -``` - -Build a typed, partial profile overlay from a mapping. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - --- @@ -460,7 +40,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -512,12 +92,10 @@ Capabilities describe what the selected runtime can support; callers should stil **Attributes:** - - `session`: Whether stateful multi-turn sessions are supported. - `service`: Whether long-lived service handles are supported. - `streaming`: Whether event streaming is supported. - `updates`: Whether runtime configuration updates are supported. - `cancellation`: Whether in-flight cancellation is supported. - - `concurrent_invocations`: Whether invocations may overlap safely. - `metadata`: Additional capability details. @@ -534,7 +112,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -605,7 +183,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -675,7 +253,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -744,7 +322,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -813,7 +391,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -830,86 +408,6 @@ from_mapping(mapping: 'Mapping[str, Any]') → 'FabricMapping' Validate and copy a mapping into the requested typed model. ---- - - -### method `to_dict` - -```python -to_dict() → dict[str, Any] -``` - -Return the same detached representation as ``to_mapping()``. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `RunRequest` -One normalized invocation request. - -``input`` and all mapping fields must be JSON-compatible. Fabric generates a request identifier when callers omit one and preserves unknown mapping fields for forward compatibility. - - - -**Attributes:** - - - `input`: Harness input; defaults to an empty string. - - `request_id`: Caller-provided or generated request identifier. - - `context`: Caller-owned metadata propagated with the invocation. - - `overrides`: Optional invocation-scoped config overrides. - - `extra_fields`: Preserved extension fields not recognized by this SDK. - - -### method `__init__` - -```python -__init__( - input: 'Any' = ..., - request_id: 'str | None' = None, - context: 'Mapping[str, Any] | None' = None, - overrides: 'Mapping[str, Any] | None' = None, - extra_fields: 'Mapping[str, Any] | None' = None -) → None -``` - - - - - - ---- - -#### property extra_fields - -Return an immutable view of preserved extension fields. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(value: 'Mapping[str, Any]') → 'RunRequest' -``` - - - - - --- @@ -963,7 +461,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1033,7 +531,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1100,7 +598,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1170,7 +668,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1240,7 +738,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1296,7 +794,6 @@ Applications should treat ``runtime_binding`` as opaque. Fabric validates the ha - `runtime_binding`: Opaque integrity-bound runtime binding. - `agent_name`: Resolved agent name. - `harness`: Stable harness identifier. - - `mode`: Runtime lifecycle mode. - `adapter_kind`: Adapter execution mechanism. - `adapter_id`: Optional Fabric adapter identifier. - `environment`: Prepared environment snapshot. @@ -1315,7 +812,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. @@ -1397,217 +894,7 @@ __init__(mapping: 'Mapping[str, Any]') → None --- -#### property extra_fields - -Return an immutable view of preserved extension fields. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(mapping: 'Mapping[str, Any]') → 'FabricMapping' -``` - -Validate and copy a mapping into the requested typed model. - ---- - - -### method `to_dict` - -```python -to_dict() → dict[str, Any] -``` - -Return the same detached representation as ``to_mapping()``. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `SessionInfo` -Read-only metadata snapshot for an active or stopped session. - - - -**Attributes:** - - - `session_id`: Stable conversation identifier. - - `runtime_id`: Runtime lifecycle identifier. - - `agent_name`: Resolved agent name. - - `profiles`: Applied profile names. - - `harness`: Stable harness identifier. - - `adapter_id`: Fabric adapter identifier. - - `adapter_kind`: Adapter execution mechanism. - - `status`: Current session lifecycle state. - - `capabilities`: Operations declared by the runtime. - - -### method `__init__` - -```python -__init__(mapping: 'Mapping[str, Any]') → None -``` - - - - - - ---- - -#### property extra_fields - -Return an immutable view of preserved extension fields. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(mapping: 'Mapping[str, Any]') → 'FabricMapping' -``` - -Validate and copy a mapping into the requested typed model. - ---- - - -### method `to_dict` - -```python -to_dict() → dict[str, Any] -``` - -Return the same detached representation as ``to_mapping()``. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `RuntimeUpdate` -Capability-gated update requested for a running session. - - - -**Attributes:** - - - `overrides`: Config overrides to apply to the runtime. - - `metadata`: Caller-owned update metadata. - - -### method `__init__` - -```python -__init__(mapping: 'Mapping[str, Any]') → None -``` - - - - - - ---- - -#### property extra_fields - -Return an immutable view of preserved extension fields. - - - ---- - - -### classmethod `from_mapping` - -```python -from_mapping(mapping: 'Mapping[str, Any]') → 'FabricMapping' -``` - -Validate and copy a mapping into the requested typed model. - ---- - - -### method `to_dict` - -```python -to_dict() → dict[str, Any] -``` - -Return the same detached representation as ``to_mapping()``. - ---- - - -### method `to_mapping` - -```python -to_mapping() → dict[str, Any] -``` - -Return a detached, JSON-compatible mapping for serialization. - - ---- - - -## class `RuntimeUpdateResult` -Normalized outcome of a runtime update request. - - - -**Attributes:** - - - `status`: Terminal update status. - - `applied`: Overrides accepted by the runtime. - - `rejected`: Overrides rejected by the runtime. - - `reason`: Optional explanation for partial or complete rejection. - - -### method `__init__` - -```python -__init__(mapping: 'Mapping[str, Any]') → None -``` - - - - - - ---- - -#### property extra_fields +### property extra_fields Return an immutable view of preserved extension fields. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/constant-adapter-contract-version.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/constant-adapter-contract-version.mdx new file mode 100644 index 000000000..1ecc2a8da --- /dev/null +++ b/docs/reference/api/rust-library-reference/fabric-core/config/constant-adapter-contract-version.mdx @@ -0,0 +1,14 @@ +--- +title: "Constant ADAPTER_CONTRACT_VERSION" +sidebar-title: "ADAPTER_CONTRACT_VERSION" +description: "Adapter descriptor contract version supported by this core." +position: 1 +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +Generated from `cargo doc --no-deps -p fabric-core`. + +
str = \"fabric.adapter/v1alpha1\";"}} />
+ +Adapter descriptor contract version supported by this core. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterdescriptorsource.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterdescriptorsource.mdx index d5600a091..ad57a44e6 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterdescriptorsource.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterdescriptorsource.mdx @@ -2,7 +2,7 @@ title: "Enum Adapter Descriptor Source" sidebar-title: "AdapterDescriptorSource" description: "Where Fabric resolved an adapter descriptor from." -position: 3 +position: 4 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -36,23 +36,23 @@ Descriptor registered by the agent package or local development config. ### `impl Clone for AdapterDescriptorSource` -
Clone for AdapterDescriptorSource"}} />
+
Clone for AdapterDescriptorSource"}} />
#### `clone` -
clone(&self) -> AdapterDescriptorSource"}} />
+
clone(&self) -> AdapterDescriptorSource"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterDescriptorSource` -
Debug for AdapterDescriptorSource"}} />
+
Debug for AdapterDescriptorSource"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for AdapterDescriptorSource` @@ -60,7 +60,7 @@ Descriptor registered by the agent package or local development config. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterDescriptorSource` @@ -68,11 +68,11 @@ Descriptor registered by the agent package or local development config. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -80,19 +80,19 @@ Descriptor registered by the agent package or local development config. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterDescriptorSource` -
PartialEq for AdapterDescriptorSource"}} />
+
PartialEq for AdapterDescriptorSource"}} />
#### `eq` -
eq(&self, other: &AdapterDescriptorSource) -> bool"}} />
+
eq(&self, other: &AdapterDescriptorSource) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterDescriptorSource` @@ -100,16 +100,16 @@ Descriptor registered by the agent package or local development config. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for AdapterDescriptorSource` -
Copy for AdapterDescriptorSource"}} />
+
Copy for AdapterDescriptorSource"}} />
### `impl Eq for AdapterDescriptorSource` -
Eq for AdapterDescriptorSource"}} />
+
Eq for AdapterDescriptorSource"}} />
### `impl StructuralPartialEq for AdapterDescriptorSource` -
StructuralPartialEq for AdapterDescriptorSource"}} />
+
StructuralPartialEq for AdapterDescriptorSource"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterkind.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterkind.mdx index dd7af4a1a..f5cc539ea 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterkind.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-adapterkind.mdx @@ -2,7 +2,7 @@ title: "Enum Adapter Kind" sidebar-title: "AdapterKind" description: "Adapter implementation kind." -position: 4 +position: 5 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -50,23 +50,23 @@ Delegate to a harness-native plugin package. ### `impl Clone for AdapterKind` -
Clone for AdapterKind"}} />
+
Clone for AdapterKind"}} />
#### `clone` -
clone(&self) -> AdapterKind"}} />
+
clone(&self) -> AdapterKind"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterKind` -
Debug for AdapterKind"}} />
+
Debug for AdapterKind"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for AdapterKind` @@ -74,7 +74,7 @@ Delegate to a harness-native plugin package. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterKind` @@ -82,11 +82,11 @@ Delegate to a harness-native plugin package. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -94,19 +94,19 @@ Delegate to a harness-native plugin package. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterKind` -
PartialEq for AdapterKind"}} />
+
PartialEq for AdapterKind"}} />
#### `eq` -
eq(&self, other: &AdapterKind) -> bool"}} />
+
eq(&self, other: &AdapterKind) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterKind` @@ -114,16 +114,16 @@ Delegate to a harness-native plugin package. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for AdapterKind` -
Copy for AdapterKind"}} />
+
Copy for AdapterKind"}} />
### `impl Eq for AdapterKind` -
Eq for AdapterKind"}} />
+
Eq for AdapterKind"}} />
### `impl StructuralPartialEq for AdapterKind` -
StructuralPartialEq for AdapterKind"}} />
+
StructuralPartialEq for AdapterKind"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitykind.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitykind.mdx index 34ecdb73b..0e1c60848 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitykind.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitykind.mdx @@ -43,23 +43,23 @@ MCP server. ### `impl Clone for CapabilityKind` -
Clone for CapabilityKind"}} />
+
Clone for CapabilityKind"}} />
#### `clone` -
clone(&self) -> CapabilityKind"}} />
+
clone(&self) -> CapabilityKind"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for CapabilityKind` -
Debug for CapabilityKind"}} />
+
Debug for CapabilityKind"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for CapabilityKind` @@ -67,7 +67,7 @@ MCP server. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for CapabilityKind` @@ -75,11 +75,11 @@ MCP server. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ MCP server. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for CapabilityKind` -
PartialEq for CapabilityKind"}} />
+
PartialEq for CapabilityKind"}} />
#### `eq` -
eq(&self, other: &CapabilityKind) -> bool"}} />
+
eq(&self, other: &CapabilityKind) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for CapabilityKind` @@ -107,16 +107,16 @@ MCP server. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for CapabilityKind` -
Copy for CapabilityKind"}} />
+
Copy for CapabilityKind"}} />
### `impl Eq for CapabilityKind` -
Eq for CapabilityKind"}} />
+
Eq for CapabilityKind"}} />
### `impl StructuralPartialEq for CapabilityKind` -
StructuralPartialEq for CapabilityKind"}} />
+
StructuralPartialEq for CapabilityKind"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitytarget.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitytarget.mdx index 66b627af0..dbbc0409c 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitytarget.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-capabilitytarget.mdx @@ -43,23 +43,23 @@ Capability is configured but no executable surface exists. ### `impl Clone for CapabilityTarget` -
Clone for CapabilityTarget"}} />
+
Clone for CapabilityTarget"}} />
#### `clone` -
clone(&self) -> CapabilityTarget"}} />
+
clone(&self) -> CapabilityTarget"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for CapabilityTarget` -
Debug for CapabilityTarget"}} />
+
Debug for CapabilityTarget"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for CapabilityTarget` @@ -67,7 +67,7 @@ Capability is configured but no executable surface exists. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for CapabilityTarget` @@ -75,11 +75,11 @@ Capability is configured but no executable surface exists. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Capability is configured but no executable surface exists. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for CapabilityTarget` -
PartialEq for CapabilityTarget"}} />
+
PartialEq for CapabilityTarget"}} />
#### `eq` -
eq(&self, other: &CapabilityTarget) -> bool"}} />
+
eq(&self, other: &CapabilityTarget) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for CapabilityTarget` @@ -107,16 +107,16 @@ Capability is configured but no executable surface exists. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for CapabilityTarget` -
Copy for CapabilityTarget"}} />
+
Copy for CapabilityTarget"}} />
### `impl Eq for CapabilityTarget` -
Eq for CapabilityTarget"}} />
+
Eq for CapabilityTarget"}} />
### `impl StructuralPartialEq for CapabilityTarget` -
StructuralPartialEq for CapabilityTarget"}} />
+
StructuralPartialEq for CapabilityTarget"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-controllocation.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-controllocation.mdx index 34c31c9fe..5db0df5d3 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-controllocation.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-controllocation.mdx @@ -2,7 +2,7 @@ title: "Enum Control Location" sidebar-title: "ControlLocation" description: "Where Fabric control code runs relative to the environment." -position: 8 +position: 9 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -36,23 +36,23 @@ Fabric runs inside the prepared environment with the harness. ### `impl Clone for ControlLocation` -
Clone for ControlLocation"}} />
+
Clone for ControlLocation"}} />
#### `clone` -
clone(&self) -> ControlLocation"}} />
+
clone(&self) -> ControlLocation"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ControlLocation` -
Debug for ControlLocation"}} />
+
Debug for ControlLocation"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ControlLocation` @@ -60,7 +60,7 @@ Fabric runs inside the prepared environment with the harness. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ControlLocation` @@ -68,11 +68,11 @@ Fabric runs inside the prepared environment with the harness. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -80,19 +80,19 @@ Fabric runs inside the prepared environment with the harness. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ControlLocation` -
PartialEq for ControlLocation"}} />
+
PartialEq for ControlLocation"}} />
#### `eq` -
eq(&self, other: &ControlLocation) -> bool"}} />
+
eq(&self, other: &ControlLocation) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ControlLocation` @@ -100,16 +100,16 @@ Fabric runs inside the prepared environment with the harness. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for ControlLocation` -
Copy for ControlLocation"}} />
+
Copy for ControlLocation"}} />
### `impl Eq for ControlLocation` -
Eq for ControlLocation"}} />
+
Eq for ControlLocation"}} />
### `impl StructuralPartialEq for ControlLocation` -
StructuralPartialEq for ControlLocation"}} />
+
StructuralPartialEq for ControlLocation"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-environmentownership.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-environmentownership.mdx index 40db0ba88..0d9796677 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-environmentownership.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-environmentownership.mdx @@ -2,7 +2,7 @@ title: "Enum Environment Ownership" sidebar-title: "EnvironmentOwnership" description: "Whether Fabric owns the underlying environment resource." -position: 11 +position: 12 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -36,23 +36,23 @@ Fabric created or leased the environment resource and may release it. ### `impl Clone for EnvironmentOwnership` -
Clone for EnvironmentOwnership"}} />
+
Clone for EnvironmentOwnership"}} />
#### `clone` -
clone(&self) -> EnvironmentOwnership"}} />
+
clone(&self) -> EnvironmentOwnership"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for EnvironmentOwnership` -
Debug for EnvironmentOwnership"}} />
+
Debug for EnvironmentOwnership"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for EnvironmentOwnership` @@ -60,7 +60,7 @@ Fabric created or leased the environment resource and may release it. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for EnvironmentOwnership` @@ -68,11 +68,11 @@ Fabric created or leased the environment resource and may release it. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -80,19 +80,19 @@ Fabric created or leased the environment resource and may release it. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for EnvironmentOwnership` -
PartialEq for EnvironmentOwnership"}} />
+
PartialEq for EnvironmentOwnership"}} />
#### `eq` -
eq(&self, other: &EnvironmentOwnership) -> bool"}} />
+
eq(&self, other: &EnvironmentOwnership) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for EnvironmentOwnership` @@ -100,16 +100,16 @@ Fabric created or leased the environment resource and may release it. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for EnvironmentOwnership` -
Copy for EnvironmentOwnership"}} />
+
Copy for EnvironmentOwnership"}} />
### `impl Eq for EnvironmentOwnership` -
Eq for EnvironmentOwnership"}} />
+
Eq for EnvironmentOwnership"}} />
### `impl StructuralPartialEq for EnvironmentOwnership` -
StructuralPartialEq for EnvironmentOwnership"}} />
+
StructuralPartialEq for EnvironmentOwnership"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-fabricdocument.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-fabricdocument.mdx index 81dfd20ef..a3b4bb4b9 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-fabricdocument.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-fabricdocument.mdx @@ -2,14 +2,14 @@ title: "Enum Fabric Document" sidebar-title: "FabricDocument" description: "A loaded Fabric document with resolved source path and agent root." -position: 14 +position: 15 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
PathBuf,\n        root: PathBuf,\n        config: FabricConfig,\n    },\n}"}} />
+
PathBuf,\n        root: PathBuf,\n        config: FabricConfig,\n    },\n}"}} />
A loaded Fabric document with resolved source path and agent root. @@ -39,23 +39,23 @@ Parsed config. ### `impl Clone for FabricDocument` -
Clone for FabricDocument"}} />
+
Clone for FabricDocument"}} />
#### `clone` -
clone(&self) -> FabricDocument"}} />
+
clone(&self) -> FabricDocument"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for FabricDocument` -
Debug for FabricDocument"}} />
+
Debug for FabricDocument"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for FabricDocument` @@ -63,7 +63,7 @@ Parsed config. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for FabricDocument` @@ -71,11 +71,11 @@ Parsed config. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ Parsed config. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for FabricDocument` -
PartialEq for FabricDocument"}} />
+
PartialEq for FabricDocument"}} />
#### `eq` -
eq(&self, other: &FabricDocument) -> bool"}} />
+
eq(&self, other: &FabricDocument) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for FabricDocument` @@ -103,8 +103,8 @@ Parsed config. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for FabricDocument` -
StructuralPartialEq for FabricDocument"}} />
+
StructuralPartialEq for FabricDocument"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-mcpexposure.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-mcpexposure.mdx index c0c673166..f3c98067b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-mcpexposure.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-mcpexposure.mdx @@ -2,7 +2,7 @@ title: "Enum McpExposure" sidebar-title: "McpExposure" description: "MCP exposure strategy." -position: 17 +position: 18 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -36,23 +36,23 @@ Fabric manages MCP and exposes basic tools/actions. ### `impl Clone for McpExposure` -
Clone for McpExposure"}} />
+
Clone for McpExposure"}} />
#### `clone` -
clone(&self) -> McpExposure"}} />
+
clone(&self) -> McpExposure"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for McpExposure` -
Debug for McpExposure"}} />
+
Debug for McpExposure"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for McpExposure` @@ -60,7 +60,7 @@ Fabric manages MCP and exposes basic tools/actions. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for McpExposure` @@ -68,11 +68,11 @@ Fabric manages MCP and exposes basic tools/actions. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -80,19 +80,19 @@ Fabric manages MCP and exposes basic tools/actions. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for McpExposure` -
PartialEq for McpExposure"}} />
+
PartialEq for McpExposure"}} />
#### `eq` -
eq(&self, other: &McpExposure) -> bool"}} />
+
eq(&self, other: &McpExposure) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for McpExposure` @@ -100,16 +100,16 @@ Fabric manages MCP and exposes basic tools/actions. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for McpExposure` -
Copy for McpExposure"}} />
+
Copy for McpExposure"}} />
### `impl Eq for McpExposure` -
Eq for McpExposure"}} />
+
Eq for McpExposure"}} />
### `impl StructuralPartialEq for McpExposure` -
StructuralPartialEq for McpExposure"}} />
+
StructuralPartialEq for McpExposure"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-resolutionstrategy.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-resolutionstrategy.mdx index 1ff01ebed..ab52714b4 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-resolutionstrategy.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-resolutionstrategy.mdx @@ -2,7 +2,7 @@ title: "Enum Resolution Strategy" sidebar-title: "ResolutionStrategy" description: "Adapter install or availability strategy." -position: 22 +position: 23 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -71,23 +71,23 @@ Adapter is installed through a harness-native plugin manager. ### `impl Clone for ResolutionStrategy` -
Clone for ResolutionStrategy"}} />
+
Clone for ResolutionStrategy"}} />
#### `clone` -
clone(&self) -> ResolutionStrategy"}} />
+
clone(&self) -> ResolutionStrategy"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ResolutionStrategy` -
Debug for ResolutionStrategy"}} />
+
Debug for ResolutionStrategy"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ResolutionStrategy` @@ -95,7 +95,7 @@ Adapter is installed through a harness-native plugin manager. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ResolutionStrategy` @@ -103,11 +103,11 @@ Adapter is installed through a harness-native plugin manager. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -115,19 +115,19 @@ Adapter is installed through a harness-native plugin manager. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ResolutionStrategy` -
PartialEq for ResolutionStrategy"}} />
+
PartialEq for ResolutionStrategy"}} />
#### `eq` -
eq(&self, other: &ResolutionStrategy) -> bool"}} />
+
eq(&self, other: &ResolutionStrategy) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ResolutionStrategy` @@ -135,16 +135,16 @@ Adapter is installed through a harness-native plugin manager. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for ResolutionStrategy` -
Copy for ResolutionStrategy"}} />
+
Copy for ResolutionStrategy"}} />
### `impl Eq for ResolutionStrategy` -
Eq for ResolutionStrategy"}} />
+
Eq for ResolutionStrategy"}} />
### `impl StructuralPartialEq for ResolutionStrategy` -
StructuralPartialEq for ResolutionStrategy"}} />
+
StructuralPartialEq for ResolutionStrategy"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-runtimemode.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-runtimemode.mdx deleted file mode 100644 index a02e5ba76..000000000 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-runtimemode.mdx +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: "Enum Runtime Mode" -sidebar-title: "RuntimeMode" -description: "Runtime lifecycle mode." -position: 28 ---- -{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -SPDX-License-Identifier: Apache-2.0 */} - -Generated from `cargo doc --no-deps -p fabric-core`. - -```rust -pub enum RuntimeMode { - Oneshot, - Service, - Session, -} -``` - -Runtime lifecycle mode. - -## Variants - -### `Oneshot` - -
- -Request is the lifecycle boundary. - -### `Service` - -
- -Long-running process or service is the lifecycle boundary. - -### `Session` - -
- -Session is the lifecycle boundary. - -## Trait Implementations - -### `impl Clone for RuntimeMode` - -
Clone for RuntimeMode"}} />
- -#### `clone` - -
clone(&self) -> RuntimeMode"}} />
- -#### `clone_from` - -
clone_from(&mut self, source: &Self)"}} />
- -### `impl Debug for RuntimeMode` - -
Debug for RuntimeMode"}} />
- -#### `fmt` - -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
- -### `impl<'de> Deserialize<'de> for RuntimeMode` - -
Deserialize<'de> for RuntimeMode"}} />
- -#### `deserialize` - -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
- -### `impl JsonSchema for RuntimeMode` - -
RuntimeMode"}} />
- -#### `schema_name` - -
Cow<'static, str>"}} />
- -#### `schema_id` - -
Cow<'static, str>"}} />
- -#### `json_schema` - -
- -#### `inline_schema` - -
bool"}} />
- -### `impl PartialEq for RuntimeMode` - -
PartialEq for RuntimeMode"}} />
- -#### `eq` - -
eq(&self, other: &RuntimeMode) -> bool"}} />
- -#### `ne` - -
ne(&self, other: &Rhs) -> bool"}} />
- -### `impl Serialize for RuntimeMode` - -
Serialize for RuntimeMode"}} />
- -#### `serialize` - -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
- -### `impl Copy for RuntimeMode` - -
Copy for RuntimeMode"}} />
- -### `impl Eq for RuntimeMode` - -
Eq for RuntimeMode"}} />
- -### `impl StructuralPartialEq for RuntimeMode` - -
StructuralPartialEq for RuntimeMode"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/enum-transport.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/enum-telemetryprovider.mdx similarity index 50% rename from docs/reference/api/rust-library-reference/fabric-core/config/enum-transport.mdx rename to docs/reference/api/rust-library-reference/fabric-core/config/enum-telemetryprovider.mdx index 422709d2a..030ece7d7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/enum-transport.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/enum-telemetryprovider.mdx @@ -1,7 +1,7 @@ --- -title: "Enum Transport" -sidebar-title: "Transport" -description: "Runtime transport." +title: "Enum Telemetry Provider" +sidebar-title: "TelemetryProvider" +description: "Telemetry runtime provider." position: 32 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. @@ -10,83 +10,89 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. ```rust -pub enum Transport { - Library, - Cli, - Http, - NativePlugin, +pub enum TelemetryProvider { + Relay, + Native, } ``` -Runtime transport. +Telemetry runtime provider. ## Variants -### `Library` +### `Relay` -
+
-In-process library/SDK call. +Use NeMo Relay for telemetry integration. -### `Cli` +### `Native` -
+
-CLI process. +Let the selected adapter handle telemetry natively. -### `Http` +## Implementations -
+### `impl TelemetryProvider` -HTTP service. +
TelemetryProvider"}} />
-### `NativePlugin` +#### `as_str` -
+
str"}} />
-Harness-native plugin surface. +Return the stable configuration value for this provider. ## Trait Implementations -### `impl Clone for Transport` +### `impl Clone for TelemetryProvider` -
Clone for Transport"}} />
+
Clone for TelemetryProvider"}} />
#### `clone` -
clone(&self) -> Transport"}} />
+
clone(&self) -> TelemetryProvider"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
-### `impl Debug for Transport` +### `impl Debug for TelemetryProvider` -
Debug for Transport"}} />
+
Debug for TelemetryProvider"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
-### `impl<'de> Deserialize<'de> for Transport` +### `impl Default for TelemetryProvider` -
Deserialize<'de> for Transport"}} />
+
Default for TelemetryProvider"}} />
+ +#### `default` + +
default() -> TelemetryProvider"}} />
+ +### `impl<'de> Deserialize<'de> for TelemetryProvider` + +
Deserialize<'de> for TelemetryProvider"}} />
#### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
-### `impl JsonSchema for Transport` +### `impl JsonSchema for TelemetryProvider` -
Transport"}} />
+
TelemetryProvider"}} />
#### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -94,36 +100,36 @@ Harness-native plugin surface. #### `inline_schema` -
bool"}} />
+
bool"}} />
-### `impl PartialEq for Transport` +### `impl PartialEq for TelemetryProvider` -
PartialEq for Transport"}} />
+
PartialEq for TelemetryProvider"}} />
#### `eq` -
eq(&self, other: &Transport) -> bool"}} />
+
eq(&self, other: &TelemetryProvider) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
-### `impl Serialize for Transport` +### `impl Serialize for TelemetryProvider` -
Serialize for Transport"}} />
+
Serialize for TelemetryProvider"}} />
#### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
-### `impl Copy for Transport` +### `impl Copy for TelemetryProvider` -
Copy for Transport"}} />
+
Copy for TelemetryProvider"}} />
-### `impl Eq for Transport` +### `impl Eq for TelemetryProvider` -
Eq for Transport"}} />
+
Eq for TelemetryProvider"}} />
-### `impl StructuralPartialEq for Transport` +### `impl StructuralPartialEq for TelemetryProvider` -
StructuralPartialEq for Transport"}} />
+
StructuralPartialEq for TelemetryProvider"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-adapter-descriptor.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-adapter-descriptor.mdx index 2c4fe3ae9..382c5841a 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-adapter-descriptor.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-adapter-descriptor.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n) -> Result<AdapterDescriptor>"}} />
+
AsRef<Path>,\n) -> Result<AdapterDescriptor>"}} />
Load an adapter descriptor from JSON package metadata. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-fabric-document.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-fabric-document.mdx index b81d3e60c..ad45abcd7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-fabric-document.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-load-fabric-document.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>) -> Result<FabricDocument>"}} />
+
AsRef<Path>) -> Result<FabricDocument>"}} />
Load a Fabric document from an agent directory or single agent config. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config-with-profiles.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config-with-profiles.mdx index b4d0b5fce..2b2124314 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config-with-profiles.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config-with-profiles.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n    profiles: &[String],\n) -> Result<EffectiveConfig>"}} />
+
AsRef<Path>,\n    profiles: &[String],\n) -> Result<EffectiveConfig>"}} />
Resolve an agent directory or single agent config with ordered profiles into merged effective config. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config.mdx index b5fe79290..558cba6c9 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-effective-config.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n    profile: Option<&str>,\n) -> Result<EffectiveConfig>"}} />
+
AsRef<Path>,\n    profile: Option<&str>,\n) -> Result<EffectiveConfig>"}} />
Resolve an agent directory or single agent config into merged effective config. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan-with-profiles.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan-with-profiles.mdx index 14a081a2b..ab313bb1e 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan-with-profiles.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan-with-profiles.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n    profiles: &[String],\n) -> Result<RunPlan>"}} />
+
AsRef<Path>,\n    profiles: &[String],\n) -> Result<RunPlan>"}} />
Resolve an agent directory or single agent config with ordered profile application. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan.mdx index 9b1590553..90951e465 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-resolve-run-plan.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n    profile: Option<&str>,\n) -> Result<RunPlan>"}} />
+
AsRef<Path>,\n    profile: Option<&str>,\n) -> Result<RunPlan>"}} />
Resolve an agent directory or single agent config into a runnable plan. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/fn-validate-agent-directory.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/fn-validate-agent-directory.mdx index 824443862..d4740ba0d 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/fn-validate-agent-directory.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/fn-validate-agent-directory.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>) -> Result<()>"}} />
+
AsRef<Path>) -> Result<()>"}} />
Validate an agent directory or config, including discoverable profile YAMLs. diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/index.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/index.mdx index 1459a2b14..b175aebcb 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/index.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/index.mdx @@ -36,7 +36,7 @@ Fabric config models and loading helpers. - [ResolvedAdapterDescriptor](/reference/api/rust-library-reference/fabric-core/config/struct-resolvedadapterdescriptor): Adapter descriptor selected for a run plan. - [RunPlan](/reference/api/rust-library-reference/fabric-core/config/struct-runplan): Resolved Fabric run plan. - [RuntimeCapabilities](/reference/api/rust-library-reference/fabric-core/config/struct-runtimecapabilities): Lifecycle behavior implemented by a resolved runtime path. -- [RuntimeConfig](/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig): Runtime mode and input/output contract. +- [RuntimeConfig](/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig): Runtime input/output contract. - [SkillConfig](/reference/api/rust-library-reference/fabric-core/config/struct-skillconfig): Skill capability configuration. - [TelemetryConfig](/reference/api/rust-library-reference/fabric-core/config/struct-telemetryconfig): Telemetry configuration. - [TelemetryPlan](/reference/api/rust-library-reference/fabric-core/config/struct-telemetryplan): Resolved telemetry plan. @@ -52,8 +52,11 @@ Fabric config models and loading helpers. - [FabricDocument](/reference/api/rust-library-reference/fabric-core/config/enum-fabricdocument): A loaded Fabric document with resolved source path and agent root. - [McpExposure](/reference/api/rust-library-reference/fabric-core/config/enum-mcpexposure): MCP exposure strategy. - [ResolutionStrategy](/reference/api/rust-library-reference/fabric-core/config/enum-resolutionstrategy): Adapter install or availability strategy. -- [RuntimeMode](/reference/api/rust-library-reference/fabric-core/config/enum-runtimemode): Runtime lifecycle mode. -- [Transport](/reference/api/rust-library-reference/fabric-core/config/enum-transport): Runtime transport. +- [TelemetryProvider](/reference/api/rust-library-reference/fabric-core/config/enum-telemetryprovider): Telemetry runtime provider. + +## Constants + +- [ADAPTER_CONTRACT_VERSION](/reference/api/rust-library-reference/fabric-core/config/constant-adapter-contract-version): Adapter descriptor contract version supported by this core. ## Functions diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterconfigsupport.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterconfigsupport.mdx index f02da8d68..c5df2a07b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterconfigsupport.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterconfigsupport.mdx @@ -2,14 +2,14 @@ title: "Struct Adapter Config Support" sidebar-title: "AdapterConfigSupport" description: "Adapter config support." -position: 1 +position: 2 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Vec<String>,\n    pub generates: Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
Vec<String>,\n    pub generates: Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Adapter config support. @@ -31,31 +31,31 @@ Additive adapter config-support fields. ### `impl Clone for AdapterConfigSupport` -
Clone for AdapterConfigSupport"}} />
+
Clone for AdapterConfigSupport"}} />
#### `clone` -
clone(&self) -> AdapterConfigSupport"}} />
+
clone(&self) -> AdapterConfigSupport"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterConfigSupport` -
Debug for AdapterConfigSupport"}} />
+
Debug for AdapterConfigSupport"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for AdapterConfigSupport` -
Default for AdapterConfigSupport"}} />
+
Default for AdapterConfigSupport"}} />
#### `default` -
default() -> AdapterConfigSupport"}} />
+
default() -> AdapterConfigSupport"}} />
### `impl<'de> Deserialize<'de> for AdapterConfigSupport` @@ -63,7 +63,7 @@ Additive adapter config-support fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterConfigSupport` @@ -71,11 +71,11 @@ Additive adapter config-support fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ Additive adapter config-support fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterConfigSupport` -
PartialEq for AdapterConfigSupport"}} />
+
PartialEq for AdapterConfigSupport"}} />
#### `eq` -
eq(&self, other: &AdapterConfigSupport) -> bool"}} />
+
eq(&self, other: &AdapterConfigSupport) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterConfigSupport` @@ -103,8 +103,8 @@ Additive adapter config-support fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for AdapterConfigSupport` -
StructuralPartialEq for AdapterConfigSupport"}} />
+
StructuralPartialEq for AdapterConfigSupport"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterdescriptor.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterdescriptor.mdx index 08d4da063..08bc1513a 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterdescriptor.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterdescriptor.mdx @@ -2,19 +2,23 @@ title: "Struct Adapter Descriptor" sidebar-title: "AdapterDescriptor" description: "Language-neutral adapter descriptor for a harness integration." -position: 2 +position: 3 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub harness: String,\n    pub adapter_kind: AdapterKind,\n    pub runner: Map<String, Value>,\n    pub requirements: AdapterRequirements,\n    pub config: AdapterConfigSupport,\n    pub telemetry: AdapterTelemetrySupport,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub adapter_id: String,\n    pub harness: String,\n    pub adapter_kind: AdapterKind,\n    pub runner: Map<String, Value>,\n    pub requirements: AdapterRequirements,\n    pub config: AdapterConfigSupport,\n    pub telemetry: AdapterTelemetrySupport,\n    pub capabilities: RuntimeCapabilities,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Language-neutral adapter descriptor for a harness integration. ## Fields +### `contract_version: String` + +Adapter descriptor contract version. + ### `adapter_id: String` Unique id for this adapter implementation. @@ -43,6 +47,10 @@ Fabric config areas this adapter consumes or generates. Telemetry support declared by this adapter. +### `capabilities: RuntimeCapabilities` + +Runtime lifecycle operations supported by this adapter. + ### `extensions: BTreeMap` Additive adapter descriptor fields. @@ -51,23 +59,23 @@ Additive adapter descriptor fields. ### `impl Clone for AdapterDescriptor` -
Clone for AdapterDescriptor"}} />
+
Clone for AdapterDescriptor"}} />
#### `clone` -
clone(&self) -> AdapterDescriptor"}} />
+
clone(&self) -> AdapterDescriptor"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterDescriptor` -
Debug for AdapterDescriptor"}} />
+
Debug for AdapterDescriptor"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for AdapterDescriptor` @@ -75,7 +83,7 @@ Additive adapter descriptor fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterDescriptor` @@ -83,11 +91,11 @@ Additive adapter descriptor fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -95,19 +103,19 @@ Additive adapter descriptor fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterDescriptor` -
PartialEq for AdapterDescriptor"}} />
+
PartialEq for AdapterDescriptor"}} />
#### `eq` -
eq(&self, other: &AdapterDescriptor) -> bool"}} />
+
eq(&self, other: &AdapterDescriptor) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterDescriptor` @@ -115,8 +123,8 @@ Additive adapter descriptor fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for AdapterDescriptor` -
StructuralPartialEq for AdapterDescriptor"}} />
+
StructuralPartialEq for AdapterDescriptor"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterrequirements.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterrequirements.mdx index d8af59edd..b60aa467f 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterrequirements.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adapterrequirements.mdx @@ -2,14 +2,14 @@ title: "Struct Adapter Requirements" sidebar-title: "AdapterRequirements" description: "Adapter runtime requirements." -position: 5 +position: 6 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Vec<String>,\n    pub env: Vec<String>,\n    pub files: Vec<PathBuf>,\n    pub services: Vec<String>,\n    pub plugin_hooks: Vec<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
Vec<String>,\n    pub env: Vec<String>,\n    pub files: Vec<PathBuf>,\n    pub services: Vec<String>,\n    pub plugin_hooks: Vec<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Adapter runtime requirements. @@ -43,31 +43,31 @@ Additive requirement fields. ### `impl Clone for AdapterRequirements` -
Clone for AdapterRequirements"}} />
+
Clone for AdapterRequirements"}} />
#### `clone` -
clone(&self) -> AdapterRequirements"}} />
+
clone(&self) -> AdapterRequirements"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterRequirements` -
Debug for AdapterRequirements"}} />
+
Debug for AdapterRequirements"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for AdapterRequirements` -
Default for AdapterRequirements"}} />
+
Default for AdapterRequirements"}} />
#### `default` -
default() -> AdapterRequirements"}} />
+
default() -> AdapterRequirements"}} />
### `impl<'de> Deserialize<'de> for AdapterRequirements` @@ -75,7 +75,7 @@ Additive requirement fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterRequirements` @@ -83,11 +83,11 @@ Additive requirement fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -95,19 +95,19 @@ Additive requirement fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterRequirements` -
PartialEq for AdapterRequirements"}} />
+
PartialEq for AdapterRequirements"}} />
#### `eq` -
eq(&self, other: &AdapterRequirements) -> bool"}} />
+
eq(&self, other: &AdapterRequirements) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterRequirements` @@ -115,8 +115,8 @@ Additive requirement fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for AdapterRequirements` -
StructuralPartialEq for AdapterRequirements"}} />
+
StructuralPartialEq for AdapterRequirements"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adaptertelemetrysupport.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adaptertelemetrysupport.mdx index 6971b5ce1..49accd8b7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-adaptertelemetrysupport.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-adaptertelemetrysupport.mdx @@ -2,14 +2,14 @@ title: "Struct Adapter Telemetry Support" sidebar-title: "AdapterTelemetrySupport" description: "Adapter telemetry support." -position: 6 +position: 7 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Vec<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
Vec<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Adapter telemetry support. @@ -27,31 +27,31 @@ Additive adapter telemetry fields. ### `impl Clone for AdapterTelemetrySupport` -
Clone for AdapterTelemetrySupport"}} />
+
Clone for AdapterTelemetrySupport"}} />
#### `clone` -
clone(&self) -> AdapterTelemetrySupport"}} />
+
clone(&self) -> AdapterTelemetrySupport"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterTelemetrySupport` -
Debug for AdapterTelemetrySupport"}} />
+
Debug for AdapterTelemetrySupport"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for AdapterTelemetrySupport` -
Default for AdapterTelemetrySupport"}} />
+
Default for AdapterTelemetrySupport"}} />
#### `default` -
default() -> AdapterTelemetrySupport"}} />
+
default() -> AdapterTelemetrySupport"}} />
### `impl<'de> Deserialize<'de> for AdapterTelemetrySupport` @@ -59,7 +59,7 @@ Additive adapter telemetry fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterTelemetrySupport` @@ -67,11 +67,11 @@ Additive adapter telemetry fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive adapter telemetry fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterTelemetrySupport` -
PartialEq for AdapterTelemetrySupport"}} />
+
PartialEq for AdapterTelemetrySupport"}} />
#### `eq` -
eq(&self, other: &AdapterTelemetrySupport) -> bool"}} />
+
eq(&self, other: &AdapterTelemetrySupport) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterTelemetrySupport` @@ -99,8 +99,8 @@ Additive adapter telemetry fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for AdapterTelemetrySupport` -
StructuralPartialEq for AdapterTelemetrySupport"}} />
+
StructuralPartialEq for AdapterTelemetrySupport"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityplan.mdx index 9a021c46c..84700ab35 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityplan.mdx @@ -2,14 +2,14 @@ title: "Struct Capability Plan" sidebar-title: "CapabilityPlan" description: "Resolved capability configuration." -position: 7 +position: 8 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub skill_paths: Vec<PathBuf>,\n    pub mcp_servers: BTreeMap<String, McpServerPlan>,\n    pub native: CapabilityTargetPlan,\n    pub managed: CapabilityTargetPlan,\n    pub unsupported: CapabilityTargetPlan,\n    pub routes: Vec<CapabilityRoute>,\n}"}} />
+
bool,\n    pub skill_paths: Vec<PathBuf>,\n    pub mcp_servers: BTreeMap<String, McpServerPlan>,\n    pub native: CapabilityTargetPlan,\n    pub managed: CapabilityTargetPlan,\n    pub unsupported: CapabilityTargetPlan,\n    pub routes: Vec<CapabilityRoute>,\n}"}} />
Resolved capability configuration. @@ -47,31 +47,31 @@ Routing decisions made while resolving the effective config. ### `impl Clone for CapabilityPlan` -
Clone for CapabilityPlan"}} />
+
Clone for CapabilityPlan"}} />
#### `clone` -
clone(&self) -> CapabilityPlan"}} />
+
clone(&self) -> CapabilityPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for CapabilityPlan` -
Debug for CapabilityPlan"}} />
+
Debug for CapabilityPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for CapabilityPlan` -
Default for CapabilityPlan"}} />
+
Default for CapabilityPlan"}} />
#### `default` -
default() -> CapabilityPlan"}} />
+
default() -> CapabilityPlan"}} />
### `impl<'de> Deserialize<'de> for CapabilityPlan` @@ -79,7 +79,7 @@ Routing decisions made while resolving the effective config. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for CapabilityPlan` @@ -87,11 +87,11 @@ Routing decisions made while resolving the effective config. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -99,19 +99,19 @@ Routing decisions made while resolving the effective config. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for CapabilityPlan` -
PartialEq for CapabilityPlan"}} />
+
PartialEq for CapabilityPlan"}} />
#### `eq` -
eq(&self, other: &CapabilityPlan) -> bool"}} />
+
eq(&self, other: &CapabilityPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for CapabilityPlan` @@ -119,8 +119,8 @@ Routing decisions made while resolving the effective config. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for CapabilityPlan` -
StructuralPartialEq for CapabilityPlan"}} />
+
StructuralPartialEq for CapabilityPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityroute.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityroute.mdx index b8bba9f56..a97861931 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityroute.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilityroute.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
CapabilityKind,\n    pub name: String,\n    pub target: CapabilityTarget,\n    pub reason: String,\n}"}} />
+
CapabilityKind,\n    pub name: String,\n    pub target: CapabilityTarget,\n    pub reason: String,\n}"}} />
One capability routing decision. @@ -35,23 +35,23 @@ Human-readable reason for the selected route. ### `impl Clone for CapabilityRoute` -
Clone for CapabilityRoute"}} />
+
Clone for CapabilityRoute"}} />
#### `clone` -
clone(&self) -> CapabilityRoute"}} />
+
clone(&self) -> CapabilityRoute"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for CapabilityRoute` -
Debug for CapabilityRoute"}} />
+
Debug for CapabilityRoute"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for CapabilityRoute` @@ -59,7 +59,7 @@ Human-readable reason for the selected route. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for CapabilityRoute` @@ -67,11 +67,11 @@ Human-readable reason for the selected route. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Human-readable reason for the selected route. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for CapabilityRoute` -
PartialEq for CapabilityRoute"}} />
+
PartialEq for CapabilityRoute"}} />
#### `eq` -
eq(&self, other: &CapabilityRoute) -> bool"}} />
+
eq(&self, other: &CapabilityRoute) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for CapabilityRoute` @@ -99,8 +99,8 @@ Human-readable reason for the selected route. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for CapabilityRoute` -
StructuralPartialEq for CapabilityRoute"}} />
+
StructuralPartialEq for CapabilityRoute"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilitytargetplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilitytargetplan.mdx index 25dfdb29a..e60af403b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilitytargetplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-capabilitytargetplan.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub skill_paths: Vec<PathBuf>,\n    pub mcp_servers: BTreeMap<String, McpServerPlan>,\n}"}} />
+
bool,\n    pub skill_paths: Vec<PathBuf>,\n    pub mcp_servers: BTreeMap<String, McpServerPlan>,\n}"}} />
Capabilities routed to one target. @@ -31,31 +31,31 @@ MCP servers for this target. ### `impl Clone for CapabilityTargetPlan` -
Clone for CapabilityTargetPlan"}} />
+
Clone for CapabilityTargetPlan"}} />
#### `clone` -
clone(&self) -> CapabilityTargetPlan"}} />
+
clone(&self) -> CapabilityTargetPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for CapabilityTargetPlan` -
Debug for CapabilityTargetPlan"}} />
+
Debug for CapabilityTargetPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for CapabilityTargetPlan` -
Default for CapabilityTargetPlan"}} />
+
Default for CapabilityTargetPlan"}} />
#### `default` -
default() -> CapabilityTargetPlan"}} />
+
default() -> CapabilityTargetPlan"}} />
### `impl<'de> Deserialize<'de> for CapabilityTargetPlan` @@ -63,7 +63,7 @@ MCP servers for this target. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for CapabilityTargetPlan` @@ -71,11 +71,11 @@ MCP servers for this target. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ MCP servers for this target. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for CapabilityTargetPlan` -
PartialEq for CapabilityTargetPlan"}} />
+
PartialEq for CapabilityTargetPlan"}} />
#### `eq` -
eq(&self, other: &CapabilityTargetPlan) -> bool"}} />
+
eq(&self, other: &CapabilityTargetPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for CapabilityTargetPlan` @@ -103,8 +103,8 @@ MCP servers for this target. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for CapabilityTargetPlan` -
StructuralPartialEq for CapabilityTargetPlan"}} />
+
StructuralPartialEq for CapabilityTargetPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-effectiveconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-effectiveconfig.mdx index 6797389e7..29957e6e7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-effectiveconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-effectiveconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Effective Config" sidebar-title: "EffectiveConfig" description: "Merged Fabric config after applying selected profiles." -position: 9 +position: 10 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub profiles: Vec<String>,\n    pub agent_root: PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n    pub config: FabricConfig,\n}"}} />
+
String,\n    pub profiles: Vec<String>,\n    pub agent_root: PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n    pub config: FabricConfig,\n}"}} />
Merged Fabric config after applying selected profiles. @@ -43,23 +43,23 @@ Merged Fabric config with authoring-time profile discovery removed. ### `impl Clone for EffectiveConfig` -
Clone for EffectiveConfig"}} />
+
Clone for EffectiveConfig"}} />
#### `clone` -
clone(&self) -> EffectiveConfig"}} />
+
clone(&self) -> EffectiveConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for EffectiveConfig` -
Debug for EffectiveConfig"}} />
+
Debug for EffectiveConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for EffectiveConfig` @@ -67,7 +67,7 @@ Merged Fabric config with authoring-time profile discovery removed. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for EffectiveConfig` @@ -75,11 +75,11 @@ Merged Fabric config with authoring-time profile discovery removed. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Merged Fabric config with authoring-time profile discovery removed. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for EffectiveConfig` -
PartialEq for EffectiveConfig"}} />
+
PartialEq for EffectiveConfig"}} />
#### `eq` -
eq(&self, other: &EffectiveConfig) -> bool"}} />
+
eq(&self, other: &EffectiveConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for EffectiveConfig` @@ -107,8 +107,8 @@ Merged Fabric config with authoring-time profile discovery removed. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for EffectiveConfig` -
StructuralPartialEq for EffectiveConfig"}} />
+
StructuralPartialEq for EffectiveConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentconfig.mdx index 933f9e5ff..d6f6c4262 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Environment Config" sidebar-title: "EnvironmentConfig" description: "Execution environment configuration." -position: 10 +position: 11 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub control_location: ControlLocation,\n    pub ownership: EnvironmentOwnership,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub connection: Map<String, Value>,\n    pub metadata: Map<String, Value>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub control_location: ControlLocation,\n    pub ownership: EnvironmentOwnership,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub connection: Map<String, Value>,\n    pub metadata: Map<String, Value>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Execution environment configuration. @@ -37,7 +37,7 @@ Artifact path inside or outside the provider. ### `connection: Map` -Provider connection metadata, such as server URL, session id, or namespace. +Provider connection metadata, such as server URL, credential reference, or namespace. ### `metadata: Map` @@ -55,23 +55,23 @@ Additive normalized environment fields. ### `impl Clone for EnvironmentConfig` -
Clone for EnvironmentConfig"}} />
+
Clone for EnvironmentConfig"}} />
#### `clone` -
clone(&self) -> EnvironmentConfig"}} />
+
clone(&self) -> EnvironmentConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for EnvironmentConfig` -
Debug for EnvironmentConfig"}} />
+
Debug for EnvironmentConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for EnvironmentConfig` @@ -79,7 +79,7 @@ Additive normalized environment fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for EnvironmentConfig` @@ -87,11 +87,11 @@ Additive normalized environment fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -99,19 +99,19 @@ Additive normalized environment fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for EnvironmentConfig` -
PartialEq for EnvironmentConfig"}} />
+
PartialEq for EnvironmentConfig"}} />
#### `eq` -
eq(&self, other: &EnvironmentConfig) -> bool"}} />
+
eq(&self, other: &EnvironmentConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for EnvironmentConfig` @@ -119,8 +119,8 @@ Additive normalized environment fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for EnvironmentConfig` -
StructuralPartialEq for EnvironmentConfig"}} />
+
StructuralPartialEq for EnvironmentConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentplan.mdx index 4f528717a..18c2d5f4f 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-environmentplan.mdx @@ -2,14 +2,14 @@ title: "Struct Environment Plan" sidebar-title: "EnvironmentPlan" description: "Resolved environment plan." -position: 12 +position: 13 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub control_location: ControlLocation,\n    pub ownership: EnvironmentOwnership,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub connection: Map<String, Value>,\n    pub metadata: Map<String, Value>,\n    pub settings: Map<String, Value>,\n}"}} />
+
String,\n    pub control_location: ControlLocation,\n    pub ownership: EnvironmentOwnership,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub connection: Map<String, Value>,\n    pub metadata: Map<String, Value>,\n    pub settings: Map<String, Value>,\n}"}} />
Resolved environment plan. @@ -51,23 +51,23 @@ Provider-specific settings. ### `impl Clone for EnvironmentPlan` -
Clone for EnvironmentPlan"}} />
+
Clone for EnvironmentPlan"}} />
#### `clone` -
clone(&self) -> EnvironmentPlan"}} />
+
clone(&self) -> EnvironmentPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for EnvironmentPlan` -
Debug for EnvironmentPlan"}} />
+
Debug for EnvironmentPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for EnvironmentPlan` @@ -75,7 +75,7 @@ Provider-specific settings. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for EnvironmentPlan` @@ -83,11 +83,11 @@ Provider-specific settings. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -95,19 +95,19 @@ Provider-specific settings. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for EnvironmentPlan` -
PartialEq for EnvironmentPlan"}} />
+
PartialEq for EnvironmentPlan"}} />
#### `eq` -
eq(&self, other: &EnvironmentPlan) -> bool"}} />
+
eq(&self, other: &EnvironmentPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for EnvironmentPlan` @@ -115,8 +115,8 @@ Provider-specific settings. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for EnvironmentPlan` -
StructuralPartialEq for EnvironmentPlan"}} />
+
StructuralPartialEq for EnvironmentPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-fabricconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-fabricconfig.mdx index 4f9cf2d0c..9a95ee871 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-fabricconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-fabricconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Fabric Config" sidebar-title: "FabricConfig" description: "Versioned Fabric agent config." -position: 13 +position: 14 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub metadata: MetadataConfig,\n    pub harness: HarnessConfig,\n    pub models: BTreeMap<String, ModelConfig>,\n    pub runtime: RuntimeConfig,\n    pub environment: Option<EnvironmentConfig>,\n    pub tools: Option<Value>,\n    pub skills: Option<SkillConfig>,\n    pub mcp: Option<McpConfig>,\n    pub telemetry: Option<TelemetryConfig>,\n    pub profiles: ProfileRegistryConfig,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub metadata: MetadataConfig,\n    pub harness: HarnessConfig,\n    pub models: BTreeMap<String, ModelConfig>,\n    pub runtime: RuntimeConfig,\n    pub environment: Option<EnvironmentConfig>,\n    pub tools: Option<Value>,\n    pub skills: Option<SkillConfig>,\n    pub mcp: Option<McpConfig>,\n    pub telemetry: Option<TelemetryConfig>,\n    pub profiles: ProfileRegistryConfig,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Versioned Fabric agent config. @@ -33,7 +33,7 @@ Model aliases. ### `runtime: RuntimeConfig` -Runtime mode and input/output contract. +Runtime input/output contract. ### `environment: Option` @@ -67,23 +67,23 @@ Additive fields not yet recognized by this core version. ### `impl Clone for FabricConfig` -
Clone for FabricConfig"}} />
+
Clone for FabricConfig"}} />
#### `clone` -
clone(&self) -> FabricConfig"}} />
+
clone(&self) -> FabricConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for FabricConfig` -
Debug for FabricConfig"}} />
+
Debug for FabricConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for FabricConfig` @@ -91,7 +91,7 @@ Additive fields not yet recognized by this core version. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for FabricConfig` @@ -99,11 +99,11 @@ Additive fields not yet recognized by this core version. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -111,19 +111,19 @@ Additive fields not yet recognized by this core version. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for FabricConfig` -
PartialEq for FabricConfig"}} />
+
PartialEq for FabricConfig"}} />
#### `eq` -
eq(&self, other: &FabricConfig) -> bool"}} />
+
eq(&self, other: &FabricConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for FabricConfig` @@ -131,8 +131,8 @@ Additive fields not yet recognized by this core version. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for FabricConfig` -
StructuralPartialEq for FabricConfig"}} />
+
StructuralPartialEq for FabricConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-harnessconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-harnessconfig.mdx index 19d049b21..a426f513e 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-harnessconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-harnessconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Harness Config" sidebar-title: "HarnessConfig" description: "Harness selection." -position: 15 +position: 16 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub resolution: Option<ResolutionStrategy>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub resolution: Option<ResolutionStrategy>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Harness selection. @@ -35,23 +35,23 @@ Additive normalized harness fields. ### `impl Clone for HarnessConfig` -
Clone for HarnessConfig"}} />
+
Clone for HarnessConfig"}} />
#### `clone` -
clone(&self) -> HarnessConfig"}} />
+
clone(&self) -> HarnessConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for HarnessConfig` -
Debug for HarnessConfig"}} />
+
Debug for HarnessConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for HarnessConfig` @@ -59,7 +59,7 @@ Additive normalized harness fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for HarnessConfig` @@ -67,11 +67,11 @@ Additive normalized harness fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive normalized harness fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for HarnessConfig` -
PartialEq for HarnessConfig"}} />
+
PartialEq for HarnessConfig"}} />
#### `eq` -
eq(&self, other: &HarnessConfig) -> bool"}} />
+
eq(&self, other: &HarnessConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for HarnessConfig` @@ -99,8 +99,8 @@ Additive normalized harness fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for HarnessConfig` -
StructuralPartialEq for HarnessConfig"}} />
+
StructuralPartialEq for HarnessConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpconfig.mdx index 7e944bc7c..5dee0a83f 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpconfig.mdx @@ -2,14 +2,14 @@ title: "Struct McpConfig" sidebar-title: "McpConfig" description: "MCP capability configuration." -position: 16 +position: 17 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
BTreeMap<String, McpServerConfig>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
BTreeMap<String, McpServerConfig>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
MCP capability configuration. @@ -27,31 +27,31 @@ Additive MCP fields. ### `impl Clone for McpConfig` -
Clone for McpConfig"}} />
+
Clone for McpConfig"}} />
#### `clone` -
clone(&self) -> McpConfig"}} />
+
clone(&self) -> McpConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for McpConfig` -
Debug for McpConfig"}} />
+
Debug for McpConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for McpConfig` -
Default for McpConfig"}} />
+
Default for McpConfig"}} />
#### `default` -
default() -> McpConfig"}} />
+
default() -> McpConfig"}} />
### `impl<'de> Deserialize<'de> for McpConfig` @@ -59,7 +59,7 @@ Additive MCP fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for McpConfig` @@ -67,11 +67,11 @@ Additive MCP fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive MCP fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for McpConfig` -
PartialEq for McpConfig"}} />
+
PartialEq for McpConfig"}} />
#### `eq` -
eq(&self, other: &McpConfig) -> bool"}} />
+
eq(&self, other: &McpConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for McpConfig` @@ -99,8 +99,8 @@ Additive MCP fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for McpConfig` -
StructuralPartialEq for McpConfig"}} />
+
StructuralPartialEq for McpConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverconfig.mdx index 61d89b676..d32d5c809 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverconfig.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub url: String,\n    pub exposure: McpExposure,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub url: String,\n    pub exposure: McpExposure,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
MCP server configuration. @@ -35,23 +35,23 @@ Additive MCP server fields. ### `impl Clone for McpServerConfig` -
Clone for McpServerConfig"}} />
+
Clone for McpServerConfig"}} />
#### `clone` -
clone(&self) -> McpServerConfig"}} />
+
clone(&self) -> McpServerConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for McpServerConfig` -
Debug for McpServerConfig"}} />
+
Debug for McpServerConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for McpServerConfig` @@ -59,7 +59,7 @@ Additive MCP server fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for McpServerConfig` @@ -67,11 +67,11 @@ Additive MCP server fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive MCP server fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for McpServerConfig` -
PartialEq for McpServerConfig"}} />
+
PartialEq for McpServerConfig"}} />
#### `eq` -
eq(&self, other: &McpServerConfig) -> bool"}} />
+
eq(&self, other: &McpServerConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for McpServerConfig` @@ -99,8 +99,8 @@ Additive MCP server fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for McpServerConfig` -
StructuralPartialEq for McpServerConfig"}} />
+
StructuralPartialEq for McpServerConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverplan.mdx index e628da6dc..b45663fba 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-mcpserverplan.mdx @@ -2,14 +2,14 @@ title: "Struct McpServer Plan" sidebar-title: "McpServerPlan" description: "Resolved MCP server exposure." -position: 18 +position: 19 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub url: String,\n    pub exposure: McpExposure,\n}"}} />
+
String,\n    pub url: String,\n    pub exposure: McpExposure,\n}"}} />
Resolved MCP server exposure. @@ -31,23 +31,23 @@ Exposure strategy. ### `impl Clone for McpServerPlan` -
Clone for McpServerPlan"}} />
+
Clone for McpServerPlan"}} />
#### `clone` -
clone(&self) -> McpServerPlan"}} />
+
clone(&self) -> McpServerPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for McpServerPlan` -
Debug for McpServerPlan"}} />
+
Debug for McpServerPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for McpServerPlan` @@ -55,7 +55,7 @@ Exposure strategy. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for McpServerPlan` @@ -63,11 +63,11 @@ Exposure strategy. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -75,19 +75,19 @@ Exposure strategy. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for McpServerPlan` -
PartialEq for McpServerPlan"}} />
+
PartialEq for McpServerPlan"}} />
#### `eq` -
eq(&self, other: &McpServerPlan) -> bool"}} />
+
eq(&self, other: &McpServerPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for McpServerPlan` @@ -95,8 +95,8 @@ Exposure strategy. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for McpServerPlan` -
StructuralPartialEq for McpServerPlan"}} />
+
StructuralPartialEq for McpServerPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-metadataconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-metadataconfig.mdx index c5db9b677..c3888d5d8 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-metadataconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-metadataconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Metadata Config" sidebar-title: "MetadataConfig" description: "Human-readable metadata." -position: 19 +position: 20 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub description: Option<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub description: Option<String>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Human-readable metadata. @@ -31,23 +31,23 @@ Additive metadata fields. ### `impl Clone for MetadataConfig` -
Clone for MetadataConfig"}} />
+
Clone for MetadataConfig"}} />
#### `clone` -
clone(&self) -> MetadataConfig"}} />
+
clone(&self) -> MetadataConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for MetadataConfig` -
Debug for MetadataConfig"}} />
+
Debug for MetadataConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for MetadataConfig` @@ -55,7 +55,7 @@ Additive metadata fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for MetadataConfig` @@ -63,11 +63,11 @@ Additive metadata fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -75,19 +75,19 @@ Additive metadata fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for MetadataConfig` -
PartialEq for MetadataConfig"}} />
+
PartialEq for MetadataConfig"}} />
#### `eq` -
eq(&self, other: &MetadataConfig) -> bool"}} />
+
eq(&self, other: &MetadataConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for MetadataConfig` @@ -95,8 +95,8 @@ Additive metadata fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for MetadataConfig` -
StructuralPartialEq for MetadataConfig"}} />
+
StructuralPartialEq for MetadataConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-modelconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-modelconfig.mdx index 42d21a284..8784c9840 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-modelconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-modelconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Model Config" sidebar-title: "ModelConfig" description: "Model configuration." -position: 20 +position: 21 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub model: String,\n    pub temperature: Option<f64>,\n    pub api_key_env: Option<String>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub model: String,\n    pub temperature: Option<f64>,\n    pub api_key_env: Option<String>,\n    pub settings: Map<String, Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Model configuration. @@ -43,23 +43,23 @@ Additive normalized model fields. ### `impl Clone for ModelConfig` -
Clone for ModelConfig"}} />
+
Clone for ModelConfig"}} />
#### `clone` -
clone(&self) -> ModelConfig"}} />
+
clone(&self) -> ModelConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ModelConfig` -
Debug for ModelConfig"}} />
+
Debug for ModelConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ModelConfig` @@ -67,7 +67,7 @@ Additive normalized model fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ModelConfig` @@ -75,11 +75,11 @@ Additive normalized model fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Additive normalized model fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ModelConfig` -
PartialEq for ModelConfig"}} />
+
PartialEq for ModelConfig"}} />
#### `eq` -
eq(&self, other: &ModelConfig) -> bool"}} />
+
eq(&self, other: &ModelConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ModelConfig` @@ -107,8 +107,8 @@ Additive normalized model fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ModelConfig` -
StructuralPartialEq for ModelConfig"}} />
+
StructuralPartialEq for ModelConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileconfig.mdx index eb37bcfad..44dfd8a2a 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileconfig.mdx @@ -2,14 +2,14 @@ title: "Struct Profile Config" sidebar-title: "ProfileConfig" description: "Profile config applied on top of a Fabric config." -position: 21 +position: 22 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Option<String>,\n    pub name: Option<String>,\n    pub description: Option<String>,\n    pub overlay: BTreeMap<String, Value>,\n}"}} />
+
Option<String>,\n    pub name: Option<String>,\n    pub description: Option<String>,\n    pub overlay: BTreeMap<String, Value>,\n}"}} />
Profile config applied on top of a Fabric config. @@ -35,31 +35,31 @@ Raw config fields recursively merged over the base config. ### `impl Clone for ProfileConfig` -
Clone for ProfileConfig"}} />
+
Clone for ProfileConfig"}} />
#### `clone` -
clone(&self) -> ProfileConfig"}} />
+
clone(&self) -> ProfileConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ProfileConfig` -
Debug for ProfileConfig"}} />
+
Debug for ProfileConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for ProfileConfig` -
Default for ProfileConfig"}} />
+
Default for ProfileConfig"}} />
#### `default` -
default() -> ProfileConfig"}} />
+
default() -> ProfileConfig"}} />
### `impl<'de> Deserialize<'de> for ProfileConfig` @@ -67,7 +67,7 @@ Raw config fields recursively merged over the base config. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ProfileConfig` @@ -75,7 +75,7 @@ Raw config fields recursively merged over the base config. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,23 +83,23 @@ Raw config fields recursively merged over the base config. #### `inline_schema` -
bool"}} />
+
bool"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
### `impl PartialEq for ProfileConfig` -
PartialEq for ProfileConfig"}} />
+
PartialEq for ProfileConfig"}} />
#### `eq` -
eq(&self, other: &ProfileConfig) -> bool"}} />
+
eq(&self, other: &ProfileConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ProfileConfig` @@ -107,8 +107,8 @@ Raw config fields recursively merged over the base config. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ProfileConfig` -
StructuralPartialEq for ProfileConfig"}} />
+
StructuralPartialEq for ProfileConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileregistryconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileregistryconfig.mdx index 2625b7553..997207487 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileregistryconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-profileregistryconfig.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Profile discovery config for curated package profiles. @@ -27,31 +27,31 @@ Additive profile-discovery fields. ### `impl Clone for ProfileRegistryConfig` -
Clone for ProfileRegistryConfig"}} />
+
Clone for ProfileRegistryConfig"}} />
#### `clone` -
clone(&self) -> ProfileRegistryConfig"}} />
+
clone(&self) -> ProfileRegistryConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ProfileRegistryConfig` -
Debug for ProfileRegistryConfig"}} />
+
Debug for ProfileRegistryConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for ProfileRegistryConfig` -
Default for ProfileRegistryConfig"}} />
+
Default for ProfileRegistryConfig"}} />
#### `default` -
default() -> ProfileRegistryConfig"}} />
+
default() -> ProfileRegistryConfig"}} />
### `impl<'de> Deserialize<'de> for ProfileRegistryConfig` @@ -59,7 +59,7 @@ Additive profile-discovery fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ProfileRegistryConfig` @@ -67,11 +67,11 @@ Additive profile-discovery fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive profile-discovery fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ProfileRegistryConfig` -
PartialEq for ProfileRegistryConfig"}} />
+
PartialEq for ProfileRegistryConfig"}} />
#### `eq` -
eq(&self, other: &ProfileRegistryConfig) -> bool"}} />
+
eq(&self, other: &ProfileRegistryConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ProfileRegistryConfig` @@ -99,8 +99,8 @@ Additive profile-discovery fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ProfileRegistryConfig` -
StructuralPartialEq for ProfileRegistryConfig"}} />
+
StructuralPartialEq for ProfileRegistryConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvecontext.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvecontext.mdx index 05ad096a2..074e68c6f 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvecontext.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvecontext.mdx @@ -2,14 +2,14 @@ title: "Struct Resolve Context" sidebar-title: "ResolveContext" description: "Source context used when resolving an in-memory Fabric config." -position: 23 +position: 24 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n}"}} />
+
PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n}"}} />
Source context used when resolving an in-memory Fabric config. @@ -35,13 +35,13 @@ Root used to resolve config-local paths. #### `from_agent_root` -
Into<PathBuf>) -> Self"}} />
+
Into<PathBuf>) -> Self"}} />
Build a context for an agent package root. #### `from_config_path` -
Into<PathBuf>,\n    root: impl Into<PathBuf>,\n) -> Self"}} />
+
Into<PathBuf>,\n    root: impl Into<PathBuf>,\n) -> Self"}} />
Build a context for a config file and its config root. @@ -49,36 +49,36 @@ Build a context for a config file and its config root. ### `impl Clone for ResolveContext` -
Clone for ResolveContext"}} />
+
Clone for ResolveContext"}} />
#### `clone` -
clone(&self) -> ResolveContext"}} />
+
clone(&self) -> ResolveContext"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ResolveContext` -
Debug for ResolveContext"}} />
+
Debug for ResolveContext"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl PartialEq for ResolveContext` -
PartialEq for ResolveContext"}} />
+
PartialEq for ResolveContext"}} />
#### `eq` -
eq(&self, other: &ResolveContext) -> bool"}} />
+
eq(&self, other: &ResolveContext) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl StructuralPartialEq for ResolveContext` -
StructuralPartialEq for ResolveContext"}} />
+
StructuralPartialEq for ResolveContext"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvedadapterdescriptor.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvedadapterdescriptor.mdx index cb909d31e..8bc5b3e9b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvedadapterdescriptor.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-resolvedadapterdescriptor.mdx @@ -2,14 +2,14 @@ title: "Struct Resolved Adapter Descriptor" sidebar-title: "ResolvedAdapterDescriptor" description: "Adapter descriptor selected for a run plan." -position: 24 +position: 25 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AdapterDescriptorSource,\n    pub path: PathBuf,\n    pub root: PathBuf,\n    pub descriptor: AdapterDescriptor,\n}"}} />
+
AdapterDescriptorSource,\n    pub path: PathBuf,\n    pub root: PathBuf,\n    pub descriptor: AdapterDescriptor,\n}"}} />
Adapter descriptor selected for a run plan. @@ -35,23 +35,23 @@ Adapter-owned compatibility and capability metadata. ### `impl Clone for ResolvedAdapterDescriptor` -
Clone for ResolvedAdapterDescriptor"}} />
+
Clone for ResolvedAdapterDescriptor"}} />
#### `clone` -
clone(&self) -> ResolvedAdapterDescriptor"}} />
+
clone(&self) -> ResolvedAdapterDescriptor"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ResolvedAdapterDescriptor` -
Debug for ResolvedAdapterDescriptor"}} />
+
Debug for ResolvedAdapterDescriptor"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ResolvedAdapterDescriptor` @@ -59,7 +59,7 @@ Adapter-owned compatibility and capability metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ResolvedAdapterDescriptor` @@ -67,11 +67,11 @@ Adapter-owned compatibility and capability metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Adapter-owned compatibility and capability metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ResolvedAdapterDescriptor` -
PartialEq for ResolvedAdapterDescriptor"}} />
+
PartialEq for ResolvedAdapterDescriptor"}} />
#### `eq` -
eq(&self, other: &ResolvedAdapterDescriptor) -> bool"}} />
+
eq(&self, other: &ResolvedAdapterDescriptor) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ResolvedAdapterDescriptor` @@ -99,8 +99,8 @@ Adapter-owned compatibility and capability metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ResolvedAdapterDescriptor` -
StructuralPartialEq for ResolvedAdapterDescriptor"}} />
+
StructuralPartialEq for ResolvedAdapterDescriptor"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runplan.mdx index 3ee6586d9..a576fd6a0 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runplan.mdx @@ -2,14 +2,14 @@ title: "Struct RunPlan" sidebar-title: "RunPlan" description: "Resolved Fabric run plan." -position: 25 +position: 26 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
EffectiveConfig,\n    pub agent_name: String,\n    pub profiles: Vec<String>,\n    pub adapter_descriptor: Option<ResolvedAdapterDescriptor>,\n    pub resolution: Option<ResolutionStrategy>,\n    pub environment_plan: Option<EnvironmentPlan>,\n    pub capability_plan: CapabilityPlan,\n    pub capabilities: RuntimeCapabilities,\n    pub telemetry_plan: Option<TelemetryPlan>,\n    pub agent_root: PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n    pub config: FabricConfig,\n}"}} />
+
EffectiveConfig,\n    pub agent_name: String,\n    pub profiles: Vec<String>,\n    pub adapter_descriptor: Option<ResolvedAdapterDescriptor>,\n    pub resolution: Option<ResolutionStrategy>,\n    pub environment_plan: Option<EnvironmentPlan>,\n    pub capability_plan: CapabilityPlan,\n    pub capabilities: RuntimeCapabilities,\n    pub telemetry_plan: Option<TelemetryPlan>,\n    pub agent_root: PathBuf,\n    pub config_path: PathBuf,\n    pub config_root: PathBuf,\n    pub config: FabricConfig,\n}"}} />
Resolved Fabric run plan. @@ -71,23 +71,23 @@ Resolved Fabric profile config. ### `impl Clone for RunPlan` -
Clone for RunPlan"}} />
+
Clone for RunPlan"}} />
#### `clone` -
clone(&self) -> RunPlan"}} />
+
clone(&self) -> RunPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RunPlan` -
Debug for RunPlan"}} />
+
Debug for RunPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RunPlan` @@ -95,7 +95,7 @@ Resolved Fabric profile config. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RunPlan` @@ -103,11 +103,11 @@ Resolved Fabric profile config. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -115,19 +115,19 @@ Resolved Fabric profile config. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RunPlan` -
PartialEq for RunPlan"}} />
+
PartialEq for RunPlan"}} />
#### `eq` -
eq(&self, other: &RunPlan) -> bool"}} />
+
eq(&self, other: &RunPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RunPlan` @@ -135,8 +135,8 @@ Resolved Fabric profile config. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RunPlan` -
StructuralPartialEq for RunPlan"}} />
+
StructuralPartialEq for RunPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimecapabilities.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimecapabilities.mdx index 4d158be66..0470e459c 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimecapabilities.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimecapabilities.mdx @@ -2,23 +2,19 @@ title: "Struct Runtime Capabilities" sidebar-title: "RuntimeCapabilities" description: "Lifecycle behavior implemented by a resolved runtime path." -position: 26 +position: 27 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub service: bool,\n    pub streaming: bool,\n    pub updates: bool,\n    pub cancellation: bool,\n    pub concurrent_invocations: bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
bool,\n    pub streaming: bool,\n    pub updates: bool,\n    pub cancellation: bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Lifecycle behavior implemented by a resolved runtime path. ## Fields -### `session: bool` - -Whether the selected runtime supports session lifecycle operations. - ### `service: bool` Whether the selected runtime supports service lifecycle operations. @@ -35,10 +31,6 @@ Whether a running runtime can accept config updates. Whether an in-flight invocation can be cancelled. -### `concurrent_invocations: bool` - -Whether the runtime accepts concurrent invocations. - ### `metadata: BTreeMap` Additional adapter-specific capability metadata. @@ -47,31 +39,31 @@ Additional adapter-specific capability metadata. ### `impl Clone for RuntimeCapabilities` -
Clone for RuntimeCapabilities"}} />
+
Clone for RuntimeCapabilities"}} />
#### `clone` -
clone(&self) -> RuntimeCapabilities"}} />
+
clone(&self) -> RuntimeCapabilities"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RuntimeCapabilities` -
Debug for RuntimeCapabilities"}} />
+
Debug for RuntimeCapabilities"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for RuntimeCapabilities` -
Default for RuntimeCapabilities"}} />
+
Default for RuntimeCapabilities"}} />
#### `default` -
default() -> RuntimeCapabilities"}} />
+
default() -> RuntimeCapabilities"}} />
### `impl<'de> Deserialize<'de> for RuntimeCapabilities` @@ -79,7 +71,7 @@ Additional adapter-specific capability metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RuntimeCapabilities` @@ -87,11 +79,11 @@ Additional adapter-specific capability metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -99,19 +91,19 @@ Additional adapter-specific capability metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RuntimeCapabilities` -
PartialEq for RuntimeCapabilities"}} />
+
PartialEq for RuntimeCapabilities"}} />
#### `eq` -
eq(&self, other: &RuntimeCapabilities) -> bool"}} />
+
eq(&self, other: &RuntimeCapabilities) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RuntimeCapabilities` @@ -119,8 +111,8 @@ Additional adapter-specific capability metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RuntimeCapabilities` -
StructuralPartialEq for RuntimeCapabilities"}} />
+
StructuralPartialEq for RuntimeCapabilities"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig.mdx index e22cb59a9..2f08aa522 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-runtimeconfig.mdx @@ -1,28 +1,20 @@ --- title: "Struct Runtime Config" sidebar-title: "RuntimeConfig" -description: "Runtime mode and input/output contract." -position: 27 +description: "Runtime input/output contract." +position: 28 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
RuntimeMode,\n    pub transport: Transport,\n    pub input_schema: String,\n    pub output_schema: String,\n    pub artifacts: Option<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub output_schema: String,\n    pub artifacts: Option<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
-Runtime mode and input/output contract. +Runtime input/output contract. ## Fields -### `mode: RuntimeMode` - -Runtime mode. - -### `transport: Transport` - -Transport used to operate the harness. - ### `input_schema: String` Input schema label. @@ -43,23 +35,23 @@ Additive normalized runtime fields. ### `impl Clone for RuntimeConfig` -
Clone for RuntimeConfig"}} />
+
Clone for RuntimeConfig"}} />
#### `clone` -
clone(&self) -> RuntimeConfig"}} />
+
clone(&self) -> RuntimeConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RuntimeConfig` -
Debug for RuntimeConfig"}} />
+
Debug for RuntimeConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RuntimeConfig` @@ -67,7 +59,7 @@ Additive normalized runtime fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RuntimeConfig` @@ -75,11 +67,11 @@ Additive normalized runtime fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +79,19 @@ Additive normalized runtime fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RuntimeConfig` -
PartialEq for RuntimeConfig"}} />
+
PartialEq for RuntimeConfig"}} />
#### `eq` -
eq(&self, other: &RuntimeConfig) -> bool"}} />
+
eq(&self, other: &RuntimeConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RuntimeConfig` @@ -107,8 +99,8 @@ Additive normalized runtime fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RuntimeConfig` -
StructuralPartialEq for RuntimeConfig"}} />
+
StructuralPartialEq for RuntimeConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-skillconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-skillconfig.mdx index ad6e19667..f52d76b09 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-skillconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-skillconfig.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
Vec<PathBuf>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Skill capability configuration. @@ -27,31 +27,31 @@ Additive skill fields. ### `impl Clone for SkillConfig` -
Clone for SkillConfig"}} />
+
Clone for SkillConfig"}} />
#### `clone` -
clone(&self) -> SkillConfig"}} />
+
clone(&self) -> SkillConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for SkillConfig` -
Debug for SkillConfig"}} />
+
Debug for SkillConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for SkillConfig` -
Default for SkillConfig"}} />
+
Default for SkillConfig"}} />
#### `default` -
default() -> SkillConfig"}} />
+
default() -> SkillConfig"}} />
### `impl<'de> Deserialize<'de> for SkillConfig` @@ -59,7 +59,7 @@ Additive skill fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for SkillConfig` @@ -67,11 +67,11 @@ Additive skill fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additive skill fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for SkillConfig` -
PartialEq for SkillConfig"}} />
+
PartialEq for SkillConfig"}} />
#### `eq` -
eq(&self, other: &SkillConfig) -> bool"}} />
+
eq(&self, other: &SkillConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for SkillConfig` @@ -99,8 +99,8 @@ Additive skill fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for SkillConfig` -
StructuralPartialEq for SkillConfig"}} />
+
StructuralPartialEq for SkillConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryconfig.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryconfig.mdx index 0c5054ccc..0857073e1 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryconfig.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryconfig.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub mode: Option<String>,\n    pub project: Option<String>,\n    pub output_dir: Option<PathBuf>,\n    pub config: Option<Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+
bool,\n    pub provider: TelemetryProvider,\n    pub project: Option<String>,\n    pub output_dir: Option<PathBuf>,\n    pub config: Option<Value>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Telemetry configuration. @@ -17,11 +17,11 @@ Telemetry configuration. ### `enabled: bool` -Whether telemetry is enabled for this run. Relay is the Phase 1 telemetry path. +Whether telemetry is enabled for this run. -### `mode: Option` +### `provider: TelemetryProvider` -Telemetry mode, for example `sdk`, `gateway`, or `external`. +Telemetry provider responsible for runtime integration. ### `project: Option` @@ -43,23 +43,23 @@ Additive telemetry fields. ### `impl Clone for TelemetryConfig` -
Clone for TelemetryConfig"}} />
+
Clone for TelemetryConfig"}} />
#### `clone` -
clone(&self) -> TelemetryConfig"}} />
+
clone(&self) -> TelemetryConfig"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for TelemetryConfig` -
Debug for TelemetryConfig"}} />
+
Debug for TelemetryConfig"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for TelemetryConfig` @@ -67,7 +67,7 @@ Additive telemetry fields. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for TelemetryConfig` @@ -75,11 +75,11 @@ Additive telemetry fields. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Additive telemetry fields. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for TelemetryConfig` -
PartialEq for TelemetryConfig"}} />
+
PartialEq for TelemetryConfig"}} />
#### `eq` -
eq(&self, other: &TelemetryConfig) -> bool"}} />
+
eq(&self, other: &TelemetryConfig) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for TelemetryConfig` @@ -107,8 +107,8 @@ Additive telemetry fields. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for TelemetryConfig` -
StructuralPartialEq for TelemetryConfig"}} />
+
StructuralPartialEq for TelemetryConfig"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryplan.mdx b/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryplan.mdx index 6854fa20d..ed5349d70 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryplan.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/config/struct-telemetryplan.mdx @@ -9,19 +9,19 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub relay_mode: Option<String>,\n    pub relay_project: Option<String>,\n    pub relay_output_dir: Option<PathBuf>,\n    pub relay_config: Option<Value>,\n    pub adapter_outputs: Vec<String>,\n}"}} />
+
TelemetryProvider,\n    pub relay_enabled: bool,\n    pub relay_project: Option<String>,\n    pub relay_output_dir: Option<PathBuf>,\n    pub relay_config: Option<Value>,\n    pub adapter_outputs: Vec<String>,\n}"}} />
Resolved telemetry plan. ## Fields -### `relay_enabled: bool` +### `provider: TelemetryProvider` -Whether Relay is enabled. +Telemetry provider selected for this run. -### `relay_mode: Option` +### `relay_enabled: bool` -Relay mode, when configured. +Whether Relay is enabled. ### `relay_project: Option` @@ -43,23 +43,23 @@ Telemetry outputs declared by the selected adapter descriptor. ### `impl Clone for TelemetryPlan` -
Clone for TelemetryPlan"}} />
+
Clone for TelemetryPlan"}} />
#### `clone` -
clone(&self) -> TelemetryPlan"}} />
+
clone(&self) -> TelemetryPlan"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for TelemetryPlan` -
Debug for TelemetryPlan"}} />
+
Debug for TelemetryPlan"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for TelemetryPlan` @@ -67,7 +67,7 @@ Telemetry outputs declared by the selected adapter descriptor. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for TelemetryPlan` @@ -75,11 +75,11 @@ Telemetry outputs declared by the selected adapter descriptor. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Telemetry outputs declared by the selected adapter descriptor. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for TelemetryPlan` -
PartialEq for TelemetryPlan"}} />
+
PartialEq for TelemetryPlan"}} />
#### `eq` -
eq(&self, other: &TelemetryPlan) -> bool"}} />
+
eq(&self, other: &TelemetryPlan) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for TelemetryPlan` @@ -107,8 +107,8 @@ Telemetry outputs declared by the selected adapter descriptor. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for TelemetryPlan` -
StructuralPartialEq for TelemetryPlan"}} />
+
StructuralPartialEq for TelemetryPlan"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/doctor/enum-doctorstatus.mdx b/docs/reference/api/rust-library-reference/fabric-core/doctor/enum-doctorstatus.mdx index 0099dfbc3..bd2ac54c7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/doctor/enum-doctorstatus.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/doctor/enum-doctorstatus.mdx @@ -43,23 +43,23 @@ Check failed. ### `impl Clone for DoctorStatus` -
Clone for DoctorStatus"}} />
+
Clone for DoctorStatus"}} />
#### `clone` -
clone(&self) -> DoctorStatus"}} />
+
clone(&self) -> DoctorStatus"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for DoctorStatus` -
Debug for DoctorStatus"}} />
+
Debug for DoctorStatus"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for DoctorStatus` @@ -67,7 +67,7 @@ Check failed. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for DoctorStatus` @@ -75,11 +75,11 @@ Check failed. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ Check failed. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for DoctorStatus` -
PartialEq for DoctorStatus"}} />
+
PartialEq for DoctorStatus"}} />
#### `eq` -
eq(&self, other: &DoctorStatus) -> bool"}} />
+
eq(&self, other: &DoctorStatus) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for DoctorStatus` @@ -107,16 +107,16 @@ Check failed. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for DoctorStatus` -
Copy for DoctorStatus"}} />
+
Copy for DoctorStatus"}} />
### `impl Eq for DoctorStatus` -
Eq for DoctorStatus"}} />
+
Eq for DoctorStatus"}} />
### `impl StructuralPartialEq for DoctorStatus` -
StructuralPartialEq for DoctorStatus"}} />
+
StructuralPartialEq for DoctorStatus"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorcheck.mdx b/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorcheck.mdx index 9eb290388..5e97be328 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorcheck.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorcheck.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub status: DoctorStatus,\n    pub message: String,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub status: DoctorStatus,\n    pub message: String,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Diagnostic check result. @@ -35,23 +35,23 @@ Optional structured metadata. ### `impl Clone for DoctorCheck` -
Clone for DoctorCheck"}} />
+
Clone for DoctorCheck"}} />
#### `clone` -
clone(&self) -> DoctorCheck"}} />
+
clone(&self) -> DoctorCheck"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for DoctorCheck` -
Debug for DoctorCheck"}} />
+
Debug for DoctorCheck"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for DoctorCheck` @@ -59,7 +59,7 @@ Optional structured metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for DoctorCheck` @@ -67,11 +67,11 @@ Optional structured metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Optional structured metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for DoctorCheck` -
PartialEq for DoctorCheck"}} />
+
PartialEq for DoctorCheck"}} />
#### `eq` -
eq(&self, other: &DoctorCheck) -> bool"}} />
+
eq(&self, other: &DoctorCheck) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for DoctorCheck` @@ -99,8 +99,8 @@ Optional structured metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for DoctorCheck` -
StructuralPartialEq for DoctorCheck"}} />
+
StructuralPartialEq for DoctorCheck"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorreport.mdx b/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorreport.mdx index 7a2124530..372c5faa0 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorreport.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/doctor/struct-doctorreport.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub profiles: Vec<String>,\n    pub status: DoctorStatus,\n    pub checks: Vec<DoctorCheck>,\n}"}} />
+
String,\n    pub profiles: Vec<String>,\n    pub status: DoctorStatus,\n    pub checks: Vec<DoctorCheck>,\n}"}} />
Diagnostic report for a resolved run plan. @@ -35,23 +35,23 @@ Checks. ### `impl Clone for DoctorReport` -
Clone for DoctorReport"}} />
+
Clone for DoctorReport"}} />
#### `clone` -
clone(&self) -> DoctorReport"}} />
+
clone(&self) -> DoctorReport"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for DoctorReport` -
Debug for DoctorReport"}} />
+
Debug for DoctorReport"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for DoctorReport` @@ -59,7 +59,7 @@ Checks. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for DoctorReport` @@ -67,11 +67,11 @@ Checks. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Checks. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for DoctorReport` -
PartialEq for DoctorReport"}} />
+
PartialEq for DoctorReport"}} />
#### `eq` -
eq(&self, other: &DoctorReport) -> bool"}} />
+
eq(&self, other: &DoctorReport) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for DoctorReport` @@ -99,8 +99,8 @@ Checks. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for DoctorReport` -
StructuralPartialEq for DoctorReport"}} />
+
StructuralPartialEq for DoctorReport"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/error/enum-fabricerror.mdx b/docs/reference/api/rust-library-reference/fabric-core/error/enum-fabricerror.mdx index 6df01dc66..a181c6299 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/error/enum-fabricerror.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/error/enum-fabricerror.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
PathBuf),\n    MissingEntrypoint(PathBuf),\n    UnsupportedExtension(PathBuf),\n    UnknownProfile {\n        profile: String,\n        agent: String,\n        available: Vec<String>,\n    },\n    ProfileError {\n        path: PathBuf,\n        message: String,\n    },\n    UnknownAdapter {\n        adapter_id: String,\n        available: Vec<String>,\n    },\n    AdapterDescriptorMismatch {\n        path: PathBuf,\n        field: &'static str,\n        expected: String,\n        actual: String,\n    },\n    AdapterDescriptorUnsupported {\n        adapter_id: String,\n        field: &'static str,\n        value: String,\n    },\n    InvalidAdapterDescriptor {\n        path: PathBuf,\n        message: String,\n    },\n    UnknownSchema {\n        schema: String,\n        available: Vec<String>,\n    },\n    ProfileTargetNotConfig {\n        profile: String,\n        path: PathBuf,\n    },\n    ProfileSelectionNotSupported(PathBuf),\n    UnsupportedRuntimeAdapter {\n        harness: String,\n        adapter_kind: AdapterKind,\n    },\n    RuntimeHandleMismatch {\n        field: &'static str,\n        expected: String,\n        actual: String,\n        runtime_id: String,\n    },\n    UnsupportedEnvironmentProvider {\n        provider: String,\n        adapter_kind: AdapterKind,\n    },\n    InvalidProcessSettings {\n        path: PathBuf,\n        source: Error,\n    },\n    InvalidPythonSettings {\n        path: PathBuf,\n        source: Error,\n    },\n    ProcessRunner {\n        command: String,\n        source: Error,\n    },\n    SerializeJson(Error),\n    Read {\n        path: PathBuf,\n        source: Error,\n    },\n    Write {\n        path: PathBuf,\n        source: Error,\n    },\n    ParseYaml {\n        path: PathBuf,\n        source: Error,\n    },\n    ParseJson {\n        path: PathBuf,\n        source: Error,\n    },\n}"}} />
+
PathBuf),\n    MissingEntrypoint(PathBuf),\n    UnsupportedExtension(PathBuf),\n    UnknownProfile {\n        profile: String,\n        agent: String,\n        available: Vec<String>,\n    },\n    ProfileError {\n        path: PathBuf,\n        message: String,\n    },\n    UnknownAdapter {\n        adapter_id: String,\n        available: Vec<String>,\n    },\n    AdapterDescriptorMismatch {\n        path: PathBuf,\n        field: &'static str,\n        expected: String,\n        actual: String,\n    },\n    AdapterDescriptorUnsupported {\n        adapter_id: String,\n        field: &'static str,\n        value: String,\n    },\n    InvalidAdapterDescriptor {\n        path: PathBuf,\n        message: String,\n    },\n    UnknownSchema {\n        schema: String,\n        available: Vec<String>,\n    },\n    ProfileTargetNotConfig {\n        profile: String,\n        path: PathBuf,\n    },\n    ProfileSelectionNotSupported(PathBuf),\n    UnsupportedRuntimeAdapter {\n        harness: String,\n        adapter_kind: AdapterKind,\n    },\n    RuntimeHandleMismatch {\n        field: &'static str,\n        expected: String,\n        actual: String,\n        runtime_id: String,\n    },\n    UnsupportedEnvironmentProvider {\n        provider: String,\n        adapter_kind: AdapterKind,\n    },\n    InvalidProcessSettings {\n        path: PathBuf,\n        source: Error,\n    },\n    InvalidPythonSettings {\n        path: PathBuf,\n        source: Error,\n    },\n    ProcessRunner {\n        command: String,\n        source: Error,\n    },\n    SerializeJson(Error),\n    Read {\n        path: PathBuf,\n        source: Error,\n    },\n    Write {\n        path: PathBuf,\n        source: Error,\n    },\n    ParseYaml {\n        path: PathBuf,\n        source: Error,\n    },\n    ParseJson {\n        path: PathBuf,\n        source: Error,\n    },\n}"}} />
Errors raised by Fabric config loading and validation. @@ -17,19 +17,19 @@ Errors raised by Fabric config loading and validation. ### `PathNotFound(PathBuf)` -
PathBuf)"}} />
+
PathBuf)"}} />
The requested path does not exist. ### `MissingEntrypoint(PathBuf)` -
PathBuf)"}} />
+
PathBuf)"}} />
A directory did not contain the expected Fabric entrypoint. ### `UnsupportedExtension(PathBuf)` -
PathBuf)"}} />
+
PathBuf)"}} />
A file extension is not recognized for Fabric config loading. @@ -179,7 +179,7 @@ Resolved path. ### `ProfileSelectionNotSupported(PathBuf)` -
PathBuf)"}} />
+
PathBuf)"}} />
A profile was requested for a single profile config. @@ -361,36 +361,36 @@ Underlying JSON error. ### `impl Debug for FabricError` -
Debug for FabricError"}} />
+
Debug for FabricError"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Display for FabricError` -
Display for FabricError"}} />
+
Display for FabricError"}} />
#### `fmt` -
fmt(&self, __formatter: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, __formatter: &mut Formatter<'_>) -> Result"}} />
### `impl Error for FabricError` -
Error for FabricError"}} />
+
Error for FabricError"}} />
#### `source` -
source(&self) -> Option<&(dyn Error + 'static)>"}} />
+
source(&self) -> Option<&(dyn Error + 'static)>"}} />
#### `description` -
description(&self) -> &str"}} />
+
description(&self) -> &str"}} />
#### `cause` -
cause(&self) -> Option<&dyn Error>"}} />
+
cause(&self) -> Option<&dyn Error>"}} />
#### `provide` -
provide<'a>(&'a self, request: &mut Request<'a>)"}} />
+
provide<'a>(&'a self, request: &mut Request<'a>)"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/error/type-result.mdx b/docs/reference/api/rust-library-reference/fabric-core/error/type-result.mdx index b7b66d3ac..508833955 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/error/type-result.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/error/type-result.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Result<T, FabricError>;"}} />
+
Result<T, FabricError>;"}} />
Core Fabric result type. diff --git a/docs/reference/api/rust-library-reference/fabric-core/fn-version.mdx b/docs/reference/api/rust-library-reference/fabric-core/fn-version.mdx index 376c4b05a..e98d68d11 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/fn-version.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/fn-version.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
str"}} />
+
str"}} />
Returns the crate version compiled into this build. diff --git a/docs/reference/api/rust-library-reference/fabric-core/index.mdx b/docs/reference/api/rust-library-reference/fabric-core/index.mdx index 960a65a71..cf59388d5 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/index.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/index.mdx @@ -13,6 +13,7 @@ Core config and runtime contract for NeMo Fabric. ## Re-exports +- `pub use config::ADAPTER_CONTRACT_VERSION;` - `pub use config::AdapterConfigSupport;` - `pub use config::AdapterDescriptor;` - `pub use config::AdapterDescriptorSource;` @@ -40,11 +41,10 @@ Core config and runtime contract for NeMo Fabric. - `pub use config::RunPlan;` - `pub use config::RuntimeCapabilities;` - `pub use config::RuntimeConfig;` -- `pub use config::RuntimeMode;` - `pub use config::SkillConfig;` - `pub use config::TelemetryConfig;` - `pub use config::TelemetryPlan;` -- `pub use config::Transport;` +- `pub use config::TelemetryProvider;` - `pub use config::load_adapter_descriptor;` - `pub use config::load_fabric_document;` - `pub use config::resolve_effective_config;` diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-errorstage.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-errorstage.mdx index 98562dfe7..5672630e9 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-errorstage.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-errorstage.mdx @@ -78,23 +78,23 @@ Artifact collection or writing failed. ### `impl Clone for ErrorStage` -
Clone for ErrorStage"}} />
+
Clone for ErrorStage"}} />
#### `clone` -
clone(&self) -> ErrorStage"}} />
+
clone(&self) -> ErrorStage"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ErrorStage` -
Debug for ErrorStage"}} />
+
Debug for ErrorStage"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ErrorStage` @@ -102,7 +102,7 @@ Artifact collection or writing failed. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ErrorStage` @@ -110,11 +110,11 @@ Artifact collection or writing failed. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -122,19 +122,19 @@ Artifact collection or writing failed. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ErrorStage` -
PartialEq for ErrorStage"}} />
+
PartialEq for ErrorStage"}} />
#### `eq` -
eq(&self, other: &ErrorStage) -> bool"}} />
+
eq(&self, other: &ErrorStage) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ErrorStage` @@ -142,16 +142,16 @@ Artifact collection or writing failed. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for ErrorStage` -
Copy for ErrorStage"}} />
+
Copy for ErrorStage"}} />
### `impl Eq for ErrorStage` -
Eq for ErrorStage"}} />
+
Eq for ErrorStage"}} />
### `impl StructuralPartialEq for ErrorStage` -
StructuralPartialEq for ErrorStage"}} />
+
StructuralPartialEq for ErrorStage"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-runstatus.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-runstatus.mdx index c2d1f8889..114597957 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-runstatus.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/enum-runstatus.mdx @@ -43,23 +43,23 @@ The invocation or runtime was cancelled. ### `impl Clone for RunStatus` -
Clone for RunStatus"}} />
+
Clone for RunStatus"}} />
#### `clone` -
clone(&self) -> RunStatus"}} />
+
clone(&self) -> RunStatus"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RunStatus` -
Debug for RunStatus"}} />
+
Debug for RunStatus"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RunStatus` @@ -67,7 +67,7 @@ The invocation or runtime was cancelled. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RunStatus` @@ -75,11 +75,11 @@ The invocation or runtime was cancelled. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -87,19 +87,19 @@ The invocation or runtime was cancelled. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RunStatus` -
PartialEq for RunStatus"}} />
+
PartialEq for RunStatus"}} />
#### `eq` -
eq(&self, other: &RunStatus) -> bool"}} />
+
eq(&self, other: &RunStatus) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RunStatus` @@ -107,16 +107,16 @@ The invocation or runtime was cancelled. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl Copy for RunStatus` -
Copy for RunStatus"}} />
+
Copy for RunStatus"}} />
### `impl Eq for RunStatus` -
Eq for RunStatus"}} />
+
Eq for RunStatus"}} />
### `impl StructuralPartialEq for RunStatus` -
StructuralPartialEq for RunStatus"}} />
+
StructuralPartialEq for RunStatus"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/fn-stop-runtime.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/fn-stop-runtime.mdx index 6851d5e79..cdec665fd 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/fn-stop-runtime.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/fn-stop-runtime.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
RunPlan,\n    runtime: &RuntimeHandle,\n) -> Result<Vec<FabricEvent>>"}} />
+
RunPlan,\n    runtime: &RuntimeHandle,\n) -> Result<Vec<FabricEvent>>"}} />
Stop or detach from a harness runtime. diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-adapterinvocation.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-adapterinvocation.mdx index 1030c4cde..401abab6a 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-adapterinvocation.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-adapterinvocation.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
EffectiveConfig,\n    pub runtime_context: RuntimeContext,\n    pub request: RunRequest,\n    pub capability_plan: CapabilityPlan,\n    pub telemetry_plan: Option<TelemetryPlan>,\n}"}} />
+
EffectiveConfig,\n    pub runtime_context: RuntimeContext,\n    pub request: RunRequest,\n    pub capability_plan: CapabilityPlan,\n    pub telemetry_plan: Option<TelemetryPlan>,\n}"}} />
Adapter-facing invocation payload. @@ -39,23 +39,23 @@ Derived telemetry plan for the selected adapter. ### `impl Clone for AdapterInvocation` -
Clone for AdapterInvocation"}} />
+
Clone for AdapterInvocation"}} />
#### `clone` -
clone(&self) -> AdapterInvocation"}} />
+
clone(&self) -> AdapterInvocation"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for AdapterInvocation` -
Debug for AdapterInvocation"}} />
+
Debug for AdapterInvocation"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for AdapterInvocation` @@ -63,7 +63,7 @@ Derived telemetry plan for the selected adapter. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for AdapterInvocation` @@ -71,11 +71,11 @@ Derived telemetry plan for the selected adapter. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ Derived telemetry plan for the selected adapter. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for AdapterInvocation` -
PartialEq for AdapterInvocation"}} />
+
PartialEq for AdapterInvocation"}} />
#### `eq` -
eq(&self, other: &AdapterInvocation) -> bool"}} />
+
eq(&self, other: &AdapterInvocation) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for AdapterInvocation` @@ -103,8 +103,8 @@ Derived telemetry plan for the selected adapter. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for AdapterInvocation` -
StructuralPartialEq for AdapterInvocation"}} />
+
StructuralPartialEq for AdapterInvocation"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactmanifest.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactmanifest.mdx index ba338262f..0586b49a7 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactmanifest.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactmanifest.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Option<PathBuf>,\n    pub artifacts: Vec<ArtifactRef>,\n}"}} />
+
Option<PathBuf>,\n    pub artifacts: Vec<ArtifactRef>,\n}"}} />
Manifest of run artifacts. @@ -27,31 +27,31 @@ Artifact entries. ### `impl Clone for ArtifactManifest` -
Clone for ArtifactManifest"}} />
+
Clone for ArtifactManifest"}} />
#### `clone` -
clone(&self) -> ArtifactManifest"}} />
+
clone(&self) -> ArtifactManifest"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ArtifactManifest` -
Debug for ArtifactManifest"}} />
+
Debug for ArtifactManifest"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for ArtifactManifest` -
Default for ArtifactManifest"}} />
+
Default for ArtifactManifest"}} />
#### `default` -
default() -> ArtifactManifest"}} />
+
default() -> ArtifactManifest"}} />
### `impl<'de> Deserialize<'de> for ArtifactManifest` @@ -59,7 +59,7 @@ Artifact entries. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ArtifactManifest` @@ -67,11 +67,11 @@ Artifact entries. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Artifact entries. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ArtifactManifest` -
PartialEq for ArtifactManifest"}} />
+
PartialEq for ArtifactManifest"}} />
#### `eq` -
eq(&self, other: &ArtifactManifest) -> bool"}} />
+
eq(&self, other: &ArtifactManifest) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ArtifactManifest` @@ -99,8 +99,8 @@ Artifact entries. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ArtifactManifest` -
StructuralPartialEq for ArtifactManifest"}} />
+
StructuralPartialEq for ArtifactManifest"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactref.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactref.mdx index 787032d38..487b0d9e2 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactref.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-artifactref.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub kind: String,\n    pub path: PathBuf,\n    pub media_type: Option<String>,\n}"}} />
+
String,\n    pub kind: String,\n    pub path: PathBuf,\n    pub media_type: Option<String>,\n}"}} />
Reference to one artifact. @@ -35,23 +35,23 @@ Optional media type. ### `impl Clone for ArtifactRef` -
Clone for ArtifactRef"}} />
+
Clone for ArtifactRef"}} />
#### `clone` -
clone(&self) -> ArtifactRef"}} />
+
clone(&self) -> ArtifactRef"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ArtifactRef` -
Debug for ArtifactRef"}} />
+
Debug for ArtifactRef"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ArtifactRef` @@ -59,7 +59,7 @@ Optional media type. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ArtifactRef` @@ -67,11 +67,11 @@ Optional media type. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Optional media type. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ArtifactRef` -
PartialEq for ArtifactRef"}} />
+
PartialEq for ArtifactRef"}} />
#### `eq` -
eq(&self, other: &ArtifactRef) -> bool"}} />
+
eq(&self, other: &ArtifactRef) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ArtifactRef` @@ -99,8 +99,8 @@ Optional media type. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ArtifactRef` -
StructuralPartialEq for ArtifactRef"}} />
+
StructuralPartialEq for ArtifactRef"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-environmenthandle.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-environmenthandle.mdx index 50dff8814..3b46f7730 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-environmenthandle.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-environmenthandle.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub provider: String,\n    pub control_location: ControlLocation,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub ownership: EnvironmentOwnership,\n    pub connection: BTreeMap<String, Value>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub provider: String,\n    pub control_location: ControlLocation,\n    pub workspace: Option<PathBuf>,\n    pub artifacts: Option<PathBuf>,\n    pub ownership: EnvironmentOwnership,\n    pub connection: BTreeMap<String, Value>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Resolved execution environment context. @@ -51,23 +51,23 @@ Provider-specific metadata. ### `impl Clone for EnvironmentHandle` -
Clone for EnvironmentHandle"}} />
+
Clone for EnvironmentHandle"}} />
#### `clone` -
clone(&self) -> EnvironmentHandle"}} />
+
clone(&self) -> EnvironmentHandle"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for EnvironmentHandle` -
Debug for EnvironmentHandle"}} />
+
Debug for EnvironmentHandle"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for EnvironmentHandle` @@ -75,7 +75,7 @@ Provider-specific metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for EnvironmentHandle` @@ -83,11 +83,11 @@ Provider-specific metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -95,19 +95,19 @@ Provider-specific metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for EnvironmentHandle` -
PartialEq for EnvironmentHandle"}} />
+
PartialEq for EnvironmentHandle"}} />
#### `eq` -
eq(&self, other: &EnvironmentHandle) -> bool"}} />
+
eq(&self, other: &EnvironmentHandle) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for EnvironmentHandle` @@ -115,8 +115,8 @@ Provider-specific metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for EnvironmentHandle` -
StructuralPartialEq for EnvironmentHandle"}} />
+
StructuralPartialEq for EnvironmentHandle"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-errorinfo.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-errorinfo.mdx index 41c9f5551..a94fe3830 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-errorinfo.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-errorinfo.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
ErrorStage,\n    pub code: String,\n    pub message: String,\n    pub retryable: bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
ErrorStage,\n    pub code: String,\n    pub message: String,\n    pub retryable: bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Normalized error metadata. @@ -39,23 +39,23 @@ Adapter or runtime metadata useful for diagnostics. ### `impl Clone for ErrorInfo` -
Clone for ErrorInfo"}} />
+
Clone for ErrorInfo"}} />
#### `clone` -
clone(&self) -> ErrorInfo"}} />
+
clone(&self) -> ErrorInfo"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for ErrorInfo` -
Debug for ErrorInfo"}} />
+
Debug for ErrorInfo"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for ErrorInfo` @@ -63,7 +63,7 @@ Adapter or runtime metadata useful for diagnostics. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for ErrorInfo` @@ -71,11 +71,11 @@ Adapter or runtime metadata useful for diagnostics. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ Adapter or runtime metadata useful for diagnostics. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for ErrorInfo` -
PartialEq for ErrorInfo"}} />
+
PartialEq for ErrorInfo"}} />
#### `eq` -
eq(&self, other: &ErrorInfo) -> bool"}} />
+
eq(&self, other: &ErrorInfo) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for ErrorInfo` @@ -103,8 +103,8 @@ Adapter or runtime metadata useful for diagnostics. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for ErrorInfo` -
StructuralPartialEq for ErrorInfo"}} />
+
StructuralPartialEq for ErrorInfo"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-fabricevent.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-fabricevent.mdx index 2ad825c26..843ef136b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-fabricevent.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-fabricevent.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub timestamp_millis: u128,\n    pub kind: String,\n    pub message: String,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub timestamp_millis: u128,\n    pub kind: String,\n    pub message: String,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Fabric lifecycle or progress event. @@ -39,23 +39,23 @@ Event metadata. ### `impl Clone for FabricEvent` -
Clone for FabricEvent"}} />
+
Clone for FabricEvent"}} />
#### `clone` -
clone(&self) -> FabricEvent"}} />
+
clone(&self) -> FabricEvent"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for FabricEvent` -
Debug for FabricEvent"}} />
+
Debug for FabricEvent"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for FabricEvent` @@ -63,7 +63,7 @@ Event metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for FabricEvent` @@ -71,11 +71,11 @@ Event metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -83,19 +83,19 @@ Event metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for FabricEvent` -
PartialEq for FabricEvent"}} />
+
PartialEq for FabricEvent"}} />
#### `eq` -
eq(&self, other: &FabricEvent) -> bool"}} />
+
eq(&self, other: &FabricEvent) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for FabricEvent` @@ -103,8 +103,8 @@ Event metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for FabricEvent` -
StructuralPartialEq for FabricEvent"}} />
+
StructuralPartialEq for FabricEvent"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-invocationhandle.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-invocationhandle.mdx index 36f9b4266..0280f67b9 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-invocationhandle.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-invocationhandle.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub request_id: String,\n    pub runtime_id: String,\n}"}} />
+
String,\n    pub request_id: String,\n    pub runtime_id: String,\n}"}} />
One request sent to a runtime. @@ -31,23 +31,23 @@ Runtime id. ### `impl Clone for InvocationHandle` -
Clone for InvocationHandle"}} />
+
Clone for InvocationHandle"}} />
#### `clone` -
clone(&self) -> InvocationHandle"}} />
+
clone(&self) -> InvocationHandle"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for InvocationHandle` -
Debug for InvocationHandle"}} />
+
Debug for InvocationHandle"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for InvocationHandle` @@ -55,7 +55,7 @@ Runtime id. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for InvocationHandle` @@ -63,11 +63,11 @@ Runtime id. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -75,19 +75,19 @@ Runtime id. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for InvocationHandle` -
PartialEq for InvocationHandle"}} />
+
PartialEq for InvocationHandle"}} />
#### `eq` -
eq(&self, other: &InvocationHandle) -> bool"}} />
+
eq(&self, other: &InvocationHandle) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for InvocationHandle` @@ -95,8 +95,8 @@ Runtime id. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for InvocationHandle` -
StructuralPartialEq for InvocationHandle"}} />
+
StructuralPartialEq for InvocationHandle"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runrequest.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runrequest.mdx index 24ac8b5a6..9ebe077db 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runrequest.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runrequest.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub input: Value,\n    pub context: BTreeMap<String, Value>,\n    pub overrides: Option<Value>,\n}"}} />
+
String,\n    pub input: Value,\n    pub context: BTreeMap<String, Value>,\n    pub overrides: Option<Value>,\n}"}} />
A request passed to a Fabric-managed harness runtime. @@ -25,7 +25,7 @@ Request payload for the harness. ### `context: BTreeMap` -Runtime context such as task, rollout, session, or caller metadata. +Runtime context such as task, rollout, workflow, or caller metadata. ### `overrides: Option` @@ -39,7 +39,7 @@ Per-invocation overrides allowed by the resolved profile. #### `text` -
Into<String>) -> Self"}} />
+
Into<String>) -> Self"}} />
Build a text request. @@ -47,31 +47,31 @@ Build a text request. ### `impl Clone for RunRequest` -
Clone for RunRequest"}} />
+
Clone for RunRequest"}} />
#### `clone` -
clone(&self) -> RunRequest"}} />
+
clone(&self) -> RunRequest"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RunRequest` -
Debug for RunRequest"}} />
+
Debug for RunRequest"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl Default for RunRequest` -
Default for RunRequest"}} />
+
Default for RunRequest"}} />
#### `default` -
default() -> RunRequest"}} />
+
default() -> RunRequest"}} />
### `impl<'de> Deserialize<'de> for RunRequest` @@ -79,7 +79,7 @@ Build a text request. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RunRequest` @@ -87,11 +87,11 @@ Build a text request. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -99,19 +99,19 @@ Build a text request. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RunRequest` -
PartialEq for RunRequest"}} />
+
PartialEq for RunRequest"}} />
#### `eq` -
eq(&self, other: &RunRequest) -> bool"}} />
+
eq(&self, other: &RunRequest) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RunRequest` @@ -119,8 +119,8 @@ Build a text request. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RunRequest` -
StructuralPartialEq for RunRequest"}} />
+
StructuralPartialEq for RunRequest"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runresult.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runresult.mdx index 38b6eae12..b2a4ae42a 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runresult.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runresult.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub profiles: Vec<String>,\n    pub harness: String,\n    pub adapter_kind: AdapterKind,\n    pub adapter_id: Option<String>,\n    pub runtime_id: String,\n    pub invocation_id: String,\n    pub request_id: String,\n    pub status: RunStatus,\n    pub output: Value,\n    pub error: Option<ErrorInfo>,\n    pub artifacts: ArtifactManifest,\n    pub telemetry: Option<TelemetryRef>,\n    pub events: Vec<FabricEvent>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
String,\n    pub profiles: Vec<String>,\n    pub harness: String,\n    pub adapter_kind: AdapterKind,\n    pub adapter_id: Option<String>,\n    pub runtime_id: String,\n    pub invocation_id: String,\n    pub request_id: String,\n    pub status: RunStatus,\n    pub output: Value,\n    pub error: Option<ErrorInfo>,\n    pub artifacts: ArtifactManifest,\n    pub telemetry: Option<TelemetryRef>,\n    pub events: Vec<FabricEvent>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Result from a Fabric-managed harness invocation. @@ -79,23 +79,23 @@ Adapter-specific metadata. ### `impl Clone for RunResult` -
Clone for RunResult"}} />
+
Clone for RunResult"}} />
#### `clone` -
clone(&self) -> RunResult"}} />
+
clone(&self) -> RunResult"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RunResult` -
Debug for RunResult"}} />
+
Debug for RunResult"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RunResult` @@ -103,7 +103,7 @@ Adapter-specific metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RunResult` @@ -111,11 +111,11 @@ Adapter-specific metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -123,19 +123,19 @@ Adapter-specific metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RunResult` -
PartialEq for RunResult"}} />
+
PartialEq for RunResult"}} />
#### `eq` -
eq(&self, other: &RunResult) -> bool"}} />
+
eq(&self, other: &RunResult) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RunResult` @@ -143,8 +143,8 @@ Adapter-specific metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RunResult` -
StructuralPartialEq for RunResult"}} />
+
StructuralPartialEq for RunResult"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimecontext.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimecontext.mdx index 29cff062c..eb26d707b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimecontext.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimecontext.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub session_id: Option<String>,\n    pub invocation_id: String,\n    pub request_id: String,\n    pub environment: EnvironmentHandle,\n    pub artifacts: ArtifactManifest,\n    pub telemetry: Option<RuntimeTelemetryContext>,\n}"}} />
+
String,\n    pub invocation_id: String,\n    pub request_id: String,\n    pub environment: EnvironmentHandle,\n    pub artifacts: ArtifactManifest,\n    pub telemetry: Option<RuntimeTelemetryContext>,\n}"}} />
Per-run/per-invocation context passed to harness adapters. @@ -19,10 +19,6 @@ Per-run/per-invocation context passed to harness adapters. Runtime handle id. -### `session_id: Option` - -Optional caller-provided harness conversation id. - ### `invocation_id: String` Invocation handle id. @@ -47,23 +43,23 @@ Runtime telemetry context generated for this invocation. ### `impl Clone for RuntimeContext` -
Clone for RuntimeContext"}} />
+
Clone for RuntimeContext"}} />
#### `clone` -
clone(&self) -> RuntimeContext"}} />
+
clone(&self) -> RuntimeContext"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RuntimeContext` -
Debug for RuntimeContext"}} />
+
Debug for RuntimeContext"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RuntimeContext` @@ -71,7 +67,7 @@ Runtime telemetry context generated for this invocation. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RuntimeContext` @@ -79,11 +75,11 @@ Runtime telemetry context generated for this invocation. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -91,19 +87,19 @@ Runtime telemetry context generated for this invocation. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RuntimeContext` -
PartialEq for RuntimeContext"}} />
+
PartialEq for RuntimeContext"}} />
#### `eq` -
eq(&self, other: &RuntimeContext) -> bool"}} />
+
eq(&self, other: &RuntimeContext) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RuntimeContext` @@ -111,8 +107,8 @@ Runtime telemetry context generated for this invocation. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RuntimeContext` -
StructuralPartialEq for RuntimeContext"}} />
+
StructuralPartialEq for RuntimeContext"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimehandle.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimehandle.mdx index b2e7aab0a..359f65c9b 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimehandle.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimehandle.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
String,\n    pub runtime_binding: String,\n    pub agent_name: String,\n    pub harness: String,\n    pub mode: RuntimeMode,\n    pub adapter_kind: AdapterKind,\n    pub adapter_id: Option<String>,\n    pub environment: EnvironmentHandle,\n}"}} />
+
String,\n    pub runtime_binding: String,\n    pub agent_name: String,\n    pub harness: String,\n    pub adapter_kind: AdapterKind,\n    pub adapter_id: Option<String>,\n    pub environment: EnvironmentHandle,\n}"}} />
Active or resumable harness runtime. @@ -31,10 +31,6 @@ Agent name. Stable machine-readable harness identifier. -### `mode: RuntimeMode` - -Runtime mode. - ### `adapter_kind: AdapterKind` Adapter kind. @@ -51,23 +47,23 @@ Prepared environment. ### `impl Clone for RuntimeHandle` -
Clone for RuntimeHandle"}} />
+
Clone for RuntimeHandle"}} />
#### `clone` -
clone(&self) -> RuntimeHandle"}} />
+
clone(&self) -> RuntimeHandle"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RuntimeHandle` -
Debug for RuntimeHandle"}} />
+
Debug for RuntimeHandle"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RuntimeHandle` @@ -75,7 +71,7 @@ Prepared environment. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RuntimeHandle` @@ -83,11 +79,11 @@ Prepared environment. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -95,19 +91,19 @@ Prepared environment. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RuntimeHandle` -
PartialEq for RuntimeHandle"}} />
+
PartialEq for RuntimeHandle"}} />
#### `eq` -
eq(&self, other: &RuntimeHandle) -> bool"}} />
+
eq(&self, other: &RuntimeHandle) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RuntimeHandle` @@ -115,8 +111,8 @@ Prepared environment. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RuntimeHandle` -
StructuralPartialEq for RuntimeHandle"}} />
+
StructuralPartialEq for RuntimeHandle"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimetelemetrycontext.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimetelemetrycontext.mdx index 43ee7913b..a8ba11ce6 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimetelemetrycontext.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-runtimetelemetrycontext.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub config_path: Option<PathBuf>,\n    pub env: BTreeMap<String, String>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
bool,\n    pub config_path: Option<PathBuf>,\n    pub env: BTreeMap<String, String>,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Runtime telemetry config passed to adapters. @@ -35,23 +35,23 @@ Additional telemetry metadata surfaced to consumers and adapters. ### `impl Clone for RuntimeTelemetryContext` -
Clone for RuntimeTelemetryContext"}} />
+
Clone for RuntimeTelemetryContext"}} />
#### `clone` -
clone(&self) -> RuntimeTelemetryContext"}} />
+
clone(&self) -> RuntimeTelemetryContext"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for RuntimeTelemetryContext` -
Debug for RuntimeTelemetryContext"}} />
+
Debug for RuntimeTelemetryContext"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for RuntimeTelemetryContext` @@ -59,7 +59,7 @@ Additional telemetry metadata surfaced to consumers and adapters. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for RuntimeTelemetryContext` @@ -67,11 +67,11 @@ Additional telemetry metadata surfaced to consumers and adapters. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -79,19 +79,19 @@ Additional telemetry metadata surfaced to consumers and adapters. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for RuntimeTelemetryContext` -
PartialEq for RuntimeTelemetryContext"}} />
+
PartialEq for RuntimeTelemetryContext"}} />
#### `eq` -
eq(&self, other: &RuntimeTelemetryContext) -> bool"}} />
+
eq(&self, other: &RuntimeTelemetryContext) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for RuntimeTelemetryContext` @@ -99,8 +99,8 @@ Additional telemetry metadata surfaced to consumers and adapters. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for RuntimeTelemetryContext` -
StructuralPartialEq for RuntimeTelemetryContext"}} />
+
StructuralPartialEq for RuntimeTelemetryContext"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-telemetryref.mdx b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-telemetryref.mdx index f9bbcdc9a..3bc07e7e8 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-telemetryref.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/runtime/struct-telemetryref.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
+
bool,\n    pub metadata: BTreeMap<String, Value>,\n}"}} />
Reference to telemetry emitted by Relay or another configured telemetry path. @@ -27,23 +27,23 @@ Telemetry metadata. ### `impl Clone for TelemetryRef` -
Clone for TelemetryRef"}} />
+
Clone for TelemetryRef"}} />
#### `clone` -
clone(&self) -> TelemetryRef"}} />
+
clone(&self) -> TelemetryRef"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for TelemetryRef` -
Debug for TelemetryRef"}} />
+
Debug for TelemetryRef"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl<'de> Deserialize<'de> for TelemetryRef` @@ -51,7 +51,7 @@ Telemetry metadata. #### `deserialize` -
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
### `impl JsonSchema for TelemetryRef` @@ -59,11 +59,11 @@ Telemetry metadata. #### `schema_name` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `schema_id` -
Cow<'static, str>"}} />
+
Cow<'static, str>"}} />
#### `json_schema` @@ -71,19 +71,19 @@ Telemetry metadata. #### `inline_schema` -
bool"}} />
+
bool"}} />
### `impl PartialEq for TelemetryRef` -
PartialEq for TelemetryRef"}} />
+
PartialEq for TelemetryRef"}} />
#### `eq` -
eq(&self, other: &TelemetryRef) -> bool"}} />
+
eq(&self, other: &TelemetryRef) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Serialize for TelemetryRef` @@ -91,8 +91,8 @@ Telemetry metadata. #### `serialize` -
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
### `impl StructuralPartialEq for TelemetryRef` -
StructuralPartialEq for TelemetryRef"}} />
+
StructuralPartialEq for TelemetryRef"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/schema/enum-schemaname.mdx b/docs/reference/api/rust-library-reference/fabric-core/schema/enum-schemaname.mdx index c5b5c3787..92515e072 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/schema/enum-schemaname.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/schema/enum-schemaname.mdx @@ -131,25 +131,25 @@ Fabric lifecycle event schema. #### `ALL` -
15]"}} />
+
15]"}} />
All public schemas in stable output order. #### `as_str` -
str"}} />
+
str"}} />
Stable file stem for this schema. #### `filename` -
String"}} />
+
String"}} />
Snapshot filename for this schema. #### `parse` -
str) -> Result<Self>"}} />
+
str) -> Result<Self>"}} />
Parse a schema name from CLI/user input. @@ -157,44 +157,44 @@ Parse a schema name from CLI/user input. ### `impl Clone for SchemaName` -
Clone for SchemaName"}} />
+
Clone for SchemaName"}} />
#### `clone` -
clone(&self) -> SchemaName"}} />
+
clone(&self) -> SchemaName"}} />
#### `clone_from` -
clone_from(&mut self, source: &Self)"}} />
+
clone_from(&mut self, source: &Self)"}} />
### `impl Debug for SchemaName` -
Debug for SchemaName"}} />
+
Debug for SchemaName"}} />
#### `fmt` -
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
### `impl PartialEq for SchemaName` -
PartialEq for SchemaName"}} />
+
PartialEq for SchemaName"}} />
#### `eq` -
eq(&self, other: &SchemaName) -> bool"}} />
+
eq(&self, other: &SchemaName) -> bool"}} />
#### `ne` -
ne(&self, other: &Rhs) -> bool"}} />
+
ne(&self, other: &Rhs) -> bool"}} />
### `impl Copy for SchemaName` -
Copy for SchemaName"}} />
+
Copy for SchemaName"}} />
### `impl Eq for SchemaName` -
Eq for SchemaName"}} />
+
Eq for SchemaName"}} />
### `impl StructuralPartialEq for SchemaName` -
StructuralPartialEq for SchemaName"}} />
+
StructuralPartialEq for SchemaName"}} />
diff --git a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-all-schemas.mdx b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-all-schemas.mdx index fda705902..e768a3529 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-all-schemas.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-all-schemas.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
Result<BTreeMap<String, Value>>"}} />
+
Result<BTreeMap<String, Value>>"}} />
Generate all schemas keyed by stable schema name. diff --git a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-schema-json.mdx b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-schema-json.mdx index a130076a1..80c70ca32 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-schema-json.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-generate-schema-json.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
SchemaName) -> Result<String>"}} />
+
SchemaName) -> Result<String>"}} />
Generate one schema as pretty JSON. diff --git a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-write-schema-snapshots.mdx b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-write-schema-snapshots.mdx index 5d1496898..d09327651 100644 --- a/docs/reference/api/rust-library-reference/fabric-core/schema/fn-write-schema-snapshots.mdx +++ b/docs/reference/api/rust-library-reference/fabric-core/schema/fn-write-schema-snapshots.mdx @@ -9,6 +9,6 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p fabric-core`. -
AsRef<Path>,\n) -> Result<Vec<PathBuf>>"}} />
+
AsRef<Path>,\n) -> Result<Vec<PathBuf>>"}} />
Write all schema snapshots to `directory`. diff --git a/docs/sdk/python.mdx b/docs/sdk/python.mdx new file mode 100644 index 000000000..cb94a8f8b --- /dev/null +++ b/docs/sdk/python.mdx @@ -0,0 +1,492 @@ +--- +title: "Python SDK" +description: "Use the NeMo Fabric Python SDK from applications, services, and evaluation harnesses." +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +# Python SDK + +The Python SDK is the application-facing interface for NeMo Fabric. Use it to +configure an agent harness, inspect the resolved plan, run one request or a +multi-turn runtime, and collect normalized results, events, artifacts, and +telemetry references. + +The SDK is config-first. Applications should construct a Pydantic +`FabricConfig` from their own job, deployment, or evaluation config. +Portable file formats such as `agent.yaml` remain useful for examples, CI, and +reproducibility, but the SDK does not require callers to write intermediate +files before invoking Fabric. + +Generated API reference pages remain the source of truth for exact signatures. +This guide explains how the pieces are intended to fit together. + +For installation and a package-backed example, start with the +[Getting Started overview](/getting-started/overview). + +## Start With One Run + +Construct a typed config, then pass it to `Fabric.run(...)`. The SDK starts a +runtime, invokes it once, collects the result, and stops the runtime. + +```python +import asyncio + +from nemo_fabric import ( + Fabric, + FabricConfig, + HarnessConfig, + MetadataConfig, + ModelConfig, +) + +config = FabricConfig( + metadata=MetadataConfig(name="review-agent"), + harness=HarnessConfig(adapter_id="nvidia.fabric.hermes.sdk"), + models={ + "default": ModelConfig( + provider="nvidia", + model="nvidia/nemotron-3-nano-30b-a3b", + api_key_env="NVIDIA_API_KEY", + ) + }, +) + + +async def main() -> None: + fabric = Fabric() + result = await fabric.run( + config, + input="Review the workspace changes.", + ) + + print(result.status) + print(result.output) + + +asyncio.run(main()) +``` + +Use `base_dir` to resolve relative paths in an in-memory config. Before running +in a new environment, call `plan(...)` to inspect adapter selection and +`doctor(...)` to check runtime requirements. + +The remaining examples reuse this `config` value. + +## Execution Model + +Fabric separates configuration, planning, runtime lifecycle, and individual +invocations. It does not expose a separate portable session layer. + +```mermaid +flowchart TB + Config["FabricConfig"] + Plan["RunPlan"] + Runtime["Runtime"] + Invoke1["Invocation
turn 1"] + Invoke2["Invocation
turn 2"] + Result1["RunResult
turn 1"] + Result2["RunResult
turn 2"] + + Config --> Plan + Plan --> Runtime + Runtime --> Invoke1 --> Result1 + Runtime --> Invoke2 --> Result2 +``` + +Most application code works with four objects: + +| Concept | What It Represents | How Consumers Use It | +| --- | --- | --- | +| `FabricConfig` | Typed configuration for the harness, models, runtime, and capabilities. | Construct it from application config before planning or running. | +| `RunPlan` | The resolved config, selected adapter, and declared capabilities. | Inspect it before starting work when adapter selection or feature support matters. | +| `Runtime` | The Python object for one logical, stateful harness execution. | Use it to send ordered invocations and stop the execution. Use it as an async context manager for cleanup. | +| `RunResult` | The normalized outcome of one invocation. | Read its status, output, error, events, artifacts, and correlation IDs. | + +`Fabric` is a lightweight, reusable SDK facade. It resolves configuration, +creates plans and runtimes, and runs one-shot requests, but it does not +represent a started execution and does not require cleanup. A `Runtime` owns +stateful execution and shutdown, so it is the object used as an async context +manager. + +`RuntimeHandle` and `InvocationHandle` carry lifecycle identity across the +native boundary. Most Python callers use their `runtime_id` and +`invocation_id` through `Runtime` and `RunResult` rather than manipulating the +handles directly. + +A runtime is a logical execution boundary, not necessarily an operating-system +process. An adapter may use an in-process SDK, a process, or shared service +infrastructure while preserving isolated state for each Fabric runtime. + +Harness-native threads, sessions, and conversations remain adapter-owned state +associated with the Fabric runtime. They are not additional Fabric lifecycle +objects. + +Fabric provides the runtime contract. Applications own scheduling, queues, +retries, worker scaling, and the number of runtimes to run. + +## Configure Agents In Code + +Build the complete nested `FabricConfig` directly, or start with a base +config and use helpers to add capabilities. For example, extend the config from +the first example with skills, MCP, and telemetry: + +```python +capability_config = config.model_copy(deep=True) +capability_config.add_skill_path("./skills/code-review") +capability_config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="harness_native", +) +capability_config.enable_relay( + project="fabric-review", + output_dir="./artifacts/relay", +) +``` + +Config helpers edit the typed config before planning or starting a runtime. They +do not modify already-started runtimes. Use `remove_mcp_server(name)` and +`remove_skill_path(path)` to remove capabilities from a copied config. + +For evaluation or deployment variations, use ordinary Python functions and +copies of the typed config. Supply the final config to Fabric when the variation +does not need file-profile merge semantics. + +```python +def review_agent_config(base, *, github_mcp: bool, relay: bool): + config = base.model_copy(deep=True) + if github_mcp: + config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="harness_native", + ) + if relay: + config.enable_relay(project="fabric-review", output_dir="./artifacts/relay") + return config + +variant = review_agent_config(config, github_mcp=True, relay=True) +``` + +The repository's +[code-review example](https://github.com/NVIDIA/NeMo-Fabric/tree/main/examples/code_review_agent) +uses this pattern for complete Hermes SDK, Hermes CLI, Codex CLI, environment, +MCP, and telemetry variants. + +When an in-memory caller needs the same ordered overlay behavior as file-backed +profiles, construct `FabricProfileConfig` values and pass them through +`profiles=[...]`. Fabric does not accept raw profile mappings. + +If a config contains relative paths, pass a `base_dir` to `resolve(...)`, +`plan(...)`, `doctor(...)`, `run(...)`, or `start_runtime(...)`. The base +directory anchors skills, workspaces, artifacts, and other relative paths to +the caller's package or job layout. + +## API Inventory + +Create `Fabric()` as the primary SDK entrypoint. It is a regular Python object, +not a lifecycle context manager, and may be reused to plan, diagnose, or start +multiple independent runtimes. + +| API | Async | Use When | Notes | +| --- | --- | --- | --- | +| `Fabric.resolve(config, base_dir=...)` | No | You need the normalized effective config without resolving an adapter. | Does not start a runtime. | +| `Fabric.plan(config, base_dir=...)` | No | You need to inspect the selected adapter, capability mapping, and runtime capabilities before running. | Does not start a runtime. | +| `Fabric.doctor(config, base_dir=...)` | Yes | You need preflight diagnostics for adapter availability, config support, and environment assumptions. | Checks may touch runtime dependencies. | +| `Fabric.run(config, input=...)` | Yes | You need one complete start, invoke, result, stop lifecycle. | Pass a `RunRequest` instead when the invocation needs IDs, context, or overrides. | +| `Fabric.start_runtime(config, ...)` | Yes | You need state across multiple ordered invocations. | Returns a `Runtime`. Use it as an async context manager. | +| `Runtime.invoke(...)` | Yes | You need one turn on an existing runtime. | A runtime permits one active invocation at a time. | +| `Runtime.stop()` | Yes | You need to stop or detach from the runtime. | Called automatically when using `async with`. | + +## One-Shot Runs + +Use `run(...)` when the application has one input and does not need to preserve +runtime state after the result is collected. + +```python +from nemo_fabric import Fabric, RunRequest + +request = RunRequest( + input="Review the workspace changes.", + request_id="request-123", + context={"source": "review-service"}, +) + +fabric = Fabric() +result = await fabric.run( + config, + base_dir="/workspace/review-agent", + request=request, +) + +print(result.status) +print(result.output) +print(result.artifacts) +``` + +Use `input=...` for the common case. Use `request=RunRequest(...)` for structured +invocation metadata. Applications read files themselves and pass either the +loaded input or a validated request to Fabric. + +Fabric generates runtime and invocation IDs for lifecycle correlation. An +application may include its own identifiers in opaque request metadata, but +Fabric does not interpret them as job, session, scheduling, or resume state. + +## Multi-Turn Runtimes + +Use `start_runtime(...)` when the selected harness should keep state across +turns. Every call creates a new logical Fabric runtime; callers reuse the +returned object rather than selecting it with a job or session ID. The runtime +stops when its async context exits. + +```python +from nemo_fabric import Fabric + +fabric = Fabric() +async with await fabric.start_runtime( + config, + base_dir="/workspace/review-agent", +) as runtime: + first = await runtime.invoke(input="Inspect the repository") + second = await runtime.invoke(input="Now review the latest patch") + +print(first.status, second.status) +``` + +The adapter reuses its native state between calls. For example, the Codex +adapter uses one Codex thread for the runtime and maps each invocation to one +turn. That thread ID remains adapter-internal. + +## Application-Owned Parallelism + +Applications create independent runtimes when they want parallel work. Fabric +does not own a queue, worker pool, semaphore, retry policy, timeout policy, or +numeric concurrency limit. Each `Runtime` accepts one invocation at a time so +its ordered harness state cannot be changed by two calls at once. If an +application overlaps calls on the same `Runtime`, the second call raises +`FabricStateError`. To perform work in parallel, start independent runtimes; +the application decides how many to run. + +Async lifecycle calls run blocking native work outside the Python event loop, +so independent runtimes can make progress concurrently. This does not add a +Fabric concurrency limit or scheduler. + +```python +import asyncio + + +async def review_one(prompt: str): + return await Fabric().run(config, input=prompt) + + +async def main() -> None: + results = await asyncio.gather( + review_one("Review patch A"), + review_one("Review patch B"), + ) + print([result.status for result in results]) + + +asyncio.run(main()) +``` + +For example, each Harbor job starts an independent Fabric runtime. Harbor owns +job IDs and concurrency policy; Fabric does not use a job ID to select or +resume runtime state. + +## Unified Run Results + +Every invocation that reaches the adapter boundary returns a normalized +`RunResult`, even when the harness invocation itself failed. Inspect `status`, +`error`, `events`, and `artifacts` first, then process `output` when the status +is successful. + +Important fields: + +| Field | Meaning | +| --- | --- | +| `status` | Terminal invocation status such as success, failure, or cancellation. | +| `output` | Harness output normalized to the configured output schema. | +| `error` | Structured failure metadata when available. | +| `artifacts` | Output files, logs, patches, native artifacts, and other materialized references. | +| `telemetry` | References to Relay or other telemetry streams produced by the run. | +| `events` | Ordered normalized lifecycle and invocation events. | +| `metadata` | Result-specific structured metadata. | +| `runtime_id`, `invocation_id`, `request_id` | IDs for correlation across runtimes, logs, telemetry, and artifacts. | + +These are structured correlation fields, not interchangeable metadata: +`runtime_id` identifies the runtime lifecycle, `invocation_id` identifies one +invocation within that runtime, and `request_id` correlates the caller's +request. Fabric-generated values use type-specific prefixes such as `runtime-`, +`invocation-`, and `request-`; callers may provide their own `request_id`. +Consumers should store and log each field separately and otherwise treat its +value as opaque rather than parsing the identifier encoding. + +If Fabric cannot resolve config, start a runtime, or obtain a normalized result, +the SDK raises a `FabricError` subclass instead of returning a partial +`RunResult`. + +## Events + +Each `RunResult` includes the normalized events collected for that invocation. +Event kinds and detail may vary by adapter, but their lifecycle and correlation +fields use the common Fabric contract. + +Events are useful for: + +- rendering invocation history in application or service UIs; +- forwarding logs and status to evaluation harnesses; +- correlating runtime, invocation, adapter, and telemetry IDs; +- reporting structured failures alongside the terminal result. + +## Feature Support Across Harness Adapters + +The SDK presents one consistent shape across adapters, but adapters differ in +their runtime requirements, accepted configuration, and optional capabilities. + +Use `plan(...)` and `doctor(...)` before relying on optional features: + +```python +fabric = Fabric() +plan = fabric.plan(config) +report = await fabric.doctor(config) + +print(plan.adapter.adapter_id) +print(report.status) +``` + +Use the plan to confirm adapter selection and capability routing. Use the doctor +report to catch missing dependencies, unsupported settings, and environment +problems before starting a runtime. + +## Install And Runtime Responsibilities + +In production, the consumer or execution environment is responsible for +installing Fabric, the selected harness, adapter dependencies, model access, +credentials, and any required native tools. Fabric validates and diagnoses the +runtime assumptions, but it does not silently install harnesses or credentials at +invocation time. + +Development environments may use extras, virtual environments, or local source +checkouts to make iteration easy. Production environments should prefer explicit +images, preinstalled dependencies, or managed deployment packages. + +Runtime compatibility checks should validate: + +- Fabric SDK and native extension versions; +- selected adapter version; +- selected harness version or version range; +- required environment variables or secret references; +- optional capability support such as Relay, MCP, or tool exposure. + +## Custom Fields And Adapter Settings + +Use normalized Fabric fields for portable behavior: models, runtime, +environment, skills, MCP, telemetry, tools, artifacts, and request context. + +Use `harness.settings` for adapter-owned configuration that the selected adapter +understands. Examples include Hermes-specific launch options, Codex CLI flags, +or adapter-specific config file locations. + +Use `metadata` for caller-owned annotations that Fabric should preserve and echo +back, but not interpret. + +Adapter settings are not portable by default. An adapter must explicitly read +and implement a setting before it affects runtime behavior. Use `doctor(...)` to +catch unsupported, ignored, or malformed adapter settings before launching a run. + +## Errors + +All public SDK errors inherit from `FabricError`. + +| Error | Meaning | +| --- | --- | +| `FabricConfigError` | Invalid config, request, or override. | +| `FabricCapabilityError` | Selected adapter does not support the requested operation. | +| `FabricRuntimeError` | Runtime startup, invocation, or shutdown failed before a normalized result could be returned. | +| `FabricStateError` | Invalid runtime state transition, such as invoking after stop or starting overlapping invocations. | +| `FabricNativeUnavailableError` | Native extension is not installed or importable. | + +Consumers own job-level retries and rollout-level failure policy. One-shot runs +attempt to stop the runtime before returning. A `Runtime` used with `async with` +also attempts cleanup after an invocation error; if cleanup fails, that failure +is attached to the original exception rather than replacing it. Fabric records +structured error metadata when possible and returns enough detail for the +consumer to decide what to do next. + +## SDK Contract Boundaries + +Fabric keeps a narrow execution contract. Applications own product behavior +around that contract. + +### Schemas And Python Models + +The SDK's Pydantic models are maintained against the Rust-generated public +schemas. Use the schemas for persisted field definitions and the generated API +reference for exact Python signatures. New application code should prefer +`FabricConfig` and `RunRequest` for validated SDK inputs. + +| Contract | Source | +| --- | --- | +| SDK Pydantic models | `nemo_fabric.models`; see the generated [Models reference](/reference/api/python-library-reference/models) | +| Agent config | `schemas/agent.schema.json` | +| Run plan | `schemas/run-plan.schema.json` | +| Request, result, and events | `schemas/run-request.schema.json`, `schemas/run-result.schema.json`, `schemas/fabric-event.schema.json` | +| Runtime and invocation handles | `schemas/runtime-handle.schema.json`, `schemas/invocation-handle.schema.json` | +| Artifacts and errors | `schemas/artifact-manifest.schema.json`, `schemas/error-info.schema.json` | + +### Versioning + +Fabric uses explicit contract versions where persisted or independently +maintained artifacts cross package boundaries: + +- `schema_version` identifies portable config documents such as `agent.yaml` + and profile files. +- `contract_version` identifies the adapter descriptor contract implemented by + a `fabric-adapter.json` file. +- Python and Rust package versions identify the installed SDK/core release. + +Fabric versions top-level persisted documents and independently maintained +contracts. It does not version each config subsection independently. For +example, MCP, skills, models, telemetry, and runtime fields evolve under the +enclosing `schema_version`. + +Fabric validates adapter descriptor contract versions during planning. Package +semver identifies the installed implementation, but it is not the compatibility +contract for saved agent packages or independently maintained adapters. + +### Config Extensibility + +The public schema has typed fields for stable Fabric concepts and controlled +extension points for adapter- or application-owned data. Use known fields for +portable concepts such as harness selection, models, runtime, skills, MCP, +telemetry, and artifacts. Use adapter-owned `harness.settings`, metadata, or +preserved extension fields for data Fabric should carry but not interpret. + +Additive optional fields may be introduced within the existing document schema +version when old configs remain valid. Required fields, removed fields, or +semantic changes that alter how existing configs are interpreted require a new +enclosing document schema version or an explicit compatibility path. + +Unknown data is not the same as supported behavior. An adapter must advertise +and implement a capability before Fabric treats it as runnable. + +### Resilience + +Fabric reports lifecycle failures; applications own recovery policy. If a +runtime process dies, a connection is permanently lost, or an adapter cannot +complete an invocation, Fabric marks the relevant runtime/invocation as +failed and returns structured error metadata when possible. + +Transient I/O failures may be marked with retryable error metadata, but Fabric +does not perform job-level retries by default. Consumers decide whether to retry +the request, start a replacement runtime, fail the job, or escalate to a user. + +### Capacity And Backpressure + +If a harness reports capacity pressure, an adapter should surface it as a +structured error or event such as busy, rate limited, capacity exceeded, or +backpressure. The consumer decides whether to wait, retry, scale out, or fail. diff --git a/examples/README.md b/examples/README.md index 72e6674ea..b7258f759 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,28 +5,34 @@ SPDX-License-Identifier: Apache-2.0 # Examples -This directory holds sample Fabric agent packages and single-file configs used tests and demos. +This directory holds runnable Fabric examples. -The first example focuses on the shared Fabric contract: +## Code review agent -- validating `agent.yaml`; -- resolving named profiles from configured profile directories; -- resolving direct profile YAML paths into run plans; -- resolving an environment context without requiring Fabric to provision it; -- resolving maintained Hermes adapters from the repository adapter registry. +[`code_review_agent`](code_review_agent/README.md) demonstrates the +application-facing Python SDK contract: + +- constructing complete `FabricConfig` values with Pydantic models; +- creating harness, environment, capability, and telemetry variants from deep + copies; +- resolving relative workspace and skill paths with `base_dir`; +- running maintained Hermes and Codex adapters through the Python SDK. Start with: ```bash -just build-rust -export PATH="$HOME/.cargo/bin:$PATH" - -fabric validate examples/code-review-agent -fabric inspect examples/code-review-agent -fabric plan examples/code-review-agent -fabric plan examples/code-review-agent --profile env_local --profile mcp_github -fabric plan examples/code-review-agent --profile hermes_cli +just build-all +.venv/bin/python -m examples.code_review_agent \ + --input "Reply with exactly: fabric works" ``` -The dependency-free Hermes shim used by tests lives under -`tests/fixtures/hermes-shim-agent`; it is not a maintained adapter. +## Harbor + +[`harbor`](harbor/README.md) demonstrates the installed `FabricAgent` +integration through a complete Harbor task, config matrix, verifier, and +multi-harness demo. + +Portable manifest and profile behavior is covered by +`tests/fixtures/file-config-agent`. The dependency-free Hermes shim used by +runtime tests lives under `tests/fixtures/hermes-shim-agent`; neither fixture is +a public SDK example. diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 000000000..9931a5b25 --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Runnable NeMo Fabric examples.""" diff --git a/examples/code-review-agent/profiles/hermes-cli-session.yaml b/examples/code-review-agent/profiles/hermes-cli-session.yaml deleted file mode 100644 index 0db998184..000000000 --- a/examples/code-review-agent/profiles/hermes-cli-session.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -schema_version: fabric.profile/v1alpha1 -name: hermes_cli_session -description: Drive the Hermes CLI adapter as a multi-turn session (runtime mode session). - -harness: - adapter_id: nvidia.fabric.hermes.cli - 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_iterations: 1 - terminal_timeout: 60 - enabled_toolsets: [] - system_prompt: You are a concise smoke test assistant. - -runtime: - mode: session - transport: cli - input_schema: chat - output_schema: message - artifacts: ./artifacts/hermes-cli-session - -environment: - provider: local - workspace: ./repos/my-service - artifacts: ./artifacts/hermes-cli-session - -telemetry: - enabled: false diff --git a/examples/code-review-agent/profiles/hermes-session.yaml b/examples/code-review-agent/profiles/hermes-session.yaml deleted file mode 100644 index 876c9c1fa..000000000 --- a/examples/code-review-agent/profiles/hermes-session.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# 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_iterations: 1 - 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 diff --git a/examples/code_review_agent/README.md b/examples/code_review_agent/README.md new file mode 100644 index 000000000..a8cd2a207 --- /dev/null +++ b/examples/code_review_agent/README.md @@ -0,0 +1,99 @@ + + +# Code Review Agent + +This example reviews the repository under `repos/my-service`. It constructs a +complete `FabricConfig` with the public Pydantic models and passes it directly +to the Python SDK. Variants are independent deep copies of that config. + +The example does not serialize configs to YAML or use profiles. + +## Set up + +Run commands from the repository root. Build Fabric and install its Python SDK +into the project virtual environment: + +```bash +just build-all +``` + +The default variant uses Hermes SDK with an NVIDIA-hosted model. Follow the +[Hermes SDK quick start](../../README.md#quick-start-hermes-sdk) to install +Hermes, then set `NVIDIA_API_KEY` and `HERMES_PYTHON` as described there. + +The config also demonstrates a harness-native GitHub MCP server. Set +`GITHUB_MCP_URL` when you want to use that server; the review prompt below does +not require it. + +## Inspect the plan + +Resolve the default config without starting a runtime or calling a model: + +```bash +.venv/bin/python -m examples.code_review_agent --plan +``` + +The JSON output shows the selected adapter, resolved workspace, capabilities, +environment, and telemetry plan. + +## Run the agent + +Run one request through the default Hermes SDK variant: + +```bash +.venv/bin/python -m examples.code_review_agent \ + --input "Reply with exactly: fabric works" +``` + +The command prints a normalized `RunResult` and writes runtime artifacts under +`examples/code_review_agent/artifacts/hermes-sdk/`. + +## Choose a variant + +The entrypoint exposes the three complete harness configs defined in +[`config.py`](./config.py): + +| Variant | Command option | Additional setup | +| --- | --- | --- | +| Hermes SDK | `--variant hermes-sdk` | `NVIDIA_API_KEY` and `HERMES_PYTHON` | +| Hermes CLI | `--variant hermes-cli` | Installed [Hermes CLI adapter requirements](../../adapters/hermes-cli/README.md) | +| Codex CLI | `--variant codex-cli` | Installed and authenticated [Codex CLI](../../adapters/codex-cli/README.md) | + +Add `--relay` to any variant to enable the Relay ATOF and ATIF configuration: + +Relay runs require the optional NeMo Relay dependency in the selected adapter +environment. + +```bash +.venv/bin/python -m examples.code_review_agent \ + --variant hermes-sdk \ + --relay \ + --input "Review calculator.py" +``` + +Use `--plan` with these options to inspect a variant before running it. + +## Compose configs in Python + +The config module also provides environment, MCP, and telemetry functions for +application-owned composition: + +```python +from examples.code_review_agent import ( + BASE_DIR, + hermes_sdk_config, + with_opensandbox, + with_relay, +) + +config = hermes_sdk_config() +relay_config = with_relay(config) +sandbox_config = with_opensandbox(config) +``` + +Each function returns a deep copy. `config`, `relay_config`, and +`sandbox_config` can therefore be planned or run independently with +`base_dir=BASE_DIR`. diff --git a/examples/code_review_agent/__init__.py b/examples/code_review_agent/__init__.py new file mode 100644 index 000000000..6b57240e5 --- /dev/null +++ b/examples/code_review_agent/__init__.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Configuration builders for the code-review agent example.""" + +from examples.code_review_agent.config import ( + BASE_DIR, + base_config, + codex_cli_config, + hermes_cli_config, + hermes_sdk_config, + with_fabric_managed_github_mcp, + with_native_otel, + with_opensandbox, + with_relay, + with_relay_openinference, + with_relay_otel, +) + +__all__ = [ + "BASE_DIR", + "base_config", + "codex_cli_config", + "hermes_cli_config", + "hermes_sdk_config", + "with_fabric_managed_github_mcp", + "with_native_otel", + "with_opensandbox", + "with_relay", + "with_relay_openinference", + "with_relay_otel", +] diff --git a/examples/code_review_agent/__main__.py b/examples/code_review_agent/__main__.py new file mode 100644 index 000000000..15d9012d6 --- /dev/null +++ b/examples/code_review_agent/__main__.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Run the code-review agent example.""" + +from __future__ import annotations + +import argparse +import asyncio +import json +from collections.abc import Callable + +from nemo_fabric import Fabric, FabricConfig + +from examples.code_review_agent.config import ( + BASE_DIR, + codex_cli_config, + hermes_cli_config, + hermes_sdk_config, + with_relay, +) + +CONFIG_BUILDERS: dict[str, Callable[[], FabricConfig]] = { + "hermes-sdk": hermes_sdk_config, + "hermes-cli": hermes_cli_config, + "codex-cli": codex_cli_config, +} + + +async def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--variant", choices=CONFIG_BUILDERS, default="hermes-sdk") + parser.add_argument("--relay", action="store_true") + parser.add_argument( + "--plan", + action="store_true", + help="Print the resolved run plan without starting a runtime.", + ) + parser.add_argument("--input", default="Review the workspace changes.") + args = parser.parse_args() + + config = CONFIG_BUILDERS[args.variant]() + if args.relay: + config = with_relay(config) + + fabric = Fabric() + if args.plan: + output = fabric.plan(config, base_dir=BASE_DIR) + else: + output = await fabric.run(config, base_dir=BASE_DIR, input=args.input) + print(json.dumps(output.to_mapping(), indent=2)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/code_review_agent/config.py b/examples/code_review_agent/config.py new file mode 100644 index 000000000..6f7dd2590 --- /dev/null +++ b/examples/code_review_agent/config.py @@ -0,0 +1,302 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Complete Fabric configs and clone-based variants for the example agent.""" + +from __future__ import annotations + +from pathlib import Path + +from nemo_fabric import ( + EnvironmentConfig, + FabricConfig, + HarnessConfig, + MetadataConfig, + ModelConfig, + RuntimeConfig, + TelemetryConfig, +) + +BASE_DIR = Path(__file__).resolve().parent +WORKSPACE = "./repos/my-service" +SKILL_PATH = "./skills/code-review" + + +def base_config() -> FabricConfig: + """Return a fresh common code-review config.""" + + config = FabricConfig( + metadata=MetadataConfig( + name="code-review-agent", + description="Reviews code changes and summarizes correctness risks.", + ), + harness=HarnessConfig( + adapter_id="nvidia.fabric.hermes.sdk", + resolution="preinstalled", + settings={"workspace": WORKSPACE}, + ), + models={ + "default": ModelConfig( + provider="nvidia", + model="nvidia/nemotron-3-nano-30b-a3b", + temperature=0.0, + api_key_env="NVIDIA_API_KEY", + ) + }, + runtime=RuntimeConfig( + input_schema="chat", + output_schema="message", + artifacts="./artifacts", + ), + environment=EnvironmentConfig( + provider="local", + workspace=WORKSPACE, + artifacts="./artifacts/local", + ), + telemetry=TelemetryConfig(enabled=False), + ) + config.add_skill_path(SKILL_PATH) + config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="harness_native", + ) + return config + + +def hermes_sdk_config() -> FabricConfig: + """Return the complete Hermes SDK variant.""" + + config = base_config().model_copy(deep=True) + config.harness = HarnessConfig( + adapter_id="nvidia.fabric.hermes.sdk", + resolution="preinstalled", + settings={ + "python_env": "HERMES_PYTHON", + "workspace": WORKSPACE, + "hermes_home": "./artifacts/hermes-home", + "base_url": "https://integrate.api.nvidia.com/v1", + "max_iterations": 1, + "max_tokens": 512, + "temperature": 0.0, + "reasoning_config": {"effort": "none"}, + "enabled_toolsets": [], + "system_prompt": "You are a concise smoke test assistant.", + }, + ) + config.runtime = RuntimeConfig( + input_schema="chat", + output_schema="message", + artifacts="./artifacts/hermes-sdk", + ) + config.environment = EnvironmentConfig( + provider="local", + workspace=WORKSPACE, + artifacts="./artifacts/hermes-sdk", + ) + return config + + +def hermes_cli_config() -> FabricConfig: + """Return the complete Hermes CLI variant.""" + + config = base_config().model_copy(deep=True) + config.harness = HarnessConfig( + adapter_id="nvidia.fabric.hermes.cli", + resolution="preinstalled", + settings={ + "workspace": WORKSPACE, + "hermes_home": "./artifacts/hermes-cli/home", + "base_url": "https://integrate.api.nvidia.com/v1", + "max_iterations": 1, + "terminal_timeout": 60, + "enabled_toolsets": [], + }, + ) + config.runtime = RuntimeConfig( + input_schema="chat", + output_schema="message", + artifacts="./artifacts/hermes-cli", + ) + config.environment = EnvironmentConfig( + provider="local", + workspace=WORKSPACE, + artifacts="./artifacts/hermes-cli", + ) + return config + + +def codex_cli_config() -> FabricConfig: + """Return the complete Codex CLI variant without inherited capabilities.""" + + config = base_config().model_copy(deep=True) + config.harness = HarnessConfig( + adapter_id="nvidia.fabric.codex.cli", + resolution="preinstalled", + settings={ + "sandbox": "workspace-write", + "skip_git_repo_check": True, + "config_overrides": {"model_reasoning_effort": "high"}, + }, + ) + config.models = { + "default": ModelConfig(provider="openai", model="openai/gpt-5.4") + } + config.runtime = RuntimeConfig( + input_schema="text", + output_schema="message", + artifacts="./artifacts/codex-cli", + ) + config.environment = EnvironmentConfig( + provider="local", + workspace=WORKSPACE, + artifacts="./artifacts/codex-cli", + ) + config.remove_mcp_server("github") + config.remove_skill_path(SKILL_PATH) + return config + + +def with_opensandbox(base: FabricConfig) -> FabricConfig: + """Return a copy configured for an externally controlled OpenSandbox.""" + + config = base.model_copy(deep=True) + config.environment = EnvironmentConfig( + provider="opensandbox", + control_location="external_control", + workspace="/workspace", + artifacts="/workspace/artifacts", + metadata={ + "server_url": "http://127.0.0.1:8080", + "image": "nvcr.io/nvidia/nemo/fabric-hermes:latest", + }, + ) + return config + + +def with_fabric_managed_github_mcp(base: FabricConfig) -> FabricConfig: + """Return a copy that routes the GitHub MCP server through Fabric.""" + + config = base.model_copy(deep=True) + config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="fabric_managed", + ) + return config + + +def with_relay(base: FabricConfig) -> FabricConfig: + """Return a copy with Relay ATOF and ATIF telemetry enabled.""" + + config = base.model_copy(deep=True) + config.enable_relay( + output_dir="./artifacts/relay", + config={ + "version": 1, + "components": [ + { + "kind": "observability", + "enabled": True, + "config": { + "version": 1, + "atif": { + "enabled": True, + "output_directory": "./artifacts/relay", + "filename_template": "trajectory-{session_id}.atif.json", + "agent_name": "code-review-agent", + "agent_version": "fabric-sdk-example", + }, + "atof": { + "enabled": True, + "output_directory": "./artifacts/relay", + "filename": "events.atof.jsonl", + "mode": "overwrite", + }, + }, + } + ], + }, + ) + return config + + +def with_relay_otel(base: FabricConfig) -> FabricConfig: + """Return a copy with Relay OpenTelemetry export enabled.""" + + config = base.model_copy(deep=True) + config.enable_relay( + output_dir="./artifacts/relay-otel", + config={ + "version": 1, + "components": [ + { + "kind": "observability", + "enabled": True, + "config": { + "version": 1, + "opentelemetry": { + "enabled": True, + "transport": "http_binary", + "endpoint": "http://localhost:4318/v1/traces", + "service_name": "code-review-agent", + "service_namespace": "fabric", + "service_version": "fabric-sdk-example", + "instrumentation_scope": "nemo-relay-otel", + "timeout_millis": 3000, + "resource_attributes": {"deployment.environment": "dev"}, + }, + }, + } + ], + }, + ) + return config + + +def with_relay_openinference(base: FabricConfig) -> FabricConfig: + """Return a copy with Relay OpenInference export enabled.""" + + config = with_relay(base) + assert config.telemetry is not None and config.telemetry.config is not None + component = config.telemetry.config["components"][0]["config"] + component["openinference"] = { + "enabled": True, + "transport": "http_binary", + "endpoint": "http://localhost:6006/v1/traces", + } + config.telemetry.output_dir = "./artifacts/relay-openinference" + component["atif"]["output_directory"] = "./artifacts/relay-openinference" + component["atof"]["output_directory"] = "./artifacts/relay-openinference" + return config + + +def with_native_otel(base: FabricConfig) -> FabricConfig: + """Return a copy with adapter-native OpenTelemetry enabled.""" + + config = base.model_copy(deep=True) + config.telemetry = TelemetryConfig( + enabled=True, + provider="native", + config={ + "version": 1, + "components": [ + { + "kind": "observability", + "enabled": True, + "config": { + "version": 1, + "opentelemetry": { + "enabled": True, + "transport": "http_binary", + "endpoint": "http://localhost:4318/v1/traces", + "resource_attributes": {"deployment.environment": "dev"}, + }, + }, + } + ], + }, + ) + return config diff --git a/examples/code-review-agent/repos/my-service/README.md b/examples/code_review_agent/repos/my-service/README.md similarity index 73% rename from examples/code-review-agent/repos/my-service/README.md rename to examples/code_review_agent/repos/my-service/README.md index a5545987c..dbaced97a 100644 --- a/examples/code-review-agent/repos/my-service/README.md +++ b/examples/code_review_agent/repos/my-service/README.md @@ -5,5 +5,5 @@ SPDX-License-Identifier: Apache-2.0 # Example Workspace -This placeholder directory stands in for the repository or task workspace that a -Fabric-managed harness would operate on during a local POC run. +This placeholder directory stands in for the repository or task workspace that +the example harness operates on. diff --git a/examples/code_review_agent/repos/my-service/calculator.py b/examples/code_review_agent/repos/my-service/calculator.py new file mode 100644 index 000000000..bf3cdfe85 --- /dev/null +++ b/examples/code_review_agent/repos/my-service/calculator.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +def answer() -> int: + """Return the value inspected by the example review agent.""" + + return 41 diff --git a/examples/code-review-agent/skills/code-review/README.md b/examples/code_review_agent/skills/code-review/README.md similarity index 72% rename from examples/code-review-agent/skills/code-review/README.md rename to examples/code_review_agent/skills/code-review/README.md index 728c8a1c4..e48509a37 100644 --- a/examples/code-review-agent/skills/code-review/README.md +++ b/examples/code_review_agent/skills/code-review/README.md @@ -5,4 +5,4 @@ SPDX-License-Identifier: Apache-2.0 # Code Review Skill -Minimal skill directory used by the config validation examples. +Minimal skill directory used by the code-review example. diff --git a/examples/harbor/README.md b/examples/harbor/README.md new file mode 100644 index 000000000..7a03cb264 --- /dev/null +++ b/examples/harbor/README.md @@ -0,0 +1,176 @@ + + +# Harbor Example + +This example shows how to use the installed +[`FabricAgent`](../../python/src/nemo_fabric/integrations/harbor/fabric_agent.py) +to run a Fabric harness inside a Harbor task environment. + +Harbor owns task and dataset materialization, container lifecycle, verification, +rewards, retries, concurrency, and job layout. Fabric owns config validation, +harness lifecycle, normalized results, artifacts, and telemetry references. One +Harbor agent run creates one independent Fabric runtime. + +```text +Harbor task + -> FabricAgent + -> HarborRunSpec JSON + -> sandbox-local Fabric.run() + -> selected harness + -> RunResult JSON + -> Harbor metadata and verifier +``` + +## Where the Fabric SDK runs + +`FabricAgent` runs on Harbor's host side, so it does not open the Fabric config +or task workspace itself. It packages the instruction and Harbor-owned inputs +into a `HarborRunSpec`, uploads that file, and starts the Fabric runner inside +the task environment. + +The runner can access the task files. It loads the YAML as a `FabricConfig`, +applies Harbor's model, MCP server, and skill inputs to a copy, and calls +`Fabric().run(...)`. That call handles the complete start, invoke, and stop +lifecycle for one independent Fabric runtime. The integration therefore does +not call `start_runtime()` directly. + +## Install + +Harbor requires Python 3.12 or later. Install the Fabric runtime and Harbor +integration in the environment that launches Harbor: + +```bash +python3 -m pip install "nemo-fabric[runtime,harbor]" +``` + +For a source checkout, run this from the repository root: + +```bash +python3 -m pip install -e ".[runtime,harbor]" +python3 -m pip install -e ../harbor +``` + +The Harbor task environment separately needs the Fabric runtime, the selected +adapter and its dependencies, and the config file. Bake or install them into the +task image; the [multi-harness demo](demo/README.md) shows one complete setup. + +## Prepare a Fabric config + +Create one complete config for the execution path. For example: + +```yaml +schema_version: fabric.agent/v1alpha1 + +metadata: + name: harbor-review-agent + +harness: + adapter_id: nvidia.fabric.hermes.cli + resolution: preinstalled + settings: + cwd: /app + base_url: https://integrate.api.nvidia.com/v1 + +models: + default: + provider: nvidia + model: nvidia/nemotron-3-nano-30b-a3b + +runtime: + input_schema: text + output_schema: message + artifacts: /logs/agent/fabric-artifacts + +environment: + provider: local + workspace: /app + artifacts: /logs/agent/fabric-artifacts + +telemetry: + enabled: false +``` + +Copy the config into the task image, for example at +`/opt/fabric/configs/hermes.yaml`. `fabric_config_path` always refers to the +path inside the task environment, not the host checkout. + +## Run the task + +Pass one complete Fabric config path through Harbor's agent arguments: + +```bash +harbor run --path \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --model nvidia/nemotron-3-nano-30b-a3b \ + --ak fabric_config_path=/opt/fabric/configs/hermes.yaml \ + --ae NVIDIA_API_KEY="$NVIDIA_API_KEY" +``` + +`fabric_config_path` is resolved inside the task container. The config selects +the harness, runtime, environment, and telemetry behavior. Harbor supplies the +task instruction and may supply a replacement model, MCP servers, or skill +directory. + +## Config composition + +The sandbox runner validates the YAML as `FabricConfig`, makes a deep copy, and +then applies Harbor-owned inputs: + +- when provided, `--model` replaces `models.default` with a `ModelConfig`; +- when provided, Harbor MCP servers replace the config's MCP section through + `add_mcp_server()`; +- when provided, Harbor's skill directory replaces the config's skill section + through `add_skill_path()`. + +If Harbor does not provide MCP servers or a skill directory, those sections +remain unchanged from the complete Fabric config. + +The final config is passed directly to `Fabric.run()` with a `RunRequest`. +Harbor scheduling values and job IDs do not enter the Fabric config or runtime. + +## Inspect the result + +The host writes a validated `HarborRunSpec` to the Harbor log directory and +uploads it to a unique task-environment path. The sandbox runner writes one +normalized `RunResult` to a unique result path. The host validates that result +before copying summary fields into `AgentContext.metadata["fabric"]`. + +The Harbor agent log directory contains `fabric-run-.json` and +`fabric-result-.json`. The result includes Fabric status, harness and +adapter identity, runtime and invocation IDs, artifacts, telemetry references, +and structured errors. Use Harbor's viewer to inspect the trial and reward: + +```bash +harbor view +``` + +## Optional agent arguments + +`FabricAgent` accepts these additional constructor arguments: + +- `fabric_python`: Python executable used to start the sandbox runner; +- `fabric_cwd`: working directory for installation and execution; +- `fabric_install_command`: environment bootstrap command; +- `fabric_timeout_sec`: timeout for bootstrap and execution. + +## Demo and tests + +The [multi-harness demo](demo/README.md) provides complete configs and commands +for a credential-free smoke run, Hermes CLI, Hermes with Relay, and Codex CLI. + +Run the lightweight integration tests with: + +```bash +pytest tests/python/test_harbor_integration.py \ + tests/integrations/test_harbor_runner.py +``` + +The Docker-backed SWE-Bench check is opt-in: + +```bash +RUN_FABRIC_HARBOR_SWEBENCH_DOCKER=1 \ +pytest tests/e2e/test_harbor_swebench_task.py +``` diff --git a/examples/harbor/demo/README.md b/examples/harbor/demo/README.md new file mode 100644 index 000000000..7b650eb63 --- /dev/null +++ b/examples/harbor/demo/README.md @@ -0,0 +1,197 @@ +# Harbor Multi-Harness Demo + +This demo keeps one Harbor task and one `FabricAgent` class while complete +Fabric configs select the execution harness and telemetry behavior. Harbor owns +the task, container, verifier, reward, concurrency, and run layout. Fabric runs +one independent harness runtime for each Harbor agent run. + +## Requirements + +- Python 3.12+ +- `uv` +- Docker +- this repository checkout, with the changes under test committed +- a host `codex login` for the Codex run + +The first image build can take several minutes. + +## Prepare the build context + +Harbor builds `task/environment/Dockerfile` with the environment directory as +its Docker context. Export committed `HEAD` so the image installs the exact +Fabric revision under test: + +```bash +DEMO_DIR="$PWD/examples/harbor/demo" +TASK_DIR="$DEMO_DIR/task" +RUNS_DIR="$DEMO_DIR/runs" +VENDOR_DIR="$TASK_DIR/environment/vendor/nemo-fabric" + +rm -rf "$TASK_DIR/environment/vendor" +mkdir -p "$VENDOR_DIR" +git archive HEAD | tar -x -C "$VENDOR_DIR" +``` + +Keep this shell open for the commands below. Use a new `--job-name`, or remove +the matching generated directory under `$RUNS_DIR`, before repeating a run. + +## Harbor arguments + +| Argument | Meaning | +| --- | --- | +| `--path` | Harbor task directory containing the environment and verifier | +| `--agent` | Harbor agent class imported from Fabric | +| `--ak` | Constructor argument passed to `FabricAgent` | +| `fabric_config_path` | Complete Fabric config inside the task container | +| `--model` | Harbor model selection applied to a copy of the Fabric config | +| `--ae` | Environment variable passed to the Harbor agent | +| `--mounts` | Host-to-container mounts managed by Harbor | +| `--extra-docker-compose` | Compose overlay for the task environment | +| `--job-name` | Harbor output directory name for this run | +| `--force-build` | Rebuild the task image from the prepared context | + +## 1. Credential-free smoke + +This run checks Harbor setup, spec upload, sandbox-local SDK execution, +workspace mutation, result download, and verification: + +```bash +uv run --extra runtime --extra harbor harbor run \ + --path "$TASK_DIR" \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --ak fabric_config_path=/opt/fabric-demo/configs/smoke.yaml \ + --job-name fabric-smoke \ + --jobs-dir "$RUNS_DIR" \ + --n-concurrent 1 \ + --n-attempts 1 \ + --force-build +``` + +Expected Harbor summary: one trial, zero exceptions, and mean reward `1.000`. + +## 2. Hermes CLI + +```bash +export NVIDIA_API_KEY=... + +uv run --extra runtime --extra harbor harbor run \ + --path "$TASK_DIR" \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --ak fabric_config_path=/opt/fabric-demo/configs/hermes.yaml \ + --model nvidia/nemotron-3-nano-30b-a3b \ + --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" \ + --job-name fabric-hermes \ + --jobs-dir "$RUNS_DIR" \ + --n-concurrent 1 \ + --n-attempts 1 \ + --force-build +``` + +Harbor's model value replaces `models.default` in the config copy used for this +run. + +## 3. Hermes with Relay telemetry + +Start Phoenix on the host: + +```bash +docker rm -f fabric-phoenix 2>/dev/null || true +docker run --rm --detach \ + --name fabric-phoenix \ + --publish 6006:6006 \ + arizephoenix/phoenix:latest + +until curl --fail --silent http://localhost:6006 >/dev/null; do sleep 1; done +``` + +Visit `http://localhost:6006`. The Compose overlay maps +`host.docker.internal` to the host gateway on Linux. + +```bash +uv run --extra runtime --extra harbor harbor run \ + --path "$TASK_DIR" \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --ak fabric_config_path=/opt/fabric-demo/configs/hermes-relay.yaml \ + --model nvidia/nemotron-3-nano-30b-a3b \ + --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" \ + --extra-docker-compose "$DEMO_DIR/host-gateway.compose.yaml" \ + --job-name fabric-hermes-relay \ + --jobs-dir "$RUNS_DIR" \ + --n-concurrent 1 \ + --n-attempts 1 \ + --force-build +``` + +The completed run appears in Phoenix and writes ATOF and ATIF records into the +Harbor agent logs: + +```bash +find "$RUNS_DIR/fabric-hermes-relay" \ + -path '*/agent/fabric-artifacts/hermes-relay/relay/events.atof.jsonl' \ + -print -exec sed -n '1,5p' {} \; + +find "$RUNS_DIR/fabric-hermes-relay" \ + -path '*/agent/fabric-artifacts/hermes-relay/relay/*.atif.json' \ + -print -exec python -m json.tool {} \; +``` + +## 4. Codex CLI + +Harbor mounts the host Codex login as a read-only secret. The setup command +copies it into a writable container-local `CODEX_HOME`; Fabric only passes that +environment to Codex. + +```bash +codex login status + +CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}" +test -f "$CODEX_HOME_DIR/auth.json" +CODEX_AUTH_MOUNT="[{\"type\":\"bind\",\"source\":\"$CODEX_HOME_DIR/auth.json\",\"target\":\"/run/secrets/codex-auth.json\",\"read_only\":true}]" + +uv run --extra runtime --extra harbor harbor run \ + --path "$TASK_DIR" \ + --agent nemo_fabric.integrations.harbor:FabricAgent \ + --ak fabric_config_path=/opt/fabric-demo/configs/codex.yaml \ + --ak 'fabric_install_command=mkdir -p "$CODEX_HOME" && cp /run/secrets/codex-auth.json "$CODEX_HOME/auth.json"' \ + --model openai/gpt-5.4 \ + --ae CODEX_HOME=/tmp/fabric-codex-home \ + --mounts "$CODEX_AUTH_MOUNT" \ + --job-name fabric-codex \ + --jobs-dir "$RUNS_DIR" \ + --n-concurrent 1 \ + --n-attempts 1 \ + --force-build +``` + +The image pins Codex CLI `0.142.4`. The config uses +`danger-full-access` because Harbor's task container is the outer sandbox and +nested Linux namespace creation is unavailable there. + +## Inspect results + +Fabric result files use unique names in each trial's agent logs: + +```bash +find "$RUNS_DIR/fabric-smoke" -path '*/agent/fabric-result-*.json' -print -exec cat {} \; +cat "$RUNS_DIR/fabric-smoke/result.json" +uv run --extra runtime --extra harbor harbor view "$RUNS_DIR" +``` + +Check Fabric status, harness and adapter identity, runtime and invocation IDs, +artifacts, telemetry, Harbor exceptions, and reward. A successful smoke run has +Fabric status `succeeded` and Harbor mean reward `1.0`. + +After the demo, remove the generated build-context copy: + +```bash +rm -rf "$TASK_DIR/environment/vendor" +``` + +## Recording flow + +1. Show the common `--agent` argument and the four complete config files. +2. Run the credential-free smoke and inspect its Fabric result. +3. Run Hermes and Codex, changing the config, model, and credential inputs. +4. Start Phoenix, run the Hermes Relay config, and open its trace. +5. Show the same run's ATOF and ATIF records. +6. Open all four jobs with `harbor view`. diff --git a/examples/harbor/demo/host-gateway.compose.yaml b/examples/harbor/demo/host-gateway.compose.yaml new file mode 100644 index 000000000..37f3de060 --- /dev/null +++ b/examples/harbor/demo/host-gateway.compose.yaml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +services: + main: + extra_hosts: + - host.docker.internal=host-gateway diff --git a/integrations/harbor/demo/task/environment/Dockerfile b/examples/harbor/demo/task/environment/Dockerfile similarity index 79% rename from integrations/harbor/demo/task/environment/Dockerfile rename to examples/harbor/demo/task/environment/Dockerfile index e62a5197d..506358209 100644 --- a/integrations/harbor/demo/task/environment/Dockerfile +++ b/examples/harbor/demo/task/environment/Dockerfile @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + FROM python:3.12-slim-bookworm RUN apt-get update \ @@ -11,7 +14,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-ini ENV PATH=/root/.cargo/bin:$PATH COPY vendor/nemo-fabric /opt/nemo-fabric -RUN pip install --no-cache-dir -e "/opt/nemo-fabric[codex,harbor,hermes,relay]" \ +RUN pip install --no-cache-dir -e "/opt/nemo-fabric[codex,harbor,hermes,relay,runtime]" \ && npm install --global @openai/codex@0.142.4 COPY calculator.py /app/calculator.py diff --git a/integrations/harbor/demo/task/environment/calculator.py b/examples/harbor/demo/task/environment/calculator.py similarity index 71% rename from integrations/harbor/demo/task/environment/calculator.py rename to examples/harbor/demo/task/environment/calculator.py index 485c157d8..5613acb8d 100644 --- a/integrations/harbor/demo/task/environment/calculator.py +++ b/examples/harbor/demo/task/environment/calculator.py @@ -1,9 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -def add(a, b): +def add(a: int, b: int) -> int: return a + b -def multiply(a, b): +def multiply(a: int, b: int) -> int: return a - b diff --git a/integrations/harbor/demo/task/environment/fabric/adapters/scripted/fabric-adapter.json b/examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/fabric-adapter.json similarity index 80% rename from integrations/harbor/demo/task/environment/fabric/adapters/scripted/fabric-adapter.json rename to examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/fabric-adapter.json index 7c371bb92..e456718d3 100644 --- a/integrations/harbor/demo/task/environment/fabric/adapters/scripted/fabric-adapter.json +++ b/examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/fabric-adapter.json @@ -1,4 +1,5 @@ { + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "demo.fabric.scripted", "harness": "scripted", "adapter_kind": "process", diff --git a/integrations/harbor/demo/task/environment/fabric/adapters/scripted/run.py b/examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/run.py old mode 100755 new mode 100644 similarity index 89% rename from integrations/harbor/demo/task/environment/fabric/adapters/scripted/run.py rename to examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/run.py index b1d88c3ae..fe12b6b50 --- a/integrations/harbor/demo/task/environment/fabric/adapters/scripted/run.py +++ b/examples/harbor/demo/task/environment/fabric/configs/adapters/scripted/run.py @@ -2,6 +2,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +"""Deterministic adapter used by the credential-free Harbor example.""" + import json import sys from pathlib import Path diff --git a/examples/code-review-agent/profiles/codex-cli-session.yaml b/examples/harbor/demo/task/environment/fabric/configs/codex.yaml similarity index 54% rename from examples/code-review-agent/profiles/codex-cli-session.yaml rename to examples/harbor/demo/task/environment/fabric/configs/codex.yaml index 057965082..527f0746b 100644 --- a/examples/code-review-agent/profiles/codex-cli-session.yaml +++ b/examples/harbor/demo/task/environment/fabric/configs/codex.yaml @@ -1,17 +1,19 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -schema_version: fabric.profile/v1alpha1 -name: codex_cli_session -description: Reuse one Codex thread across a Fabric runtime session. +schema_version: fabric.agent/v1alpha1 + +metadata: + name: harbor-calculator-demo + description: Codex CLI code-repair example in a Harbor task environment. harness: adapter_id: nvidia.fabric.codex.cli resolution: preinstalled settings: - sandbox: workspace-write + cwd: /app + sandbox: danger-full-access skip_git_repo_check: true - codex_state_dir: ./artifacts/codex-cli/session-state config_overrides: model_reasoning_effort: high @@ -19,23 +21,16 @@ models: default: provider: openai model: openai/gpt-5.4 - api_key_env: null - temperature: null - -skills: null -mcp: null runtime: - mode: session - transport: cli input_schema: text output_schema: message - artifacts: ./artifacts/codex-cli + artifacts: /logs/agent/fabric-artifacts/codex environment: provider: local - workspace: ./repos/my-service - artifacts: ./artifacts/codex-cli + workspace: /app + artifacts: /logs/agent/fabric-artifacts/codex telemetry: enabled: false diff --git a/integrations/harbor/demo/task/environment/fabric/profiles/telemetry.yaml b/examples/harbor/demo/task/environment/fabric/configs/hermes-relay.yaml similarity index 55% rename from integrations/harbor/demo/task/environment/fabric/profiles/telemetry.yaml rename to examples/harbor/demo/task/environment/fabric/configs/hermes-relay.yaml index 0e46fed27..0cef67d18 100644 --- a/integrations/harbor/demo/task/environment/fabric/profiles/telemetry.yaml +++ b/examples/harbor/demo/task/environment/fabric/configs/hermes-relay.yaml @@ -1,16 +1,41 @@ -schema_version: fabric.profile/v1alpha1 -name: telemetry -description: Add Relay OpenInference, ATOF, and ATIF telemetry to the selected Hermes run. +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +schema_version: fabric.agent/v1alpha1 + +metadata: + name: harbor-calculator-demo + description: Hermes CLI and Relay example in a Harbor task environment. + +harness: + adapter_id: nvidia.fabric.hermes.cli + resolution: preinstalled + settings: + cwd: /app + hermes_home: /tmp/fabric-hermes + base_url: https://integrate.api.nvidia.com/v1 + max_iterations: 4 + terminal_timeout: 120 + +models: + default: + provider: nvidia + model: nvidia/nemotron-3-nano-30b-a3b + temperature: 0.0 runtime: + input_schema: text + output_schema: message artifacts: /logs/agent/fabric-artifacts/hermes-relay environment: + provider: local + workspace: /app artifacts: /logs/agent/fabric-artifacts/hermes-relay telemetry: enabled: true - mode: sdk + provider: relay output_dir: /logs/agent/fabric-artifacts/hermes-relay/relay config: version: 1 @@ -24,7 +49,6 @@ telemetry: output_directory: /logs/agent/fabric-artifacts/hermes-relay/relay filename_template: trajectory-{session_id}.atif.json agent_name: harbor-calculator-demo - agent_version: fabric-mvp atof: enabled: true output_directory: /logs/agent/fabric-artifacts/hermes-relay/relay diff --git a/integrations/harbor/demo/task/environment/fabric/profiles/hermes.yaml b/examples/harbor/demo/task/environment/fabric/configs/hermes.yaml similarity index 58% rename from integrations/harbor/demo/task/environment/fabric/profiles/hermes.yaml rename to examples/harbor/demo/task/environment/fabric/configs/hermes.yaml index a5ea0e1e7..2aec928a1 100644 --- a/integrations/harbor/demo/task/environment/fabric/profiles/hermes.yaml +++ b/examples/harbor/demo/task/environment/fabric/configs/hermes.yaml @@ -1,6 +1,11 @@ -schema_version: fabric.profile/v1alpha1 -name: hermes -description: Run Hermes CLI against the Harbor task workspace. +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +schema_version: fabric.agent/v1alpha1 + +metadata: + name: harbor-calculator-demo + description: Hermes CLI code-repair example in a Harbor task environment. harness: adapter_id: nvidia.fabric.hermes.cli @@ -19,10 +24,12 @@ models: temperature: 0.0 runtime: - transport: cli + input_schema: text + output_schema: message artifacts: /logs/agent/fabric-artifacts/hermes environment: + provider: local workspace: /app artifacts: /logs/agent/fabric-artifacts/hermes diff --git a/integrations/harbor/demo/task/environment/fabric/agent.yaml b/examples/harbor/demo/task/environment/fabric/configs/smoke.yaml similarity index 60% rename from integrations/harbor/demo/task/environment/fabric/agent.yaml rename to examples/harbor/demo/task/environment/fabric/configs/smoke.yaml index 450e90f5c..aacdfc865 100644 --- a/integrations/harbor/demo/task/environment/fabric/agent.yaml +++ b/examples/harbor/demo/task/environment/fabric/configs/smoke.yaml @@ -1,16 +1,17 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + schema_version: fabric.agent/v1alpha1 metadata: name: harbor-calculator-demo - description: One Harbor agent surface with Fabric-selected harnesses. + description: Deterministic Harbor example pipeline check. harness: adapter_id: demo.fabric.scripted resolution: preinstalled runtime: - mode: oneshot - transport: cli input_schema: text output_schema: message artifacts: /logs/agent/fabric-artifacts/smoke @@ -20,6 +21,5 @@ environment: workspace: /app artifacts: /logs/agent/fabric-artifacts/smoke -profiles: - directories: - - ./profiles +telemetry: + enabled: false diff --git a/integrations/harbor/demo/task/instruction.md b/examples/harbor/demo/task/instruction.md similarity index 63% rename from integrations/harbor/demo/task/instruction.md rename to examples/harbor/demo/task/instruction.md index cd9bd1176..c26f5709e 100644 --- a/integrations/harbor/demo/task/instruction.md +++ b/examples/harbor/demo/task/instruction.md @@ -1,3 +1,8 @@ + + # Fix the calculator `/app/calculator.py` contains a broken `multiply(a, b)` implementation. diff --git a/integrations/harbor/demo/task/solution/solve.sh b/examples/harbor/demo/task/solution/solve.sh similarity index 68% rename from integrations/harbor/demo/task/solution/solve.sh rename to examples/harbor/demo/task/solution/solve.sh index 4fd1ce635..9911fc4bc 100755 --- a/integrations/harbor/demo/task/solution/solve.sh +++ b/examples/harbor/demo/task/solution/solve.sh @@ -1,4 +1,6 @@ #!/bin/sh +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 python3 - <<'PY' from pathlib import Path diff --git a/integrations/harbor/demo/task/task.toml b/examples/harbor/demo/task/task.toml similarity index 78% rename from integrations/harbor/demo/task/task.toml rename to examples/harbor/demo/task/task.toml index 0f5385781..ffa9adecc 100644 --- a/integrations/harbor/demo/task/task.toml +++ b/examples/harbor/demo/task/task.toml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + schema_version = "1.3" [task] diff --git a/examples/harbor/demo/task/tests/test.sh b/examples/harbor/demo/task/tests/test.sh new file mode 100755 index 000000000..041422236 --- /dev/null +++ b/examples/harbor/demo/task/tests/test.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +if python3 /tests/verify.py; then + echo 1 > /logs/verifier/reward.txt +else + echo 0 > /logs/verifier/reward.txt +fi diff --git a/integrations/harbor/demo/task/tests/verify.py b/examples/harbor/demo/task/tests/verify.py similarity index 89% rename from integrations/harbor/demo/task/tests/verify.py rename to examples/harbor/demo/task/tests/verify.py index 120ac19d9..8028c25ae 100644 --- a/integrations/harbor/demo/task/tests/verify.py +++ b/examples/harbor/demo/task/tests/verify.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +"""Verify the calculator behavior after the Harbor example run.""" + import importlib.util from pathlib import Path diff --git a/integrations/harbor/README.md b/integrations/harbor/README.md deleted file mode 100644 index 270f04d74..000000000 --- a/integrations/harbor/README.md +++ /dev/null @@ -1,114 +0,0 @@ - - -# Harbor Integration - -Fabric provides a Harbor `BaseAgent` wrapper at -`nemo_fabric.integrations.harbor:FabricAgent`. - -Use this when Harbor should keep ownership of evaluation semantics while Fabric -owns the selected agent harness invocation. - -## Ownership - -Harbor owns: - -- task and dataset materialization; -- environment/container lifecycle; -- verifier execution and reward calculation; -- Harbor job, trial, log, and artifact layout. - -Fabric owns: - -- Fabric agent config/profile resolution; -- selected harness invocation, such as Hermes SDK or CLI; -- normalized `RunRequest` / `RunResult` handling; -- Fabric artifacts, logs, patch metadata, and telemetry references. - -The integration shape is: - -```text -Harbor task/env -> FabricAgent -> Fabric SDK runner -> harness runtime -> Fabric result -> Harbor metadata/verifier -``` - -## Install - -Install the Harbor extra when the environment does not already provide Harbor: - -```bash -pip install "nemo-fabric[harbor]" -``` - -For local checkout development: - -```bash -python3 -m pip install -e . -python3 -m pip install -e ../harbor -``` - -## Using FabricAgent - -`FabricAgent` follows Harbor 0.16.1's external-agent contract. The runner and -config files must be installed or copied into the task environment: - -```bash -harbor run --path \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --model nvidia/nemotron-3-nano-30b-a3b \ - --ak fabric_config_path=/opt/fabric/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric/profiles/hermes.yaml"]' \ - --ae NVIDIA_API_KEY="$NVIDIA_API_KEY" -``` - -Important kwargs: - -- `fabric_config_path`: YAML config path visible inside the Harbor environment. -- `fabric_profile_paths`: YAML profile path or ordered profile-path list. -- `fabric_python`: Python command used for the sandbox-local SDK runner. -- `fabric_cwd`: optional working directory for Fabric commands. -- `fabric_install_command`: optional explicit install/bootstrap command. -- `fabric_timeout_sec`: optional timeout for Fabric install/run commands. - -Harbor passes the task instruction to Fabric as `RunRequest.input`. Harbor -metadata such as model name, skills directory, and MCP server definitions are -included under `RunRequest.context`. The sandbox-local runner loads YAML into -`FabricConfig` and `FabricProfileConfig`, then calls `FabricClient.run()`. -The normalized result is saved as `fabric-result.json`, and summary fields are -copied into `context.metadata["fabric"]`. - -## Multi-Harness Demo - -The runnable MVP demo includes explicit Harbor CLI commands for a -credential-free pipeline check plus real Hermes, Hermes-with-Relay, and Codex -variants. See [`demo/README.md`](demo/README.md) for the commands and recording -flow. - -## Local Test - -The lightweight test uses a fake Harbor environment and validates command -construction plus metadata propagation: - -```bash -pytest tests/python/test_harbor_integration.py -``` - -## SWE-Bench Test - -The Docker-backed SWE-Bench test is opt-in because it requires Docker, a local -SWE-Bench image, and a Harbor-generated task directory. Harbor still owns task -materialization and verification; Fabric only invokes the configured harness and -captures artifacts. - -```bash -RUN_FABRIC_HARBOR_SWEBENCH_DOCKER=1 pytest tests/e2e/test_harbor_swebench_task.py -``` - -To run the verifier path as well: - -```bash -RUN_FABRIC_HARBOR_SWEBENCH_DOCKER=1 \ -RUN_FABRIC_HARBOR_SWEBENCH_VERIFY=1 \ -pytest tests/e2e/test_harbor_swebench_task.py -``` diff --git a/integrations/harbor/demo/README.md b/integrations/harbor/demo/README.md deleted file mode 100644 index 55401eb08..000000000 --- a/integrations/harbor/demo/README.md +++ /dev/null @@ -1,218 +0,0 @@ -# Harbor Multi-Harness Demo - -This demo keeps one Harbor external-agent surface while Fabric selects the -execution harness from an ordered profile stack. Harbor owns the task, -container, verifier, reward, and run layout. `FabricAgent` invokes the Fabric -Python SDK inside the task container; it does not invoke the Fabric CLI. - -## Requirements - -- Python 3.12+ -- `uv` -- Docker -- this repository checkout, with the changes to test committed -- a host `codex login` for the real Codex variant - -The first image build can take several minutes. - -## Prepare the Build Context - -Harbor builds `task/environment/Dockerfile` with the environment directory as -its Docker context. Export committed `HEAD` there so the image installs the -exact Fabric revision under test: - -```bash -DEMO_DIR="$PWD/integrations/harbor/demo" -TASK_DIR="$DEMO_DIR/task" -RUNS_DIR="$DEMO_DIR/runs" -VENDOR_DIR="$TASK_DIR/environment/vendor/nemo-fabric" - -rm -rf "$TASK_DIR/environment/vendor" -mkdir -p "$VENDOR_DIR" -git archive HEAD | tar -x -C "$VENDOR_DIR" -``` - -Keep this shell open for the commands below. Use a new `--job-name`, or remove -the matching generated directory under `$RUNS_DIR`, before repeating a run. - -## Harbor Arguments - -| Argument | Meaning | -| --- | --- | -| `--path` | Harbor task directory containing `task.toml`, environment, and verifier | -| `--agent` | Harbor external agent class imported from the local Fabric package | -| `--ak` | Constructor argument passed by Harbor to `FabricAgent` | -| `fabric_config_path` | Base Fabric YAML path inside the task container | -| `fabric_profile_paths` | Ordered Fabric profile YAML paths inside the task container | -| `--model` | Harbor model selection; Fabric applies it after the file-backed profiles | -| `--ae` | Environment variable passed to the Harbor agent inside the container | -| `--mounts` | Host-to-container mounts managed by Harbor | -| `--extra-docker-compose` | Compose overlay applied to the Harbor task environment | -| `--job-name` | Stable Harbor output directory name for this variant | -| `--force-build` | Rebuild the Harbor task image from the prepared context | - -The JSON array passed to `fabric_profile_paths` is one Harbor `--ak` value. It -is not a Fabric CLI argument. - -## 1. Credential-Free Smoke - -This proves Harbor task setup, the external agent import, sandbox-local SDK -execution, Fabric profile resolution, workspace mutation, and verification: - -```bash -uv run --extra harbor harbor run \ - --path "$TASK_DIR" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/smoke.yaml"]' \ - --job-name fabric-smoke \ - --jobs-dir "$RUNS_DIR" \ - --n-concurrent 1 \ - --n-attempts 1 \ - --force-build -``` - -Expected Harbor summary: one trial, zero exceptions, and mean reward `1.000`. - -## 2. Hermes CLI - -The Harbor command is unchanged except for model selection, the credential, -and the Fabric profile path: - -```bash -export NVIDIA_API_KEY=... - -uv run --extra harbor harbor run \ - --path "$TASK_DIR" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/hermes.yaml"]' \ - --model nvidia/nemotron-3-nano-30b-a3b \ - --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" \ - --job-name fabric-hermes \ - --jobs-dir "$RUNS_DIR" \ - --n-concurrent 1 \ - --n-attempts 1 \ - --force-build -``` - -## 3. Hermes with Relay Telemetry - -This composes two ordered Fabric profiles. The first selects Hermes; the second -adds Relay OpenInference traces, ATOF events, and an ATIF trajectory without -changing the Harbor agent. Start Phoenix on the host before the Harbor run: - -```bash -docker rm -f fabric-phoenix 2>/dev/null || true -docker run --rm --detach \ - --name fabric-phoenix \ - --publish 6006:6006 \ - arizephoenix/phoenix:latest - -until curl --fail --silent http://localhost:6006 >/dev/null; do sleep 1; done -``` - -Visit `http://localhost:6006` in a browser. - -The telemetry profile sends OTLP/HTTP traces from the Harbor task container to -Phoenix at `host.docker.internal`. The checked-in Compose overlay maps that name -to Docker's host gateway, including on Linux. Then run: - -```bash -uv run --extra harbor harbor run \ - --path "$TASK_DIR" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/hermes.yaml","/opt/fabric-demo/profiles/telemetry.yaml"]' \ - --model nvidia/nemotron-3-nano-30b-a3b \ - --ae "NVIDIA_API_KEY=$NVIDIA_API_KEY" \ - --extra-docker-compose "$DEMO_DIR/host-gateway.compose.yaml" \ - --job-name fabric-hermes-relay \ - --jobs-dir "$RUNS_DIR" \ - --n-concurrent 1 \ - --n-attempts 1 \ - --force-build -``` - -Keep Phoenix open. The completed run appears as an OpenInference trace in its -Traces view. Relay also writes the portable ATOF and ATIF records into Harbor's -collected agent logs: - -```bash -find "$RUNS_DIR/fabric-hermes-relay" \ - -path '*/agent/fabric-artifacts/hermes-relay/relay/events.atof.jsonl' \ - -print -exec sed -n '1,5p' {} \; - -find "$RUNS_DIR/fabric-hermes-relay" \ - -path '*/agent/fabric-artifacts/hermes-relay/relay/*.atif.json' \ - -print -exec python -m json.tool {} \; -``` - -## 4. Codex CLI - -Codex uses the same Harbor agent and task. For this local Docker demo, Harbor -mounts the host Codex login as a read-only secret. The setup command copies it -into a writable container-local `CODEX_HOME`; Fabric only inherits that -environment and never reads the credential. - -```bash -codex login status - -CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}" -test -f "$CODEX_HOME_DIR/auth.json" -CODEX_AUTH_MOUNT="[{\"type\":\"bind\",\"source\":\"$CODEX_HOME_DIR/auth.json\",\"target\":\"/run/secrets/codex-auth.json\",\"read_only\":true}]" - -uv run --extra harbor harbor run \ - --path "$TASK_DIR" \ - --agent nemo_fabric.integrations.harbor:FabricAgent \ - --ak fabric_config_path=/opt/fabric-demo/agent.yaml \ - --ak 'fabric_profile_paths=["/opt/fabric-demo/profiles/codex.yaml"]' \ - --ak 'fabric_install_command=mkdir -p "$CODEX_HOME" && cp /run/secrets/codex-auth.json "$CODEX_HOME/auth.json"' \ - --model openai/gpt-5.4 \ - --ae CODEX_HOME=/tmp/fabric-codex-home \ - --mounts "$CODEX_AUTH_MOUNT" \ - --job-name fabric-codex \ - --jobs-dir "$RUNS_DIR" \ - --n-concurrent 1 \ - --n-attempts 1 \ - --force-build -``` - -The image pins Codex CLI `0.142.4`. Harbor passes the selected model to the -Fabric SDK as the final typed profile, and the Codex profile pins a compatible -reasoning effort. The profile uses Codex `danger-full-access` because Harbor's -task container is the outer sandbox and nested Linux namespace creation is not -available there. The auth mount grants that trusted container access to your -Codex account for this run. - -## Inspect the Result - -Harbor records Fabric's normalized result in the trial's agent logs. For the -smoke variant: - -```bash -find "$RUNS_DIR/fabric-smoke" -path '*/agent/fabric-result.json' -print -exec cat {} \; -cat "$RUNS_DIR/fabric-smoke/result.json" -uv run --extra harbor harbor view "$RUNS_DIR" -``` - -Check `status`, `profiles`, `harness`, `adapter_id`, runtime and invocation IDs, -artifacts, telemetry, Harbor exceptions, and reward. A successful smoke run has -Fabric status `succeeded` and Harbor mean reward `1.0`. - -After the demo, remove the generated build-context copy: - -```bash -rm -rf "$TASK_DIR/environment/vendor" -``` - -## Recording Flow - -1. Show the common `--agent` and `fabric_config_path` values. -2. Run the credential-free smoke and inspect `fabric-result.json`. -3. Run Hermes, then Codex, changing only profile, model, and credential - provisioning. -4. Start Phoenix, run Hermes plus telemetry, and open the resulting - OpenInference trace. -5. Show the same run's ATOF events and ATIF trajectory from Harbor's logs. -6. Open all four jobs with `harbor view`. diff --git a/integrations/harbor/demo/host-gateway.compose.yaml b/integrations/harbor/demo/host-gateway.compose.yaml deleted file mode 100644 index addce5e98..000000000 --- a/integrations/harbor/demo/host-gateway.compose.yaml +++ /dev/null @@ -1,4 +0,0 @@ -services: - main: - extra_hosts: - - host.docker.internal=host-gateway diff --git a/integrations/harbor/demo/task/environment/fabric/profiles/codex.yaml b/integrations/harbor/demo/task/environment/fabric/profiles/codex.yaml deleted file mode 100644 index 0ec5cfe06..000000000 --- a/integrations/harbor/demo/task/environment/fabric/profiles/codex.yaml +++ /dev/null @@ -1,25 +0,0 @@ -schema_version: fabric.profile/v1alpha1 -name: codex -description: Run Codex CLI against the Harbor task workspace. - -harness: - adapter_id: nvidia.fabric.codex.cli - resolution: preinstalled - settings: - cwd: /app - sandbox: danger-full-access - skip_git_repo_check: true - config_overrides: - model_reasoning_effort: high - -runtime: - mode: oneshot - transport: cli - artifacts: /logs/agent/fabric-artifacts/codex - -environment: - workspace: /app - artifacts: /logs/agent/fabric-artifacts/codex - -telemetry: - enabled: false diff --git a/integrations/harbor/demo/task/environment/fabric/profiles/smoke.yaml b/integrations/harbor/demo/task/environment/fabric/profiles/smoke.yaml deleted file mode 100644 index c1624c928..000000000 --- a/integrations/harbor/demo/task/environment/fabric/profiles/smoke.yaml +++ /dev/null @@ -1,14 +0,0 @@ -schema_version: fabric.profile/v1alpha1 -name: smoke -description: Deterministic end-to-end Harbor and Fabric pipeline check. - -harness: - adapter_id: demo.fabric.scripted - resolution: preinstalled - -runtime: - artifacts: /logs/agent/fabric-artifacts/smoke - -environment: - workspace: /app - artifacts: /logs/agent/fabric-artifacts/smoke diff --git a/integrations/harbor/demo/task/tests/test.sh b/integrations/harbor/demo/task/tests/test.sh deleted file mode 100755 index e3762f699..000000000 --- a/integrations/harbor/demo/task/tests/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if python3 /tests/verify.py; then - echo 1 > /logs/verifier/reward.txt -else - echo 0 > /logs/verifier/reward.txt -fi diff --git a/python/pyproject.toml b/python/pyproject.toml index eda6fc464..d2661f0be 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -9,7 +9,10 @@ build-backend = "maturin" name = "nemo-fabric-runtime" description = "Python SDK and native bindings for NeMo Fabric" requires-python = ">=3.11" -dependencies = [] +dependencies = [ + "pydantic>=2.10,<3", + "typing-extensions>=4.12", +] dynamic = ["version"] [tool.maturin] diff --git a/python/src/nemo_fabric/__init__.py b/python/src/nemo_fabric/__init__.py index e9987db2d..561fcb332 100644 --- a/python/src/nemo_fabric/__init__.py +++ b/python/src/nemo_fabric/__init__.py @@ -3,7 +3,7 @@ """Python SDK surface for NeMo Fabric.""" -from nemo_fabric.client import FabricClient +from nemo_fabric.client import Fabric from nemo_fabric.errors import ( FabricCapabilityError, FabricConfigError, @@ -12,7 +12,23 @@ FabricRuntimeError, FabricStateError, ) -from nemo_fabric.session import Session, SessionStatus +from nemo_fabric.runtime import Runtime, RuntimeStatus +from nemo_fabric.models import ( + EnvironmentConfig, + FabricBaseModel, + FabricConfig, + FabricProfileConfig, + HarnessConfig, + McpConfig, + McpServerConfig, + MetadataConfig, + ModelConfig, + ProfileRegistryConfig, + RunRequest, + RuntimeConfig, + SkillConfig, + TelemetryConfig, +) from nemo_fabric.types import ( AdapterInfo, ArtifactManifest, @@ -20,22 +36,12 @@ DoctorCheck, DoctorReport, EffectiveConfig, - EnvironmentConfig, ErrorInfo, - FabricConfig, FabricEvent, - FabricProfileConfig, - HarnessConfig, - MetadataConfig, RunPlan, - RunRequest, RunResult, RuntimeCapabilities, RuntimeHandle, - RuntimeConfig, - RuntimeUpdate, - RuntimeUpdateResult, - SessionInfo, TelemetryRef, ) @@ -48,15 +54,20 @@ "EffectiveConfig", "EnvironmentConfig", "ErrorInfo", + "Fabric", + "FabricBaseModel", "FabricConfig", + "FabricProfileConfig", "FabricCapabilityError", - "FabricClient", "FabricConfigError", "FabricError", "FabricEvent", - "FabricProfileConfig", "HarnessConfig", + "McpConfig", + "McpServerConfig", "MetadataConfig", + "ModelConfig", + "ProfileRegistryConfig", "FabricNativeUnavailableError", "FabricRuntimeError", "FabricStateError", @@ -66,10 +77,9 @@ "RuntimeCapabilities", "RuntimeHandle", "RuntimeConfig", - "RuntimeUpdate", - "RuntimeUpdateResult", - "Session", - "SessionInfo", - "SessionStatus", + "Runtime", + "RuntimeStatus", + "SkillConfig", + "TelemetryConfig", "TelemetryRef", ] diff --git a/python/src/nemo_fabric/_config_sources.py b/python/src/nemo_fabric/_config_sources.py index ead0a8462..7b3337d33 100644 --- a/python/src/nemo_fabric/_config_sources.py +++ b/python/src/nemo_fabric/_config_sources.py @@ -11,12 +11,13 @@ from typing import Any from nemo_fabric.errors import FabricConfigError -from nemo_fabric.types import FabricConfig, FabricProfileConfig +from nemo_fabric.models import FabricConfig, FabricProfileConfig PathSource = str | os.PathLike[str] -AgentSource = PathSource | FabricConfig -ProfileSource = str | FabricProfileConfig +TypedConfigSource = FabricConfig +AgentSource = PathSource | TypedConfigSource PathProfiles = str | Sequence[str] +TypedProfiles = Sequence[FabricProfileConfig] def is_config_source(value: Any) -> bool: @@ -31,7 +32,9 @@ def path_arg(value: Any) -> str: "agent mappings are not accepted directly; " "use FabricConfig.from_mapping(...) first" ) - raise FabricConfigError("agent must be a path-like source or FabricConfig") + raise FabricConfigError( + "agent must be a path-like source or FabricConfig" + ) def path_profiles(profiles: PathProfiles | None) -> list[str]: @@ -51,7 +54,7 @@ def path_profiles(profiles: PathProfiles | None) -> list[str]: def config_profiles( - profiles: Sequence[FabricProfileConfig] | None, + profiles: TypedProfiles | None, ) -> list[FabricProfileConfig]: if profiles is None: return [] @@ -62,22 +65,21 @@ def config_profiles( values = list(profiles) if not all(isinstance(profile, FabricProfileConfig) for profile in values): raise FabricConfigError( - "FabricConfig profiles must contain FabricProfileConfig values; " - "use FabricProfileConfig.from_mapping(...) for mappings" + "FabricConfig profiles must contain FabricProfileConfig values" ) return values def validate_base_dir(agent: AgentSource, base_dir: PathSource | None) -> str | None: - if not isinstance(agent, FabricConfig): + if not is_config_source(agent): if base_dir is not None: - raise FabricConfigError("base_dir is only valid with a FabricConfig source") + raise FabricConfigError("base_dir is only valid with a typed config source") return None return None if base_dir is None else os.fspath(base_dir) -def config_json(config: FabricConfig) -> str: - if not isinstance(config, FabricConfig): +def config_json(config: TypedConfigSource) -> str: + if not is_config_source(config): raise FabricConfigError("config must be a FabricConfig") return json.dumps(config.to_mapping()) diff --git a/python/src/nemo_fabric/client.py b/python/src/nemo_fabric/client.py index 6ba120bdc..9224fdb5a 100644 --- a/python/src/nemo_fabric/client.py +++ b/python/src/nemo_fabric/client.py @@ -5,16 +5,17 @@ from __future__ import annotations +import asyncio import importlib import json from collections.abc import Mapping, Sequence -from pathlib import Path from typing import Any, overload from nemo_fabric._config_sources import ( AgentSource, PathProfiles, PathSource, + TypedProfiles, config_json, config_profiles, is_config_source, @@ -24,27 +25,23 @@ validate_base_dir, ) from nemo_fabric.errors import ( - FabricCapabilityError, FabricConfigError, FabricError, FabricNativeUnavailableError, FabricRuntimeError, ) -from nemo_fabric.session import ( - Session, +from nemo_fabric.models import FabricConfig, RunRequest +from nemo_fabric.runtime import ( + Runtime, _call_blocking, _json_mapping, - _require_session_runtime, _run_native_lifecycle, _run_request_payload, ) from nemo_fabric.types import ( DoctorReport, EffectiveConfig, - FabricConfig, - FabricProfileConfig, RunPlan, - RunRequest, RunResult, ) @@ -54,34 +51,24 @@ _native = None -class FabricClient: +class Fabric: """Primary Python entrypoint for NeMo Fabric. The client accepts either a path-backed agent package or a typed - ``FabricConfig``. Path-backed sources select profiles by name; typed - sources accept ordered ``FabricProfileConfig`` values and may use + ``FabricConfig``. Path-backed sources select profiles by name; typed sources + accept ordered ``FabricProfileConfig`` values and may use ``base_dir`` to resolve relative paths. All inspection and execution APIs return typed, read-only mapping models. - ``FabricClient`` is native-only. The ``fabric`` CLI is a separate public + ``Fabric`` is native-only. The ``fabric`` CLI is a separate public surface over the same Rust core; SDK calls raise ``FabricNativeUnavailableError`` when the native extension is not installed. - The client is also an asynchronous context manager. Leaving the context - does not stop independently created sessions; use each ``Session`` as - an asynchronous context manager or call ``Session.stop()`` explicitly. - See the Getting Started overview for runnable one-shot, typed-config, and multi-turn examples. """ - async def __aenter__(self) -> "FabricClient": - return self - - async def __aexit__(self, exc_type: object, exc: object, traceback: object) -> None: - return None - @overload def resolve( self, @@ -96,7 +83,7 @@ def resolve( self, agent: FabricConfig, *, - profiles: Sequence[FabricProfileConfig] | None = None, + profiles: TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> EffectiveConfig: ... @@ -104,7 +91,7 @@ def resolve( self, agent: AgentSource, *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, + profiles: PathProfiles | TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> EffectiveConfig: """Resolve an agent source and its ordered profile overlays. @@ -115,13 +102,14 @@ def resolve( Args: agent: Agent-package directory or config-file path, or a typed - ``FabricConfig``. Raw mappings are not accepted; convert - them with ``FabricConfig.from_mapping()``. + ``FabricConfig``. Raw + mappings are not accepted; convert them with + ``FabricConfig.from_mapping()``. profiles: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. + config. Valid only when ``agent`` is a typed config source. Returns: The normalized ``EffectiveConfig`` snapshot. @@ -167,7 +155,7 @@ def plan( self, agent: FabricConfig, *, - profiles: Sequence[FabricProfileConfig] | None = None, + profiles: TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> RunPlan: ... @@ -175,23 +163,24 @@ def plan( self, agent: AgentSource, *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, + profiles: PathProfiles | TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> RunPlan: """Resolve an agent source into an immutable execution plan. Planning applies profiles, resolves the selected adapter, and reports - the runtime capabilities that gate session, service, streaming, update, - cancellation, and concurrency APIs. It does not start the runtime. + optional runtime capabilities such as streaming, updates, and + cancellation. It does not start the runtime. Args: agent: Agent-package directory or config-file path, or a typed - ``FabricConfig``. Raw mappings are not accepted. + ``FabricConfig``. Raw + mappings are not accepted. profiles: One profile name or an ordered sequence of names for a path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. + config. Valid only when ``agent`` is a typed config source. Returns: A ``RunPlan`` containing the effective config, adapter, and @@ -238,7 +227,7 @@ async def doctor( self, agent: FabricConfig, *, - profiles: Sequence[FabricProfileConfig] | None = None, + profiles: TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> DoctorReport: ... @@ -246,13 +235,13 @@ async def doctor( self, agent: AgentSource, *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, + profiles: PathProfiles | TypedProfiles | None = None, base_dir: PathSource | None = None, ) -> DoctorReport: """Diagnose a planned agent without starting its runtime. Doctor checks the resolved adapter, capability mappings, and declared - environment requirements. Blocking native work runs off the event loop. + environment requirements using the native Fabric core. Args: agent: Agent-package directory or config-file path, or a typed @@ -261,7 +250,7 @@ async def doctor( path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. + config. Valid only when ``agent`` is a typed config source. Returns: A ``DoctorReport`` with aggregate status and ordered checks. @@ -305,12 +294,7 @@ async def run( profiles: PathProfiles | None = None, base_dir: None = None, input: Any = None, - input_file: str | Path | None = None, - request: RunRequest | Mapping[str, Any] | None = None, - request_file: str | Path | None = None, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, + request: RunRequest | None = None, ) -> RunResult: ... @overload @@ -318,39 +302,27 @@ async def run( self, agent: FabricConfig, *, - profiles: Sequence[FabricProfileConfig] | None = None, + profiles: TypedProfiles | None = None, base_dir: PathSource | None = None, input: Any = None, - input_file: str | Path | None = None, - request: RunRequest | Mapping[str, Any] | None = None, - request_file: str | Path | None = None, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, + request: RunRequest | None = None, ) -> RunResult: ... async def run( self, agent: AgentSource, *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, + profiles: PathProfiles | TypedProfiles | None = None, base_dir: PathSource | None = None, input: Any = None, - input_file: str | Path | None = None, - request: RunRequest | Mapping[str, Any] | None = None, - request_file: str | Path | None = None, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, + request: RunRequest | None = None, ) -> RunResult: """Execute one complete start, invoke, and stop lifecycle. - Exactly zero or one of ``input``, ``input_file``, ``request``, and - ``request_file`` may be supplied. Omitting all four produces an empty - text input. A complete ``request`` or ``request_file`` cannot be mixed - with separate ``request_id``, ``context``, or ``overrides`` fields. - Blocking native lifecycle calls run off the event loop, and Fabric - attempts to stop a started runtime even when invocation fails. + ``input`` and ``request`` are mutually exclusive. Omitting both produces + an empty text input. Use ``RunRequest`` when the invocation needs a + caller-owned request ID, context, or overrides. + Fabric attempts to stop a started runtime even when invocation fails. Args: agent: Agent-package directory or config-file path, or a typed @@ -359,22 +331,16 @@ async def run( path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. + config. Valid only when ``agent`` is a typed config source. input: JSON-compatible invocation input. - input_file: UTF-8 file whose contents become the invocation input. - request: Complete ``RunRequest`` or compatible mapping. - request_file: UTF-8 JSON file containing a complete request. - request_id: Caller-owned request identifier. Fabric generates one - when omitted. - context: Caller-owned, JSON-compatible request metadata. - overrides: JSON-compatible invocation-scoped config overrides. + request: Complete validated ``RunRequest``. Returns: The normalized ``RunResult``, including output, artifacts, telemetry references, lifecycle events, and structured error data. Raises: - FabricConfigError: If sources are combined, request data is not + FabricConfigError: If input and request are combined, request data is not JSON-compatible, or config resolution fails. FabricNativeUnavailableError: If the native extension is not installed. @@ -389,12 +355,7 @@ async def run( ) request_payload = _run_request_payload( input=input, - input_file=input_file, request=request, - request_file=request_file, - request_id=request_id, - context=context, - overrides=overrides, ) native = self._require_native_module("run") return RunResult.from_mapping( @@ -402,42 +363,37 @@ async def run( ) @overload - async def start_session( + async def start_runtime( self, agent: PathSource, *, profiles: PathProfiles | None = None, base_dir: None = None, - session_id: str | None = None, overrides: Mapping[str, Any] | None = None, - ) -> Session: ... + ) -> Runtime: ... @overload - async def start_session( + async def start_runtime( self, agent: FabricConfig, *, - profiles: Sequence[FabricProfileConfig] | None = None, + profiles: TypedProfiles | None = None, base_dir: PathSource | None = None, - session_id: str | None = None, overrides: Mapping[str, Any] | None = None, - ) -> Session: ... + ) -> Runtime: ... - async def start_session( + async def start_runtime( self, agent: AgentSource, *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, + profiles: PathProfiles | TypedProfiles | None = None, base_dir: PathSource | None = None, - session_id: str | None = None, overrides: Mapping[str, Any] | None = None, - ) -> Session: - """Start a stateful, multi-turn session runtime. + ) -> Runtime: + """Start a stateful runtime for one or more ordered invocations. - The resolved plan must declare the session capability. Each call starts - a new runtime. ``session_id`` is the stable conversation identifier; if - omitted, the new runtime identifier is used. Session-scoped overrides - are recursively merged below invocation-scoped overrides. + Each call starts a new logical runtime. Runtime-scoped overrides are + recursively merged below invocation-scoped overrides. Args: agent: Agent-package directory or config-file path, or a typed @@ -446,117 +402,60 @@ async def start_session( path-backed source. For a typed source, an ordered sequence of ``FabricProfileConfig`` values. base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. - session_id: Stable caller-owned conversation identifier. Defaults - to the generated runtime identifier. + config. Valid only when ``agent`` is a typed config source. overrides: JSON-compatible overrides applied to every invocation - in the session unless superseded by invocation overrides. + in the runtime unless superseded by invocation overrides. Returns: - An active ``Session``. Use it as an asynchronous context + An active ``Runtime``. Use it as an asynchronous context manager to guarantee runtime shutdown. Raises: FabricConfigError: If inputs or overrides are invalid. FabricNativeUnavailableError: If the native extension is not installed. - FabricCapabilityError: If the resolved runtime does not support - sessions. FabricRuntimeError: If runtime startup fails. """ - session_overrides = _json_mapping(overrides, "session overrides") + runtime_overrides = _json_mapping(overrides, "runtime overrides") plan = await _call_blocking( lambda: self.plan( # type: ignore[arg-type] agent, profiles=profiles, base_dir=base_dir ) ) - _require_session_runtime(plan, "start_session") - native = self._require_native_module("start_session") + native = self._require_native_module("start_runtime") + started_runtime: dict[str, Any] | None = None + + def start() -> dict[str, Any]: + nonlocal started_runtime + started_runtime = json.loads(native.start_runtime(json.dumps(plan.to_mapping()))) + return started_runtime + try: - runtime = await _call_blocking( - lambda: json.loads(native.start_runtime(json.dumps(plan.to_mapping()))) - ) + runtime = await _call_blocking(start) + except asyncio.CancelledError: + if started_runtime is not None: + try: + await _call_blocking( + lambda: json.loads( + native.stop_runtime( + json.dumps(plan.to_mapping()), + json.dumps(started_runtime), + ) + ) + ) + except Exception: + pass + raise except FabricError: raise except Exception as error: raise FabricRuntimeError(str(error), stage="start") from error - return Session( + return Runtime( client=self, plan=plan, runtime=runtime, - overrides=session_overrides, - session_id=session_id, - ) - - @overload - async def start_service( - self, - agent: PathSource, - *, - profiles: PathProfiles | None = None, - base_dir: None = None, - service_id: str | None = None, - overrides: Mapping[str, Any] | None = None, - ) -> Any: ... - - @overload - async def start_service( - self, - agent: FabricConfig, - *, - profiles: Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - service_id: str | None = None, - overrides: Mapping[str, Any] | None = None, - ) -> Any: ... - - async def start_service( - self, - agent: AgentSource, - *, - profiles: PathProfiles | Sequence[FabricProfileConfig] | None = None, - base_dir: PathSource | None = None, - service_id: str | None = None, - overrides: Mapping[str, Any] | None = None, - ) -> Any: - """Validate a service request and report the unsupported operation. - - Service handles are part of the reserved SDK contract, but the current - Fabric runtime does not implement service creation. This method validates - inputs and resolves the plan before raising - ``FabricCapabilityError`` with code ``service_not_supported``. - - Args: - agent: Agent-package directory or config-file path, or a typed - ``FabricConfig``. - profiles: One profile name or an ordered sequence of names for a - path-backed source. For a typed source, an ordered sequence of - ``FabricProfileConfig`` values. - base_dir: Base directory for resolving relative paths in a typed - config. Valid only when ``agent`` is a ``FabricConfig``. - service_id: Reserved caller-owned service identifier. - overrides: JSON-compatible service-scoped config overrides. - - Raises: - FabricConfigError: If inputs or overrides are invalid. - FabricNativeUnavailableError: If the native extension is not - installed. - FabricCapabilityError: Always, because service creation is not yet - implemented. - """ - - _json_mapping(overrides, "service overrides") - plan = await _call_blocking( - lambda: self.plan( # type: ignore[arg-type] - agent, profiles=profiles, base_dir=base_dir - ) - ) - raise FabricCapabilityError( - "service mode is not implemented by this Fabric runtime", - stage="start", - code="service_not_supported", - details={"service": plan.capabilities.service, "service_id": service_id}, + overrides=runtime_overrides, ) def _native_module(self) -> Any | None: diff --git a/python/src/nemo_fabric/errors.py b/python/src/nemo_fabric/errors.py index b82f3fbf9..401704a9a 100644 --- a/python/src/nemo_fabric/errors.py +++ b/python/src/nemo_fabric/errors.py @@ -59,7 +59,7 @@ class FabricRuntimeError(FabricError): class FabricStateError(FabricRuntimeError): - """Operation rejected because a local session handle is in the wrong state.""" + """Operation rejected because a local runtime is in the wrong state.""" class FabricCapabilityError(FabricRuntimeError): diff --git a/python/src/nemo_fabric/integrations/harbor/README.md b/python/src/nemo_fabric/integrations/harbor/README.md index bd3d427aa..745af2bc5 100644 --- a/python/src/nemo_fabric/integrations/harbor/README.md +++ b/python/src/nemo_fabric/integrations/harbor/README.md @@ -5,62 +5,40 @@ SPDX-License-Identifier: Apache-2.0 # Harbor Integration -This package lets Harbor use Fabric as its agent execution layer. The public -Harbor entrypoint is: +The public Harbor entrypoint is: ```text nemo_fabric.integrations.harbor:FabricAgent ``` -Harbor continues to own task materialization, the task environment, verification, -rewards, and job layout. Fabric resolves its config and profiles, invokes the -selected harness, and returns a normalized result. +`FabricAgent` builds a `HarborRunSpec`, uploads it with +`BaseEnvironment.upload_file()`, and invokes +`nemo_fabric.integrations.harbor.runner` inside the task environment. The +runner validates one complete YAML config as `FabricConfig`, clones it, applies +Harbor's model, MCP, and skill inputs, and calls `Fabric.run()` directly. -## Execution Flow +The sandbox writes a normalized `RunResult`. The host downloads and validates +that result before populating `AgentContext.metadata["fabric"]`. Each run uses +unique specification and result paths. -```text -harbor run - -> FabricAgent on the host - -> Harbor BaseEnvironment.exec(...) - -> python -m nemo_fabric.integrations.harbor.runner in the task environment - -> FabricClient.run(...) - -> selected Fabric harness adapter -``` - -`FabricAgent` writes a JSON run specification into the Harbor environment. The -runner loads the referenced YAML files as `FabricConfig` and -`FabricProfileConfig`, applies Harbor's model selection as the final profile, -and invokes the Fabric Python SDK. This path does not invoke the Fabric CLI. - -The runner must execute inside the task environment because that is where the -harness reads and modifies the task workspace. Fabric, its adapter, and all -referenced config/profile paths must therefore be available there. - -## Package Layout - -- `__init__.py` implements the Harbor `BaseAgent` integration, command - construction, and result propagation. -- `runner.py` is the sandbox-side SDK entrypoint. - -The normalized Fabric result is written to `/logs/agent/fabric-result.json` by -default, downloaded to the Harbor agent log directory, and summarized in -`AgentContext.metadata["fabric"]`. A failed setup, runner invocation, or result -transfer fails the Harbor agent run rather than producing partial metadata. +## Package layout -## Configuration +- `models.py` defines `HarborRunSpec` and `HarborMcpServer`. +- `fabric_agent.py` implements the Harbor `BaseAgent` wrapper, transport, and + result handling. +- `runner.py` composes the final Fabric config and owns the sandbox-local SDK + call. +- `__init__.py` exports `FabricAgent` as the package entrypoint. -`FabricAgent` accepts these Fabric-specific constructor arguments through -Harbor's `--ak` flags: +## Constructor arguments -- `fabric_config_path`: Fabric YAML config path inside the task environment. -- `fabric_profile_paths`: one profile path or an ordered list of profile paths. -- `fabric_python`: Python executable used to start the runner. -- `fabric_cwd`: optional working directory for installation and execution. -- `fabric_install_command`: optional environment bootstrap command. +- `fabric_config_path`: complete Fabric YAML config inside the task environment; +- `fabric_python`: Python executable used to start the runner; +- `fabric_cwd`: optional working directory for installation and execution; +- `fabric_install_command`: optional environment bootstrap command; - `fabric_timeout_sec`: optional timeout for bootstrap and execution. -- `fabric_spec_path` and `fabric_result_path`: internal exchange-file paths. -See [`integrations/harbor/README.md`](../../../../../integrations/harbor/README.md) +See [`examples/harbor/README.md`](../../../../../examples/harbor/README.md) for installation and usage, and -[`integrations/harbor/demo/README.md`](../../../../../integrations/harbor/demo/README.md) -for runnable Harbor CLI examples. +[`examples/harbor/demo/README.md`](../../../../../examples/harbor/demo/README.md) +for runnable Harbor commands. diff --git a/python/src/nemo_fabric/integrations/harbor/__init__.py b/python/src/nemo_fabric/integrations/harbor/__init__.py index 3c10cebd0..6ce7d0cd5 100644 --- a/python/src/nemo_fabric/integrations/harbor/__init__.py +++ b/python/src/nemo_fabric/integrations/harbor/__init__.py @@ -1,179 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Harbor consumer integration for NeMo Fabric.""" +"""Harbor integration for NeMo Fabric.""" -from __future__ import annotations +from nemo_fabric.integrations.harbor.fabric_agent import FabricAgent -import json -import shlex -import uuid -from collections.abc import Sequence -from pathlib import Path -from typing import Any - -from harbor.agents.base import BaseAgent -from harbor.environments.base import BaseEnvironment -from harbor.models.agent.context import AgentContext - - -class FabricAgent(BaseAgent): - """Harbor agent wrapper that delegates harness execution to Fabric. - - Harbor owns task materialization, environment lifecycle, verification, and - reward calculation. Fabric owns the selected agent harness invocation. - """ - - def __init__( - self, - logs_dir: Path, - fabric_config_path: str, - fabric_profile_paths: str | Sequence[str] | None = None, - fabric_python: str = "python3", - fabric_spec_path: str = "/tmp/fabric-run.json", - fabric_result_path: str = "/logs/agent/fabric-result.json", - fabric_install_command: str | None = None, - fabric_cwd: str | None = None, - fabric_timeout_sec: int | None = None, - *args: Any, - **kwargs: Any, - ) -> None: - super().__init__(logs_dir=logs_dir, *args, **kwargs) - self.fabric_config_path = fabric_config_path - self.fabric_profile_paths = normalize_paths(fabric_profile_paths) - self.fabric_python = fabric_python - self.fabric_spec_path = fabric_spec_path - self.fabric_result_path = fabric_result_path - self.fabric_install_command = fabric_install_command - self.fabric_cwd = fabric_cwd - self.fabric_timeout_sec = fabric_timeout_sec - - @staticmethod - def name() -> str: - return "fabric" - - def version(self) -> str | None: - return "0.1.0" - - async def setup(self, environment: BaseEnvironment) -> None: - result = await environment.exec("mkdir -p /logs/agent /tmp", timeout_sec=30) - ensure_success("Fabric setup failed", result) - if self.fabric_install_command: - result = await environment.exec( - self.fabric_install_command, - cwd=self.fabric_cwd, - env=self.extra_env, - timeout_sec=self.fabric_timeout_sec, - ) - ensure_success("Fabric install command failed", result) - - async def run( - self, - instruction: str, - environment: BaseEnvironment, - context: AgentContext, - ) -> None: - spec = { - "config_path": self.fabric_config_path, - "profile_paths": self.fabric_profile_paths, - "request": self._build_request(instruction), - } - result = await environment.exec( - write_json_command(self.fabric_spec_path, spec), - cwd=self.fabric_cwd, - timeout_sec=30, - ) - ensure_success("Fabric run specification write failed", result) - - result = await environment.exec( - fabric_runner_command( - fabric_python=self.fabric_python, - spec_path=self.fabric_spec_path, - result_path=self.fabric_result_path, - ), - cwd=self.fabric_cwd, - env=self.extra_env, - timeout_sec=self.fabric_timeout_sec, - ) - ensure_success("Fabric run failed", result) - - host_result_path = self.logs_dir / "fabric-result.json" - await environment.download_file(self.fabric_result_path, host_result_path) - populate_context_from_result(context, host_result_path) - - def _build_request(self, instruction: str) -> dict[str, Any]: - return { - "request_id": f"harbor-{uuid.uuid4()}", - "input": instruction, - "context": { - "source": "harbor", - "model_name": self.model_name, - "skills_dir": self.skills_dir, - "mcp_servers": [dump_mcp_server(server) for server in self.mcp_servers], - }, - } - - -def normalize_paths(paths: str | Sequence[str] | None) -> list[str]: - if paths is None: - return [] - if isinstance(paths, str): - return [paths] - return [path for path in paths if path] - - -def write_json_command(path: str, payload: dict[str, Any]) -> str: - encoded = json.dumps(payload, indent=2) - return f"cat > {shlex.quote(path)} <<'FABRIC_JSON'\n{encoded}\nFABRIC_JSON" - - -def fabric_runner_command( - *, - fabric_python: str, - spec_path: str, - result_path: str, -) -> str: - parts = [ - shlex.quote(fabric_python), - "-m", - "nemo_fabric.integrations.harbor.runner", - "--spec", - shlex.quote(spec_path), - "--result", - shlex.quote(result_path), - ] - return " ".join(parts) - - -def ensure_success(message: str, result: Any) -> None: - if getattr(result, "return_code", 1) == 0: - return - stdout = getattr(result, "stdout", "") - stderr = getattr(result, "stderr", "") - raise RuntimeError(f"{message} (exit {result.return_code}): {stderr or stdout}") - - -def dump_mcp_server(server: Any) -> dict[str, Any]: - if hasattr(server, "model_dump"): - return server.model_dump(mode="json") - if hasattr(server, "dict"): - return server.dict() - return dict(server) - - -def populate_context_from_result(context: AgentContext, path: Path) -> None: - result = json.loads(path.read_text(encoding="utf-8")) - if context.metadata is None: - context.metadata = {} - context.metadata["fabric"] = { - "status": result.get("status"), - "runtime_id": result.get("runtime_id"), - "invocation_id": result.get("invocation_id"), - "request_id": result.get("request_id"), - "profiles": result.get("profiles", []), - "harness": result.get("harness"), - "adapter_id": result.get("adapter_id"), - "artifacts": result.get("artifacts", {}), - "telemetry": result.get("telemetry"), - "error": result.get("error"), - } +__all__ = ["FabricAgent"] diff --git a/python/src/nemo_fabric/integrations/harbor/fabric_agent.py b/python/src/nemo_fabric/integrations/harbor/fabric_agent.py new file mode 100644 index 000000000..d13070ecf --- /dev/null +++ b/python/src/nemo_fabric/integrations/harbor/fabric_agent.py @@ -0,0 +1,183 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Harbor agent implementation backed by the Fabric Python SDK.""" + +from __future__ import annotations + +import json +import shlex +import uuid +from pathlib import Path +from typing import Any + +from nemo_fabric import RunRequest, RunResult +from nemo_fabric.integrations.harbor.models import HarborMcpServer, HarborRunSpec + +try: + from harbor.agents.base import BaseAgent + from harbor.environments.base import BaseEnvironment + from harbor.models.agent.context import AgentContext +except ModuleNotFoundError as error: # pragma: no cover - exercised without harbor extra + _HARBOR_IMPORT_ERROR = error +else: + _HARBOR_IMPORT_ERROR = None + + +if _HARBOR_IMPORT_ERROR is not None: + + class FabricAgent: + """Placeholder that reports the missing Harbor optional dependency.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + raise ModuleNotFoundError( + "nemo_fabric.integrations.harbor requires the Harbor optional " + "dependency; install nemo-fabric with the harbor extra" + ) from _HARBOR_IMPORT_ERROR + + @staticmethod + def name() -> str: + return "fabric" + +else: + + class FabricAgent(BaseAgent): + """Harbor agent wrapper that delegates harness execution to Fabric. + + Harbor owns task materialization, environment lifecycle, verification, and + reward calculation. Fabric owns the selected agent harness invocation. + """ + + def __init__( + self, + logs_dir: Path, + fabric_config_path: str, + fabric_python: str = "python3", + fabric_install_command: str | None = None, + fabric_cwd: str | None = None, + fabric_timeout_sec: int | None = None, + *args: Any, + **kwargs: Any, + ) -> None: + super().__init__(logs_dir=logs_dir, *args, **kwargs) + self.fabric_config_path = fabric_config_path + self.fabric_python = fabric_python + self.fabric_install_command = fabric_install_command + self.fabric_cwd = fabric_cwd + self.fabric_timeout_sec = fabric_timeout_sec + + @staticmethod + def name() -> str: + return "fabric" + + def version(self) -> str | None: + return "0.1.0" + + async def setup(self, environment: BaseEnvironment) -> None: + result = await environment.exec("mkdir -p /logs/agent /tmp", timeout_sec=30) + ensure_success("Fabric setup failed", result) + if self.fabric_install_command: + result = await environment.exec( + self.fabric_install_command, + cwd=self.fabric_cwd, + env=self.extra_env, + timeout_sec=self.fabric_timeout_sec, + ) + ensure_success("Fabric install command failed", result) + + async def run( + self, + instruction: str, + environment: BaseEnvironment, + context: AgentContext, + ) -> None: + token = uuid.uuid4().hex + spec = self._build_spec(instruction) + host_spec_path = self.logs_dir / f"fabric-run-{token}.json" + remote_spec_path = f"/tmp/fabric-run-{token}.json" + remote_result_path = f"/tmp/fabric-result-{token}.json" + host_result_path = self.logs_dir / f"fabric-result-{token}.json" + self.logs_dir.mkdir(parents=True, exist_ok=True) + host_spec_path.write_text(spec.model_dump_json(indent=2), encoding="utf-8") + await environment.upload_file(host_spec_path, remote_spec_path) + + result = await environment.exec( + fabric_runner_command( + fabric_python=self.fabric_python, + spec_path=remote_spec_path, + result_path=remote_result_path, + ), + cwd=self.fabric_cwd, + env=self.extra_env, + timeout_sec=self.fabric_timeout_sec, + ) + ensure_success("Fabric run failed", result) + + await environment.download_file(remote_result_path, host_result_path) + populate_context_from_result(context, host_result_path) + + def _build_request(self, instruction: str) -> RunRequest: + return RunRequest(input=instruction, context={"source": "harbor"}) + + def _build_spec(self, instruction: str) -> HarborRunSpec: + return HarborRunSpec( + config_path=self.fabric_config_path, + request=self._build_request(instruction), + model_name=self.model_name, + skills_dir=self.skills_dir, + mcp_servers=tuple( + HarborMcpServer.model_validate(server.model_dump(mode="python")) + for server in self.mcp_servers + ), + ) + + +def fabric_runner_command( + *, + fabric_python: str, + spec_path: str, + result_path: str, +) -> str: + """Build the sandbox-local runner command.""" + + parts = [ + shlex.quote(fabric_python), + "-m", + "nemo_fabric.integrations.harbor.runner", + "--spec", + shlex.quote(spec_path), + "--result", + shlex.quote(result_path), + ] + return " ".join(parts) + + +def ensure_success(message: str, result: Any) -> None: + """Raise when a Harbor environment command fails.""" + + if getattr(result, "return_code", 1) == 0: + return + stdout = getattr(result, "stdout", "") + stderr = getattr(result, "stderr", "") + raise RuntimeError(f"{message} (exit {result.return_code}): {stderr or stdout}") + + +def populate_context_from_result(context: AgentContext, path: Path) -> RunResult: + """Validate a downloaded result and copy its summary into Harbor metadata.""" + + result = RunResult.from_mapping(json.loads(path.read_text(encoding="utf-8"))) + mapping = result.to_mapping() + if context.metadata is None: + context.metadata = {} + context.metadata["fabric"] = { + "status": mapping["status"], + "runtime_id": mapping["runtime_id"], + "invocation_id": mapping["invocation_id"], + "request_id": mapping["request_id"], + "harness": mapping["harness"], + "adapter_id": mapping.get("adapter_id"), + "artifacts": mapping["artifacts"], + "telemetry": mapping["telemetry"], + "error": mapping.get("error"), + } + return result diff --git a/python/src/nemo_fabric/integrations/harbor/models.py b/python/src/nemo_fabric/integrations/harbor/models.py new file mode 100644 index 000000000..f57ee080b --- /dev/null +++ b/python/src/nemo_fabric/integrations/harbor/models.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Transport contracts for the Harbor integration.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field, model_validator +from typing_extensions import Self + +from nemo_fabric import RunRequest + + +class HarborMcpServer(BaseModel): + """One Harbor-provided MCP server.""" + + model_config = ConfigDict(extra="forbid") + + name: str = Field(min_length=1) + transport: Literal["stdio", "sse", "streamable-http"] + url: str | None = None + command: str | None = None + args: tuple[str, ...] = () + + @model_validator(mode="after") + def validate_target(self) -> Self: + if self.transport == "stdio": + if not self.command: + raise ValueError("stdio MCP servers require command") + elif not self.url: + raise ValueError(f"{self.transport} MCP servers require url") + return self + + +class HarborRunSpec(BaseModel): + """Host-to-environment specification for one Harbor agent run.""" + + model_config = ConfigDict(extra="forbid") + + config_path: Path + request: RunRequest + model_name: str | None = None + skills_dir: Path | None = None + mcp_servers: tuple[HarborMcpServer, ...] = () diff --git a/python/src/nemo_fabric/integrations/harbor/runner.py b/python/src/nemo_fabric/integrations/harbor/runner.py index d42650446..b943f63d3 100644 --- a/python/src/nemo_fabric/integrations/harbor/runner.py +++ b/python/src/nemo_fabric/integrations/harbor/runner.py @@ -9,38 +9,59 @@ import asyncio import json from pathlib import Path -from typing import Any +from typing import Any, cast import yaml -from nemo_fabric import FabricClient, FabricConfig, FabricProfileConfig, RunRequest - - -def load_sources( - spec: dict[str, Any], -) -> tuple[FabricConfig, list[FabricProfileConfig]]: - config_path = Path(spec["config_path"]) - config = FabricConfig.from_mapping(load_yaml(config_path)) - profiles = [ - FabricProfileConfig.from_mapping(load_yaml(Path(path))) - for path in spec.get("profile_paths", []) - ] - - model_name = (spec.get("request", {}).get("context") or {}).get("model_name") - if isinstance(model_name, str) and model_name: - provider = model_name.split("/", maxsplit=1)[0] if "/" in model_name else "openai" - profiles.append( - FabricProfileConfig( - name="harbor_model", - models={ - "default": { - "provider": provider, - "model": model_name, - } - }, - ) +from nemo_fabric import Fabric, FabricConfig, ModelConfig, RunResult +from nemo_fabric.integrations.harbor.models import HarborRunSpec + + +def load_config(path: Path) -> FabricConfig: + """Load one complete Fabric config from the task environment.""" + + return FabricConfig.model_validate(load_yaml(path)) + + +def compose_config(base: FabricConfig, spec: HarborRunSpec) -> FabricConfig: + """Apply Harbor-owned values to an independent config copy.""" + + config = base.model_copy(deep=True) + if spec.model_name: + config.models["default"] = ModelConfig( + provider=model_provider(spec.model_name), + model=spec.model_name, ) - return config, profiles + + if spec.mcp_servers: + config.mcp = None + for server in spec.mcp_servers: + if server.transport == "stdio": + config.add_mcp_server( + server.name, + transport="stdio", + url=cast(str, server.command), + exposure="harness_native", + extra_fields={"args": list(server.args)}, + ) + else: + config.add_mcp_server( + server.name, + transport=server.transport, + url=cast(str, server.url), + exposure="harness_native", + ) + + if spec.skills_dir is not None: + config.skills = None + config.add_skill_path(spec.skills_dir) + return config + + +def model_provider(model_name: str) -> str: + """Derive the provider prefix used by the Fabric model config.""" + + return model_name.split("/", maxsplit=1)[0] if "/" in model_name else "openai" def load_yaml(path: Path) -> dict[str, Any]: @@ -50,17 +71,15 @@ def load_yaml(path: Path) -> dict[str, Any]: return value -async def run(spec: dict[str, Any]) -> dict[str, Any]: - config, profiles = load_sources(spec) - request = RunRequest.from_mapping(spec.get("request", {})) - async with FabricClient() as client: - result = await client.run( - config, - profiles=profiles, - base_dir=Path(spec["config_path"]).parent, - request=request, - ) - return result.to_mapping() +async def run(spec: HarborRunSpec) -> RunResult: + base = load_config(spec.config_path) + config = compose_config(base, spec) + result = await Fabric().run( + config, + base_dir=spec.config_path.parent, + request=spec.request, + ) + return result def main() -> None: @@ -69,10 +88,10 @@ def main() -> None: parser.add_argument("--result", type=Path, required=True) args = parser.parse_args() - spec = json.loads(args.spec.read_text(encoding="utf-8")) + spec = HarborRunSpec.model_validate_json(args.spec.read_text(encoding="utf-8")) result = asyncio.run(run(spec)) args.result.parent.mkdir(parents=True, exist_ok=True) - args.result.write_text(json.dumps(result, indent=2), encoding="utf-8") + args.result.write_text(json.dumps(result.to_mapping(), indent=2), encoding="utf-8") if __name__ == "__main__": diff --git a/python/src/nemo_fabric/models.py b/python/src/nemo_fabric/models.py new file mode 100644 index 000000000..cc3fc38b5 --- /dev/null +++ b/python/src/nemo_fabric/models.py @@ -0,0 +1,434 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Pydantic SDK models for NeMo Fabric configuration and requests. + +The Rust core remains the source of truth for persisted schema snapshots. These +models provide the Python SDK's typed authoring surface and intentionally keep +extension fields so consumers can carry adapter- or application-owned data +without waiting for a schema release. +""" + +from __future__ import annotations + +import math +import uuid +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from typing_extensions import Self + + +def _json_value(value: Any, name: str) -> Any: + """Validate and detach a JSON-compatible value.""" + + if value is None or isinstance(value, (str, bool, int)): + return value + if isinstance(value, float): + if not math.isfinite(value): + raise ValueError(f"{name} must contain only finite JSON numbers") + return value + if isinstance(value, list): + return [_json_value(item, name) for item in value] + if isinstance(value, Mapping): + result: dict[str, Any] = {} + for key, item in value.items(): + if not isinstance(key, str): + raise ValueError(f"{name} JSON object keys must be strings") + result[key] = _json_value(item, name) + return result + raise ValueError(f"{name} must be JSON-compatible") + + +class FabricBaseModel(BaseModel): + """Base class for SDK-facing Pydantic models.""" + + model_config = ConfigDict( + extra="allow", + validate_assignment=True, + populate_by_name=True, + use_enum_values=True, + allow_inf_nan=False, + ) + + @classmethod + def from_mapping(cls, value: Mapping[str, Any]) -> Self: + """Validate a mapping using this Pydantic model.""" + + return cls.model_validate(value) + + @property + def extra_fields(self) -> dict[str, Any]: + """Return fields preserved by the extension point for this model.""" + + return dict(self.model_extra or {}) + + def to_mapping(self) -> dict[str, Any]: + """Return a detached JSON-compatible mapping for Rust/core calls.""" + + data = self.model_dump(mode="json", exclude_none=True) + return {key: item for key, item in data.items() if item not in ({}, [])} + + +class MetadataConfig(FabricBaseModel): + """Human-readable agent identity.""" + + name: str = Field(min_length=1) + description: str | None = None + + +class HarnessConfig(FabricBaseModel): + """Harness adapter selection plus adapter-owned settings.""" + + adapter_id: str = Field(min_length=1) + resolution: ( + Literal[ + "preinstalled", + "image_provided", + "pip_uv", + "npm", + "source", + "service", + "native_plugin", + ] + | None + ) = None + settings: dict[str, Any] = Field(default_factory=dict) + + +class RuntimeConfig(FabricBaseModel): + """Runtime input/output contract.""" + + input_schema: str | None = None + output_schema: str | None = None + artifacts: str | Path | None = None + + +class EnvironmentConfig(FabricBaseModel): + """Execution environment configuration supplied by the consumer. + + ``provider`` selects the environment implementation. ``workspace`` is the + path visible to the harness, while ``artifacts`` is the provider-specific + output location. ``settings`` configures the selected provider; + ``connection`` describes how Fabric reaches an existing environment; and + ``metadata`` carries consumer-owned values that Fabric does not interpret. + ``ownership`` identifies who tears the environment down, and + ``control_location`` identifies whether Fabric control code runs inside or + outside it. + """ + + provider: str = Field( + default="local", + min_length=1, + description="Environment provider, such as local, docker, opensandbox, or k8s.", + ) + workspace: str | Path | None = Field( + default=None, + description="Workspace path visible to the harness.", + ) + artifacts: str | Path | None = Field( + default=None, + description="Environment-specific artifact path.", + ) + settings: dict[str, Any] = Field( + default_factory=dict, + description="Provider-specific configuration interpreted by the environment provider.", + ) + metadata: dict[str, Any] = Field( + default_factory=dict, + description="Consumer-owned environment metadata passed through without Fabric semantics.", + ) + connection: dict[str, Any] = Field( + default_factory=dict, + description="Connection data for an existing environment, such as URL, namespace, or credential reference.", + ) + ownership: Literal["caller_owned", "fabric_owned"] = Field( + default="caller_owned", + description="Whether the caller or Fabric owns environment teardown.", + ) + control_location: Literal["external_control", "in_env_control"] = Field( + default="in_env_control", + description="Whether Fabric control code runs outside or inside the environment.", + ) + + +class ModelConfig(FabricBaseModel): + """Model alias configuration.""" + + provider: str = Field(min_length=1) + model: str = Field(min_length=1) + api_key_env: str | None = None + temperature: float | None = None + settings: dict[str, Any] = Field(default_factory=dict) + + +class SkillConfig(FabricBaseModel): + """Skill capability configuration.""" + + paths: list[str | Path] = Field(default_factory=list) + + def add_path(self, path: str | Path) -> Self: + """Add a skill path if absent.""" + + value = str(path) + paths = [str(item) for item in self.paths] + if value not in paths: + self.paths = [*paths, value] + return self + + def remove_path(self, path: str | Path) -> Self: + """Remove a skill path if present.""" + + value = str(path) + self.paths = [item for item in self.paths if str(item) != value] + return self + + +class McpServerConfig(FabricBaseModel): + """MCP server configuration.""" + + transport: str = Field(min_length=1) + url: str = Field(min_length=1) + exposure: Literal["harness_native", "fabric_managed"] = "harness_native" + + +class McpConfig(FabricBaseModel): + """MCP capability configuration.""" + + servers: dict[str, McpServerConfig] = Field(default_factory=dict) + + def add_server( + self, + name: str, + *, + transport: str, + url: str, + exposure: Literal["harness_native", "fabric_managed"] = "harness_native", + extra_fields: Mapping[str, Any] | None = None, + ) -> Self: + """Add or replace a named MCP server.""" + + self.servers[name] = McpServerConfig( + transport=transport, + url=url, + exposure=exposure, + **dict(extra_fields or {}), + ) + return self + + def remove_server(self, name: str) -> Self: + """Remove a named MCP server if present.""" + + self.servers.pop(name, None) + return self + + +class TelemetryConfig(FabricBaseModel): + """Telemetry configuration.""" + + enabled: bool = False + provider: Literal["relay", "native"] | None = None + project: str | None = None + output_dir: str | Path | None = None + config: dict[str, Any] | None = None + + def enable_relay( + self, + *, + project: str | None = None, + output_dir: str | Path | None = None, + config: Mapping[str, Any] | None = None, + ) -> Self: + """Enable NeMo Relay telemetry for subsequently started runtimes.""" + + self.enabled = True + self.provider = "relay" + if project is not None: + self.project = project + if output_dir is not None: + self.output_dir = output_dir + if config is not None: + self.config = dict(config) + return self + + def enable_native(self) -> Self: + """Let the selected adapter handle telemetry natively.""" + + self.enabled = True + self.provider = "native" + return self + + def disable(self) -> Self: + """Disable telemetry.""" + + self.enabled = False + return self + + +class ProfileRegistryConfig(FabricBaseModel): + """Profile discovery config for portable file-backed agent packages.""" + + directories: list[str | Path] = Field(default_factory=list) + + +class FabricConfig(FabricBaseModel): + """SDK-facing typed Fabric agent configuration.""" + + schema_version: str = "fabric.agent/v1alpha1" + metadata: MetadataConfig + harness: HarnessConfig + runtime: RuntimeConfig = Field(default_factory=RuntimeConfig) + environment: EnvironmentConfig | None = None + models: dict[str, ModelConfig | dict[str, Any]] = Field(default_factory=dict) + mcp: McpConfig | None = None + skills: SkillConfig | None = None + telemetry: TelemetryConfig | None = None + profiles: ProfileRegistryConfig | dict[str, Any] | None = None + tools: Any = None + + @classmethod + def from_mapping(cls, value: Mapping[str, Any]) -> Self: + """Validate the public agent config mapping shape.""" + + return cls.model_validate(value) + + def to_mapping(self) -> dict[str, Any]: + """Return a detached mapping matching the Rust ``FabricConfig`` schema.""" + + data = super().to_mapping() + data.setdefault("schema_version", "fabric.agent/v1alpha1") + data.setdefault("runtime", {}) + return data + + def add_mcp_server( + self, + name: str, + *, + transport: str, + url: str, + exposure: Literal["harness_native", "fabric_managed"] = "harness_native", + extra_fields: Mapping[str, Any] | None = None, + ) -> Self: + """Add or replace a named MCP server and return this config.""" + + if self.mcp is None: + self.mcp = McpConfig() + self.mcp.add_server( + name, + transport=transport, + url=url, + exposure=exposure, + extra_fields=extra_fields, + ) + return self + + def remove_mcp_server(self, name: str) -> Self: + """Remove a named MCP server and return this config.""" + + if self.mcp is not None: + self.mcp.remove_server(name) + if not self.mcp.servers: + self.mcp = None + return self + + def add_skill_path(self, path: str | Path) -> Self: + """Add a skill path and return this config.""" + + if self.skills is None: + self.skills = SkillConfig() + self.skills.add_path(path) + return self + + def remove_skill_path(self, path: str | Path) -> Self: + """Remove a skill path and return this config.""" + + if self.skills is not None: + self.skills.remove_path(path) + if not self.skills.paths: + self.skills = None + return self + + def enable_relay( + self, + *, + project: str | None = None, + output_dir: str | Path | None = None, + config: Mapping[str, Any] | None = None, + ) -> Self: + """Enable NeMo Relay telemetry and return this config.""" + + if self.telemetry is None: + self.telemetry = TelemetryConfig() + self.telemetry.enable_relay( + project=project, + output_dir=output_dir, + config=config, + ) + return self + + +class FabricProfileConfig(FabricBaseModel): + """Typed profile overlay used when a Python caller wants file-style overlays.""" + + schema_version: str = "fabric.profile/v1alpha1" + name: str = Field(min_length=1) + description: str | None = None + harness: HarnessConfig | dict[str, Any] | None = None + runtime: RuntimeConfig | dict[str, Any] | None = None + environment: EnvironmentConfig | dict[str, Any] | None = None + models: dict[str, ModelConfig | dict[str, Any]] | None = None + mcp: McpConfig | dict[str, Any] | None = None + skills: SkillConfig | dict[str, Any] | None = None + telemetry: TelemetryConfig | dict[str, Any] | None = None + tools: Any = None + + +class RunRequest(FabricBaseModel): + """One validated Fabric invocation request.""" + + input: Any = "" + request_id: str = Field( + default_factory=lambda: f"request-{uuid.uuid4().hex}", + min_length=1, + ) + context: dict[str, Any] = Field(default_factory=dict) + overrides: dict[str, Any] | None = None + + @field_validator("input", mode="before") + @classmethod + def _validate_input(cls, value: Any) -> Any: + return _json_value("" if value is None else value, "request input") + + @field_validator("context", mode="before") + @classmethod + def _validate_context(cls, value: Any) -> Any: + if not isinstance(value, Mapping): + raise ValueError("request context must be a JSON object") + return _json_value(value, "request context") + + @field_validator("overrides", mode="before") + @classmethod + def _validate_overrides(cls, value: Any) -> Any: + if value is None: + return None + if not isinstance(value, Mapping): + raise ValueError("request overrides must be a JSON object") + return _json_value(value, "request overrides") + + @model_validator(mode="after") + def _validate_extensions(self) -> Self: + for name, value in (self.model_extra or {}).items(): + _json_value(value, f"request extension {name!r}") + return self + + def to_mapping(self) -> dict[str, Any]: + """Return a detached request mapping for the Rust runtime.""" + + data = _json_value( + self.model_dump(mode="python", exclude_none=True), + "request", + ) + assert isinstance(data, dict) + return data diff --git a/python/src/nemo_fabric/runtime.py b/python/src/nemo_fabric/runtime.py new file mode 100644 index 000000000..3fd736b4e --- /dev/null +++ b/python/src/nemo_fabric/runtime.py @@ -0,0 +1,397 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Runtime lifecycle support for the Fabric Python SDK.""" + +from __future__ import annotations + +import asyncio +import json +from collections.abc import Mapping, Sequence +from copy import deepcopy +from enum import Enum +from typing import Any + +from pydantic import ValidationError + +from nemo_fabric.errors import FabricConfigError, FabricError, FabricRuntimeError, FabricStateError +from nemo_fabric.models import RunRequest +from nemo_fabric.types import RunPlan, RunResult, RuntimeHandle + + +class RuntimeStatus(str, Enum): + """Lifecycle state of a runtime. + + ``ACTIVE`` accepts invocations, ``STOPPED`` has released its runtime, and + ``FAILED`` records a lifecycle failure that prevents further invocations + but still permits cleanup. + """ + + ACTIVE = "active" + STOPPED = "stopped" + FAILED = "failed" + + +class Runtime: + """One logical, stateful harness execution. + + Create runtimes with ``Fabric.start_runtime()`` rather than calling the + constructor. A runtime serializes invocations and preserves adapter-owned + harness state across turns. Use it as an asynchronous context manager to + stop the runtime on exit. + + Runtime-scoped overrides are recursively merged with invocation overrides; + invocation values win. + """ + + def __init__( + self, + *, + client: Any, + plan: RunPlan | Mapping[str, Any], + runtime: RuntimeHandle | Mapping[str, Any], + overrides: Mapping[str, Any] | None = None, + ) -> None: + """lazydocs: ignore""" + + self._plan = plan if isinstance(plan, RunPlan) else RunPlan.from_mapping(plan) + self._runtime = ( + runtime if isinstance(runtime, RuntimeHandle) else RuntimeHandle.from_mapping(runtime) + ) + self._client = client + self._overrides = _json_mapping(overrides, "runtime overrides") + self._messages: list[Any] = [] + self._invocations: list[dict[str, Any]] = [] + self._status = RuntimeStatus.ACTIVE + self._current_task: asyncio.Task[Any] | None = None + self._closing = False + + @property + def status(self) -> RuntimeStatus: + """Return the current ``ACTIVE``, ``STOPPED``, or ``FAILED`` state.""" + + return self._status + + @property + def messages(self) -> list[Any]: + """Return a deep copy of the latest harness-provided message history.""" + + return deepcopy(self._messages) + + @property + def invocations(self) -> list[dict[str, Any]]: + """Return copied request, runtime, and invocation IDs for completed turns.""" + + return deepcopy(self._invocations) + + @property + def handle(self) -> RuntimeHandle: + """Return a detached snapshot of the runtime handle.""" + + return RuntimeHandle.from_mapping(self._runtime.to_mapping()) + + @property + def runtime_id(self) -> str: + """Return the unique identifier for this started runtime lifecycle.""" + + return self._runtime.runtime_id + + async def invoke( + self, + *, + input: Any = None, + request: RunRequest | None = None, + ) -> RunResult: + """Run one turn on this runtime. + + ``input`` and ``request`` are mutually exclusive. Runtime overrides are + merged below invocation overrides from ``RunRequest``. Concurrent turns + on the same runtime are rejected. + + Args: + input: JSON-compatible turn input. + request: Complete validated ``RunRequest``. + + Returns: + The normalized ``RunResult`` for this turn. + + Raises: + FabricConfigError: If request fields conflict or are not + JSON-compatible. + FabricStateError: If the runtime is not active, is stopping, or is + already running a turn. + FabricNativeUnavailableError: If the native extension is missing. + FabricRuntimeError: If native invocation fails before returning a + normalized result. + """ + + if self._status is not RuntimeStatus.ACTIVE: + raise FabricStateError(f"cannot invoke a {self._status.value} runtime") + if self._closing: + raise FabricStateError("cannot invoke while runtime shutdown is in progress") + if self._current_task is not None: + raise FabricStateError("runtime is already running an invocation") + self._current_task = asyncio.current_task() + try: + payload = _run_request_payload( + input=input, + request=request, + ) + merged = _merge_overrides(self._overrides, payload.get("overrides")) + if merged: + payload["overrides"] = merged + else: + payload.pop("overrides", None) + native_result: dict[str, Any] | None = None + try: + native = self._client._require_native_module("invoke") + + def invoke() -> dict[str, Any]: + nonlocal native_result + native_result = json.loads( + native.invoke_runtime( + json.dumps(self._plan.to_mapping()), + json.dumps(self._runtime.to_mapping()), + json.dumps(payload), + ) + ) + return native_result + + result = await _call_blocking(invoke) + typed_result = RunResult.from_mapping(result) + except asyncio.CancelledError: + if native_result is not None: + try: + self._absorb(RunResult.from_mapping(native_result)) + except Exception: + pass + stopped = False + + def stop_after_cancel() -> Any: + nonlocal stopped + result = json.loads( + native.stop_runtime( + json.dumps(self._plan.to_mapping()), + json.dumps(self._runtime.to_mapping()), + ) + ) + stopped = True + return result + + try: + await _call_blocking(stop_after_cancel) + except asyncio.CancelledError: + self._status = RuntimeStatus.STOPPED if stopped else RuntimeStatus.FAILED + raise + except Exception: + self._status = RuntimeStatus.FAILED + else: + self._status = RuntimeStatus.STOPPED + raise + except FabricError: + self._status = RuntimeStatus.FAILED + raise + except Exception as error: + self._status = RuntimeStatus.FAILED + raise FabricRuntimeError(str(error), stage="invoke") from error + self._absorb(typed_result) + return typed_result + except FabricError: + raise + except Exception as error: + raise FabricRuntimeError(str(error), stage="invoke") from error + finally: + self._current_task = None + + async def stop(self) -> None: + """Destroy an idle runtime exactly once. + + Repeated calls after a successful stop are no-ops. A failed runtime may + still be stopped so its resources are released. + + Raises: + FabricStateError: If the runtime is already stopping or has an + invocation in flight. + FabricNativeUnavailableError: If the native extension is missing. + FabricRuntimeError: If native runtime shutdown fails. + """ + + if self._status is RuntimeStatus.STOPPED: + return + if self._current_task is not None: + raise FabricStateError("cannot stop while a turn is in flight") + if self._closing: + raise FabricStateError("runtime shutdown is already in progress") + self._closing = True + stopped = False + try: + native = self._client._require_native_module("stop") + + def stop() -> Any: + nonlocal stopped + result = json.loads( + native.stop_runtime( + json.dumps(self._plan.to_mapping()), + json.dumps(self._runtime.to_mapping()), + ) + ) + stopped = True + return result + + await _call_blocking(stop) + except asyncio.CancelledError: + self._status = RuntimeStatus.STOPPED if stopped else RuntimeStatus.FAILED + raise + except FabricError: + self._status = RuntimeStatus.FAILED + raise + except Exception as error: + self._status = RuntimeStatus.FAILED + raise FabricRuntimeError(str(error), stage="stop") from error + else: + self._status = RuntimeStatus.STOPPED + finally: + self._closing = False + + def _absorb(self, result: RunResult) -> None: + self._invocations.append( + { + "request_id": result.request_id, + "runtime_id": result.runtime_id, + "invocation_id": result.invocation_id, + } + ) + output = result.output + messages = output.get("messages") if isinstance(output, Mapping) else None + if isinstance(messages, Sequence) and not isinstance(messages, (str, bytes)): + self._messages = deepcopy(list(messages)) + + async def __aenter__(self) -> "Runtime": + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object, + ) -> None: + try: + await self.stop() + except Exception as cleanup_error: + if exc is None: + raise + exc.add_note(f"runtime cleanup failed: {cleanup_error}") + + +def _json_mapping(value: Mapping[str, Any] | None, name: str) -> dict[str, Any]: + if value is None: + return {} + if not isinstance(value, Mapping): + raise FabricConfigError(f"{name} must be a JSON object") + pending: list[Any] = [value] + seen: set[int] = set() + while pending: + item = pending.pop() + if isinstance(item, (Mapping, list, tuple)): + identity = id(item) + if identity in seen: + continue + seen.add(identity) + if isinstance(item, Mapping): + if any(not isinstance(key, str) for key in item): + raise FabricConfigError(f"{name} keys must be strings") + pending.extend(item.values()) + elif isinstance(item, (list, tuple)): + pending.extend(item) + try: + return json.loads(json.dumps(dict(value), allow_nan=False)) + except (TypeError, ValueError) as error: + raise FabricConfigError(f"{name} must contain JSON-compatible values") from error + + +def _merge_overrides( + base: Mapping[str, Any] | None, + extra: Mapping[str, Any] | None, +) -> dict[str, Any]: + result = _json_mapping(base, "request overrides") + for key, value in _json_mapping(extra, "request overrides").items(): + current = result.get(key) + if isinstance(current, dict) and isinstance(value, dict): + result[key] = _merge_overrides(current, value) + else: + result[key] = value + return result + + +def _run_request_payload( + *, + input: Any, + request: RunRequest | None, +) -> dict[str, Any]: + if input is not None and request is not None: + raise FabricConfigError("input and request are mutually exclusive") + try: + if request is not None: + if not isinstance(request, RunRequest): + raise FabricConfigError("request must be a RunRequest") + payload = request.to_mapping() + else: + payload = RunRequest(input=input).to_mapping() + except ValidationError as error: + raise FabricConfigError(str(error)) from error + return payload + + +async def _run_native_lifecycle( + native: Any, + plan: Mapping[str, Any], + request: Mapping[str, Any], +) -> dict[str, Any]: + def run() -> dict[str, Any]: + plan_json = json.dumps(dict(plan)) + runtime = json.loads(native.start_runtime(plan_json)) + runtime_json = json.dumps(runtime) + result: dict[str, Any] | None = None + invoke_error: Exception | None = None + try: + try: + result = json.loads( + native.invoke_runtime(plan_json, runtime_json, json.dumps(dict(request))) + ) + except Exception as error: + invoke_error = error + raise + return result + finally: + try: + stop_events = json.loads(native.stop_runtime(plan_json, runtime_json)) + except Exception: + if invoke_error is None: + raise + stop_events = [] + if result is not None and isinstance(stop_events, list): + result.setdefault("events", []).extend(stop_events) + + try: + return await _call_blocking(run) + except FabricError: + raise + except Exception as error: + raise FabricRuntimeError(str(error), stage="run") from error + + +async def _call_blocking(func: Any) -> Any: + worker = asyncio.create_task(asyncio.to_thread(func)) + try: + return await asyncio.shield(worker) + except asyncio.CancelledError as cancelled: + while not worker.done(): + try: + await asyncio.shield(worker) + except asyncio.CancelledError: + continue + try: + worker.result() + except BaseException: + pass + raise cancelled diff --git a/python/src/nemo_fabric/session.py b/python/src/nemo_fabric/session.py deleted file mode 100644 index f89c420de..000000000 --- a/python/src/nemo_fabric/session.py +++ /dev/null @@ -1,532 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Session lifecycle support for the Fabric Python SDK.""" - -from __future__ import annotations - -import asyncio -import json -from collections.abc import AsyncIterator, Mapping, Sequence -from copy import deepcopy -from enum import Enum -from pathlib import Path -from typing import Any - -from nemo_fabric.errors import ( - FabricCapabilityError, - FabricConfigError, - FabricError, - FabricRuntimeError, - FabricStateError, -) -from nemo_fabric.types import ( - FabricEvent, - RunPlan, - RunRequest, - RunResult, - RuntimeHandle, - RuntimeUpdate, - RuntimeUpdateResult, - SessionInfo, -) - - -class SessionStatus(str, Enum): - """Lifecycle state of a session runtime. - - ``ACTIVE`` accepts invocations, ``STOPPED`` has released its runtime, and - ``FAILED`` records a lifecycle failure that prevents further use. - """ - - ACTIVE = "active" - STOPPED = "stopped" - FAILED = "failed" - - -class Session: - """One ordered multi-turn conversation over a Fabric runtime. - - Create sessions with ``FabricClient.start_session()`` rather than calling - the constructor. A session owns one started runtime, serializes invocations, - and preserves harness state across turns. Use it as an asynchronous context - manager to stop the runtime on exit. - - Session-scoped overrides are recursively merged with invocation overrides; - invocation values win. Runtime identity and conversation identity are - distinct: ``runtime_id`` identifies this lifecycle, while ``session_id`` is - the stable caller-owned resume key. - """ - - def __init__( - self, - *, - client: Any, - plan: RunPlan | Mapping[str, Any], - runtime: RuntimeHandle | Mapping[str, Any], - overrides: Mapping[str, Any] | None = None, - session_id: str | None = None, - ) -> None: - """lazydocs: ignore""" - - self._plan = plan if isinstance(plan, RunPlan) else RunPlan.from_mapping(plan) - _require_session_runtime(self._plan, "Session") - self._runtime = ( - runtime if isinstance(runtime, RuntimeHandle) else RuntimeHandle.from_mapping(runtime) - ) - self._client = client - self._overrides = _json_mapping(overrides, "session overrides") - self._session_id = session_id - self._messages: list[Any] = [] - self._invocations: list[dict[str, Any]] = [] - self._status = SessionStatus.ACTIVE - self._current_task: asyncio.Task[Any] | None = None - self._closing = False - - @property - def status(self) -> SessionStatus: - """Return the current ``ACTIVE``, ``STOPPED``, or ``FAILED`` state.""" - - return self._status - - @property - def messages(self) -> list[Any]: - """Return a deep copy of the latest harness-provided message history.""" - - return deepcopy(self._messages) - - @property - def invocations(self) -> list[dict[str, Any]]: - """Return copied request, runtime, and invocation IDs for completed turns.""" - - return deepcopy(self._invocations) - - @property - def runtime(self) -> RuntimeHandle: - """Return a detached snapshot of the underlying runtime handle.""" - - return RuntimeHandle.from_mapping(self._runtime.to_mapping()) - - @property - def runtime_id(self) -> str: - """Return the unique identifier for this started runtime lifecycle.""" - - return self._runtime.runtime_id - - @property - def session_id(self) -> str: - """Return the stable conversation ID, defaulting to ``runtime_id``.""" - - return self._session_id or self.runtime_id - - @property - def info(self) -> SessionInfo: - """Return a typed snapshot of session identity, status, and capabilities.""" - - return SessionInfo.from_mapping( - { - "session_id": self.session_id, - "runtime_id": self.runtime_id, - "agent_name": self._runtime.agent_name, - "profiles": self._plan.profiles, - "harness": self._runtime.harness, - "adapter_id": self._runtime.adapter_id, - "adapter_kind": self._runtime.adapter_kind, - "status": self._status.value, - "capabilities": self._plan.capabilities, - } - ) - - async def invoke( - self, - *, - input: Any = None, - request: RunRequest | Mapping[str, Any] | None = None, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, - ) -> RunResult: - """Run one turn on the session's existing runtime. - - A complete ``request`` cannot be combined with separate ``request_id``, - ``context``, or ``overrides`` fields. The session identifier is injected - into request context, and session overrides are merged below invocation - overrides. Concurrent turns on the same handle are rejected. - - Args: - input: JSON-compatible turn input. - request: Complete ``RunRequest`` or compatible mapping. - request_id: Caller-owned request identifier; generated when omitted. - context: Caller-owned, JSON-compatible request metadata. - overrides: JSON-compatible invocation-scoped config overrides. - - Returns: - The normalized ``RunResult`` for this turn. - - Raises: - FabricConfigError: If request fields conflict or are not - JSON-compatible. - FabricStateError: If the session is not active, is stopping, or is - already running a turn. - FabricNativeUnavailableError: If the native extension is missing. - FabricRuntimeError: If native invocation fails before returning a - normalized result. - """ - - if self._status is not SessionStatus.ACTIVE: - raise FabricStateError(f"cannot invoke a {self._status.value} session") - if self._closing: - raise FabricStateError("cannot invoke while session shutdown is in progress") - if self._current_task is not None: - raise FabricStateError("session is already running a turn") - self._current_task = asyncio.current_task() - try: - payload = _run_request_payload( - input=input, - input_file=None, - request=request, - request_file=None, - request_id=request_id, - context=context, - overrides=overrides, - ) - payload["context"] = { - **payload.get("context", {}), - "session_id": self.session_id, - } - merged = _merge_overrides(self._overrides, payload.get("overrides")) - if merged: - payload["overrides"] = merged - else: - payload.pop("overrides", None) - try: - native = self._client._require_native_module("invoke") - result = await _call_blocking( - lambda: json.loads( - native.invoke_runtime( - json.dumps(self._plan.to_mapping()), - json.dumps(self._runtime.to_mapping()), - json.dumps(payload), - ) - ) - ) - typed_result = RunResult.from_mapping(result) - except FabricError: - self._status = SessionStatus.FAILED - raise - except Exception as error: - self._status = SessionStatus.FAILED - raise FabricRuntimeError(str(error), stage="invoke") from error - self._absorb(typed_result) - return typed_result - except FabricError: - raise - except Exception as error: - raise FabricRuntimeError(str(error), stage="invoke") from error - finally: - self._current_task = None - - async def stream( - self, - *, - input: Any = None, - request: RunRequest | Mapping[str, Any] | None = None, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, - ) -> AsyncIterator[FabricEvent | RunResult]: - """Yield buffered events followed by one terminal result. - - Current adapters may buffer internally; this API does not promise that - events arrive in real time. Request validation and failure behavior are - identical to ``invoke()``. - - Args: - input: JSON-compatible turn input. - request: Complete ``RunRequest`` or compatible mapping. - request_id: Caller-owned request identifier; generated when omitted. - context: Caller-owned, JSON-compatible request metadata. - overrides: JSON-compatible invocation-scoped config overrides. - - Yields: - Each normalized ``FabricEvent``, then the terminal ``RunResult``. - """ - - result = await self.invoke( - input=input, - request=request, - request_id=request_id, - context=context, - overrides=overrides, - ) - for event in result.events: - yield event - yield result - - async def update(self, update: RuntimeUpdate) -> RuntimeUpdateResult: - """Validate a runtime update and report transport availability. - - Args: - update: Typed update containing overrides and caller metadata. - - Raises: - FabricConfigError: If ``update`` is not a ``RuntimeUpdate``. - FabricCapabilityError: If updates are unsupported or the update - transport is not yet implemented. - """ - - if not isinstance(update, RuntimeUpdate): - raise FabricConfigError("update must be a RuntimeUpdate") - if not self._plan.capabilities.updates: - raise FabricCapabilityError( - "runtime updates are not supported", - stage="update", - code="updates_not_supported", - ) - raise FabricCapabilityError( - "runtime update transport is not implemented", - stage="update", - code="updates_not_implemented", - ) - - async def cancel(self) -> None: - """Report whether runtime cancellation is available. - - Raises: - FabricCapabilityError: If cancellation is unsupported or the - cancellation transport is not yet implemented. - """ - - if not self._plan.capabilities.cancellation: - raise FabricCapabilityError( - "runtime cancellation is not supported", - stage="cancel", - code="cancellation_not_supported", - ) - raise FabricCapabilityError( - "runtime cancellation transport is not implemented", - stage="cancel", - code="cancellation_not_implemented", - ) - - async def stop(self) -> None: - """Destroy an idle runtime exactly once. - - Repeated calls after a successful stop are no-ops. A failed session or - an in-flight invocation must reach a terminal state before cleanup can - proceed. - - Raises: - FabricStateError: If the session failed, is already stopping, or - has an invocation in flight. - FabricNativeUnavailableError: If the native extension is missing. - FabricRuntimeError: If native runtime shutdown fails. - """ - - if self._status is SessionStatus.STOPPED: - return - if self._status is SessionStatus.FAILED: - raise FabricStateError("cannot stop a failed session") - if self._current_task is not None: - raise FabricStateError("cannot stop while a turn is in flight") - if self._closing: - raise FabricStateError("session shutdown is already in progress") - self._closing = True - try: - native = self._client._require_native_module("stop") - await _call_blocking( - lambda: json.loads( - native.stop_runtime( - json.dumps(self._plan.to_mapping()), - json.dumps(self._runtime.to_mapping()), - ) - ) - ) - except FabricError: - self._status = SessionStatus.FAILED - raise - except Exception as error: - self._status = SessionStatus.FAILED - raise FabricRuntimeError(str(error), stage="stop") from error - else: - self._status = SessionStatus.STOPPED - finally: - self._closing = False - - def _absorb(self, result: RunResult) -> None: - self._invocations.append( - { - "request_id": result.request_id, - "runtime_id": result.runtime_id, - "invocation_id": result.invocation_id, - } - ) - output = result.output - messages = output.get("messages") if isinstance(output, Mapping) else None - if isinstance(messages, Sequence) and not isinstance(messages, (str, bytes)): - self._messages = deepcopy(list(messages)) - - async def __aenter__(self) -> "Session": - return self - - async def __aexit__(self, exc_type: object, exc: object, traceback: object) -> None: - if self._status is not SessionStatus.FAILED: - await self.stop() - - -def _json_mapping(value: Mapping[str, Any] | None, name: str) -> dict[str, Any]: - if value is None: - return {} - if not isinstance(value, Mapping): - raise FabricConfigError(f"{name} must be a JSON object") - pending: list[Any] = [value] - seen: set[int] = set() - while pending: - item = pending.pop() - if isinstance(item, (Mapping, list, tuple)): - identity = id(item) - if identity in seen: - continue - seen.add(identity) - if isinstance(item, Mapping): - if any(not isinstance(key, str) for key in item): - raise FabricConfigError(f"{name} keys must be strings") - pending.extend(item.values()) - elif isinstance(item, (list, tuple)): - pending.extend(item) - try: - return json.loads(json.dumps(dict(value), allow_nan=False)) - except (TypeError, ValueError) as error: - raise FabricConfigError(f"{name} must contain JSON-compatible values") from error - - -def _merge_overrides( - base: Mapping[str, Any] | None, - extra: Mapping[str, Any] | None, -) -> dict[str, Any]: - result = _json_mapping(base, "request overrides") - for key, value in _json_mapping(extra, "request overrides").items(): - current = result.get(key) - if isinstance(current, dict) and isinstance(value, dict): - result[key] = _merge_overrides(current, value) - else: - result[key] = value - return result - - -def _run_request_payload( - *, - input: Any, - input_file: str | Path | None, - request: RunRequest | Mapping[str, Any] | None, - request_file: str | Path | None, - request_id: str | None, - context: Mapping[str, Any] | None, - overrides: Mapping[str, Any] | None, -) -> dict[str, Any]: - primary_sources = [ - input is not None, - input_file is not None, - request is not None, - request_file is not None, - ] - if sum(primary_sources) > 1: - raise FabricConfigError( - "at most one input source is allowed: input, input_file, request, or request_file" - ) - separate_fields = request_id is not None or context is not None or overrides is not None - if (request is not None or request_file is not None) and separate_fields: - raise FabricConfigError( - "a complete request cannot be combined with separate request fields" - ) - if request_file is not None: - try: - raw = json.loads(Path(request_file).read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError) as error: - raise FabricConfigError(f"failed to read request file: {error}") from error - payload = RunRequest.from_mapping(raw).to_mapping() - elif request is not None: - payload = ( - request.to_mapping() - if isinstance(request, RunRequest) - else RunRequest.from_mapping(request).to_mapping() - ) - elif input_file is not None: - try: - file_input = Path(input_file).read_text(encoding="utf-8") - except OSError as error: - raise FabricConfigError(f"failed to read input file: {error}") from error - payload = RunRequest( - input=file_input, - request_id=request_id, - context=context, - overrides=overrides, - ).to_mapping() - else: - payload = RunRequest( - input=input, - request_id=request_id, - context=context, - overrides=overrides, - ).to_mapping() - return payload - - -async def _run_native_lifecycle( - native: Any, - plan: Mapping[str, Any], - request: Mapping[str, Any], -) -> dict[str, Any]: - def run() -> dict[str, Any]: - plan_json = json.dumps(dict(plan)) - runtime = json.loads(native.start_runtime(plan_json)) - runtime_json = json.dumps(runtime) - result: dict[str, Any] | None = None - invoke_error: Exception | None = None - try: - try: - result = json.loads( - native.invoke_runtime(plan_json, runtime_json, json.dumps(dict(request))) - ) - except Exception as error: - invoke_error = error - raise - return result - finally: - try: - stop_events = json.loads(native.stop_runtime(plan_json, runtime_json)) - except Exception: - if invoke_error is None: - raise - stop_events = [] - if result is not None and isinstance(stop_events, list): - result.setdefault("events", []).extend(stop_events) - - try: - return await _call_blocking(run) - except FabricError: - raise - except Exception as error: - raise FabricRuntimeError(str(error), stage="run") from error - - -async def _call_blocking(func: Any) -> Any: - task = asyncio.create_task(asyncio.to_thread(func)) - try: - return await asyncio.shield(task) - except asyncio.CancelledError: - try: - await asyncio.shield(task) - except Exception: - pass - raise - - -def _require_session_runtime(plan: RunPlan | Mapping[str, Any], method: str) -> None: - typed_plan = plan if isinstance(plan, RunPlan) else RunPlan.from_mapping(plan) - if not typed_plan.capabilities.session: - raise FabricCapabilityError( - f"{method} requires session capability", - stage="start", - code="session_not_supported", - ) diff --git a/python/src/nemo_fabric/types.py b/python/src/nemo_fabric/types.py index 7877279d7..ec0d38774 100644 --- a/python/src/nemo_fabric/types.py +++ b/python/src/nemo_fabric/types.py @@ -6,23 +6,25 @@ from __future__ import annotations import math -import uuid from collections.abc import Iterator, Mapping, Sequence from copy import deepcopy from pathlib import Path from types import MappingProxyType from typing import Any, TypeVar +from pydantic import BaseModel + from nemo_fabric.errors import FabricConfigError JSONScalar = str | int | float | bool | None JSONValue = JSONScalar | list["JSONValue"] | dict[str, "JSONValue"] -_UNSET = object() _T = TypeVar("_T") def _plain(value: Any) -> Any: + if isinstance(value, BaseModel): + return _plain(value.model_dump(mode="json", exclude_none=True)) if isinstance(value, Path): return str(value) if isinstance(value, _ConfigMapping): @@ -46,6 +48,8 @@ def _plain(value: Any) -> Any: def _mapping(value: Any, name: str) -> dict[str, Any]: + if isinstance(value, BaseModel): + value = value.model_dump(mode="json", exclude_none=True) if not isinstance(value, Mapping): raise FabricConfigError(f"{name} must be a JSON object") return _plain(value) @@ -75,6 +79,8 @@ def _boolean(value: Any, name: str) -> bool: def _coerce(model: type[_T], value: _T | Mapping[str, Any], name: str) -> _T: if isinstance(value, model): return deepcopy(value) + if isinstance(value, BaseModel): + return model.from_mapping(value.model_dump(mode="json", exclude_none=True)) # type: ignore[attr-defined,no-any-return] if isinstance(value, Mapping): return model.from_mapping(value) # type: ignore[attr-defined,no-any-return] raise FabricConfigError(f"{name} must be a {model.__name__} or JSON object") @@ -140,7 +146,7 @@ def to_mapping(self) -> dict[str, Any]: return data -class MetadataConfig(_ConfigMapping): +class _MetadataConfig(_ConfigMapping): """Agent identity and human-readable metadata. Attributes: @@ -164,7 +170,7 @@ def __init__( super().__init__(values, extra_fields=extra_fields) @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "MetadataConfig": + def from_mapping(cls, value: Mapping[str, Any]) -> "_MetadataConfig": """Validate a metadata mapping and preserve unknown extension fields.""" data = _mapping(value, "metadata") @@ -175,7 +181,7 @@ def from_mapping(cls, value: Mapping[str, Any]) -> "MetadataConfig": ) -class HarnessConfig(_ConfigMapping): +class _HarnessConfig(_ConfigMapping): """Harness adapter selection and adapter-owned settings. Attributes: @@ -208,7 +214,7 @@ def __init__( super().__init__(values, extra_fields=extra_fields) @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "HarnessConfig": + def from_mapping(cls, value: Mapping[str, Any]) -> "_HarnessConfig": """Validate a harness mapping and preserve unknown extension fields.""" data = _mapping(value, "harness") @@ -220,37 +226,28 @@ def from_mapping(cls, value: Mapping[str, Any]) -> "HarnessConfig": ) -class RuntimeConfig(_ConfigMapping): - """Runtime lifecycle mode and input/output contract. +class _RuntimeConfig(_ConfigMapping): + """Runtime input/output contract. Attributes: - mode: Lifecycle mode: ``oneshot``, ``session``, or ``service``. - transport: Optional adapter transport such as ``library`` or ``stdio``. input_schema: Optional logical input contract identifier. output_schema: Optional logical output contract identifier. artifacts: Optional artifact-root path. extra_fields: Preserved extension fields not recognized by this SDK. """ - _fields = frozenset( - {"mode", "transport", "input_schema", "output_schema", "artifacts"} - ) + _fields = frozenset({"input_schema", "output_schema", "artifacts"}) def __init__( self, *, - mode: str = "oneshot", - transport: str | None = None, input_schema: str | None = None, output_schema: str | None = None, artifacts: str | Path | None = None, extra_fields: Mapping[str, Any] | None = None, ) -> None: - if mode not in {"oneshot", "session", "service"}: - raise FabricConfigError(f"unsupported runtime mode: {mode!r}") - values: dict[str, Any] = {"mode": mode} + values: dict[str, Any] = {} for key, item in ( - ("transport", transport), ("input_schema", input_schema), ("output_schema", output_schema), ("artifacts", artifacts), @@ -260,21 +257,23 @@ def __init__( super().__init__(values, extra_fields=extra_fields) @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "RuntimeConfig": + def from_mapping(cls, value: Mapping[str, Any]) -> "_RuntimeConfig": """Validate a runtime mapping and apply stable constructor defaults.""" data = _mapping(value, "runtime") return cls( - mode=data.get("mode", "oneshot"), - transport=data.get("transport"), input_schema=data.get("input_schema"), output_schema=data.get("output_schema"), artifacts=data.get("artifacts"), - extra_fields={key: item for key, item in data.items() if key not in cls._fields}, + extra_fields={ + key: item + for key, item in data.items() + if key not in cls._fields + }, ) -class EnvironmentConfig(_ConfigMapping): +class _EnvironmentConfig(_ConfigMapping): """Execution environment configuration. Attributes: @@ -319,7 +318,7 @@ def __init__( super().__init__(values, extra_fields=extra_fields) @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "EnvironmentConfig": + def from_mapping(cls, value: Mapping[str, Any]) -> "_EnvironmentConfig": """Validate an environment mapping and preserve extension fields.""" data = _mapping(value, "environment") @@ -333,7 +332,205 @@ def from_mapping(cls, value: Mapping[str, Any]) -> "EnvironmentConfig": ) -class FabricConfig(_ConfigMapping): +class _SkillConfig(_ConfigMapping): + """Skill capability configuration. + + The shape matches the ``skills`` section in ``agent.yaml`` while providing + small authoring helpers for Python callers. + """ + + _fields = frozenset({"paths"}) + _omit_if_empty = frozenset({"paths"}) + + def __init__( + self, + *, + paths: Sequence[str | Path] | None = None, + extra_fields: Mapping[str, Any] | None = None, + ) -> None: + values: dict[str, Any] = { + "paths": [str(path) for path in ([] if paths is None else paths)] + } + super().__init__(values, extra_fields=extra_fields) + + @classmethod + def from_mapping(cls, value: Mapping[str, Any]) -> "_SkillConfig": + """Validate a skill mapping and preserve extension fields.""" + + data = _mapping(value, "skills") + return cls( + paths=data.get("paths", []), + extra_fields={key: item for key, item in data.items() if key not in cls._fields}, + ) + + def add_path(self, path: str | Path) -> "_SkillConfig": + """Add a skill path to this config if it is not already present.""" + + value = str(path) + paths = list(self.get("paths", [])) + if value not in paths: + paths.append(value) + self["paths"] = paths + return self + + def remove_path(self, path: str | Path) -> "_SkillConfig": + """Remove a skill path from this config if present.""" + + value = str(path) + self["paths"] = [item for item in self.get("paths", []) if item != value] + return self + + +class _McpConfig(_ConfigMapping): + """MCP capability configuration with authoring helpers.""" + + _fields = frozenset({"servers"}) + _omit_if_empty = frozenset({"servers"}) + _EXPOSURES = frozenset({"harness_native", "fabric_managed"}) + + def __init__( + self, + *, + servers: Mapping[str, Any] | None = None, + extra_fields: Mapping[str, Any] | None = None, + ) -> None: + values: dict[str, Any] = { + "servers": _mapping({} if servers is None else servers, "mcp servers") + } + super().__init__(values, extra_fields=extra_fields) + + @classmethod + def from_mapping(cls, value: Mapping[str, Any]) -> "_McpConfig": + """Validate an MCP mapping and preserve extension fields.""" + + data = _mapping(value, "mcp") + return cls( + servers=data.get("servers", {}), + extra_fields={key: item for key, item in data.items() if key not in cls._fields}, + ) + + def add_server( + self, + name: str, + *, + transport: str, + url: str, + exposure: str = "harness_native", + extra_fields: Mapping[str, Any] | None = None, + ) -> "_McpConfig": + """Add or replace a named MCP server.""" + + if exposure not in self._EXPOSURES: + allowed = ", ".join(sorted(self._EXPOSURES)) + raise FabricConfigError(f"mcp exposure must be one of: {allowed}") + server = { + "transport": _required_text(transport, "mcp transport"), + "url": _required_text(url, "mcp url"), + "exposure": exposure, + } + server.update( + _mapping( + {} if extra_fields is None else extra_fields, + "mcp server extra_fields", + ) + ) + servers = dict(self.get("servers", {})) + servers[_required_text(name, "mcp server name")] = server + self["servers"] = servers + return self + + def remove_server(self, name: str) -> "_McpConfig": + """Remove a named MCP server if present.""" + + servers = dict(self.get("servers", {})) + servers.pop(name, None) + self["servers"] = servers + return self + + +class _TelemetryConfig(_ConfigMapping): + """Telemetry configuration with authoring helpers.""" + + _fields = frozenset({"enabled", "provider", "project", "output_dir", "config"}) + _PROVIDERS = frozenset({"relay", "native"}) + + def __init__( + self, + *, + enabled: bool = False, + provider: str | None = None, + project: str | None = None, + output_dir: str | Path | None = None, + config: Mapping[str, Any] | None = None, + extra_fields: Mapping[str, Any] | None = None, + ) -> None: + values: dict[str, Any] = {"enabled": _boolean(enabled, "telemetry enabled")} + if provider is not None: + values["provider"] = self._provider(provider) + if project is not None: + values["project"] = project + if output_dir is not None: + values["output_dir"] = output_dir + if config is not None: + values["config"] = config + super().__init__(values, extra_fields=extra_fields) + + @classmethod + def from_mapping(cls, value: Mapping[str, Any]) -> "_TelemetryConfig": + """Validate a telemetry mapping and preserve extension fields.""" + + data = _mapping(value, "telemetry") + return cls( + enabled=data.get("enabled", False), + provider=data.get("provider"), + project=data.get("project"), + output_dir=data.get("output_dir"), + config=data.get("config"), + extra_fields={key: item for key, item in data.items() if key not in cls._fields}, + ) + + @classmethod + def _provider(cls, provider: str) -> str: + value = _required_text(provider, "telemetry provider") + if value not in cls._PROVIDERS: + allowed = ", ".join(sorted(cls._PROVIDERS)) + raise FabricConfigError(f"telemetry provider must be one of: {allowed}") + return value + + def enable_relay( + self, + *, + project: str | None = None, + output_dir: str | Path | None = None, + config: Mapping[str, Any] | None = None, + ) -> "_TelemetryConfig": + """Enable NeMo Relay telemetry for subsequently started runtimes.""" + + self["enabled"] = True + self["provider"] = "relay" + if project is not None: + self["project"] = project + if output_dir is not None: + self["output_dir"] = str(output_dir) + if config is not None: + self["config"] = _mapping(config, "telemetry config") + return self + + def enable_native(self) -> "_TelemetryConfig": + """Let the selected harness adapter handle telemetry natively.""" + + self["enabled"] = True + self["provider"] = "native" + return self + + def disable(self) -> "_TelemetryConfig": + """Disable telemetry for subsequently started runtimes.""" + + self["enabled"] = False + return self + + +class _ResolvedFabricConfig(_ConfigMapping): """Mutable typed representation of a Fabric agent configuration. The object follows the same schema as ``agent.yaml``. It is mutable while @@ -344,7 +541,7 @@ class FabricConfig(_ConfigMapping): schema_version: Agent schema identifier. metadata: Required ``MetadataConfig`` agent identity. harness: Required ``HarnessConfig`` adapter selection. - runtime: Runtime lifecycle configuration; defaults to oneshot. + runtime: Runtime input/output configuration. environment: Optional execution environment configuration. models: Named, JSON-compatible model configurations. mcp: Optional MCP configuration. @@ -370,16 +567,16 @@ class FabricConfig(_ConfigMapping): "tools", } ) - _omit_if_empty = frozenset({"models"}) + _omit_if_empty = frozenset({"models", "mcp", "skills"}) def __init__( self, *, - metadata: MetadataConfig | Mapping[str, Any], - harness: HarnessConfig | Mapping[str, Any], - runtime: RuntimeConfig | Mapping[str, Any] | None = None, + metadata: _MetadataConfig | Mapping[str, Any], + harness: _HarnessConfig | Mapping[str, Any], + runtime: _RuntimeConfig | Mapping[str, Any] | None = None, schema_version: str = "fabric.agent/v1alpha1", - environment: EnvironmentConfig | Mapping[str, Any] | None = None, + environment: _EnvironmentConfig | Mapping[str, Any] | None = None, models: Mapping[str, Any] | None = None, mcp: Mapping[str, Any] | None = None, skills: Mapping[str, Any] | None = None, @@ -388,17 +585,24 @@ def __init__( tools: Any = None, extra_fields: Mapping[str, Any] | None = None, ) -> None: - metadata_value = _coerce(MetadataConfig, metadata, "metadata") - harness_value = _coerce(HarnessConfig, harness, "harness") + metadata_value = _coerce(_MetadataConfig, metadata, "metadata") + harness_value = _coerce(_HarnessConfig, harness, "harness") runtime_value = _coerce( - RuntimeConfig, - RuntimeConfig() if runtime is None else runtime, + _RuntimeConfig, + _RuntimeConfig() if runtime is None else runtime, "runtime", ) environment_value = ( None if environment is None - else _coerce(EnvironmentConfig, environment, "environment") + else _coerce(_EnvironmentConfig, environment, "environment") + ) + mcp_value = None if mcp is None else _coerce(_McpConfig, mcp, "mcp") + skills_value = None if skills is None else _coerce(_SkillConfig, skills, "skills") + telemetry_value = ( + None + if telemetry is None + else _coerce(_TelemetryConfig, telemetry, "telemetry") ) values: dict[str, Any] = { "schema_version": _required_text(schema_version, "schema_version"), @@ -409,9 +613,9 @@ def __init__( } for key, item in ( ("environment", environment_value), - ("mcp", mcp), - ("skills", skills), - ("telemetry", telemetry), + ("mcp", mcp_value), + ("skills", skills_value), + ("telemetry", telemetry_value), ("profiles", profiles), ("tools", tools), ): @@ -420,7 +624,7 @@ def __init__( super().__init__(values, extra_fields=extra_fields) @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "FabricConfig": + def from_mapping(cls, value: Mapping[str, Any]) -> "_ResolvedFabricConfig": """Build a typed agent config from the ``agent.yaml`` mapping shape.""" data = _mapping(value, "FabricConfig") @@ -443,108 +647,87 @@ def from_mapping(cls, value: Mapping[str, Any]) -> "FabricConfig": extra_fields={key: item for key, item in data.items() if key not in cls._fields}, ) + @property + def mcp(self) -> _McpConfig: + """Mutable MCP capability config, created on first access.""" -class FabricProfileConfig(_ConfigMapping): - """Mutable, partial overlay applied to a typed ``FabricConfig``. + return self._ensure_section("mcp", _McpConfig) - Profile sections recursively overlay the base config in caller order. A - profile may omit fields required by a complete agent config because Fabric - validates only after all overlays have been applied. + @mcp.setter + def mcp(self, value: _McpConfig | Mapping[str, Any]) -> None: + self["mcp"] = _coerce(_McpConfig, value, "mcp") - Attributes: - schema_version: Profile schema identifier. - name: Stable, non-empty profile name. - description: Optional human-readable description. - harness: Optional partial harness overlay. - runtime: Optional partial runtime overlay. - environment: Optional partial environment overlay. - models: Optional partial model overlay. - mcp: Optional partial MCP overlay. - skills: Optional partial skill overlay. - telemetry: Optional partial telemetry overlay. - tools: Optional tool overlay. - extra_fields: Preserved extension fields not recognized by this SDK. - """ + @property + def skills(self) -> _SkillConfig: + """Mutable skill capability config, created on first access.""" - _fields = frozenset( - { - "schema_version", - "name", - "description", - "harness", - "runtime", - "environment", - "models", - "mcp", - "skills", - "telemetry", - "tools", - } - ) + return self._ensure_section("skills", _SkillConfig) - def __init__( + @skills.setter + def skills(self, value: _SkillConfig | Mapping[str, Any]) -> None: + self["skills"] = _coerce(_SkillConfig, value, "skills") + + @property + def telemetry(self) -> _TelemetryConfig: + """Mutable telemetry config, created on first access.""" + + return self._ensure_section("telemetry", _TelemetryConfig) + + @telemetry.setter + def telemetry(self, value: _TelemetryConfig | Mapping[str, Any]) -> None: + self["telemetry"] = _coerce(_TelemetryConfig, value, "telemetry") + + def _ensure_section(self, key: str, model: type[_T]) -> _T: + value = self.get(key) + if value is None: + value = model() # type: ignore[call-arg] + self[key] = value + elif not isinstance(value, model): + value = _coerce(model, value, key) + self[key] = value + return value + + def add_mcp_server( self, - *, name: str, - schema_version: str = "fabric.profile/v1alpha1", - description: str | None = None, - harness: HarnessConfig | Mapping[str, Any] | None = None, - runtime: RuntimeConfig | Mapping[str, Any] | None = None, - environment: EnvironmentConfig | Mapping[str, Any] | None = None, - models: Mapping[str, Any] | None = None, - mcp: Mapping[str, Any] | None = None, - skills: Mapping[str, Any] | None = None, - telemetry: Mapping[str, Any] | None = None, - tools: Any = None, + *, + transport: str, + url: str, + exposure: str = "harness_native", extra_fields: Mapping[str, Any] | None = None, - ) -> None: - values: dict[str, Any] = { - "schema_version": _required_text(schema_version, "schema_version"), - "name": _required_text(name, "profile name"), - } - if description is not None: - values["description"] = description - for key, item in ( - ("harness", harness), - ("runtime", runtime), - ("environment", environment), - ): - if item is not None: - values[key] = ( - deepcopy(item) - if isinstance(item, _ConfigMapping) - else _mapping(item, key) - ) - for key, item in ( - ("models", models), - ("mcp", mcp), - ("skills", skills), - ("telemetry", telemetry), - ("tools", tools), - ): - if item is not None: - values[key] = item - super().__init__(values, extra_fields=extra_fields) + ) -> "_ResolvedFabricConfig": + """Add or replace a named MCP server and return this config.""" + + self.mcp.add_server( + name, + transport=transport, + url=url, + exposure=exposure, + extra_fields=extra_fields, + ) + return self - @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "FabricProfileConfig": - """Build a typed, partial profile overlay from a mapping.""" + def add_skill_path(self, path: str | Path) -> "_ResolvedFabricConfig": + """Add a skill path and return this config.""" - data = _mapping(value, "FabricProfileConfig") - return cls( - schema_version=data.get("schema_version", "fabric.profile/v1alpha1"), - name=data.get("name"), - description=data.get("description"), - harness=data.get("harness"), - runtime=data.get("runtime"), - environment=data.get("environment"), - models=data.get("models"), - mcp=data.get("mcp"), - skills=data.get("skills"), - telemetry=data.get("telemetry"), - tools=data.get("tools"), - extra_fields={key: item for key, item in data.items() if key not in cls._fields}, + self.skills.add_path(path) + return self + + def enable_relay( + self, + *, + project: str | None = None, + output_dir: str | Path | None = None, + config: Mapping[str, Any] | None = None, + ) -> "_ResolvedFabricConfig": + """Enable NeMo Relay telemetry and return this config.""" + + self.telemetry.enable_relay( + project=project, + output_dir=output_dir, + config=config, ) + return self def _freeze(value: Any) -> Any: @@ -689,30 +872,24 @@ class RuntimeCapabilities(FabricMapping): not implemented. Attributes: - session: Whether stateful multi-turn sessions are supported. service: Whether long-lived service handles are supported. streaming: Whether event streaming is supported. updates: Whether runtime configuration updates are supported. cancellation: Whether in-flight cancellation is supported. - concurrent_invocations: Whether invocations may overlap safely. metadata: Additional capability details. """ - session: bool service: bool streaming: bool updates: bool cancellation: bool - concurrent_invocations: bool metadata: Mapping[str, Any] _fields = frozenset( { - "session", "service", "streaming", "updates", "cancellation", - "concurrent_invocations", "metadata", } ) @@ -744,7 +921,7 @@ class EffectiveConfig(FabricMapping): agent_root: Path config_path: Path | None config_root: Path - config: FabricConfig + config: _ResolvedFabricConfig _fields = frozenset( {"agent_name", "profiles", "agent_root", "config_path", "config_root", "config"} ) @@ -757,7 +934,7 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: data["config_path"] = ( None if data.get("config_path") is None else Path(data["config_path"]) ) - data["config"] = FabricConfig.from_mapping(data.get("config", {})) + data["config"] = _ResolvedFabricConfig.from_mapping(data.get("config", {})) return data @@ -841,71 +1018,6 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: return data -class RunRequest(FabricMapping): - """One normalized invocation request. - - ``input`` and all mapping fields must be JSON-compatible. Fabric generates - a request identifier when callers omit one and preserves unknown mapping - fields for forward compatibility. - - Attributes: - input: Harness input; defaults to an empty string. - request_id: Caller-provided or generated request identifier. - context: Caller-owned metadata propagated with the invocation. - overrides: Optional invocation-scoped config overrides. - extra_fields: Preserved extension fields not recognized by this SDK. - """ - - input: Any - request_id: str - context: Mapping[str, Any] - overrides: Mapping[str, Any] | None - _fields = frozenset({"input", "request_id", "context", "overrides"}) - _json_fields = frozenset({"input", "context", "overrides"}) - - def __init__( - self, - *, - input: Any = _UNSET, - request_id: str | None = None, - context: Mapping[str, Any] | None = None, - overrides: Mapping[str, Any] | None = None, - extra_fields: Mapping[str, Any] | None = None, - ) -> None: - data: dict[str, Any] = { - "input": "" if input is _UNSET or input is None else input, - "request_id": request_id or f"request-{uuid.uuid4().hex}", - "context": _mapping( - {} if context is None else context, - "request context", - ), - } - if overrides is not None: - data["overrides"] = _mapping(overrides, "request overrides") - extras = _mapping( - {} if extra_fields is None else extra_fields, - "request extra_fields", - ) - overlap = self._fields.intersection(extras) - if overlap: - raise FabricConfigError( - f"request extra_fields duplicates known fields: {', '.join(sorted(overlap))}" - ) - data.update(extras) - FabricMapping.__init__(self, data) - - @classmethod - def from_mapping(cls, value: Mapping[str, Any]) -> "RunRequest": - data = _mapping(value, "RunRequest") - return cls( - input=data.get("input", _UNSET), - request_id=data.get("request_id"), - context=data.get("context"), - overrides=data.get("overrides"), - extra_fields={key: item for key, item in data.items() if key not in cls._fields}, - ) - - class ErrorInfo(FabricMapping): """Structured failure returned inside a normalized ``RunResult``. @@ -1052,7 +1164,6 @@ class RuntimeHandle(FabricMapping): runtime_binding: Opaque integrity-bound runtime binding. agent_name: Resolved agent name. harness: Stable harness identifier. - mode: Runtime lifecycle mode. adapter_kind: Adapter execution mechanism. adapter_id: Optional Fabric adapter identifier. environment: Prepared environment snapshot. @@ -1062,7 +1173,6 @@ class RuntimeHandle(FabricMapping): runtime_binding: str agent_name: str harness: str - mode: str adapter_kind: str adapter_id: str | None environment: Mapping[str, Any] @@ -1072,7 +1182,6 @@ class RuntimeHandle(FabricMapping): "runtime_binding", "agent_name", "harness", - "mode", "adapter_kind", "adapter_id", "environment", @@ -1087,7 +1196,6 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: "runtime_binding", "agent_name", "harness", - "mode", "adapter_kind", ): data[field] = _required_text(data.get(field), field.replace("_", " ")) @@ -1191,80 +1299,3 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: ) data["metadata"] = _mapping(data.get("metadata", {}), "result metadata") return data - - -class SessionInfo(FabricMapping): - """Read-only metadata snapshot for an active or stopped session. - - Attributes: - session_id: Stable conversation identifier. - runtime_id: Runtime lifecycle identifier. - agent_name: Resolved agent name. - profiles: Applied profile names. - harness: Stable harness identifier. - adapter_id: Fabric adapter identifier. - adapter_kind: Adapter execution mechanism. - status: Current session lifecycle state. - capabilities: Operations declared by the runtime. - """ - - session_id: str - runtime_id: str - agent_name: str - profiles: Sequence[str] - harness: str - adapter_id: str - adapter_kind: str - status: str - capabilities: RuntimeCapabilities - _fields = frozenset( - { - "session_id", - "runtime_id", - "agent_name", - "profiles", - "harness", - "adapter_id", - "adapter_kind", - "status", - "capabilities", - } - ) - - @classmethod - def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: - data["profiles"] = _required_profiles(data, "SessionInfo") - data["capabilities"] = RuntimeCapabilities.from_mapping(data.get("capabilities", {})) - return data - - -class RuntimeUpdate(FabricMapping): - """Capability-gated update requested for a running session. - - Attributes: - overrides: Config overrides to apply to the runtime. - metadata: Caller-owned update metadata. - """ - - overrides: Mapping[str, Any] - metadata: Mapping[str, Any] - _fields = frozenset({"overrides", "metadata"}) - _json_fields = frozenset({"overrides", "metadata"}) - - -class RuntimeUpdateResult(FabricMapping): - """Normalized outcome of a runtime update request. - - Attributes: - status: Terminal update status. - applied: Overrides accepted by the runtime. - rejected: Overrides rejected by the runtime. - reason: Optional explanation for partial or complete rejection. - """ - - status: str - applied: Mapping[str, Any] - rejected: Mapping[str, Any] - reason: str | None - _fields = frozenset({"status", "applied", "rejected", "reason"}) - _json_fields = frozenset({"applied", "rejected"}) diff --git a/python/uv.lock b/python/uv.lock index 823fd0619..bf5458ea5 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -2,6 +2,163 @@ version = 1 revision = 3 requires-python = ">=3.11" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "nemo-fabric-runtime" source = { editable = "." } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.10,<3" }, + { name = "typing-extensions", specifier = ">=4.12" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] diff --git a/schemas/SCHEMA.md b/schemas/SCHEMA.md index 5ef55fb76..65e2756c5 100644 --- a/schemas/SCHEMA.md +++ b/schemas/SCHEMA.md @@ -8,6 +8,11 @@ SPDX-License-Identifier: Apache-2.0 This directory contains committed JSON Schema snapshots for the public Fabric contract. The files are generated from the Rust core types, not edited by hand. +The Python SDK exposes Pydantic authoring models for application callers. Those +models are hand-maintained against these Rust-generated schemas for now. When a +schema-backed Rust type changes, update the matching Pydantic model and its +schema-alignment tests in the same change. + ## Exported Schemas `fabric schema` exports the current public typed contract. @@ -16,7 +21,9 @@ contract. The files are generated from the Rust core types, not edited by hand. - `agent`: portable base `agent.yaml` config. - `profile`: profile config applied over an agent config. -- `adapter-descriptor`: minimal adapter descriptor consumed by Fabric. +- `adapter-descriptor`: minimal adapter descriptor consumed by Fabric. Each + descriptor declares a `contract_version`; Fabric rejects descriptors for + unsupported adapter contracts during planning. - `effective-config`: merged config after profile resolution. - `run-plan`: executable plan derived from effective config. @@ -31,7 +38,7 @@ contract. The files are generated from the Rust core types, not edited by hand. ### Runtime Lifecycle - `environment-handle`: prepared execution environment context. -- `runtime-handle`: active or resumable harness runtime. +- `runtime-handle`: active harness runtime identity and opaque adapter binding. - `invocation-handle`: one request/turn sent to a runtime. ### Results, Artifacts, And Diagnostics diff --git a/schemas/adapter-descriptor.schema.json b/schemas/adapter-descriptor.schema.json index a04c09589..937d84cf8 100644 --- a/schemas/adapter-descriptor.schema.json +++ b/schemas/adapter-descriptor.schema.json @@ -101,6 +101,37 @@ } }, "type": "object" + }, + "RuntimeCapabilities": { + "description": "Lifecycle behavior implemented by a resolved runtime path.", + "properties": { + "cancellation": { + "default": false, + "description": "Whether an in-flight invocation can be cancelled.", + "type": "boolean" + }, + "metadata": { + "additionalProperties": true, + "description": "Additional adapter-specific capability metadata.", + "type": "object" + }, + "service": { + "default": false, + "description": "Whether the selected runtime supports service lifecycle operations.", + "type": "boolean" + }, + "streaming": { + "default": false, + "description": "Whether invocations can emit progressive output.", + "type": "boolean" + }, + "updates": { + "default": false, + "description": "Whether a running runtime can accept config updates.", + "type": "boolean" + } + }, + "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -116,11 +147,26 @@ "$ref": "#/$defs/AdapterKind", "description": "Adapter implementation kind." }, + "capabilities": { + "$ref": "#/$defs/RuntimeCapabilities", + "default": { + "cancellation": false, + "service": false, + "streaming": false, + "updates": false + }, + "description": "Runtime lifecycle operations supported by this adapter." + }, "config": { "$ref": "#/$defs/AdapterConfigSupport", "default": {}, "description": "Fabric config areas this adapter consumes or generates." }, + "contract_version": { + "description": "Adapter descriptor contract version.", + "minLength": 1, + "type": "string" + }, "harness": { "description": "Stable machine-readable harness identifier implemented by this adapter.", "minLength": 1, @@ -143,6 +189,7 @@ } }, "required": [ + "contract_version", "adapter_id", "harness", "adapter_kind" diff --git a/schemas/adapter-invocation.schema.json b/schemas/adapter-invocation.schema.json index baa949fc9..eea68e66e 100644 --- a/schemas/adapter-invocation.schema.json +++ b/schemas/adapter-invocation.schema.json @@ -265,7 +265,7 @@ }, "connection": { "additionalProperties": true, - "description": "Provider connection metadata, such as server URL, session id, or namespace.", + "description": "Provider connection metadata, such as server URL, credential reference, or namespace.", "type": "object" }, "control_location": { @@ -419,7 +419,7 @@ }, "runtime": { "$ref": "#/$defs/RuntimeConfig", - "description": "Runtime mode and input/output contract." + "description": "Runtime input/output contract." }, "schema_version": { "description": "Config schema version.", @@ -684,7 +684,7 @@ "properties": { "context": { "additionalProperties": true, - "description": "Runtime context such as task, rollout, session, or caller metadata.", + "description": "Runtime context such as task, rollout, workflow, or caller metadata.", "type": "object" }, "input": { @@ -706,7 +706,7 @@ }, "RuntimeConfig": { "additionalProperties": true, - "description": "Runtime mode and input/output contract.", + "description": "Runtime input/output contract.", "properties": { "artifacts": { "description": "Artifact directory.", @@ -720,24 +720,12 @@ "description": "Input schema label.", "type": "string" }, - "mode": { - "$ref": "#/$defs/RuntimeMode", - "description": "Runtime mode." - }, "output_schema": { "default": "text", "description": "Output schema label.", "type": "string" - }, - "transport": { - "$ref": "#/$defs/Transport", - "default": "library", - "description": "Transport used to operate the harness." } }, - "required": [ - "mode" - ], "type": "object" }, "RuntimeContext": { @@ -763,13 +751,6 @@ "description": "Runtime handle id.", "type": "string" }, - "session_id": { - "description": "Optional caller-provided harness conversation id.", - "type": [ - "string", - "null" - ] - }, "telemetry": { "anyOf": [ { @@ -791,26 +772,6 @@ ], "type": "object" }, - "RuntimeMode": { - "description": "Runtime lifecycle mode.", - "oneOf": [ - { - "const": "oneshot", - "description": "Request is the lifecycle boundary.", - "type": "string" - }, - { - "const": "service", - "description": "Long-running process or service is the lifecycle boundary.", - "type": "string" - }, - { - "const": "session", - "description": "Session is the lifecycle boundary.", - "type": "string" - } - ] - }, "RuntimeTelemetryContext": { "description": "Runtime telemetry config passed to adapters.", "properties": { @@ -869,13 +830,6 @@ "description": "Whether telemetry is enabled for this run.", "type": "boolean" }, - "mode": { - "description": "Telemetry mode, for example `sdk`, `gateway`, or `external`.", - "type": [ - "string", - "null" - ] - }, "output_dir": { "description": "Optional telemetry output directory.", "type": [ @@ -919,13 +873,6 @@ "description": "Whether Relay is enabled.", "type": "boolean" }, - "relay_mode": { - "description": "Relay mode, when configured.", - "type": [ - "string", - "null" - ] - }, "relay_output_dir": { "description": "Relay output directory, when configured.", "type": [ @@ -961,31 +908,6 @@ "type": "string" } ] - }, - "Transport": { - "description": "Runtime transport.", - "oneOf": [ - { - "const": "library", - "description": "In-process library/SDK call.", - "type": "string" - }, - { - "const": "cli", - "description": "CLI process.", - "type": "string" - }, - { - "const": "http", - "description": "HTTP service.", - "type": "string" - }, - { - "const": "native_plugin", - "description": "Harness-native plugin surface.", - "type": "string" - } - ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/schemas/agent.schema.json b/schemas/agent.schema.json index 69338b444..bb2ddc5b5 100644 --- a/schemas/agent.schema.json +++ b/schemas/agent.schema.json @@ -28,7 +28,7 @@ }, "connection": { "additionalProperties": true, - "description": "Provider connection metadata, such as server URL, session id, or namespace.", + "description": "Provider connection metadata, such as server URL, credential reference, or namespace.", "type": "object" }, "control_location": { @@ -282,7 +282,7 @@ }, "RuntimeConfig": { "additionalProperties": true, - "description": "Runtime mode and input/output contract.", + "description": "Runtime input/output contract.", "properties": { "artifacts": { "description": "Artifact directory.", @@ -296,46 +296,14 @@ "description": "Input schema label.", "type": "string" }, - "mode": { - "$ref": "#/$defs/RuntimeMode", - "description": "Runtime mode." - }, "output_schema": { "default": "text", "description": "Output schema label.", "type": "string" - }, - "transport": { - "$ref": "#/$defs/Transport", - "default": "library", - "description": "Transport used to operate the harness." } }, - "required": [ - "mode" - ], "type": "object" }, - "RuntimeMode": { - "description": "Runtime lifecycle mode.", - "oneOf": [ - { - "const": "oneshot", - "description": "Request is the lifecycle boundary.", - "type": "string" - }, - { - "const": "service", - "description": "Long-running process or service is the lifecycle boundary.", - "type": "string" - }, - { - "const": "session", - "description": "Session is the lifecycle boundary.", - "type": "string" - } - ] - }, "SkillConfig": { "additionalProperties": true, "description": "Skill capability configuration.", @@ -362,13 +330,6 @@ "description": "Whether telemetry is enabled for this run.", "type": "boolean" }, - "mode": { - "description": "Telemetry mode, for example `sdk`, `gateway`, or `external`.", - "type": [ - "string", - "null" - ] - }, "output_dir": { "description": "Optional telemetry output directory.", "type": [ @@ -405,31 +366,6 @@ "type": "string" } ] - }, - "Transport": { - "description": "Runtime transport.", - "oneOf": [ - { - "const": "library", - "description": "In-process library/SDK call.", - "type": "string" - }, - { - "const": "cli", - "description": "CLI process.", - "type": "string" - }, - { - "const": "http", - "description": "HTTP service.", - "type": "string" - }, - { - "const": "native_plugin", - "description": "Harness-native plugin surface.", - "type": "string" - } - ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -479,7 +415,7 @@ }, "runtime": { "$ref": "#/$defs/RuntimeConfig", - "description": "Runtime mode and input/output contract." + "description": "Runtime input/output contract." }, "schema_version": { "description": "Config schema version.", diff --git a/schemas/effective-config.schema.json b/schemas/effective-config.schema.json index 840cd9e03..78bdaba2d 100644 --- a/schemas/effective-config.schema.json +++ b/schemas/effective-config.schema.json @@ -28,7 +28,7 @@ }, "connection": { "additionalProperties": true, - "description": "Provider connection metadata, such as server URL, session id, or namespace.", + "description": "Provider connection metadata, such as server URL, credential reference, or namespace.", "type": "object" }, "control_location": { @@ -130,7 +130,7 @@ }, "runtime": { "$ref": "#/$defs/RuntimeConfig", - "description": "Runtime mode and input/output contract." + "description": "Runtime input/output contract." }, "schema_version": { "description": "Config schema version.", @@ -369,7 +369,7 @@ }, "RuntimeConfig": { "additionalProperties": true, - "description": "Runtime mode and input/output contract.", + "description": "Runtime input/output contract.", "properties": { "artifacts": { "description": "Artifact directory.", @@ -383,46 +383,14 @@ "description": "Input schema label.", "type": "string" }, - "mode": { - "$ref": "#/$defs/RuntimeMode", - "description": "Runtime mode." - }, "output_schema": { "default": "text", "description": "Output schema label.", "type": "string" - }, - "transport": { - "$ref": "#/$defs/Transport", - "default": "library", - "description": "Transport used to operate the harness." } }, - "required": [ - "mode" - ], "type": "object" }, - "RuntimeMode": { - "description": "Runtime lifecycle mode.", - "oneOf": [ - { - "const": "oneshot", - "description": "Request is the lifecycle boundary.", - "type": "string" - }, - { - "const": "service", - "description": "Long-running process or service is the lifecycle boundary.", - "type": "string" - }, - { - "const": "session", - "description": "Session is the lifecycle boundary.", - "type": "string" - } - ] - }, "SkillConfig": { "additionalProperties": true, "description": "Skill capability configuration.", @@ -449,13 +417,6 @@ "description": "Whether telemetry is enabled for this run.", "type": "boolean" }, - "mode": { - "description": "Telemetry mode, for example `sdk`, `gateway`, or `external`.", - "type": [ - "string", - "null" - ] - }, "output_dir": { "description": "Optional telemetry output directory.", "type": [ @@ -492,31 +453,6 @@ "type": "string" } ] - }, - "Transport": { - "description": "Runtime transport.", - "oneOf": [ - { - "const": "library", - "description": "In-process library/SDK call.", - "type": "string" - }, - { - "const": "cli", - "description": "CLI process.", - "type": "string" - }, - { - "const": "http", - "description": "HTTP service.", - "type": "string" - }, - { - "const": "native_plugin", - "description": "Harness-native plugin surface.", - "type": "string" - } - ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/schemas/run-plan.schema.json b/schemas/run-plan.schema.json index 498d98823..419962680 100644 --- a/schemas/run-plan.schema.json +++ b/schemas/run-plan.schema.json @@ -34,11 +34,26 @@ "$ref": "#/$defs/AdapterKind", "description": "Adapter implementation kind." }, + "capabilities": { + "$ref": "#/$defs/RuntimeCapabilities", + "default": { + "cancellation": false, + "service": false, + "streaming": false, + "updates": false + }, + "description": "Runtime lifecycle operations supported by this adapter." + }, "config": { "$ref": "#/$defs/AdapterConfigSupport", "default": {}, "description": "Fabric config areas this adapter consumes or generates." }, + "contract_version": { + "description": "Adapter descriptor contract version.", + "minLength": 1, + "type": "string" + }, "harness": { "description": "Stable machine-readable harness identifier implemented by this adapter.", "minLength": 1, @@ -61,6 +76,7 @@ } }, "required": [ + "contract_version", "adapter_id", "harness", "adapter_kind" @@ -378,7 +394,7 @@ }, "connection": { "additionalProperties": true, - "description": "Provider connection metadata, such as server URL, session id, or namespace.", + "description": "Provider connection metadata, such as server URL, credential reference, or namespace.", "type": "object" }, "control_location": { @@ -532,7 +548,7 @@ }, "runtime": { "$ref": "#/$defs/RuntimeConfig", - "description": "Runtime mode and input/output contract." + "description": "Runtime input/output contract." }, "schema_version": { "description": "Config schema version.", @@ -824,48 +840,36 @@ "description": "Lifecycle behavior implemented by a resolved runtime path.", "properties": { "cancellation": { + "default": false, "description": "Whether an in-flight invocation can be cancelled.", "type": "boolean" }, - "concurrent_invocations": { - "description": "Whether the runtime accepts concurrent invocations.", - "type": "boolean" - }, "metadata": { "additionalProperties": true, "description": "Additional adapter-specific capability metadata.", "type": "object" }, "service": { + "default": false, "description": "Whether the selected runtime supports service lifecycle operations.", "type": "boolean" }, - "session": { - "description": "Whether the selected runtime supports session lifecycle operations.", - "type": "boolean" - }, "streaming": { + "default": false, "description": "Whether invocations can emit progressive output.", "type": "boolean" }, "updates": { + "default": false, "description": "Whether a running runtime can accept config updates.", "type": "boolean" } }, - "required": [ - "session", - "service", - "streaming", - "updates", - "cancellation", - "concurrent_invocations" - ], "type": "object" }, "RuntimeConfig": { "additionalProperties": true, - "description": "Runtime mode and input/output contract.", + "description": "Runtime input/output contract.", "properties": { "artifacts": { "description": "Artifact directory.", @@ -879,46 +883,14 @@ "description": "Input schema label.", "type": "string" }, - "mode": { - "$ref": "#/$defs/RuntimeMode", - "description": "Runtime mode." - }, "output_schema": { "default": "text", "description": "Output schema label.", "type": "string" - }, - "transport": { - "$ref": "#/$defs/Transport", - "default": "library", - "description": "Transport used to operate the harness." } }, - "required": [ - "mode" - ], "type": "object" }, - "RuntimeMode": { - "description": "Runtime lifecycle mode.", - "oneOf": [ - { - "const": "oneshot", - "description": "Request is the lifecycle boundary.", - "type": "string" - }, - { - "const": "service", - "description": "Long-running process or service is the lifecycle boundary.", - "type": "string" - }, - { - "const": "session", - "description": "Session is the lifecycle boundary.", - "type": "string" - } - ] - }, "SkillConfig": { "additionalProperties": true, "description": "Skill capability configuration.", @@ -945,13 +917,6 @@ "description": "Whether telemetry is enabled for this run.", "type": "boolean" }, - "mode": { - "description": "Telemetry mode, for example `sdk`, `gateway`, or `external`.", - "type": [ - "string", - "null" - ] - }, "output_dir": { "description": "Optional telemetry output directory.", "type": [ @@ -995,13 +960,6 @@ "description": "Whether Relay is enabled.", "type": "boolean" }, - "relay_mode": { - "description": "Relay mode, when configured.", - "type": [ - "string", - "null" - ] - }, "relay_output_dir": { "description": "Relay output directory, when configured.", "type": [ @@ -1037,31 +995,6 @@ "type": "string" } ] - }, - "Transport": { - "description": "Runtime transport.", - "oneOf": [ - { - "const": "library", - "description": "In-process library/SDK call.", - "type": "string" - }, - { - "const": "cli", - "description": "CLI process.", - "type": "string" - }, - { - "const": "http", - "description": "HTTP service.", - "type": "string" - }, - { - "const": "native_plugin", - "description": "Harness-native plugin surface.", - "type": "string" - } - ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/schemas/run-request.schema.json b/schemas/run-request.schema.json index b0e0e841d..506d095c6 100644 --- a/schemas/run-request.schema.json +++ b/schemas/run-request.schema.json @@ -4,7 +4,7 @@ "properties": { "context": { "additionalProperties": true, - "description": "Runtime context such as task, rollout, session, or caller metadata.", + "description": "Runtime context such as task, rollout, workflow, or caller metadata.", "type": "object" }, "input": { diff --git a/schemas/runtime-context.schema.json b/schemas/runtime-context.schema.json index f97175860..c6be10700 100644 --- a/schemas/runtime-context.schema.json +++ b/schemas/runtime-context.schema.json @@ -188,13 +188,6 @@ "description": "Runtime handle id.", "type": "string" }, - "session_id": { - "description": "Optional caller-provided harness conversation id.", - "type": [ - "string", - "null" - ] - }, "telemetry": { "anyOf": [ { diff --git a/schemas/runtime-handle.schema.json b/schemas/runtime-handle.schema.json index 0d69badaa..85ba2296f 100644 --- a/schemas/runtime-handle.schema.json +++ b/schemas/runtime-handle.schema.json @@ -106,26 +106,6 @@ "type": "string" } ] - }, - "RuntimeMode": { - "description": "Runtime lifecycle mode.", - "oneOf": [ - { - "const": "oneshot", - "description": "Request is the lifecycle boundary.", - "type": "string" - }, - { - "const": "service", - "description": "Long-running process or service is the lifecycle boundary.", - "type": "string" - }, - { - "const": "session", - "description": "Session is the lifecycle boundary.", - "type": "string" - } - ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -154,10 +134,6 @@ "description": "Stable machine-readable harness identifier.", "type": "string" }, - "mode": { - "$ref": "#/$defs/RuntimeMode", - "description": "Runtime mode." - }, "runtime_binding": { "description": "Fabric-owned opaque binding for this runtime handle.", "type": "string" @@ -172,7 +148,6 @@ "runtime_binding", "agent_name", "harness", - "mode", "adapter_kind", "environment" ], diff --git a/scripts/generate_api_docs.sh b/scripts/generate_api_docs.sh index c81f9ed47..811401074 100755 --- a/scripts/generate_api_docs.sh +++ b/scripts/generate_api_docs.sh @@ -22,7 +22,8 @@ PYTHONPATH="python/src" lazydocs \ --output-path "$out" \ --overview-file "index.md" \ "nemo_fabric.client" \ - "nemo_fabric.session" \ + "nemo_fabric.runtime" \ + "nemo_fabric.models" \ "nemo_fabric.types" \ "nemo_fabric.errors" @@ -35,6 +36,9 @@ perl -0pi -e 's///gs' "$out"/*.md perl -pi -e 's//.../g' "$out"/*.md perl -pi -e 's/[ \t]+$//' "$out"/*.md perl -0pi -e 's/\A\s+//' "$out"/*.md +# lazydocs nests properties at h4 directly under h2 class sections. Flatten +# those headings to h3 so generated pages satisfy markdown heading order. +perl -pi -e 's/^#### (property<\/kbd>)/### $1/' "$out"/*.md add_frontmatter() { local file="$1" @@ -61,13 +65,18 @@ add_frontmatter \ add_frontmatter \ "$out/nemo_fabric.client.md" \ "Client" \ - "Resolve, plan, diagnose, and run agents with FabricClient." \ + "Resolve, plan, diagnose, and run agents with Fabric." \ "/reference/api/python-library-reference/client" add_frontmatter \ - "$out/nemo_fabric.session.md" \ - "Sessions" \ - "Drive stateful multi-turn runtimes through the Session API." \ - "/reference/api/python-library-reference/sessions" + "$out/nemo_fabric.runtime.md" \ + "Runtime" \ + "Drive stateful multi-turn execution through the Runtime API." \ + "/reference/api/python-library-reference/runtime" +add_frontmatter \ + "$out/nemo_fabric.models.md" \ + "Models" \ + "Pydantic authoring models for Fabric config and request inputs." \ + "/reference/api/python-library-reference/models" add_frontmatter \ "$out/nemo_fabric.types.md" \ "Types" \ diff --git a/tests/adapters/test_adapaters_common_utils.py b/tests/adapters/test_adapaters_common_utils.py index 67c08c154..d7ab0171e 100644 --- a/tests/adapters/test_adapaters_common_utils.py +++ b/tests/adapters/test_adapaters_common_utils.py @@ -69,16 +69,35 @@ def test_load_payload_falls_back_to_stdin( @pytest.mark.parametrize( ("runtime_context", "expected"), [ - ({"session_id": "caller-session", "runtime_id": "runtime-1"}, "caller-session"), ({"runtime_id": "runtime-1"}, "runtime-1"), - ({}, None), ], ) -def test_runtime_session_id_prefers_caller_session_id( +def test_runtime_id_reads_required_runtime_context( runtime_context: dict[str, object], - expected: str | None, + expected: str, ): - assert common_utils.runtime_session_id({"runtime_context": runtime_context}) == expected + assert common_utils.runtime_id({"runtime_context": runtime_context}) == expected + + +def test_runtime_id_requires_runtime_context(): + with pytest.raises(ValueError, match="runtime_context.runtime_id"): + common_utils.runtime_id({"runtime_context": {}}) + + +def test_runtime_state_directory_is_scoped_to_runtime( + tmp_path: Path, +): + first = common_utils.runtime_state_directory( + tmp_path / "hermes-home", + {"runtime_context": {"runtime_id": "runtime-1"}}, + ) + second = common_utils.runtime_state_directory( + tmp_path / "hermes-home", + {"runtime_context": {"runtime_id": "runtime-2"}}, + ) + + assert first == tmp_path / "hermes-home" / "runtimes" / "runtime-1" + assert second == tmp_path / "hermes-home" / "runtimes" / "runtime-2" def test_dump_yaml_falls_back_to_json_when_yaml_is_unavailable( diff --git a/tests/adapters/test_codex_cli.py b/tests/adapters/test_codex_cli.py index 96132aa8c..b461fbd63 100644 --- a/tests/adapters/test_codex_cli.py +++ b/tests/adapters/test_codex_cli.py @@ -11,7 +11,7 @@ import pytest import yaml -from nemo_fabric import FabricClient, FabricConfig +from nemo_fabric import Fabric, FabricConfig ROOT = Path(__file__).resolve().parents[2] ADAPTER_PATH = ( @@ -63,7 +63,7 @@ def codex_payload_fixture(tmp_path): "model": "openai/gpt-5.4", } }, - "runtime": {"mode": "oneshot"}, + "runtime": {}, }, }, "runtime_context": { @@ -131,7 +131,7 @@ def write_mock_codex(path): path.chmod(0o755) -def fabric_config(tmp_path, mock_codex, *, mode): +def fabric_config(tmp_path, mock_codex): return FabricConfig.from_mapping( { "schema_version": "fabric.agent/v1alpha1", @@ -146,8 +146,6 @@ def fabric_config(tmp_path, mock_codex, *, mode): }, }, "runtime": { - "mode": mode, - "transport": "cli", "artifacts": str(tmp_path / "artifacts"), }, "environment": { @@ -174,9 +172,10 @@ def test_oneshot_command_uses_fabric_overrides_and_codex_owned_auth( exec_index = command.index("exec") assert command[0] == "codex" assert command[1:exec_index] == [] - assert command[exec_index : exec_index + 3] == ["exec", "--json", "--ephemeral"] - assert ["--sandbox", "read-only"] == command[exec_index + 3 : exec_index + 5] - assert ["--profile", "fabric-runtime-1"] == command[exec_index + 5 : exec_index + 7] + assert command[exec_index : exec_index + 2] == ["exec", "--json"] + assert "--ephemeral" not in command + assert ["--sandbox", "read-only"] == command[exec_index + 2 : exec_index + 4] + assert ["--profile", "fabric-runtime-1"] == command[exec_index + 4 : exec_index + 6] assert "--dangerously-bypass-hook-trust" not in command assert ["--model", "gpt-5.4"] == command[-3:-1] assert command[-1] == "-" @@ -331,14 +330,13 @@ def test_relay_routes_codex_through_standalone_gateway( def test_native_otel_profile_writes_codex_telemetry_config(codex_payload, tmp_path): + from examples.code_review_agent import codex_cli_config, with_native_otel + adapter = load_codex_adapter() - profile = yaml.safe_load( - (ROOT / "examples/code-review-agent/profiles/native-otel.yaml").read_text( - encoding="utf-8" - ) - ) config = codex_payload["effective_config"]["config"] - config["telemetry"] = profile["telemetry"] + typed = with_native_otel(codex_cli_config()) + assert typed.telemetry is not None + config["telemetry"] = typed.telemetry.to_mapping() config["harness"]["settings"]["config_overrides"] = {} codex_settings = adapter.write_config_files(codex_payload) @@ -536,10 +534,8 @@ def test_config_override_values_reject_nested_non_finite_numbers(value): adapter.toml_value(value) -def test_session_reuses_codex_thread_across_invocations(codex_payload, monkeypatch, tmp_path): +def test_runtime_reuses_codex_thread_across_invocations(codex_payload, monkeypatch, tmp_path): adapter = load_codex_adapter() - codex_payload["effective_config"]["config"]["runtime"]["mode"] = "session" - codex_payload["runtime_context"]["session_id"] = "review-session" mock_run = MagicMock( side_effect=[ subprocess.CompletedProcess( @@ -576,7 +572,6 @@ def test_session_reuses_codex_thread_across_invocations(codex_payload, monkeypat assert second_command[-3:] == ["resume", "thread-123", "-"] assert first["response"] == "first response" assert second["response"] == "second response" - assert second["session_id"] == "review-session" assert second["thread_id"] == "thread-123" assert second["usage"]["cached_input_tokens"] == 2 child_env = mock_run.call_args_list[0].kwargs["env"] @@ -586,14 +581,24 @@ def test_session_reuses_codex_thread_across_invocations(codex_payload, monkeypat assert "FABRIC_UNRELATED_SECRET" not in child_env assert mock_run.call_args_list[0].kwargs["timeout"] == 1800 - state_path = adapter.session_state_path(codex_payload, "review-session") + state_path = adapter.runtime_state_path(codex_payload, "runtime-1") assert json.loads(state_path.read_text(encoding="utf-8")) == { - "session_id": "review-session", + "runtime_id": "runtime-1", "thread_id": "thread-123", } -def test_oneshot_does_not_persist_codex_thread(codex_payload, monkeypatch): +def test_runtime_rejects_corrupt_codex_thread_state(codex_payload): + adapter = load_codex_adapter() + state_path = adapter.runtime_state_path(codex_payload, "runtime-1") + state_path.parent.mkdir(parents=True) + state_path.write_text("{", encoding="utf-8") + + with pytest.raises(RuntimeError, match="invalid Codex runtime state"): + adapter.load_thread_id(codex_payload, "runtime-1") + + +def test_runtime_persists_codex_thread_state(codex_payload, monkeypatch): adapter = load_codex_adapter() mock_run = MagicMock( return_value=subprocess.CompletedProcess( @@ -612,7 +617,7 @@ def test_oneshot_does_not_persist_codex_thread(codex_payload, monkeypatch): assert "events" not in output assert "stdout" not in output assert "stderr" not in output - assert not (Path(output["state_dir"]) / "sessions").exists() + assert (Path(output["state_dir"]) / "runtimes").exists() def test_adapter_rejects_structured_input_until_chat_is_supported(codex_payload): @@ -661,6 +666,28 @@ def test_process_launch_failures_return_structured_results( assert "stderr" not in output +def test_thread_mismatch_preserves_process_error(codex_payload, monkeypatch): + adapter = load_codex_adapter() + adapter.save_thread_id(codex_payload, "runtime-1", "thread-persisted") + monkeypatch.setattr( + adapter.subprocess, + "run", + MagicMock( + return_value=subprocess.CompletedProcess( + args=[], + returncode=1, + stdout=codex_jsonl("thread-unexpected", "failed response"), + stderr="Codex process failed", + ) + ), + ) + + output = adapter.run_codex(codex_payload) + + assert output["failed"] is True + assert output["error"] == "Codex process failed" + + @pytest.mark.parametrize("timeout", [0, -1, float("inf"), "30"]) def test_adapter_rejects_invalid_timeout(codex_payload, timeout): adapter = load_codex_adapter() @@ -671,19 +698,10 @@ def test_adapter_rejects_invalid_timeout(codex_payload, timeout): adapter.run_codex(codex_payload) -def test_adapter_rejects_unsupported_runtime_mode(codex_payload): - adapter = load_codex_adapter() - codex_payload["effective_config"]["config"]["runtime"]["mode"] = "service" - - with pytest.raises(ValueError, match="supports only oneshot and session"): - adapter.run_codex(codex_payload) - - -def test_session_fails_if_codex_does_not_return_thread_identity( +def test_runtime_fails_if_codex_does_not_return_thread_identity( codex_payload, monkeypatch ): adapter = load_codex_adapter() - codex_payload["effective_config"]["config"]["runtime"]["mode"] = "session" mock_run = MagicMock( return_value=subprocess.CompletedProcess( args=[], @@ -728,19 +746,17 @@ def test_successful_process_without_final_response_is_failed(codex_payload, monk assert "final agent message" in output["error"] -async def test_fabric_session_invokes_codex_then_resumes(tmp_path): +async def test_fabric_runtime_invokes_codex_then_resumes(tmp_path): mock_codex = tmp_path / "codex" write_mock_codex(mock_codex) - config = fabric_config(tmp_path, mock_codex, mode="session") + config = fabric_config(tmp_path, mock_codex) - async with await FabricClient().start_session( + async with await Fabric().start_runtime( config, base_dir=tmp_path, - session_id="fabric-session", - ) as session: - assert session.session_id == "fabric-session" - first = await session.invoke(input="first") - second = await session.invoke(input="second") + ) as runtime: + first = await runtime.invoke(input="first") + second = await runtime.invoke(input="second") assert first.runtime_id == second.runtime_id assert first.output["response"] == "thread-fake:first" @@ -750,19 +766,19 @@ async def test_fabric_session_invokes_codex_then_resumes(tmp_path): assert second.output["command"][-3:] == ["resume", "thread-fake", "-"] -async def test_fabric_oneshot_is_ephemeral_and_uses_cached_codex_auth(tmp_path): +async def test_fabric_oneshot_uses_cached_codex_auth(tmp_path): mock_codex = tmp_path / "codex" write_mock_codex(mock_codex) - config = fabric_config(tmp_path, mock_codex, mode="oneshot") + config = fabric_config(tmp_path, mock_codex) os.environ.pop("OPENAI_API_KEY", None) - async with FabricClient() as client: - report = await client.doctor(config, base_dir=tmp_path) - result = await client.run( - config, - base_dir=tmp_path, - input="inspect", - ) + client = Fabric() + report = await client.doctor(config, base_dir=tmp_path) + result = await client.run( + config, + base_dir=tmp_path, + input="inspect", + ) assert report.status == "pass" assert any( @@ -771,28 +787,21 @@ async def test_fabric_oneshot_is_ephemeral_and_uses_cached_codex_auth(tmp_path): ) assert not any(check.name == "requirement.env" for check in report.checks) assert result.output["response"] == "thread-fake:inspect" - assert "--ephemeral" in result.output["command"] - assert result.output["session_id"] is None + assert "--ephemeral" not in result.output["command"] -@pytest.mark.parametrize( - ("profile", "mode", "session_capability"), - [ - ("codex_cli", "oneshot", False), - ("codex_cli_session", "session", True), - ], -) -def test_codex_profiles_resolve_runtime_mode(profile, mode, session_capability): - plan = FabricClient().plan( - ROOT / "examples" / "code-review-agent", - profiles=[profile], +def test_codex_profile_resolves_runtime_adapter(): + from examples.code_review_agent import BASE_DIR, codex_cli_config + + plan = Fabric().plan( + codex_cli_config(), + base_dir=BASE_DIR, ) assert plan.adapter.adapter_id == "nvidia.fabric.codex.cli" assert plan.adapter.harness == "codex" - assert plan.effective_config.config.runtime.mode == mode + assert "mode" not in plan.effective_config.config.runtime assert plan.effective_config.config.runtime.input_schema == "text" - assert plan.capabilities.session is session_capability settings = plan.effective_config.config.harness.settings assert settings["config_overrides"]["model_reasoning_effort"] == "high" unsupported = plan["capability_plan"]["unsupported"] diff --git a/tests/adapters/test_hermes_cli.py b/tests/adapters/test_hermes_cli.py index 5bdd02b80..30901ce66 100644 --- a/tests/adapters/test_hermes_cli.py +++ b/tests/adapters/test_hermes_cli.py @@ -4,17 +4,16 @@ import types from pathlib import Path -from nemo_fabric import FabricClient +from nemo_fabric import Fabric async def test_hermes_cli_fields(hermes_command: Path, hermes_agent_dir: Path, hermes_cli_profile: str): # Ensure the hermes_cli adapter returns expected fields - async with FabricClient() as client: - result = await client.run( - hermes_agent_dir, - profiles=[hermes_cli_profile], - input="who are you?", - ) + result = await Fabric().run( + hermes_agent_dir, + profiles=[hermes_cli_profile], + input="who are you?", + ) assert result["status"] == "succeeded" assert result["adapter_kind"] == "python" @@ -24,7 +23,7 @@ async def test_hermes_cli_fields(hermes_command: Path, hermes_agent_dir: Path, h assert output["adapter"] == "cli" assert output["command"][0] == hermes_command.as_posix() assert output["harness"] == "hermes" - assert output["mode"] == "hermes_cli_oneshot" + assert output["mode"] == "hermes_cli_runtime" assert output["model"] == "test-model" assert output["fabric_home"] is None @@ -40,6 +39,8 @@ async def test_hermes_cli_fields(hermes_command: Path, hermes_agent_dir: Path, h # Ensure these fields are present in the output, even if they are None assert field in output, f"Missing field in output: {field}" + assert Path(output["hermes_home"]).parts[-2:] == ("runtimes", result["runtime_id"]) + async def test_hermes_cli_rejects_native_telemetry( hermes_agent_dir: Path, @@ -57,12 +58,11 @@ async def test_hermes_cli_rejects_native_telemetry( encoding="utf-8", ) - async with FabricClient() as client: - result = await client.run( - hermes_agent_dir, - profiles=[hermes_cli_profile, "native_telemetry"], - input="who are you?", - ) + result = await Fabric().run( + hermes_agent_dir, + profiles=[hermes_cli_profile, "native_telemetry"], + input="who are you?", + ) assert result["status"] == "failed" assert "only relay telemetry is supported for Hermes" in result["error"]["message"] @@ -70,24 +70,24 @@ async def test_hermes_cli_rejects_native_telemetry( async def test_hermes_cli_multi_turn( hermes_agent_dir: Path, - hermes_cli_session_profile: str, + hermes_cli_runtime_profile: str, hermes_state: types.ModuleType, ): """ - Test that multi-turn sessions are tracked in the hermes session database when using the hermes_cli adapter. + Test that multi-turn runtime state is tracked in the Hermes session database. This test calls the fake-hermes.py script rather than hermes itself, thus it doesn't require an API key, however the hermes_cli adapter does use the hermes_state module, so we can test that the session is recorded propperly. """ - async with await FabricClient().start_session( + async with await Fabric().start_runtime( hermes_agent_dir, - profiles=[hermes_cli_session_profile], - ) as session: - runtime_id = session.runtime["runtime_id"] - await session.invoke(input="prompt1") - await session.invoke(input="prompt2") + profiles=[hermes_cli_runtime_profile], + ) as runtime: + runtime_id = runtime.runtime_id + await runtime.invoke(input="prompt1") + result = await runtime.invoke(input="prompt2") - session_db_path = hermes_agent_dir / "artifacts/hermes-home/state.db" + session_db_path = Path(result["output"]["hermes_home"]) / "state.db" assert session_db_path.exists(), f"Expected session DB at {session_db_path} does not exist" session_db = hermes_state.SessionDB(db_path=session_db_path) diff --git a/tests/adapters/test_hermes_cli_preflight.py b/tests/adapters/test_hermes_cli_preflight.py index 769ada84b..e03f76141 100644 --- a/tests/adapters/test_hermes_cli_preflight.py +++ b/tests/adapters/test_hermes_cli_preflight.py @@ -7,7 +7,7 @@ import pytest import yaml -from nemo_fabric import FabricClient +from nemo_fabric import Fabric @pytest.mark.parametrize("api_key_set", [True, False]) async def test_preflight_api_key_e2e(hermes_agent_dir: Path, hermes_cli_profile: str, api_key_set: bool): @@ -27,12 +27,11 @@ async def test_preflight_api_key_e2e(hermes_agent_dir: Path, hermes_cli_profile: assert "FAB_CI_FAKE_KEY" not in os.environ, "FAB_CI_FAKE_KEY should not be set in the environment for this test" - async with FabricClient() as client: - result = await client.run( - hermes_agent_dir, - profiles=[hermes_cli_profile], - input="who are you?", - ) + result = await Fabric().run( + hermes_agent_dir, + profiles=[hermes_cli_profile], + input="who are you?", + ) if api_key_set: assert result["status"] == "succeeded" else: diff --git a/tests/adapters/test_hermes_sdk_adapter.py b/tests/adapters/test_hermes_sdk_adapter.py index 808c99d25..ae1b62ebe 100644 --- a/tests/adapters/test_hermes_sdk_adapter.py +++ b/tests/adapters/test_hermes_sdk_adapter.py @@ -32,7 +32,7 @@ async def test_hermes_sdk_rejects_native_telemetry(): await adapter.run_hermes_sdk(payload) -async def test_runtime_id_drives_hermes_session_id_and_hermes_db_history( +async def test_fabric_runtime_id_drives_hermes_session_id_and_db_history( monkeypatch, tmp_path: Path, ) -> None: @@ -149,9 +149,9 @@ def run_conversation( }, }, }, - "runtime_context": { - "runtime_id": "runtime-fabric-123", - "environment": {"workspace": str(tmp_path)}, + "runtime_context": { + "runtime_id": "runtime-fabric-123", + "environment": {"workspace": str(tmp_path)}, }, "request": { "input": "hello", @@ -170,3 +170,6 @@ def run_conversation( assert captured["init"]["platform"] == "fabric" assert captured["conversation"]["conversation_history"] == db_history assert "session_id" not in output + assert Path(output["hermes_home"]) == ( + tmp_path / "hermes-home" / "runtimes" / "runtime-fabric-123" + ) diff --git a/tests/conftest.py b/tests/conftest.py index b6a905847..380170459 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import os import shutil +import sys import types from collections.abc import Iterator from pathlib import Path @@ -10,6 +11,9 @@ import pytest CUR_DIR = Path(__file__).parent.resolve() +REPO_ROOT = CUR_DIR.parent.resolve() +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) @pytest.fixture(name="restore_environ", autouse=True) def restore_environ_fixture(): @@ -79,20 +83,44 @@ def hermes_shim_agent_dir_fixture( @pytest.fixture(name="code_review_agent_dir") def code_review_agent_dir_fixture(repo_root: Path, tmp_path: Path) -> Path: """ - Creates a temporary copy of the example code review agent directory for testing. + Creates a writable copy of the example's assets for runtime tests. """ - return _copy_agent_dir(repo_root / "examples" / "code-review-agent", tmp_path, "code-review-agent") + return _copy_agent_dir( + repo_root / "examples" / "code_review_agent", + tmp_path, + "code-review-agent", + ) + + +@pytest.fixture(name="file_config_agent_dir_src", scope="session") +def file_config_agent_dir_src_fixture(repo_root: Path) -> Path: + """Return the test-only portable config package.""" + + return repo_root / "tests" / "fixtures" / "file-config-agent" + + +@pytest.fixture(name="file_config_agent_dir") +def file_config_agent_dir_fixture( + file_config_agent_dir_src: Path, + tmp_path: Path, +) -> Path: + """Create a writable copy for CLI and file-profile tests.""" + + return _copy_agent_dir(file_config_agent_dir_src, tmp_path, "file-config-agent") @pytest.fixture(name="hermes_cli_profile", scope="session") def hermes_cli_profile_fixture() -> str: return "env_local" -@pytest.fixture(name="hermes_cli_session_profile") -def hermes_cli_session_profile_fixture(repo_root: Path, hermes_agent_dir: Path) -> str: - src_yaml = repo_root / "examples/code-review-agent/profiles/hermes-cli-session.yaml" - assert src_yaml.exists(), f"Missing hermes-cli-session.yaml profile: {src_yaml}" - shutil.copy(src_yaml, hermes_agent_dir / "profiles/hermes-cli-session.yaml") - return "hermes_cli_session" +@pytest.fixture(name="hermes_cli_runtime_profile") +def hermes_cli_runtime_profile_fixture(hermes_agent_dir: Path) -> str: + import yaml + + config_path = hermes_agent_dir / "agent.yaml" + config = yaml.safe_load(config_path.read_text(encoding="utf-8")) + config["harness"]["settings"]["prepare_runtime_state"] = True + config_path.write_text(yaml.safe_dump(config), encoding="utf-8") + return "env_local" @pytest.fixture(name="hermes_command") diff --git a/tests/docs/test_python_api_docs.py b/tests/docs/test_python_api_docs.py index 68a25170d..12ae907e7 100644 --- a/tests/docs/test_python_api_docs.py +++ b/tests/docs/test_python_api_docs.py @@ -11,6 +11,7 @@ from pathlib import Path import nemo_fabric +from pydantic import BaseModel ROOT = Path(__file__).resolve().parents[2] @@ -19,7 +20,8 @@ NAVIGATION = ROOT / "docs" / "index.yml" MODULE_SLUGS = { "nemo_fabric.client": "/reference/api/python-library-reference/client", - "nemo_fabric.session": "/reference/api/python-library-reference/sessions", + "nemo_fabric.runtime": "/reference/api/python-library-reference/runtime", + "nemo_fabric.models": "/reference/api/python-library-reference/models", "nemo_fabric.types": "/reference/api/python-library-reference/types", "nemo_fabric.errors": "/reference/api/python-library-reference/errors", } @@ -75,9 +77,17 @@ def test_exported_sdk_classes_and_public_members_have_docstrings() -> None: or isinstance(raw_member, property) ): continue + if issubclass(exported, BaseModel) and hasattr(BaseModel, member_name): + continue assert getdoc(member), f"{export_name}.{member_name}" +def test_generated_reference_uses_valid_heading_order() -> None: + for page in REFERENCE_DIR.glob("*.md"): + text = page.read_text(encoding="utf-8") + assert "#### property" not in text, page + + def test_landing_page_routes_new_users_through_the_product() -> None: landing = LANDING_PAGE.read_text(encoding="utf-8") navigation = NAVIGATION.read_text(encoding="utf-8") @@ -97,8 +107,15 @@ def test_landing_page_routes_new_users_through_the_product() -> None: for destination in ( "/reference/api/python-library-reference/client", - "/reference/api/python-library-reference/sessions", + "/reference/api/python-library-reference/runtime", "/reference/api/python-library-reference/types", "/reference/api/python-library-reference/errors", ): assert destination in landing + + quick_start = landing.split("## Quick start", maxsplit=1)[1].split( + "## Choose your interface", maxsplit=1 + )[0] + assert "client.plan(" not in quick_start + assert "client.doctor(" not in quick_start + assert "/sdk/python" in quick_start diff --git a/tests/e2e/test_cli.py b/tests/e2e/test_cli.py index b424e98f1..2a5e33d54 100644 --- a/tests/e2e/test_cli.py +++ b/tests/e2e/test_cli.py @@ -14,10 +14,10 @@ def test_cli( tmp_path: Path, - code_review_agent_dir: Path, + file_config_agent_dir: Path, hermes_shim_agent_dir: Path, ): - temp_example = code_review_agent_dir + temp_example = file_config_agent_dir temp_fixture = hermes_shim_agent_dir assert call_text("validate", temp_example).startswith("validated") @@ -73,7 +73,7 @@ def test_cli( descriptor = profile_plan["adapter_descriptor"]["descriptor"] assert descriptor["adapter_id"] == adapter_id assert descriptor["adapter_kind"] == adapter_kind - assert profile_plan["config"]["runtime"]["mode"] == "oneshot" + assert "mode" not in profile_plan["config"]["runtime"] assert profile_plan["capability_plan"]["native"]["skill_paths"] assert "github" in profile_plan["capability_plan"]["native"]["mcp_servers"] telemetry_plan = profile_plan["telemetry_plan"] @@ -111,6 +111,17 @@ def test_cli( assert hermes["output"]["managed_mcp_servers"] == [] assert_relay_disabled_native_observability(hermes) + second_run = call_json( + "run", + temp_fixture, + "--profile", + "env_local", + "--input", + "hello runtime hermes", + ) + assert second_run["status"] == "succeeded" + assert second_run["output"]["runtime_id"] == second_run["runtime_id"] + request = json.dumps( { "request_id": "cli-structured-request", @@ -130,22 +141,20 @@ def test_cli( temp_fixture, "--profile", "env_local", - "--session-id", - "cli-session-123", "--verbose", ) assert chat.stdout == "" assert '"received": "hello chat"' in chat.stderr - assert '"session_id": "cli-session-123"' in chat.stderr + assert '"runtime_id": "runtime-' in chat.stderr assert "NEMO FABRIC" in chat.stderr - assert "interactive runtime session" in chat.stderr + assert "interactive runtime" in chat.stderr assert "agent: hermes-shim-agent" in chat.stderr assert "profile: env_local" in chat.stderr assert "harness: hermes" in chat.stderr assert "adapter: python" in chat.stderr - assert chat.stderr.count("session_id: cli-session-123 (provided)") >= 2 - assert "you[env_local:cli-session-123]> " in chat.stderr - assert "you[env_local:cli-session-123]> \nagent> {" in chat.stderr + assert chat.stderr.count("runtime_id: runtime-") >= 2 + assert "you[env_local:runtime-" in chat.stderr + assert "> \nagent> {" in chat.stderr assert "agent> {" in chat.stderr assert "runtime_id: runtime-" in chat.stderr assert "/verbose on|off" in chat.stderr @@ -160,12 +169,6 @@ def test_cli( assert "| invocation_id: invocation-" in chat.stderr assert "| artifact_count:" in chat.stderr - rejected_chat = run_raw("", "chat", temp_example, "--profile", "hermes_sdk") - assert rejected_chat.returncode != 0 - assert rejected_chat.stdout == "" - assert "fabric chat requires runtime.mode=session" in rejected_chat.stderr - - def call_text(*args: object) -> str: completed = run(*args) return completed.stdout.strip() diff --git a/tests/e2e/test_codex_cli.py b/tests/e2e/test_codex_cli.py index bf61a7a2e..5fb154cfe 100644 --- a/tests/e2e/test_codex_cli.py +++ b/tests/e2e/test_codex_cli.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Opt-in real Codex CLI smoke for Fabric one-shot and session modes. +"""Opt-in real Codex CLI smoke for Fabric one-shot and multi-turn runtimes. RUN_FABRIC_CODEX_INTEGRATION=1 pytest tests/e2e/test_codex_cli.py """ @@ -12,12 +12,9 @@ import os import shutil import uuid -from pathlib import Path import pytest -ROOT = Path(__file__).resolve().parents[2] - async def test_codex_cli(): if os.environ.get("RUN_FABRIC_CODEX_INTEGRATION") != "1": @@ -30,37 +27,37 @@ async def test_codex_cli(): async def _run() -> None: - from nemo_fabric import FabricClient + from examples.code_review_agent import BASE_DIR, codex_cli_config + from nemo_fabric import Fabric - agent = ROOT / "examples" / "code-review-agent" + config = codex_cli_config() nonce = f"fabric-{uuid.uuid4().hex[:8]}" - async with FabricClient() as client: - oneshot = await client.run( - agent, - profiles=["codex_cli"], - input="Reply with exactly: FABRIC_CODEX_ONESHOT_OK", - ) - assert oneshot["status"] == "succeeded", oneshot.to_mapping() - assert "fabric_codex_oneshot_ok" in oneshot["output"]["response"].lower(), ( - oneshot.to_mapping() + client = Fabric() + oneshot = await client.run( + config, + base_dir=BASE_DIR, + input="Reply with exactly: FABRIC_CODEX_ONESHOT_OK", + ) + assert oneshot["status"] == "succeeded", oneshot.to_mapping() + assert "fabric_codex_oneshot_ok" in oneshot["output"]["response"].lower(), ( + oneshot.to_mapping() + ) + assert "--ephemeral" not in oneshot["output"]["command"], oneshot.to_mapping() + + async with await client.start_runtime( + config, + base_dir=BASE_DIR, + ) as runtime: + first = await runtime.invoke(input=f"Remember this value: {nonce}") + second = await runtime.invoke( + input="Reply with only the value I asked you to remember." ) - assert "--ephemeral" in oneshot["output"]["command"], oneshot.to_mapping() - - async with await client.start_session( - agent, - profiles=["codex_cli_session"], - session_id=nonce, - ) as session: - first = await session.invoke(input=f"Remember this value: {nonce}") - second = await session.invoke( - input="Reply with only the value I asked you to remember." - ) - results = (first.to_mapping(), second.to_mapping()) - assert first["status"] == second["status"] == "succeeded", results - assert first["output"]["thread_id"] == second["output"]["thread_id"], results - assert nonce in second["output"]["response"], second.to_mapping() - assert second["output"]["command"][-3:-1] == [ - "resume", - first["output"]["thread_id"], - ], second.to_mapping() + results = (first.to_mapping(), second.to_mapping()) + assert first["status"] == second["status"] == "succeeded", results + assert first["output"]["thread_id"] == second["output"]["thread_id"], results + assert nonce in second["output"]["response"], second.to_mapping() + assert second["output"]["command"][-3:-1] == [ + "resume", + first["output"]["thread_id"], + ], second.to_mapping() diff --git a/tests/e2e/test_hermes_cli.py b/tests/e2e/test_hermes_cli.py index 3e397ed7f..ee8fea505 100644 --- a/tests/e2e/test_hermes_cli.py +++ b/tests/e2e/test_hermes_cli.py @@ -28,7 +28,7 @@ def test_hermes_cli(hermes_agent_dir: Path): assert result["metadata"]["adapter_runner"] == "python" assert result["output"]["harness"] == "hermes" assert result["output"]["adapter"] == "cli" - assert result["output"]["mode"] == "hermes_cli_oneshot" + assert result["output"]["mode"] == "hermes_cli_runtime" assert result["output"]["fabric_invocation"] is None assert result["output"]["hermes_native_config"]["mcp_servers"] == ["github"] assert result["output"]["hermes_native_config"]["skill_dirs"] diff --git a/tests/e2e/test_hermes_e2e.py b/tests/e2e/test_hermes_e2e.py index 16a6b5f9c..b66e8c52d 100644 --- a/tests/e2e/test_hermes_e2e.py +++ b/tests/e2e/test_hermes_e2e.py @@ -6,14 +6,19 @@ import json import os import sys +from collections.abc import Callable from pathlib import Path from types import ModuleType import pytest import yaml -from _utils.utils import update_base_url -from nemo_fabric import FabricClient +from examples.code_review_agent import ( + hermes_cli_config, + hermes_sdk_config, + with_relay, +) +from nemo_fabric import Fabric, FabricConfig class BaseTestHermesE2E: @@ -21,8 +26,7 @@ class BaseTestHermesE2E: Shared E2E Hermes relay assertions for adapter-specific subclasses. """ - profile_names: tuple[str, ...] - profile_file: str + config_builder: Callable[[], FabricConfig] adapter_kind: str adapter_runner: str output_adapter: str @@ -44,18 +48,16 @@ async def run_hermes_with_relay( self.code_review_agent_dir = code_review_agent_dir self.api_server = api_server - update_base_url( - code_review_agent_dir / "profiles" / self.profile_file, - api_server, + config = self.config_builder() + config.harness.settings["base_url"] = f"{api_server}/v1" + config = with_relay(config) + + self.result = await Fabric().run( + config, + base_dir=code_review_agent_dir, + input="Reply with exactly: relay ok", ) - async with FabricClient() as client: - self.result = await client.run( - code_review_agent_dir, - profiles=list(self.profile_names), - input="Reply with exactly: relay ok", - ) - self.output = self.result["output"] self.artifacts = self.result["artifacts"] self.artifact_root = Path(self.artifacts["root"]).resolve() @@ -72,7 +74,7 @@ async def test_artifacts(self): assert len(self.result.telemetry) == 1 assert self.result.telemetry[0].provider == "relay" assert self.result.telemetry[0].metadata["relay_enabled"] is True - assert self.result.telemetry[0].metadata["relay_mode"] == "sdk" + assert "relay_mode" not in self.result.telemetry[0].metadata output = self.output assert output["adapter"] == self.output_adapter @@ -121,7 +123,7 @@ async def test_artifacts(self): relay_config = json.loads(relay_config_path.read_text(encoding="utf-8")) assert relay_config["schema_version"] == "fabric.relay/v1alpha1" assert relay_config["relay"]["enabled"] is True - assert relay_config["fabric"]["profiles"] == list(self.profile_names) + assert relay_config["fabric"]["profiles"] == [] await self._additional_artifact_tests(artifact_by_name) @@ -209,12 +211,11 @@ async def test_atif_artifacts(self): class TestHermesCliE2E(BaseTestHermesE2E): - profile_names = ("hermes_cli", "relay") - profile_file = "hermes-cli.yaml" + config_builder = staticmethod(hermes_cli_config) adapter_kind = "python" adapter_runner = "python" output_adapter = "cli" - mode = "hermes_cli_oneshot" + mode = "hermes_cli_runtime" artifact_dir = "hermes-cli" atof_platform = "cli" @@ -228,8 +229,7 @@ async def _additional_artifact_tests(self, artifact_by_name: dict[str, dict[str, class TestHermesSdkE2E(BaseTestHermesE2E): - profile_names = ("hermes_sdk", "relay") - profile_file = "hermes-sdk.yaml" + config_builder = staticmethod(hermes_sdk_config) adapter_kind = "python" adapter_runner = "python" output_adapter = "python" diff --git a/tests/e2e/test_hermes_session.py b/tests/e2e/test_hermes_runtime.py similarity index 55% rename from tests/e2e/test_hermes_session.py rename to tests/e2e/test_hermes_runtime.py index dd59397bb..365194983 100644 --- a/tests/e2e/test_hermes_session.py +++ b/tests/e2e/test_hermes_runtime.py @@ -1,19 +1,17 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Opt-in integration smoke for the SDK multi-turn Session path (real Hermes). +"""Opt-in integration smoke for the SDK multi-turn Runtime path (real Hermes). -Drives ``FabricClient.start -> invoke -> invoke -> stop`` against the Hermes SDK -and CLI adapters and asserts the session carries conversation memory across +Drives ``Fabric.start -> invoke -> invoke -> stop`` against the Hermes SDK +and CLI adapters and asserts the runtime carries conversation memory across turns through the same Fabric runtime handle. -Unlike ``test_hermes_sdk.py`` (which shells out to the CLI), this exercises the -SDK session APIs through the native Fabric runtime lifecycle, so this must be -executed by an interpreter that has BOTH the nemo_fabric native extension and -Hermes importable: +This test must run in an interpreter that has both the nemo_fabric native +extension and Hermes importable: RUN_FABRIC_HERMES_INTEGRATION=1 NVIDIA_API_KEY=... \\ - /bin/python -m pytest tests/e2e/test_hermes_session.py + /bin/python -m pytest tests/e2e/test_hermes_runtime.py """ from __future__ import annotations @@ -25,17 +23,14 @@ import pytest -ROOT = Path(__file__).resolve().parents[2] - - -async def test_hermes_session(): +async def test_hermes_runtime(): if os.environ.get("RUN_FABRIC_HERMES_INTEGRATION") != "1": pytest.skip("set RUN_FABRIC_HERMES_INTEGRATION=1 to run") if not os.environ.get("NVIDIA_API_KEY"): pytest.fail("NVIDIA_API_KEY is required") if importlib.util.find_spec("nemo_fabric._native") is None: pytest.skip( - "skipping: the SDK session path needs the nemo_fabric native extension " + "skipping: the SDK runtime path needs the nemo_fabric native extension " "(pip install -e . into this interpreter)" ) if importlib.util.find_spec("run_agent") is None: @@ -63,62 +58,56 @@ async def test_hermes_session(): async def _run() -> None: - await _run_sdk_session() - await _run_cli_session() + await _run_sdk_runtime() + await _run_cli_runtime() -async def _run_sdk_session() -> None: - from nemo_fabric import FabricClient, SessionStatus +async def _run_sdk_runtime() -> None: + from examples.code_review_agent import BASE_DIR, hermes_sdk_config + from nemo_fabric import Fabric, RuntimeStatus - agent = str(ROOT / "examples" / "code-review-agent") - async with await FabricClient().start_session( - agent, - profiles=["hermes_session"], - ) as session: - assert session.status is SessionStatus.ACTIVE, session.status + async with await Fabric().start_runtime( + hermes_sdk_config(), + base_dir=BASE_DIR, + ) as runtime: + assert runtime.status is RuntimeStatus.ACTIVE, runtime.status - r1 = await session.invoke( - input="My name is Robin. Please remember it for later." - ) + r1 = await runtime.invoke(input="My name is Robin. Please remember it for later.") assert r1["status"] == "succeeded", r1 - after_turn1 = session.messages + after_turn1 = runtime.messages assert len(after_turn1) >= 2, after_turn1 - r2 = await session.invoke(input="What is my name? Reply with just the name.") + r2 = await runtime.invoke(input="What is my name? Reply with just the name.") assert r2["status"] == "succeeded", r2 assert r2["runtime_id"] == r1["runtime_id"], (r1, r2) # Hermes should return a transcript that includes the prior turn. - assert len(session.messages) > len(after_turn1), session.messages + assert len(runtime.messages) > len(after_turn1), runtime.messages # And the model must recall the name supplied in turn 1. response = (r2["output"].get("response") or "").lower() assert "robin" in response, response - assert session.status is SessionStatus.STOPPED, session.status + assert runtime.status is RuntimeStatus.STOPPED, runtime.status -async def _run_cli_session() -> None: - from nemo_fabric import FabricClient, SessionStatus +async def _run_cli_runtime() -> None: + from examples.code_review_agent import BASE_DIR, hermes_cli_config + from nemo_fabric import Fabric, RuntimeStatus - agent = str(ROOT / "examples" / "code-review-agent") - async with await FabricClient().start_session( - agent, - profiles=["hermes_cli_session"], - ) as session: - assert session.status is SessionStatus.ACTIVE, session.status + async with await Fabric().start_runtime( + hermes_cli_config(), + base_dir=BASE_DIR, + ) as runtime: + assert runtime.status is RuntimeStatus.ACTIVE, runtime.status - r1 = await session.invoke( - input="My name is Robin. Please remember it for later." - ) + r1 = await runtime.invoke(input="My name is Robin. Please remember it for later.") assert r1["status"] == "succeeded", r1 - assert r1["output"]["mode"] == "hermes_cli_session", r1 - assert r1["output"]["session_id"] == session.session_id, r1 + assert r1["output"]["mode"] == "hermes_cli_runtime", r1 - r2 = await session.invoke(input="What is my name? Reply with just the name.") + r2 = await runtime.invoke(input="What is my name? Reply with just the name.") assert r2["status"] == "succeeded", r2 assert r2["runtime_id"] == r1["runtime_id"], (r1, r2) - assert r2["output"]["session_id"] == r1["output"]["session_id"], (r1, r2) response = (r2["output"].get("response") or "").lower() assert "robin" in response, response - assert session.status is SessionStatus.STOPPED, session.status + assert runtime.status is RuntimeStatus.STOPPED, runtime.status diff --git a/examples/code-review-agent/agent.yaml b/tests/fixtures/file-config-agent/agent.yaml similarity index 89% rename from examples/code-review-agent/agent.yaml rename to tests/fixtures/file-config-agent/agent.yaml index bf27b2f72..9e9f640e7 100644 --- a/examples/code-review-agent/agent.yaml +++ b/tests/fixtures/file-config-agent/agent.yaml @@ -5,7 +5,7 @@ schema_version: fabric.agent/v1alpha1 metadata: name: code-review-agent - description: Reviews code changes and summarizes correctness risks. + description: Test fixture for file-backed configuration and profiles. harness: adapter_id: nvidia.fabric.hermes.sdk @@ -21,8 +21,6 @@ models: api_key_env: NVIDIA_API_KEY runtime: - mode: session - transport: library input_schema: chat output_schema: message artifacts: ./artifacts diff --git a/examples/code-review-agent/profiles/codex-cli.yaml b/tests/fixtures/file-config-agent/profiles/codex-cli.yaml similarity index 86% rename from examples/code-review-agent/profiles/codex-cli.yaml rename to tests/fixtures/file-config-agent/profiles/codex-cli.yaml index 231f27c7f..8bf2848ee 100644 --- a/examples/code-review-agent/profiles/codex-cli.yaml +++ b/tests/fixtures/file-config-agent/profiles/codex-cli.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: codex_cli -description: Run one isolated Codex CLI invocation using Codex-owned authentication. +description: Test Codex CLI file-profile resolution. harness: adapter_id: nvidia.fabric.codex.cli @@ -25,8 +25,6 @@ skills: null mcp: null runtime: - mode: oneshot - transport: cli input_schema: text output_schema: message artifacts: ./artifacts/codex-cli diff --git a/examples/code-review-agent/profiles/env-local.yaml b/tests/fixtures/file-config-agent/profiles/env-local.yaml similarity index 80% rename from examples/code-review-agent/profiles/env-local.yaml rename to tests/fixtures/file-config-agent/profiles/env-local.yaml index 80d4d8c2f..809940f88 100644 --- a/examples/code-review-agent/profiles/env-local.yaml +++ b/tests/fixtures/file-config-agent/profiles/env-local.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: env_local -description: Local development profile with no isolation and Relay disabled. +description: Test local environment file-profile resolution. environment: provider: local diff --git a/examples/code-review-agent/profiles/env-opensandbox.yaml b/tests/fixtures/file-config-agent/profiles/env-opensandbox.yaml similarity index 87% rename from examples/code-review-agent/profiles/env-opensandbox.yaml rename to tests/fixtures/file-config-agent/profiles/env-opensandbox.yaml index 761982b60..51ee15188 100644 --- a/examples/code-review-agent/profiles/env-opensandbox.yaml +++ b/tests/fixtures/file-config-agent/profiles/env-opensandbox.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: env_opensandbox -description: Run the harness/tools against an OpenSandbox-backed workspace and emit ATIF. +description: Test OpenSandbox environment file-profile resolution. environment: provider: opensandbox @@ -16,7 +16,6 @@ environment: telemetry: enabled: true - mode: sdk output_dir: ./artifacts/opensandbox config: version: 1 diff --git a/examples/code-review-agent/profiles/hermes-cli.yaml b/tests/fixtures/file-config-agent/profiles/hermes-cli.yaml similarity index 86% rename from examples/code-review-agent/profiles/hermes-cli.yaml rename to tests/fixtures/file-config-agent/profiles/hermes-cli.yaml index 6ee2f723e..b5ccbf8f5 100644 --- a/examples/code-review-agent/profiles/hermes-cli.yaml +++ b/tests/fixtures/file-config-agent/profiles/hermes-cli.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: hermes_cli -description: Run the Hermes CLI adapter through an installed Hermes command. +description: Test Hermes CLI file-profile resolution. harness: adapter_id: nvidia.fabric.hermes.cli @@ -17,8 +17,6 @@ harness: enabled_toolsets: [] runtime: - mode: oneshot - transport: cli input_schema: chat output_schema: message artifacts: ./artifacts/hermes-cli diff --git a/examples/code-review-agent/profiles/hermes-sdk.yaml b/tests/fixtures/file-config-agent/profiles/hermes-sdk.yaml similarity index 88% rename from examples/code-review-agent/profiles/hermes-sdk.yaml rename to tests/fixtures/file-config-agent/profiles/hermes-sdk.yaml index fc49d1861..c74832206 100644 --- a/examples/code-review-agent/profiles/hermes-sdk.yaml +++ b/tests/fixtures/file-config-agent/profiles/hermes-sdk.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: hermes_sdk -description: Run the Hermes Python adapter through an installed Hermes SDK. +description: Test Hermes SDK file-profile resolution. harness: adapter_id: nvidia.fabric.hermes.sdk @@ -22,8 +22,6 @@ harness: system_prompt: You are a concise smoke test assistant. runtime: - mode: oneshot - transport: library input_schema: chat output_schema: message artifacts: ./artifacts/hermes-sdk diff --git a/examples/code-review-agent/profiles/mcp-github.yaml b/tests/fixtures/file-config-agent/profiles/mcp-github.yaml similarity index 91% rename from examples/code-review-agent/profiles/mcp-github.yaml rename to tests/fixtures/file-config-agent/profiles/mcp-github.yaml index d1119ea1d..20d6f8245 100644 --- a/examples/code-review-agent/profiles/mcp-github.yaml +++ b/tests/fixtures/file-config-agent/profiles/mcp-github.yaml @@ -3,7 +3,7 @@ schema_version: fabric.profile/v1alpha1 name: mcp_github -description: Vary GitHub MCP exposure and enable Relay telemetry. +description: Test MCP and telemetry file-profile merging. mcp: servers: @@ -14,7 +14,6 @@ mcp: telemetry: enabled: true - mode: sdk output_dir: ./artifacts/mcp-github config: version: 1 diff --git a/examples/code-review-agent/profiles/native-otel.yaml b/tests/fixtures/file-config-agent/profiles/native-otel.yaml similarity index 84% rename from examples/code-review-agent/profiles/native-otel.yaml rename to tests/fixtures/file-config-agent/profiles/native-otel.yaml index a797eeab1..f943b4d9b 100644 --- a/examples/code-review-agent/profiles/native-otel.yaml +++ b/tests/fixtures/file-config-agent/profiles/native-otel.yaml @@ -3,12 +3,11 @@ schema_version: fabric.profile/v1alpha1 name: native_otel -description: Enable Native OpenTelemetry tracing. +description: Test native telemetry file-profile resolution. telemetry: enabled: true provider: native - mode: sdk config: version: 1 components: @@ -21,4 +20,4 @@ telemetry: transport: http_binary endpoint: http://localhost:4318/v1/traces resource_attributes: - deployment.environment: dev \ No newline at end of file + deployment.environment: dev diff --git a/examples/code-review-agent/profiles/relay-openinference.yaml b/tests/fixtures/file-config-agent/profiles/relay-openinference.yaml similarity index 93% rename from examples/code-review-agent/profiles/relay-openinference.yaml rename to tests/fixtures/file-config-agent/profiles/relay-openinference.yaml index 3602c86c1..d52878669 100644 --- a/examples/code-review-agent/profiles/relay-openinference.yaml +++ b/tests/fixtures/file-config-agent/profiles/relay-openinference.yaml @@ -3,12 +3,11 @@ schema_version: fabric.profile/v1alpha1 name: relay_openinference -description: Enable NeMo Relay OpenInference tracing. +description: Test Relay OpenInference file-profile resolution. telemetry: enabled: true provider: relay - mode: sdk output_dir: ./artifacts/relay-openinference config: version: 1 diff --git a/examples/code-review-agent/profiles/relay-otel.yaml b/tests/fixtures/file-config-agent/profiles/relay-otel.yaml similarity index 92% rename from examples/code-review-agent/profiles/relay-otel.yaml rename to tests/fixtures/file-config-agent/profiles/relay-otel.yaml index 07f1641ef..8125199f7 100644 --- a/examples/code-review-agent/profiles/relay-otel.yaml +++ b/tests/fixtures/file-config-agent/profiles/relay-otel.yaml @@ -3,12 +3,11 @@ schema_version: fabric.profile/v1alpha1 name: relay_otel -description: Enable NeMo Relay OpenTelemetry tracing. +description: Test Relay OpenTelemetry file-profile resolution. telemetry: enabled: true provider: relay - mode: sdk output_dir: ./artifacts/relay-otel config: version: 1 diff --git a/examples/code-review-agent/profiles/relay.yaml b/tests/fixtures/file-config-agent/profiles/relay.yaml similarity index 92% rename from examples/code-review-agent/profiles/relay.yaml rename to tests/fixtures/file-config-agent/profiles/relay.yaml index f538cd289..c28f8a78d 100644 --- a/examples/code-review-agent/profiles/relay.yaml +++ b/tests/fixtures/file-config-agent/profiles/relay.yaml @@ -3,12 +3,11 @@ schema_version: fabric.profile/v1alpha1 name: relay -description: Enable NeMo Relay ATOF/ATIF tracing. +description: Test Relay ATOF/ATIF file-profile resolution. telemetry: enabled: true provider: relay - mode: sdk output_dir: ./artifacts/relay config: version: 1 diff --git a/examples/code-review-agent/repos/my-service/calculator.py b/tests/fixtures/file-config-agent/repos/my-service/calculator.py similarity index 100% rename from examples/code-review-agent/repos/my-service/calculator.py rename to tests/fixtures/file-config-agent/repos/my-service/calculator.py diff --git a/tests/fixtures/file-config-agent/skills/code-review/README.md b/tests/fixtures/file-config-agent/skills/code-review/README.md new file mode 100644 index 000000000..3fd88c8fc --- /dev/null +++ b/tests/fixtures/file-config-agent/skills/code-review/README.md @@ -0,0 +1,9 @@ + + +# File Config Fixture Skill + +This directory exists only to exercise relative skill paths in file-backed +configuration tests. diff --git a/tests/fixtures/hermes-cli-agent/agent.yaml b/tests/fixtures/hermes-cli-agent/agent.yaml index d694f9d70..a7fb5d9af 100644 --- a/tests/fixtures/hermes-cli-agent/agent.yaml +++ b/tests/fixtures/hermes-cli-agent/agent.yaml @@ -14,6 +14,7 @@ harness: hermes_command: ./bin/fake-hermes.py workspace: ./repos/my-service hermes_home: ./artifacts/hermes-cli/home + prepare_runtime_state: false enabled_toolsets: [] models: @@ -23,8 +24,6 @@ models: temperature: 0.0 runtime: - mode: oneshot - transport: cli input_schema: chat output_schema: message artifacts: ./artifacts/hermes-cli diff --git a/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json b/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json index d9997ef58..7c0fdb59f 100644 --- a/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json +++ b/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json @@ -1,4 +1,5 @@ { + "contract_version": "fabric.adapter/v1alpha1", "adapter_id": "test.fabric.hermes_shim", "harness": "hermes", "adapter_kind": "python", diff --git a/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/src/nemo_fabric_test_adapters/hermes_shim/adapter.py b/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/src/nemo_fabric_test_adapters/hermes_shim/adapter.py index 5d80a2acf..8b472694a 100644 --- a/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/src/nemo_fabric_test_adapters/hermes_shim/adapter.py +++ b/tests/fixtures/hermes-shim-agent/adapters/hermes-shim/src/nemo_fabric_test_adapters/hermes_shim/adapter.py @@ -74,7 +74,7 @@ def run_shim(payload: dict[str, Any]) -> dict[str, Any]: "adapter": "test-shim", "mode": "shim", "received": request.get("input"), - "session_id": context.get("session_id") or context.get("runtime_id"), + "runtime_id": context.get("runtime_id"), "workspace": environment.get("workspace") or settings.get("workspace"), "native_skill_paths": (capabilities.get("native") or {}).get("skill_paths", []), "native_mcp_servers": sorted((capabilities.get("native") or {}).get("mcp_servers", {}).keys()), diff --git a/tests/fixtures/hermes-shim-agent/agent.yaml b/tests/fixtures/hermes-shim-agent/agent.yaml index 44e333b2b..85e735d18 100644 --- a/tests/fixtures/hermes-shim-agent/agent.yaml +++ b/tests/fixtures/hermes-shim-agent/agent.yaml @@ -20,8 +20,6 @@ models: temperature: 0.0 runtime: - mode: session - transport: library input_schema: chat output_schema: message artifacts: ./artifacts diff --git a/tests/fixtures/hermes-shim-agent/profiles/harbor-swebench-django-13741.yaml b/tests/fixtures/hermes-shim-agent/profiles/harbor-swebench-django-13741.yaml index 8ca752754..e09880b11 100644 --- a/tests/fixtures/hermes-shim-agent/profiles/harbor-swebench-django-13741.yaml +++ b/tests/fixtures/hermes-shim-agent/profiles/harbor-swebench-django-13741.yaml @@ -16,8 +16,6 @@ harness: replacement: " kwargs.setdefault(\"required\", False)\n kwargs.setdefault('disabled', True)\n super().__init__(*args, **kwargs)" runtime: - mode: oneshot - transport: library input_schema: harbor_swe_bench_task output_schema: patch_result artifacts: ./artifacts/harbor-swebench-django-13741 diff --git a/tests/fixtures/hermes-shim-agent/profiles/swebench-shim.yaml b/tests/fixtures/hermes-shim-agent/profiles/swebench-shim.yaml index d7f324e18..bb6eb51d2 100644 --- a/tests/fixtures/hermes-shim-agent/profiles/swebench-shim.yaml +++ b/tests/fixtures/hermes-shim-agent/profiles/swebench-shim.yaml @@ -18,8 +18,6 @@ harness: new_file_contents: "patched by Fabric\n" runtime: - mode: oneshot - transport: library input_schema: swe_bench_task output_schema: patch_result artifacts: ./artifacts/swebench diff --git a/tests/integrations/test_harbor_runner.py b/tests/integrations/test_harbor_runner.py index 26b2c6b75..a31d10d50 100644 --- a/tests/integrations/test_harbor_runner.py +++ b/tests/integrations/test_harbor_runner.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import asyncio import importlib.util import json import os @@ -12,28 +13,29 @@ ROOT = Path(__file__).resolve().parents[2] ROOT_README = ROOT / "README.md" -DEMO_ROOT = ROOT / "integrations" / "harbor" / "demo" +DEMO_ROOT = ROOT / "examples" / "harbor" / "demo" DEMO_README = DEMO_ROOT / "README.md" DEMO_DOCKERFILE = DEMO_ROOT / "task" / "environment" / "Dockerfile" DEMO_HOST_GATEWAY = DEMO_ROOT / "host-gateway.compose.yaml" DEMO_SOLUTION = DEMO_ROOT / "task" / "solution" / "solve.sh" -CODEX_PROFILE = ( +DEMO_CONFIGS = DEMO_ROOT / "task" / "environment" / "fabric" / "configs" +CODEX_CONFIG = ( DEMO_ROOT / "task" / "environment" / "fabric" - / "profiles" + / "configs" / "codex.yaml" ) -TELEMETRY_PROFILE = ( +RELAY_CONFIG = ( DEMO_ROOT / "task" / "environment" / "fabric" - / "profiles" - / "telemetry.yaml" + / "configs" + / "hermes-relay.yaml" ) -INTEGRATION_README = ROOT / "integrations" / "harbor" / "README.md" +INTEGRATION_README = ROOT / "examples" / "harbor" / "README.md" SDK_INTEGRATION_README = ( ROOT / "python" @@ -43,6 +45,7 @@ / "harbor" / "README.md" ) +HARBOR_PACKAGE_INIT = SDK_INTEGRATION_README.parent / "__init__.py" def load_codex_adapter(): @@ -54,84 +57,223 @@ def load_codex_adapter(): return module -def test_runner_loads_typed_sources_and_applies_harbor_model(tmp_path): - from nemo_fabric.integrations.harbor.runner import load_sources +def test_runner_composes_harbor_values_on_an_independent_config(tmp_path): + from nemo_fabric import RunRequest + from nemo_fabric.integrations.harbor.models import HarborMcpServer, HarborRunSpec + from nemo_fabric.integrations.harbor.runner import compose_config, load_config config_path = tmp_path / "agent.yaml" - profile_path = tmp_path / "profiles" / "codex.yaml" - profile_path.parent.mkdir() config_path.write_text( yaml.safe_dump( { "metadata": {"name": "harbor-demo"}, "harness": {"adapter_id": "demo.fabric.smoke"}, - "runtime": {"mode": "oneshot"}, + "runtime": {}, "models": {"default": {"provider": "demo", "model": "demo"}}, + "mcp": { + "servers": { + "base": { + "transport": "streamable-http", + "url": "https://base.example.test", + } + } + }, + "skills": {"paths": ["./base-skill"]}, } ), encoding="utf-8", ) - profile_path.write_text( - yaml.safe_dump( - { - "name": "codex", - "harness": {"adapter_id": "nvidia.fabric.codex.cli"}, - } + spec = HarborRunSpec( + config_path=config_path, + request=RunRequest(input="fix it"), + model_name="openai/gpt-5.4", + skills_dir=tmp_path / "skills", + mcp_servers=( + HarborMcpServer( + name="remote", + transport="streamable-http", + url="https://mcp.example.test", + ), + HarborMcpServer( + name="local", + transport="stdio", + command="mcp-server", + args=("--stdio",), + ), ), - encoding="utf-8", ) - config, profiles = load_sources( - { - "config_path": str(config_path), - "profile_paths": [str(profile_path)], - "request": {"context": {"model_name": "openai/gpt-5.4"}}, - } - ) + base = load_config(config_path) + config = compose_config(base, spec) - assert config.models["default"] == { + assert base.models["default"].to_mapping() == { "provider": "demo", "model": "demo", } - assert profiles[-1].models["default"] == { + assert base.mcp is not None and "base" in base.mcp.servers + assert base.skills is not None and base.skills.paths == ["./base-skill"] + assert config.models["default"].to_mapping() == { "provider": "openai", "model": "openai/gpt-5.4", } - assert [profile.name for profile in profiles] == ["codex", "harbor_model"] - assert json.loads(json.dumps(config.to_mapping()))["metadata"]["name"] == "harbor-demo" + assert config.mcp is not None + assert set(config.mcp.servers) == {"remote", "local"} + assert config.mcp.servers["local"].url == "mcp-server" + assert config.mcp.servers["local"].extra_fields["args"] == ["--stdio"] + assert config.skills is not None + assert config.skills.paths == [str(tmp_path / "skills")] + assert json.loads(json.dumps(config.to_mapping()))["metadata"]["name"] == ( + "harbor-demo" + ) + + +def test_runner_preserves_config_capabilities_without_harbor_replacements(tmp_path): + from nemo_fabric import FabricConfig, RunRequest + from nemo_fabric.integrations.harbor.models import HarborRunSpec + from nemo_fabric.integrations.harbor.runner import compose_config + + base = FabricConfig.model_validate( + { + "metadata": {"name": "harbor-demo"}, + "harness": {"adapter_id": "demo.fabric.smoke"}, + "mcp": { + "servers": { + "base": { + "transport": "streamable-http", + "url": "https://base.example.test", + } + } + }, + "skills": {"paths": ["./base-skill"]}, + } + ) + spec = HarborRunSpec( + config_path=tmp_path / "agent.yaml", + request=RunRequest(input="fix it"), + ) + + config = compose_config(base, spec) + + assert config.mcp is not None and set(config.mcp.servers) == {"base"} + assert config.skills is not None and config.skills.paths == ["./base-skill"] def test_runner_rejects_missing_config(tmp_path): - from nemo_fabric.integrations.harbor.runner import load_sources + from nemo_fabric.integrations.harbor.runner import load_config with pytest.raises(FileNotFoundError): - load_sources({"config_path": str(tmp_path / "missing.yaml")}) + load_config(tmp_path / "missing.yaml") + + +def test_runner_rejects_malformed_config(tmp_path): + from nemo_fabric.integrations.harbor.runner import load_config + + config_path = tmp_path / "agent.yaml" + config_path.write_text("harness: [", encoding="utf-8") + + with pytest.raises(yaml.YAMLError): + load_config(config_path) + + +def test_harbor_transport_models_validate_mcp_targets(): + from pydantic import ValidationError + + from nemo_fabric import RunRequest + from nemo_fabric.integrations.harbor.models import HarborMcpServer, HarborRunSpec + + spec = HarborRunSpec.model_validate_json( + HarborRunSpec( + config_path="/workspace/agent.yaml", + request=RunRequest(input="fix it"), + mcp_servers=( + HarborMcpServer( + name="github", + transport="streamable-http", + url="https://mcp.example.test", + ), + ), + ).model_dump_json() + ) + + assert spec.request.input == "fix it" + assert spec.mcp_servers[0].name == "github" + assert "profile_paths" not in HarborRunSpec.model_json_schema()["properties"] + with pytest.raises(ValidationError, match="require url"): + HarborMcpServer(name="missing", transport="sse") + with pytest.raises(ValidationError, match="require command"): + HarborMcpServer(name="missing", transport="stdio") + with pytest.raises(ValidationError, match="Extra inputs"): + HarborRunSpec.model_validate( + { + "config_path": "/workspace/agent.yaml", + "request": {"input": "fix it"}, + "profile_paths": [], + } + ) -def test_runner_rejects_malformed_profile(tmp_path): - from nemo_fabric.integrations.harbor.runner import load_sources +def test_each_harbor_job_delegates_to_an_independent_fabric_run( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +): + from nemo_fabric.integrations.harbor import runner config_path = tmp_path / "agent.yaml" - profile_path = tmp_path / "profile.yaml" config_path.write_text( yaml.safe_dump( { "metadata": {"name": "harbor-demo"}, "harness": {"adapter_id": "demo.fabric.smoke"}, - "runtime": {"mode": "oneshot"}, + "runtime": {}, } ), encoding="utf-8", ) - profile_path.write_text("harness: [", encoding="utf-8") - - with pytest.raises(yaml.YAMLError): - load_sources( - { - "config_path": str(config_path), - "profile_paths": [str(profile_path)], - } + calls: list[dict[str, object]] = [] + + class FakeResult: + def __init__(self, runtime_id: str) -> None: + self.runtime_id = runtime_id + + def to_mapping(self) -> dict[str, str]: + return {"runtime_id": self.runtime_id} + + class FakeFabric: + async def run(self, config, *, base_dir, request): + runtime_id = f"runtime-{len(calls) + 1}" + calls.append( + { + "runtime_id": runtime_id, + "request": request.to_mapping(), + } + ) + return FakeResult(runtime_id) + + monkeypatch.setattr(runner, "Fabric", FakeFabric) + from nemo_fabric.integrations.harbor.models import HarborRunSpec + + specs = [ + HarborRunSpec( + config_path=config_path, + request={ + "input": f"job {job_id}", + "context": {"job_id": job_id}, + }, ) + for job_id in ("job-1", "job-2") + ] + + async def run_specs(): + return await asyncio.gather(*(runner.run(spec) for spec in specs)) + + results = asyncio.run(run_specs()) + + assert [result.runtime_id for result in results] == ["runtime-1", "runtime-2"] + requests = [call["request"] for call in calls] + assert [request["context"]["job_id"] for request in requests] == [ # type: ignore[index] + "job-1", + "job-2", + ] def test_codex_adapter_maps_fabric_request_to_cli(tmp_path): @@ -154,7 +296,7 @@ def test_codex_adapter_maps_fabric_request_to_cli(tmp_path): "model": "openai/gpt-5.4", } }, - "runtime": {"mode": "oneshot"}, + "runtime": {}, }, }, "runtime_context": { @@ -177,7 +319,6 @@ def test_codex_adapter_maps_fabric_request_to_cli(tmp_path): "codex", "exec", "--json", - "--ephemeral", "--sandbox", "workspace-write", "--profile", @@ -194,25 +335,58 @@ def test_codex_adapter_maps_fabric_request_to_cli(tmp_path): def test_codex_demo_uses_current_adapter_contract(): - profile = yaml.safe_load(CODEX_PROFILE.read_text(encoding="utf-8")) - settings = profile["harness"]["settings"] + config = yaml.safe_load(CODEX_CONFIG.read_text(encoding="utf-8")) + settings = config["harness"]["settings"] - assert profile["runtime"]["mode"] == "oneshot" + assert config["schema_version"] == "fabric.agent/v1alpha1" + assert config["harness"]["adapter_id"] == "nvidia.fabric.codex.cli" assert settings["sandbox"] == "danger-full-access" assert settings["skip_git_repo_check"] is True assert settings["config_overrides"]["model_reasoning_effort"] == "high" dockerfile = DEMO_DOCKERFILE.read_text(encoding="utf-8") - assert 'nemo-fabric[codex,harbor,hermes,relay]' in dockerfile + assert 'nemo-fabric[codex,harbor,hermes,relay,runtime]' in dockerfile assert "@openai/codex@0.142.4" in dockerfile +def test_harbor_demo_uses_complete_configs_without_profiles(): + from nemo_fabric import FabricConfig + + configs = sorted(DEMO_CONFIGS.glob("*.yaml")) + + assert [path.name for path in configs] == [ + "codex.yaml", + "hermes-relay.yaml", + "hermes.yaml", + "smoke.yaml", + ] + for path in configs: + config = FabricConfig.model_validate(yaml.safe_load(path.read_text())) + assert config.profiles is None + assert not list((DEMO_CONFIGS.parent / "profiles").glob("*.yaml")) + + +def test_harbor_smoke_config_resolves_its_local_adapter(): + from nemo_fabric import Fabric, RunRequest + from nemo_fabric.integrations.harbor.models import HarborRunSpec + from nemo_fabric.integrations.harbor.runner import compose_config, load_config + + config_path = DEMO_CONFIGS / "smoke.yaml" + spec = HarborRunSpec(config_path=config_path, request=RunRequest(input="fix it")) + config = compose_config(load_config(config_path), spec) + plan = Fabric().plan(config, base_dir=config_path.parent) + + assert plan.adapter.adapter_id == "demo.fabric.scripted" + assert plan["adapter_descriptor"]["source"] == "local" + assert plan["adapter_descriptor"]["root"].endswith("configs/adapters/scripted") + + def test_harbor_demo_documents_explicit_cli_commands(): demo = DEMO_README.read_text(encoding="utf-8") integration = INTEGRATION_README.read_text(encoding="utf-8") assert "run.sh" not in demo assert "demo/run.sh" not in integration - assert demo.count("uv run --extra harbor harbor run") == 4 + assert demo.count("uv run --extra runtime --extra harbor harbor run") == 4 for flag in ( "--path", "--agent", @@ -240,9 +414,9 @@ def test_harbor_demo_setup_and_solution_fail_fast(): def test_harbor_telemetry_demo_exports_phoenix_atof_and_atif(): - profile = yaml.safe_load(TELEMETRY_PROFILE.read_text(encoding="utf-8")) + config = yaml.safe_load(RELAY_CONFIG.read_text(encoding="utf-8")) host_gateway = yaml.safe_load(DEMO_HOST_GATEWAY.read_text(encoding="utf-8")) - observability = profile["telemetry"]["config"]["components"][0]["config"] + observability = config["telemetry"]["config"]["components"][0]["config"] demo = DEMO_README.read_text(encoding="utf-8") assert observability["openinference"] == { @@ -271,19 +445,23 @@ def test_harbor_sdk_package_documents_execution_boundary(): from nemo_fabric.integrations.harbor import FabricAgent readme = SDK_INTEGRATION_README.read_text(encoding="utf-8") + package_init = HARBOR_PACKAGE_INIT.read_text(encoding="utf-8") assert FabricAgent.name() == "fabric" + assert FabricAgent.__module__ == "nemo_fabric.integrations.harbor.fabric_agent" assert "nemo_fabric.integrations.harbor:FabricAgent" in readme assert "nemo_fabric.integrations.harbor.runner" in readme - assert "does not invoke the Fabric CLI" in readme + assert "calls `Fabric.run()` directly" in readme + assert "fabric_profile_paths" not in readme + assert "`fabric_agent.py`" in readme + assert "class FabricAgent" not in package_init + assert "fabric_agent import FabricAgent" in package_init -def test_root_readme_documents_sdk_contract_and_harbor_example(): +def test_root_readme_routes_to_sdk_and_harbor_guides(): readme = ROOT_README.read_text(encoding="utf-8") assert "runtime execution layer" in readme - assert "docs/python-sdk-contract.md" in readme - assert "## Harbor Integration" in readme - assert "uv run --extra harbor harbor run" in readme - assert "nemo_fabric.integrations.harbor:FabricAgent" in readme - assert "integrations/harbor/demo/README.md" in readme + assert "docs/sdk/python.mdx" in readme + assert "examples/harbor/README.md" in readme + assert "examples/harbor/demo/README.md" in readme diff --git a/tests/python/test_code_review_example.py b/tests/python/test_code_review_example.py new file mode 100644 index 000000000..fb2cea023 --- /dev/null +++ b/tests/python/test_code_review_example.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Contract tests for the code-review example.""" + +import json +import subprocess +import sys + +from examples.code_review_agent import ( + BASE_DIR, + base_config, + codex_cli_config, + hermes_cli_config, + hermes_sdk_config, + with_fabric_managed_github_mcp, + with_native_otel, + with_opensandbox, + with_relay, + with_relay_openinference, + with_relay_otel, +) +from nemo_fabric import Fabric, FabricConfig + + +def test_variant_builders_return_independent_complete_configs(): + base = base_config() + sdk = hermes_sdk_config() + cli = hermes_cli_config() + codex = codex_cli_config() + + for config in (base, sdk, cli, codex): + assert isinstance(config, FabricConfig) + assert config.metadata.name == "code-review-agent" + assert config.environment is not None + assert "default" in config.models + + assert sdk is not base + assert sdk.harness is not base.harness + assert cli.harness.adapter_id == "nvidia.fabric.hermes.cli" + assert codex.harness.adapter_id == "nvidia.fabric.codex.cli" + assert codex.mcp is None + assert codex.skills is None + assert base.mcp is not None + assert base.skills is not None + + +def test_capability_and_telemetry_variants_do_not_mutate_their_input(): + base = hermes_sdk_config() + variants = ( + with_fabric_managed_github_mcp(base), + with_native_otel(base), + with_opensandbox(base), + with_relay(base), + with_relay_openinference(base), + with_relay_otel(base), + ) + + assert base.telemetry is not None + assert base.telemetry.enabled is False + assert base.environment is not None + assert base.environment.provider == "local" + assert base.mcp is not None + assert base.mcp.servers["github"].exposure == "harness_native" + assert all(variant is not base for variant in variants) + assert variants[0].mcp is not None + assert variants[0].mcp.servers["github"].exposure == "fabric_managed" + assert variants[1].telemetry is not None + assert variants[1].telemetry.provider == "native" + assert variants[2].environment is not None + assert variants[2].environment.provider == "opensandbox" + assert variants[3].telemetry is not None + assert variants[3].telemetry.provider == "relay" + + +def test_variants_plan_without_file_profiles(): + client = Fabric() + + for config in (hermes_sdk_config(), hermes_cli_config(), codex_cli_config()): + plan = client.plan(config, base_dir=BASE_DIR) + assert plan.profiles == () + assert plan.agent_name == "code-review-agent" + assert plan.adapter.adapter_id == config.harness.adapter_id + + +def test_example_entrypoint_plans_without_starting_a_runtime(): + cases = ( + ([], "nvidia.fabric.hermes.sdk", False), + (["--variant", "hermes-cli"], "nvidia.fabric.hermes.cli", False), + (["--variant", "codex-cli"], "nvidia.fabric.codex.cli", False), + (["--relay"], "nvidia.fabric.hermes.sdk", True), + ) + + for options, adapter_id, relay_enabled in cases: + completed = subprocess.run( + [ + sys.executable, + "-m", + "examples.code_review_agent", + *options, + "--plan", + ], + cwd=BASE_DIR.parents[1], + text=True, + capture_output=True, + check=False, + ) + + assert completed.returncode == 0, completed.stderr + plan = json.loads(completed.stdout) + assert plan["agent_name"] == "code-review-agent" + assert plan["profiles"] == [] + assert ( + plan["adapter_descriptor"]["descriptor"]["adapter_id"] == adapter_id + ) + assert plan["telemetry_plan"]["relay_enabled"] is relay_enabled diff --git a/tests/python/test_consumer_neutral.py b/tests/python/test_consumer_neutral.py index 4cfaef2eb..39d073667 100644 --- a/tests/python/test_consumer_neutral.py +++ b/tests/python/test_consumer_neutral.py @@ -1,20 +1,21 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Smoke test: the SDK core stays consumer-neutral and dependency-free. +"""Smoke test: the SDK core stays consumer-neutral. WS4 guardrail. The public SDK core -- the ``nemo_fabric`` package outside the -``integrations`` subpackage -- must not depend on any third-party package: no -harness (Hermes), no consumer (Harbor/Platform), no telemetry backend (Relay). -Adapters are loaded dynamically at runtime via importlib, and consumer glue -lives under ``nemo_fabric.integrations`` behind an optional extra; the core -never imports any of them. +``integrations`` subpackage -- may depend on Pydantic and typing-extensions for +its typed authoring models, but not on a harness (Hermes), consumer +(Harbor/Platform), or telemetry backend (Relay). Adapters are loaded dynamically +at runtime via importlib, and consumer glue lives under +``nemo_fabric.integrations`` behind an optional extra; the core never imports +any of them. Two checks: -1. Static -- every top-level import in the core resolves to the standard library - or ``nemo_fabric`` itself. This also pins the SDK's zero-dependency contract - (pyproject ``dependencies = []``). +1. Static -- every top-level import in the core resolves to the standard + library, ``nemo_fabric``, or a declared authoring dependency. This also pins + the SDK's direct dependency contract. 2. Runtime -- a plain ``import nemo_fabric`` (what a consumer like Platform does) pulls in no consumer/harness package. """ @@ -30,7 +31,13 @@ ROOT_DIR = Path(__file__).resolve().parents[2] SDK_ROOT = ROOT_DIR / "python" / "src" / "nemo_fabric" PYPROJECT = ROOT_DIR / "python" / "pyproject.toml" -ALLOWED = set(sys.stdlib_module_names) | {"nemo_fabric", "__future__"} +ALLOWED = set(sys.stdlib_module_names) | { + "nemo_fabric", + "pydantic", + "typing_extensions", + "__future__", +} +EXPECTED_DEPENDENCIES = ["pydantic>=2.10,<3", "typing-extensions>=4.12"] # Consumer/harness packages that must never leak into a plain ``import nemo_fabric``. CONSUMER_SPECIFIC = [ "harbor", @@ -54,8 +61,8 @@ def _top_level_imports(tree: ast.AST) -> set[str]: return roots -def core_imports_only_stdlib_and_self() -> None: - """Static: no third-party import anywhere in the core (non-integrations).""" +def core_imports_only_allowed_dependencies() -> None: + """Static: core imports stay within the declared SDK dependency boundary.""" core = sorted( path @@ -75,21 +82,23 @@ def core_imports_only_stdlib_and_self() -> None: offenders[path.name] = bad assert not offenders, ( - "SDK core must import only the standard library and nemo_fabric " + "SDK core must import only the standard library, nemo_fabric, and " + "declared authoring dependencies " "(consumer glue belongs under nemo_fabric.integrations); " f"found third-party imports: {offenders}" ) - # Pin the declared contract too: a dependency added to pyproject would slip - # past the import scan above if nothing in the core imports it yet. Assert the - # structure explicitly so a missing [project]/dependencies fails loudly - # instead of defaulting to an empty list and hiding a metadata regression. + # Pin the declared contract too: an unused dependency would not be detected + # by the import scan above. pyproject = tomllib.loads(PYPROJECT.read_text(encoding="utf-8")) assert "project" in pyproject, f"{PYPROJECT} is missing the [project] table" project = pyproject["project"] assert "dependencies" in project, f"{PYPROJECT} [project] is missing 'dependencies'" deps = project["dependencies"] - assert deps == [], f"SDK must stay zero-dependency; found dependencies={deps!r}" + assert deps == EXPECTED_DEPENDENCIES, ( + "SDK direct dependencies changed; " + f"expected={EXPECTED_DEPENDENCIES!r}, found={deps!r}" + ) def importing_the_sdk_pulls_in_no_consumer_package() -> None: @@ -114,5 +123,5 @@ def importing_the_sdk_pulls_in_no_consumer_package() -> None: def test_consumer_neutral(): - core_imports_only_stdlib_and_self() + core_imports_only_allowed_dependencies() importing_the_sdk_pulls_in_no_consumer_package() diff --git a/tests/python/test_environment_handle.py b/tests/python/test_environment_handle.py index 20f801f9e..22e6f9c51 100644 --- a/tests/python/test_environment_handle.py +++ b/tests/python/test_environment_handle.py @@ -6,26 +6,20 @@ from __future__ import annotations import os -from pathlib import Path -from nemo_fabric import FabricClient - -ROOT = Path(__file__).resolve().parents[2] +from examples.code_review_agent import BASE_DIR, base_config +from nemo_fabric import Fabric async def test_environment_handle(): - async with FabricClient() as client: - session = await client.start_session( - ROOT / "examples" / "code-review-agent", - profiles=["env_local"], - ) - try: - workspace = session.runtime["environment"]["workspace"] - finally: - await session.stop() + runtime = await Fabric().start_runtime( + base_config(), + base_dir=BASE_DIR, + ) + try: + workspace = runtime.handle["environment"]["workspace"] + finally: + await runtime.stop() assert os.path.isabs(workspace), f"workspace not absolute: {workspace}" - assert "code-review-agent/examples/code-review-agent" not in workspace, ( - f"workspace path is doubled: {workspace}" - ) - assert workspace.endswith("repos/my-service"), workspace + assert workspace == str((BASE_DIR / "repos" / "my-service").resolve()) diff --git a/tests/python/test_harbor_integration.py b/tests/python/test_harbor_integration.py index ff82bba09..b9b9b910f 100644 --- a/tests/python/test_harbor_integration.py +++ b/tests/python/test_harbor_integration.py @@ -6,12 +6,15 @@ from __future__ import annotations import json +import shlex import sys import types from dataclasses import dataclass from pathlib import Path from typing import Any +import pytest + ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "python" / "src")) @@ -34,6 +37,26 @@ class AgentContext: def __init__(self) -> None: self.metadata: dict[str, Any] | None = None + class MCPServerConfig: + def __init__( + self, + *, + name: str, + transport: str, + url: str | None = None, + command: str | None = None, + args: list[str] | None = None, + ) -> None: + self.name = name + self.transport = transport + self.url = url + self.command = command + self.args = args or [] + + def model_dump(self, *, mode: str) -> dict[str, Any]: + assert mode == "python" + return vars(self) + modules = { "harbor": types.ModuleType("harbor"), "harbor.agents": types.ModuleType("harbor.agents"), @@ -43,20 +66,25 @@ def __init__(self) -> None: "harbor.models": types.ModuleType("harbor.models"), "harbor.models.agent": types.ModuleType("harbor.models.agent"), "harbor.models.agent.context": types.ModuleType("harbor.models.agent.context"), + "harbor.models.task": types.ModuleType("harbor.models.task"), + "harbor.models.task.config": types.ModuleType("harbor.models.task.config"), } modules["harbor.agents.base"].BaseAgent = BaseAgent modules["harbor.environments.base"].BaseEnvironment = BaseEnvironment modules["harbor.models.agent.context"].AgentContext = AgentContext + modules["harbor.models.task.config"].MCPServerConfig = MCPServerConfig sys.modules.update(modules) try: from nemo_fabric.integrations.harbor import FabricAgent from harbor.models.agent.context import AgentContext + from harbor.models.task.config import MCPServerConfig except ImportError: install_harbor_stubs() from nemo_fabric.integrations.harbor import FabricAgent from harbor.models.agent.context import AgentContext + from harbor.models.task.config import MCPServerConfig @dataclass @@ -71,6 +99,7 @@ def __init__(self) -> None: self.files: dict[str, str] = {} self.commands: list[str] = [] self.environments: list[dict[str, str] | None] = [] + self.uploads: list[tuple[Path, str]] = [] async def exec( self, @@ -82,22 +111,22 @@ async def exec( ) -> ExecResult: self.commands.append(command) self.environments.append(env) - if command.startswith("cat > "): - path, contents = command.split(" <<'FABRIC_JSON'\n", maxsplit=1) - path = path.removeprefix("cat > ").strip() - contents = contents.removesuffix("\nFABRIC_JSON") - self.files[path] = contents - return ExecResult() if "nemo_fabric.integrations.harbor.runner" in command: - self.files["/logs/agent/fabric-result.json"] = json.dumps( + arguments = shlex.split(command) + result_path = arguments[arguments.index("--result") + 1] + self.files[result_path] = json.dumps( { + "agent_name": "harbor-demo", + "profiles": [], + "harness": "hermes", + "adapter_kind": "python", + "adapter_id": "nvidia.fabric.hermes.sdk", "status": "succeeded", "runtime_id": "runtime-1", "invocation_id": "invocation-1", - "request_id": "harbor-request-1", - "profiles": ["env_local", "mcp_github"], - "harness": "hermes", - "adapter_id": "nvidia.fabric.hermes.sdk", + "request_id": "request-1", + "output": {"response": "done"}, + "error": None, "artifacts": { "root": "/workspace/agent/artifacts", "artifacts": [ @@ -115,42 +144,68 @@ async def exec( }, ], }, - "telemetry": None, + "telemetry": [], + "events": [], + "metadata": {}, } ) return ExecResult() return ExecResult() + async def upload_file(self, source_path: Path, target_path: str) -> None: + self.uploads.append((source_path, target_path)) + self.files[target_path] = source_path.read_text(encoding="utf-8") + async def download_file(self, remote_path: str, host_path: Path) -> None: host_path.write_text(self.files[remote_path], encoding="utf-8") async def test_harbor_integration(tmp_path: Path): + from nemo_fabric import RunRequest + agent = FabricAgent( logs_dir=tmp_path, fabric_config_path="/opt/fabric-demo/agent.yaml", - fabric_profile_paths=[ - "/opt/fabric-demo/profiles/hermes.yaml", - "/opt/fabric-demo/profiles/telemetry.yaml", - ], model_name="nvidia/test-model", + skills_dir="/opt/fabric-demo/skills", + mcp_servers=[ + MCPServerConfig( + name="github", + transport="streamable-http", + url="https://mcp.example.test", + ) + ], extra_env={"NVIDIA_API_KEY": "test-key"}, ) environment = FakeHarborEnvironment() context = AgentContext() + assert isinstance(agent._build_request("fix the bug"), RunRequest) + await agent.setup(environment) # type: ignore[arg-type] await agent.run("fix the bug", environment, context) # type: ignore[arg-type] - spec = json.loads(environment.files["/tmp/fabric-run.json"]) + spec_paths = [ + path for path in environment.files if path.startswith("/tmp/fabric-run-") + ] + assert len(spec_paths) == 1 + assert len(environment.uploads) == 1 + spec = json.loads(environment.files[spec_paths[0]]) request = spec["request"] assert request["input"] == "fix the bug" - assert request["context"]["source"] == "harbor" - assert request["context"]["model_name"] == "nvidia/test-model" + assert request["context"] == {"source": "harbor"} + assert request["request_id"].startswith("request-") assert spec["config_path"] == "/opt/fabric-demo/agent.yaml" - assert spec["profile_paths"] == [ - "/opt/fabric-demo/profiles/hermes.yaml", - "/opt/fabric-demo/profiles/telemetry.yaml", + assert spec["model_name"] == "nvidia/test-model" + assert spec["skills_dir"] == "/opt/fabric-demo/skills" + assert spec["mcp_servers"] == [ + { + "name": "github", + "transport": "streamable-http", + "url": "https://mcp.example.test", + "command": None, + "args": [], + } ] fabric_commands = [ @@ -159,13 +214,49 @@ async def test_harbor_integration(tmp_path: Path): if "nemo_fabric.integrations.harbor.runner" in command ] assert len(fabric_commands) == 1 + assert not any(command.startswith("cat > ") for command in environment.commands) assert "python3 -m nemo_fabric.integrations.harbor.runner" in fabric_commands[0] assert environment.environments[environment.commands.index(fabric_commands[0])] == { "NVIDIA_API_KEY": "test-key" } assert context.metadata assert context.metadata["fabric"]["status"] == "succeeded" - assert context.metadata["fabric"]["profiles"] == ["env_local", "mcp_github"] + assert "profiles" not in context.metadata["fabric"] assert context.metadata["fabric"]["adapter_id"] == "nvidia.fabric.hermes.sdk" artifacts = context.metadata["fabric"]["artifacts"]["artifacts"] assert {artifact["name"] for artifact in artifacts} == {"stdout", "workspace_patch"} + + +async def test_harbor_exchange_paths_are_unique_per_run(tmp_path: Path): + agent = FabricAgent( + logs_dir=tmp_path, + fabric_config_path="/opt/fabric-demo/agent.yaml", + ) + environment = FakeHarborEnvironment() + + await agent.setup(environment) # type: ignore[arg-type] + await agent.run("first", environment, AgentContext()) # type: ignore[arg-type] + await agent.run("second", environment, AgentContext()) # type: ignore[arg-type] + + spec_paths = [path for path in environment.files if path.startswith("/tmp/fabric-run-")] + assert len(spec_paths) == 2 + assert len(set(spec_paths)) == 2 + result_paths = [ + path for path in environment.files if path.startswith("/tmp/fabric-result-") + ] + assert len(result_paths) == 2 + assert len(set(result_paths)) == 2 + assert len(list(tmp_path.glob("fabric-result-*.json"))) == 2 + + +def test_harbor_rejects_invalid_downloaded_result(tmp_path: Path): + from nemo_fabric import FabricConfigError + from nemo_fabric.integrations.harbor.fabric_agent import ( + populate_context_from_result, + ) + + result_path = tmp_path / "fabric-result.json" + result_path.write_text("{}", encoding="utf-8") + + with pytest.raises(FabricConfigError): + populate_context_from_result(AgentContext(), result_path) diff --git a/tests/python/test_native_sdk.py b/tests/python/test_native_sdk.py index 0baa38295..a062406d8 100644 --- a/tests/python/test_native_sdk.py +++ b/tests/python/test_native_sdk.py @@ -8,27 +8,25 @@ from pathlib import Path import nemo_fabric._native as native -from nemo_fabric import FabricClient, FabricConfig, FabricProfileConfig - -ROOT = Path(__file__).resolve().parents[2] +from examples.code_review_agent import BASE_DIR, base_config +from nemo_fabric import Fabric, FabricConfig, FabricProfileConfig async def test_native_sdk(hermes_shim_agent_dir: Path): assert native.version() - async with FabricClient() as client: - await smoke(client, hermes_shim_agent_dir) + await smoke(Fabric(), hermes_shim_agent_dir) -async def smoke(client: FabricClient, fixture_agent: Path) -> None: - example_agent = ROOT / "examples" / "code-review-agent" +async def smoke(client: Fabric, fixture_agent: Path) -> None: + example_config = base_config() - inspected = client.resolve(example_agent, profiles=["env_local"]) + inspected = client.resolve(example_config, base_dir=BASE_DIR) assert inspected["agent_name"] == "code-review-agent" - assert inspected.profiles == ("env_local",) + assert inspected.profiles == () assert inspected["config"]["metadata"]["name"] == "code-review-agent" - plan = client.plan(example_agent, profiles=["env_local"]) + plan = client.plan(example_config, base_dir=BASE_DIR) assert plan["agent_name"] == "code-review-agent" assert ( plan["adapter_descriptor"]["descriptor"]["adapter_id"] @@ -48,8 +46,6 @@ async def smoke(client: FabricClient, fixture_agent: Path) -> None: } ) minimal_resolved = client.resolve(minimal) - assert minimal_resolved.config.runtime.mode == "oneshot" - assert minimal_resolved.config.runtime.transport == "library" assert minimal_resolved.config.runtime.input_schema == "text" assert minimal_resolved.config.runtime.output_schema == "text" @@ -70,8 +66,6 @@ async def smoke(client: FabricClient, fixture_agent: Path) -> None: } }, "runtime": { - "mode": "session", - "transport": "library", "input_schema": "chat", "output_schema": "message", "artifacts": "./artifacts", @@ -97,16 +91,14 @@ async def smoke(client: FabricClient, fixture_agent: Path) -> None: }, } ) - typed_profile = FabricProfileConfig.from_mapping( - { - "name": "typed_relay", - "harness": {"settings": {"timeout_seconds": 30}}, - "telemetry": {"enabled": True, "output_dir": "./artifacts/relay"}, - "consumer_extension": { - "profile": True, - "nested": {"second": 2}, - }, - } + typed_profile = FabricProfileConfig( + name="typed_relay", + harness={"settings": {"timeout_seconds": 30}}, + telemetry={"enabled": True, "output_dir": "./artifacts/relay"}, + consumer_extension={ + "profile": True, + "nested": {"second": 2}, + }, ) typed_config_resolved = client.resolve( typed_config, @@ -138,12 +130,12 @@ async def smoke(client: FabricClient, fixture_agent: Path) -> None: profiles=["env_local"], input="hello native", ) - async with await client.start_session( + async with await client.start_runtime( fixture_agent, profiles=["env_local"], - ) as session: - first = await session.invoke(input="hello session one") - second = await session.invoke(input="hello session two") + ) as runtime: + first = await runtime.invoke(input="hello runtime one") + second = await runtime.invoke(input="hello runtime two") assert result["status"] == "succeeded" assert result.profiles == ("env_local",) @@ -158,4 +150,4 @@ async def smoke(client: FabricClient, fixture_agent: Path) -> None: assert first.profiles == ("env_local",) assert first.harness == "hermes" assert first["runtime_id"] == second["runtime_id"] - assert session.runtime["runtime_id"] == first["runtime_id"] + assert runtime.handle["runtime_id"] == first["runtime_id"] diff --git a/tests/python/test_readme_examples.py b/tests/python/test_readme_examples.py index 1c5eb108c..b2d0b3f35 100644 --- a/tests/python/test_readme_examples.py +++ b/tests/python/test_readme_examples.py @@ -1,62 +1,31 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Smoke test: the README "Use Fabric" examples stay accurate and runnable.""" +"""Smoke test: the README quick start stays accurate and runnable.""" from __future__ import annotations from pathlib import Path -from nemo_fabric import FabricClient, FabricConfig +from examples.code_review_agent import BASE_DIR, hermes_sdk_config +from nemo_fabric import Fabric ROOT = Path(__file__).resolve().parents[2] README = ROOT / "README.md" -EXAMPLE_AGENT = ROOT / "examples" / "code-review-agent" -# Exact invocations the README documents and this smoke mirrors. If the README -# changes any of these, update the executable mirror below (and vice versa). +# The README stays a quick start and routes detailed SDK usage to canonical docs. DOCUMENTED_SNIPPETS = [ - "fabric plan examples/code-review-agent --profile hermes_sdk", - "fabric plan examples/code-review-agent --profile env_local --profile mcp_github", - "fabric doctor examples/code-review-agent --profile hermes_sdk", - 'plan = client.plan(agent, profiles=["hermes_sdk"])', - 'report = await client.doctor(agent, profiles=["hermes_sdk"])', - "config = FabricConfig.from_mapping(", - "plan = client.plan(", - "result = await client.run(", - '"harness": {"adapter_id": "nvidia.fabric.hermes.sdk"},', - 'base_dir="examples/code-review-agent",', - "### Multi-Turn SDK Sessions", - "### Interactive CLI Chat", - "FabricClient().start_session(", - 'profiles=["hermes_session"],', - 'session_id="review-session-123",', - "fabric chat examples/code-review-agent \\", - "--profile hermes_cli_session", - "--session-id review-session-123", - "--verbose", - "requires `runtime.mode: session`; use `fabric run`", - "The CLI is a separate interface over the same Rust", + ".venv/bin/python -m examples.code_review_agent \\", + "examples/code_review_agent/config.py", + "[Python SDK guide](docs/sdk/python.mdx)", + "[generated Python API reference](docs/reference/api/python-library-reference/index.md)", ] -# The exact typed-config dict shown in the README example. -README_PLAN_CONFIG = { - "schema_version": "fabric.agent/v1alpha1", - "metadata": {"name": "code-review-agent"}, - "harness": {"adapter_id": "nvidia.fabric.hermes.sdk"}, - "models": { - "default": { - "provider": "nvidia", - "model": "nvidia/nemotron-3-nano-30b-a3b", - } - }, - "runtime": { - "mode": "session", - "transport": "library", - "input_schema": "chat", - "output_schema": "message", - }, -} +DETAILED_SDK_SNIPPETS = ( + "config = FabricConfig(", + "request = RunRequest(", + "### Multi-Turn SDK Runtimes", +) def readme_documents_each_example() -> None: @@ -65,25 +34,20 @@ def readme_documents_each_example() -> None: text = README.read_text(encoding="utf-8") missing = [snippet for snippet in DOCUMENTED_SNIPPETS if snippet not in text] assert not missing, f"README no longer documents these examples verbatim: {missing}" + duplicates = [snippet for snippet in DETAILED_SDK_SNIPPETS if snippet in text] + assert not duplicates, f"README duplicates detailed SDK guide examples: {duplicates}" async def readme_python_examples_run() -> None: - """The documented Python SDK examples execute and return documented shapes.""" - - agent = EXAMPLE_AGENT - async with FabricClient() as client: - plan = client.plan(agent, profiles=["hermes_sdk"]) - report = await client.doctor(agent, profiles=["hermes_sdk"]) - typed_plan = client.plan( - FabricConfig.from_mapping(README_PLAN_CONFIG), - base_dir=agent, - ) - - # README prints plan["agent_name"] and report["checks"]. + """The README quick-start package remains resolvable and diagnosable.""" + + config = hermes_sdk_config() + client = Fabric() + plan = client.plan(config, base_dir=BASE_DIR) + report = await client.doctor(config, base_dir=BASE_DIR) + assert plan["agent_name"] == "code-review-agent", plan["agent_name"] assert report["checks"], "doctor returned no checks" - assert typed_plan["agent_name"] == "code-review-agent" - assert typed_plan.adapter.adapter_id == "nvidia.fabric.hermes.sdk" async def test_readme_examples(): diff --git a/tests/python/test_session.py b/tests/python/test_runtime.py similarity index 50% rename from tests/python/test_session.py rename to tests/python/test_runtime.py index 795be069c..e354b0249 100644 --- a/tests/python/test_session.py +++ b/tests/python/test_runtime.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Behavior tests for the public Session lifecycle.""" +"""Behavior tests for the public Runtime lifecycle.""" from __future__ import annotations @@ -14,8 +14,7 @@ import pytest from nemo_fabric import ( - FabricCapabilityError, - FabricClient, + Fabric, FabricConfig, FabricConfigError, FabricNativeUnavailableError, @@ -23,20 +22,21 @@ FabricStateError, HarnessConfig, MetadataConfig, + RunRequest, RunResult, RuntimeConfig, - Session, - SessionStatus, + Runtime, + RuntimeStatus, ) from nemo_fabric import client as client_mod -from nemo_fabric import session as session_mod +from nemo_fabric import runtime as runtime_mod -def _plan(runtime_mode: str = "session") -> dict[str, Any]: +def _plan() -> dict[str, Any]: config = { "metadata": {"name": "demo"}, "harness": {"adapter_id": "test.fabric.shim"}, - "runtime": {"mode": runtime_mode, "transport": "library"}, + "runtime": {}, } return { "agent_name": "demo", @@ -58,23 +58,20 @@ def _plan(runtime_mode: str = "session") -> dict[str, Any]: } }, "capabilities": { - "session": runtime_mode == "session", "service": False, "streaming": False, "updates": False, "cancellation": False, - "concurrent_invocations": False, }, } -def _runtime() -> dict[str, Any]: +def _runtime(runtime_id: str = "runtime-1") -> dict[str, Any]: return { - "runtime_id": "runtime-1", + "runtime_id": runtime_id, "runtime_binding": "fabric-runtime-binding-test", "agent_name": "demo", "harness": "hermes", - "mode": "session", "adapter_kind": "python", "adapter_id": "test.fabric.shim", "environment": { @@ -86,11 +83,11 @@ def _runtime() -> dict[str, Any]: } -def _config(mode: str = "session") -> FabricConfig: +def _config() -> FabricConfig: return FabricConfig( metadata=MetadataConfig(name="demo"), harness=HarnessConfig(adapter_id="test.fabric.shim"), - runtime=RuntimeConfig(mode=mode), + runtime=RuntimeConfig(), ) @@ -106,6 +103,7 @@ def mock_native_fixture() -> MagicMock: def invoke(plan_json: str, runtime_json: str, request_json: str) -> str: request = json.loads(request_json) + runtime = json.loads(runtime_json) mock_native.requests.append(request) turn = len(mock_native.requests) return json.dumps( @@ -115,7 +113,7 @@ def invoke(plan_json: str, runtime_json: str, request_json: str) -> str: "harness": "hermes", "adapter_kind": "python", "adapter_id": "test.fabric.shim", - "runtime_id": "runtime-1", + "runtime_id": runtime["runtime_id"], "invocation_id": f"invocation-{turn}", "request_id": request["request_id"], "status": "succeeded", @@ -139,68 +137,62 @@ def invoke(plan_json: str, runtime_json: str, request_json: str) -> str: def native_client_fixture( monkeypatch: pytest.MonkeyPatch, mock_native: MagicMock, -) -> FabricClient: +) -> Fabric: monkeypatch.setattr(client_mod, "_native", mock_native) - return FabricClient() + return Fabric() -def _session(mock_native: MagicMock, *, overrides: dict[str, Any] | None = None) -> Session: - client = FabricClient() +def _runtime_wrapper( + mock_native: MagicMock, + *, + runtime_id: str = "runtime-1", + overrides: dict[str, Any] | None = None, +) -> Runtime: + client = Fabric() client._native_module = lambda: mock_native # type: ignore[method-assign] - return Session( + return Runtime( client=client, plan=_plan(), - runtime=_runtime(), + runtime=_runtime(runtime_id), overrides=overrides, ) -async def test_start_session_supports_path_and_typed_sources( - native_client: FabricClient, +async def test_start_runtime_supports_path_and_typed_sources( + native_client: Fabric, mock_native: MagicMock, ): - path_session = await native_client.start_session("agent", profiles=["typed"]) - typed_session = await native_client.start_session( + path_runtime = await native_client.start_runtime("agent", profiles=["typed"]) + typed_runtime = await native_client.start_runtime( _config(), profiles=[], base_dir=".", - session_id="caller-session", ) - assert path_session.runtime_id == "runtime-1" - assert typed_session.session_id == "caller-session" + assert path_runtime.runtime_id == "runtime-1" + assert typed_runtime.runtime_id == "runtime-1" assert mock_native.plan.call_args.args == ("agent", ["typed"]) assert mock_native.plan_config.called -async def test_start_session_rejects_non_session_capability( - native_client: FabricClient, - mock_native: MagicMock, -): - mock_native.plan.side_effect = lambda path, profiles: json.dumps(_plan("oneshot")) - - with pytest.raises(FabricCapabilityError, match="session capability"): - await native_client.start_session("agent") - - -async def test_start_session_preserves_start_stage( - native_client: FabricClient, +async def test_start_runtime_preserves_start_stage( + native_client: Fabric, mock_native: MagicMock, ): mock_native.start_runtime.side_effect = RuntimeError("start failed") with pytest.raises(FabricRuntimeError, match="start failed") as caught: - await native_client.start_session("agent") + await native_client.start_runtime("agent") assert caught.value.stage == "start" -async def test_start_session_rejects_invalid_overrides_before_start( - native_client: FabricClient, +async def test_start_runtime_rejects_invalid_overrides_before_start( + native_client: Fabric, mock_native: MagicMock, ): with pytest.raises(FabricConfigError, match="keys must be strings"): - await native_client.start_session( + await native_client.start_runtime( "agent", overrides={"nested": {1: "invalid"}}, # type: ignore[dict-item] ) @@ -208,250 +200,361 @@ async def test_start_session_rejects_invalid_overrides_before_start( mock_native.start_runtime.assert_not_called() -async def test_start_session_rejects_cyclic_overrides_before_start( - native_client: FabricClient, +async def test_start_runtime_rejects_cyclic_overrides_before_start( + native_client: Fabric, mock_native: MagicMock, ): overrides: dict[str, Any] = {} overrides["cycle"] = overrides with pytest.raises(FabricConfigError, match="JSON-compatible"): - await native_client.start_session("agent", overrides=overrides) + await native_client.start_runtime("agent", overrides=overrides) mock_native.start_runtime.assert_not_called() -async def test_session_reuses_runtime_and_orders_turns(mock_native: MagicMock): - session = _session(mock_native) +async def test_runtime_reuses_runtime_and_orders_turns(mock_native: MagicMock): + runtime = _runtime_wrapper(mock_native) - first = await session.invoke(input="one") - second = await session.invoke(input="two") + first = await runtime.invoke(input="one") + second = await runtime.invoke(input="two") assert isinstance(first, RunResult) assert first.runtime_id == second.runtime_id == "runtime-1" assert [request["input"] for request in mock_native.requests] == ["one", "two"] - assert session.messages[-1]["content"] == "reply-2" - assert len(session.invocations) == 2 + assert runtime.messages[-1]["content"] == "reply-2" + assert len(runtime.invocations) == 2 -async def test_native_invoke_failure_marks_session_failed(mock_native: MagicMock): +async def test_native_invoke_failure_marks_runtime_failed(mock_native: MagicMock): mock_native.invoke_runtime.side_effect = RuntimeError("invoke failed") - session = _session(mock_native) + runtime = _runtime_wrapper(mock_native) with pytest.raises(FabricRuntimeError, match="invoke failed"): - await session.invoke(input="hello") + await runtime.invoke(input="hello") - assert session.status is SessionStatus.FAILED + assert runtime.status is RuntimeStatus.FAILED with pytest.raises(FabricStateError, match="failed"): - await session.invoke(input="too late") + await runtime.invoke(input="too late") + + await runtime.stop() + assert runtime.status is RuntimeStatus.STOPPED + mock_native.stop_runtime.assert_called_once() async def test_failed_invoke_is_not_masked_by_context_cleanup(mock_native: MagicMock): mock_native.invoke_runtime.side_effect = RuntimeError("invoke failed") - session = _session(mock_native) + runtime = _runtime_wrapper(mock_native) with pytest.raises(FabricRuntimeError, match="invoke failed"): - async with session: - await session.invoke(input="hello") + async with runtime: + await runtime.invoke(input="hello") - assert session.status is SessionStatus.FAILED - mock_native.stop_runtime.assert_not_called() + assert runtime.status is RuntimeStatus.STOPPED + mock_native.stop_runtime.assert_called_once() -async def test_session_preserves_non_mapping_message_values(mock_native: MagicMock): +async def test_failed_cleanup_does_not_mask_invoke_failure(mock_native: MagicMock): + mock_native.invoke_runtime.side_effect = RuntimeError("invoke failed") + mock_native.stop_runtime.side_effect = RuntimeError("stop failed") + runtime = _runtime_wrapper(mock_native) + + with pytest.raises(FabricRuntimeError, match="invoke failed") as caught: + async with runtime: + await runtime.invoke(input="hello") + + assert runtime.status is RuntimeStatus.FAILED + assert caught.value.__notes__ == ["runtime cleanup failed: stop failed"] + mock_native.stop_runtime.assert_called_once() + + +async def test_runtime_preserves_non_mapping_message_values(mock_native: MagicMock): result = json.loads( mock_native.invoke_runtime.side_effect( "", - "", + json.dumps(_runtime()), json.dumps({"input": "hello", "request_id": "request-1"}), ) ) result["output"]["messages"] = ["notice", {"role": "assistant", "content": "ok"}, 1] mock_native.invoke_runtime.side_effect = None mock_native.invoke_runtime.return_value = json.dumps(result) - session = _session(mock_native) + runtime = _runtime_wrapper(mock_native) - await session.invoke(input="hello") + await runtime.invoke(input="hello") - assert session.messages == ["notice", {"role": "assistant", "content": "ok"}, 1] + assert runtime.messages == ["notice", {"role": "assistant", "content": "ok"}, 1] -async def test_session_recursively_merges_overrides(mock_native: MagicMock): - session = _session( +async def test_runtime_recursively_merges_overrides(mock_native: MagicMock): + runtime = _runtime_wrapper( mock_native, - overrides={"limits": {"turns": 2, "tokens": 10}, "mode": "session"}, + overrides={"limits": {"turns": 2, "tokens": 10}, "phase": "runtime"}, ) - await session.invoke( - input="hello", - overrides={"limits": {"tokens": 20}, "mode": None}, + await runtime.invoke( + request=RunRequest( + input="hello", + overrides={"limits": {"tokens": 20}, "mode": None}, + ), ) assert mock_native.requests[0]["overrides"] == { "limits": {"turns": 2, "tokens": 20}, + "phase": "runtime", "mode": None, } -async def test_stream_yields_terminal_result(mock_native: MagicMock): - items = [item async for item in _session(mock_native).stream(input="hello")] - - assert len(items) == 1 - assert isinstance(items[0], RunResult) - - async def test_stop_is_idempotent_and_blocks_future_invokes(mock_native: MagicMock): - session = _session(mock_native) + runtime = _runtime_wrapper(mock_native) - await session.stop() - await session.stop() + await runtime.stop() + await runtime.stop() - assert session.status is SessionStatus.STOPPED + assert runtime.status is RuntimeStatus.STOPPED assert mock_native.stop_runtime.call_count == 1 with pytest.raises(FabricStateError, match="stopped"): - await session.invoke(input="hello") + await runtime.invoke(input="hello") async def test_stop_rejects_in_flight_turn( - monkeypatch: pytest.MonkeyPatch, mock_native: MagicMock, ): - started = asyncio.Event() - release = asyncio.Event() + started = threading.Event() + release = threading.Event() + invoke = mock_native.invoke_runtime.side_effect - async def blocking(func): # type: ignore[no-untyped-def] + def blocking_invoke(*args: Any) -> str: started.set() - await release.wait() - return func() + assert release.wait(timeout=5) + return invoke(*args) - monkeypatch.setattr(session_mod, "_call_blocking", blocking) - session = _session(mock_native) - turn = asyncio.create_task(session.invoke(input="hello")) - await started.wait() + mock_native.invoke_runtime.side_effect = blocking_invoke + runtime = _runtime_wrapper(mock_native) + turn = asyncio.create_task(runtime.invoke(input="hello")) + assert await asyncio.to_thread(started.wait, 2) with pytest.raises(FabricStateError, match="in flight"): - await session.stop() + await runtime.stop() release.set() await turn async def test_concurrent_invokes_are_rejected( - monkeypatch: pytest.MonkeyPatch, mock_native: MagicMock, ): - started = asyncio.Event() - release = asyncio.Event() + started = threading.Event() + release = threading.Event() + invoke = mock_native.invoke_runtime.side_effect - async def blocking(func): # type: ignore[no-untyped-def] + def blocking_invoke(*args: Any) -> str: started.set() - await release.wait() - return func() + assert release.wait(timeout=5) + return invoke(*args) - monkeypatch.setattr(session_mod, "_call_blocking", blocking) - session = _session(mock_native) - first = asyncio.create_task(session.invoke(input="one")) - await started.wait() + mock_native.invoke_runtime.side_effect = blocking_invoke + runtime = _runtime_wrapper(mock_native) + first = asyncio.create_task(runtime.invoke(input="one")) + assert await asyncio.to_thread(started.wait, 2) with pytest.raises(FabricStateError, match="already running"): - await session.invoke(input="two") + await runtime.invoke(input="two") release.set() await first -async def test_run_stops_runtime_after_success_and_failure( - native_client: FabricClient, +async def test_independent_runtimes_can_invoke_concurrently( mock_native: MagicMock, ): - result = await native_client.run("agent", input="hello") - assert result.status == "succeeded" - assert mock_native.stop_runtime.call_count == 1 + both_started = threading.Event() + release = threading.Event() + invoke = mock_native.invoke_runtime.side_effect + lock = threading.Lock() + started = 0 + + def blocking_invoke(*args: Any) -> str: + nonlocal started + with lock: + started += 1 + if started == 2: + both_started.set() + assert release.wait(timeout=5) + return invoke(*args) - mock_native.invoke_runtime.side_effect = RuntimeError("invoke failed") - with pytest.raises(FabricRuntimeError, match="invoke failed"): - await native_client.run("agent", input="hello") - assert mock_native.stop_runtime.call_count == 2 + mock_native.invoke_runtime.side_effect = blocking_invoke + first_runtime = _runtime_wrapper(mock_native) + second_runtime = _runtime_wrapper(mock_native, runtime_id="runtime-2") + first = asyncio.create_task(first_runtime.invoke(input="one")) + second = asyncio.create_task(second_runtime.invoke(input="two")) + assert await asyncio.to_thread(both_started.wait, 2) -async def test_async_lifecycle_methods_offload_planning( - native_client: FabricClient, - monkeypatch: pytest.MonkeyPatch, -): + assert not first.done() + assert not second.done() + release.set() + first_result, second_result = await asyncio.gather(first, second) + assert {first_result.runtime_id, second_result.runtime_id} == { + "runtime-1", + "runtime-2", + } + + +async def test_blocking_native_calls_run_off_the_event_loop(): event_loop_thread = threading.get_ident() - planning_threads: list[int] = [] - original_plan = native_client.plan - def record_plan(*args: Any, **kwargs: Any): - planning_threads.append(threading.get_ident()) - return original_plan(*args, **kwargs) + worker_thread = await runtime_mod._call_blocking(threading.get_ident) - monkeypatch.setattr(native_client, "plan", record_plan) + assert worker_thread != event_loop_thread - await native_client.run("agent", input="hello") - session = await native_client.start_session("agent") - await session.stop() - with pytest.raises(FabricCapabilityError, match="service mode"): - await native_client.start_service("agent") - assert len(planning_threads) == 3 - assert all(thread != event_loop_thread for thread in planning_threads) +async def test_cancelling_invoke_waits_for_native_work_and_stops_runtime( + mock_native: MagicMock, +): + started = threading.Event() + release = threading.Event() + invoke = mock_native.invoke_runtime.side_effect + def blocking_invoke(*args: Any) -> str: + started.set() + assert release.wait(timeout=5) + return invoke(*args) -async def test_run_surfaces_cleanup_failure_after_success( - native_client: FabricClient, + mock_native.invoke_runtime.side_effect = blocking_invoke + runtime = _runtime_wrapper(mock_native) + turn = asyncio.create_task(runtime.invoke(input="hello")) + assert await asyncio.to_thread(started.wait, 2) + + turn.cancel() + await asyncio.sleep(0) + assert not turn.done() + turn.cancel() + await asyncio.sleep(0) + assert not turn.done() + + release.set() + with pytest.raises(asyncio.CancelledError): + await turn + + assert runtime.status is RuntimeStatus.STOPPED + mock_native.stop_runtime.assert_called_once() + + +async def test_cancelling_start_stops_the_completed_native_runtime( + native_client: Fabric, mock_native: MagicMock, ): - mock_native.stop_runtime.side_effect = RuntimeError("stop failed") + started = threading.Event() + release = threading.Event() - with pytest.raises(FabricRuntimeError, match="stop failed") as caught: - await native_client.run("agent", input="hello") + def blocking_start(*args: Any) -> str: + started.set() + assert release.wait(timeout=5) + return json.dumps(_runtime()) - assert caught.value.stage == "run" - assert mock_native.stop_runtime.call_count == 1 + mock_native.start_runtime.side_effect = blocking_start + start = asyncio.create_task(native_client.start_runtime("agent")) + assert await asyncio.to_thread(started.wait, 2) + + start.cancel() + await asyncio.sleep(0) + assert not start.done() + + release.set() + with pytest.raises(asyncio.CancelledError): + await start + + mock_native.stop_runtime.assert_called_once() -async def test_run_cancellation_keeps_event_loop_responsive_until_cleanup( - native_client: FabricClient, +async def test_cancelling_one_shot_run_waits_for_stop( + native_client: Fabric, mock_native: MagicMock, ): started = threading.Event() release = threading.Event() invoke = mock_native.invoke_runtime.side_effect - def blocking_invoke(*args: str) -> str: + def blocking_invoke(*args: Any) -> str: started.set() - release.wait(timeout=1) + assert release.wait(timeout=5) return invoke(*args) mock_native.invoke_runtime.side_effect = blocking_invoke run = asyncio.create_task(native_client.run("agent", input="hello")) - await asyncio.to_thread(started.wait, 1) - fallback_release = threading.Timer(1, release.set) - fallback_release.start() + assert await asyncio.to_thread(started.wait, 2) run.cancel() - await asyncio.sleep(0.01) + await asyncio.sleep(0) assert not run.done() release.set() with pytest.raises(asyncio.CancelledError): await run - fallback_release.cancel() + + mock_native.stop_runtime.assert_called_once() + + +async def test_run_stops_runtime_after_success_and_failure( + native_client: Fabric, + mock_native: MagicMock, +): + result = await native_client.run("agent", input="hello") + assert result.status == "succeeded" + assert mock_native.stop_runtime.call_count == 1 + + mock_native.invoke_runtime.side_effect = RuntimeError("invoke failed") + with pytest.raises(FabricRuntimeError, match="invoke failed"): + await native_client.run("agent", input="hello") + assert mock_native.stop_runtime.call_count == 2 + + +async def test_async_lifecycle_methods_resolve_plans( + native_client: Fabric, + monkeypatch: pytest.MonkeyPatch, +): + planning_calls: list[tuple[tuple[Any, ...], dict[str, Any]]] = [] + original_plan = native_client.plan + + def record_plan(*args: Any, **kwargs: Any): + planning_calls.append((args, kwargs)) + return original_plan(*args, **kwargs) + + monkeypatch.setattr(native_client, "plan", record_plan) + + await native_client.run("agent", input="hello") + runtime = await native_client.start_runtime("agent") + await runtime.stop() + + assert len(planning_calls) == 2 + + +async def test_run_surfaces_cleanup_failure_after_success( + native_client: Fabric, + mock_native: MagicMock, +): + mock_native.stop_runtime.side_effect = RuntimeError("stop failed") + + with pytest.raises(FabricRuntimeError, match="stop failed") as caught: + await native_client.run("agent", input="hello") + + assert caught.value.stage == "run" assert mock_native.stop_runtime.call_count == 1 async def test_context_manager_stops_runtime(mock_native: MagicMock): - session = _session(mock_native) + runtime = _runtime_wrapper(mock_native) - async with session: - await session.invoke(input="hello") + async with runtime: + await runtime.invoke(input="hello") - assert session.status is SessionStatus.STOPPED + assert runtime.status is RuntimeStatus.STOPPED async def test_native_unavailable_uses_typed_error(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(client_mod, "_native", None) with pytest.raises(FabricNativeUnavailableError, match="native extension"): - FabricClient().plan("agent") + Fabric().plan("agent") diff --git a/tests/python/test_sdk_concurrency.py b/tests/python/test_sdk_concurrency.py index 7210a1a73..54761e17f 100644 --- a/tests/python/test_sdk_concurrency.py +++ b/tests/python/test_sdk_concurrency.py @@ -9,23 +9,26 @@ from pathlib import Path from shutil import copytree -from nemo_fabric import FabricClient +from nemo_fabric import Fabric -async def run_copy( - client: FabricClient, fixture_agent: Path, root: Path, name: str -) -> dict: +async def run_runtime(client: Fabric, agent: Path, name: str) -> dict: + async with await client.start_runtime(agent, profiles=["env_local"]) as runtime: + return await runtime.invoke(input=f"hello from {name}") + + +async def run_copy(client: Fabric, fixture_agent: Path, root: Path, name: str) -> dict: agent = root / name copytree(fixture_agent, agent) return await client.run(agent, profiles=["env_local"], input=f"hello from {name}") async def test_sdk_concurrency(hermes_shim_agent_dir_src: Path, tmp_path: Path): - async with FabricClient() as client: - first, second = await asyncio.gather( - run_copy(client, hermes_shim_agent_dir_src, tmp_path, "agent-one"), - run_copy(client, hermes_shim_agent_dir_src, tmp_path, "agent-two"), - ) + client = Fabric() + first, second = await asyncio.gather( + run_copy(client, hermes_shim_agent_dir_src, tmp_path, "agent-one"), + run_copy(client, hermes_shim_agent_dir_src, tmp_path, "agent-two"), + ) assert first["status"] == "succeeded" assert second["status"] == "succeeded" @@ -34,3 +37,24 @@ async def test_sdk_concurrency(hermes_shim_agent_dir_src: Path, tmp_path: Path): assert first["output"]["received"] == "hello from agent-one" assert second["output"]["received"] == "hello from agent-two" assert first["artifacts"]["root"] != second["artifacts"]["root"] + + +async def test_independent_runtimes_isolate_files_in_shared_artifact_root( + hermes_shim_agent_dir: Path, +): + client = Fabric() + first, second = await asyncio.gather( + run_runtime(client, hermes_shim_agent_dir, "runtime-one"), + run_runtime(client, hermes_shim_agent_dir, "runtime-two"), + ) + + assert first["status"] == "succeeded" + assert second["status"] == "succeeded" + assert first["runtime_id"] != second["runtime_id"] + assert first["invocation_id"] != second["invocation_id"] + assert first["output"]["received"] == "hello from runtime-one" + assert second["output"]["received"] == "hello from runtime-two" + assert first["artifacts"]["root"] == second["artifacts"]["root"] + first_paths = {artifact["path"] for artifact in first["artifacts"]["artifacts"]} + second_paths = {artifact["path"] for artifact in second["artifacts"]["artifacts"]} + assert first_paths.isdisjoint(second_paths) diff --git a/tests/python/test_sdk_contract.py b/tests/python/test_sdk_contract.py index d2e509734..03544b2d0 100644 --- a/tests/python/test_sdk_contract.py +++ b/tests/python/test_sdk_contract.py @@ -7,10 +7,13 @@ import json from inspect import signature +from pathlib import Path from typing import Any, get_overloads import pytest +from pydantic import ValidationError +import nemo_fabric import nemo_fabric.errors as fabric_errors from nemo_fabric import ( @@ -18,7 +21,7 @@ DoctorReport, EffectiveConfig, EnvironmentConfig, - FabricClient, + Fabric, FabricCapabilityError, FabricConfig, FabricConfigError, @@ -28,27 +31,31 @@ FabricRuntimeError, FabricStateError, HarnessConfig, + McpConfig, MetadataConfig, RunPlan, RunRequest, RunResult, RuntimeCapabilities, RuntimeConfig, + Runtime, RuntimeHandle, - RuntimeUpdate, - Session, - SessionInfo, + SkillConfig, + TelemetryConfig, ) def test_public_contract_has_no_unreleased_aliases(): - assert list(signature(FabricClient).parameters) == [] + assert list(signature(Fabric).parameters) == [] + assert not hasattr(Fabric, "__aenter__") + assert not hasattr(Fabric, "__aexit__") assert not hasattr(RunRequest, "from_text") + assert not hasattr(nemo_fabric, "RunRequestModel") for name in ("plan_config", "run_config", "doctor_config", "start", "start_config"): - assert not hasattr(FabricClient, name) + assert not hasattr(Fabric, name) - for name in ("resolve", "plan", "doctor", "run", "start_session", "start_service"): - assert len(get_overloads(getattr(FabricClient, name))) == 2, name + for name in ("resolve", "plan", "doctor", "run", "start_runtime"): + assert len(get_overloads(getattr(Fabric, name))) == 2, name assert not hasattr(fabric_errors, "FabricCliError") @@ -58,7 +65,7 @@ def test_typed_config_validates_required_fields_and_preserves_extensions(): "schema_version": "fabric.agent/v1alpha1", "metadata": {"name": "demo", "owner": "sdk"}, "harness": {"adapter_id": "test.fabric.shim", "future": True}, - "runtime": {"mode": "session"}, + "runtime": {}, "future_top_level": {"enabled": True}, } @@ -69,7 +76,6 @@ def test_typed_config_validates_required_fields_and_preserves_extensions(): assert config.environment is None assert config.metadata.name == "demo" assert config.metadata.description is None - assert config.runtime.transport is None assert "transport" not in config.runtime.to_mapping() assert config.metadata.extra_fields == {"owner": "sdk"} assert config.harness.extra_fields == {"future": True} @@ -77,40 +83,30 @@ def test_typed_config_validates_required_fields_and_preserves_extensions(): assert config.to_mapping()["future_top_level"] == {"enabled": True} assert "models" not in config.to_mapping() - runtime = RuntimeConfig(mode="service") + runtime = RuntimeConfig(input_schema="http") config.runtime = runtime - config["future_runtime"] = {"enabled": True} + config.future_runtime = {"enabled": True} assert isinstance(config.runtime, RuntimeConfig) assert config.extra_fields["future_runtime"] == {"enabled": True} - with pytest.raises(TypeError): - FabricConfig( # type: ignore[call-arg] - metadata=MetadataConfig(name="demo"), - harness=HarnessConfig(adapter_id="test.fabric.shim"), - unexpected=True, - ) - with pytest.raises(FabricConfigError, match="metadata"): + with pytest.raises(ValidationError, match="metadata"): FabricConfig.from_mapping({"harness": {"adapter_id": "test.fabric.shim"}}) - with pytest.raises(FabricConfigError, match="adapter_id"): + with pytest.raises(ValidationError, match="adapter_id"): HarnessConfig(adapter_id="") - with pytest.raises(FabricConfigError, match="runtime mode"): - RuntimeConfig(mode="invalid") - with pytest.raises(FabricConfigError, match="harness settings"): + with pytest.raises(ValidationError, match="settings"): HarnessConfig( adapter_id="test.fabric.shim", settings=[], # type: ignore[arg-type] ) - with pytest.raises(FabricConfigError, match="extra_fields"): - MetadataConfig(name="demo", extra_fields=[]) # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="environment settings"): + with pytest.raises(ValidationError, match="settings"): EnvironmentConfig(settings=[]) # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="runtime must be"): + with pytest.raises(ValidationError, match="runtime"): FabricConfig( metadata=MetadataConfig(name="demo"), harness=HarnessConfig(adapter_id="test.fabric.shim"), runtime=[], # type: ignore[arg-type] ) - with pytest.raises(FabricConfigError, match="models"): + with pytest.raises(ValidationError, match="models"): FabricConfig( metadata=MetadataConfig(name="demo"), harness=HarnessConfig(adapter_id="test.fabric.shim"), @@ -118,19 +114,136 @@ def test_typed_config_validates_required_fields_and_preserves_extensions(): ) -def test_typed_profile_preserves_partial_overlay_sections(): - profile = FabricProfileConfig.from_mapping( - { - "name": "session", - "harness": {"settings": {"timeout_seconds": 30}}, - "runtime": {"mode": "session"}, - } +def test_typed_config_authoring_helpers_emit_schema_shape(): + config = FabricConfig( + metadata=MetadataConfig(name="demo"), + harness=HarnessConfig(adapter_id="test.fabric.shim"), + models={ + "default": { + "provider": "test", + "model": "test-model", + } + }, ) - assert profile.to_mapping()["harness"] == { - "settings": {"timeout_seconds": 30} + config.add_skill_path("./skills/review").add_skill_path("./skills/review") + config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="fabric_managed", + ) + config.enable_relay( + project="fabric-tests", + output_dir="./artifacts/relay", + config={"version": 1}, + ) + + assert isinstance(config.mcp, McpConfig) + assert isinstance(config.skills, SkillConfig) + assert isinstance(config.telemetry, TelemetryConfig) + + assert config.to_mapping()["skills"] == {"paths": ["./skills/review"]} + assert config.to_mapping()["mcp"] == { + "servers": { + "github": { + "transport": "streamable-http", + "url": "${GITHUB_MCP_URL}", + "exposure": "fabric_managed", + } + } + } + assert config.to_mapping()["telemetry"] == { + "enabled": True, + "provider": "relay", + "project": "fabric-tests", + "output_dir": "./artifacts/relay", + "config": {"version": 1}, } - assert profile.to_mapping()["runtime"] == {"mode": "session"} + + config.remove_mcp_server("github").remove_mcp_server("missing") + config.remove_skill_path("./skills/review").remove_skill_path("./skills/missing") + assert config.mcp is None + assert config.skills is None + assert "mcp" not in config.to_mapping() + assert "skills" not in config.to_mapping() + + with pytest.raises(ValidationError, match="exposure"): + config.add_mcp_server( + "bad", + transport="streamable-http", + url="http://example.invalid", + exposure="sideways", + ) + with pytest.raises(ValidationError, match="provider"): + TelemetryConfig(provider="sideways") + + +def test_config_emits_schema_shape_and_validates(): + config = FabricConfig( + metadata={"name": "demo", "owner": "sdk"}, + harness={"adapter_id": "test.fabric.shim", "future": True}, + models={ + "default": { + "provider": "test", + "model": "test-model", + "temperature": 0.0, + } + }, + future_top_level={"enabled": True}, + ) + config.add_skill_path("./skills/review") + config.add_mcp_server( + "github", + transport="streamable-http", + url="${GITHUB_MCP_URL}", + exposure="fabric_managed", + ) + config.enable_relay(project="fabric-tests", output_dir="./artifacts/relay") + + emitted = config.to_mapping() + + assert emitted["schema_version"] == "fabric.agent/v1alpha1" + assert emitted["metadata"]["owner"] == "sdk" + assert emitted["harness"]["future"] is True + assert emitted["runtime"] == {} + assert emitted["models"]["default"]["model"] == "test-model" + assert emitted["skills"] == {"paths": ["./skills/review"]} + assert emitted["mcp"]["servers"]["github"]["exposure"] == "fabric_managed" + assert emitted["telemetry"]["provider"] == "relay" + assert config.extra_fields == {"future_top_level": {"enabled": True}} + + normalized = FabricConfig.model_validate(config) + assert normalized.to_mapping()["future_top_level"] == {"enabled": True} + + with pytest.raises(ValidationError): + FabricConfig(metadata={"name": "missing-harness"}) # type: ignore[call-arg] + with pytest.raises(ValidationError): + config.add_mcp_server( + "bad", + transport="streamable-http", + url="http://example.invalid", + exposure="sideways", # type: ignore[arg-type] + ) + + +def test_agent_model_tracks_rust_schema_top_level_fields(): + schema = json.loads(Path("schemas/agent.schema.json").read_text(encoding="utf-8")) + pydantic_schema = FabricConfig.model_json_schema() + + assert set(pydantic_schema["properties"]).issuperset(schema["properties"]) + assert set(pydantic_schema["required"]) == {"metadata", "harness"} + assert set(schema["required"]) == {"schema_version", "metadata", "harness", "runtime"} + + +def test_environment_model_defines_extension_field_ownership(): + properties = EnvironmentConfig.model_json_schema()["properties"] + + assert "environment provider" in properties["settings"]["description"] + assert "without Fabric semantics" in properties["metadata"]["description"] + assert "existing environment" in properties["connection"]["description"] + assert "environment teardown" in properties["ownership"]["description"] + assert "outside or inside" in properties["control_location"]["description"] def test_inspection_models_are_typed_read_only_mappings(): @@ -147,7 +260,7 @@ def test_inspection_models_are_typed_read_only_mappings(): "config": { "metadata": {"name": "demo"}, "harness": {"adapter_id": "test.fabric.shim"}, - "runtime": {"mode": "session"}, + "runtime": {"input_schema": "chat"}, }, }, "adapter_descriptor": { @@ -159,12 +272,10 @@ def test_inspection_models_are_typed_read_only_mappings(): } }, "capabilities": { - "session": True, "service": False, "streaming": False, "updates": False, "cancellation": False, - "concurrent_invocations": False, "future_capability": "declared", }, } @@ -192,7 +303,6 @@ def test_runtime_handle_distinguishes_contract_and_extension_fields(): "runtime_binding": "binding-1", "agent_name": "demo", "harness": "hermes", - "mode": "session", "adapter_kind": "python", "adapter_id": "test.fabric.shim", "environment": { @@ -215,7 +325,6 @@ def test_runtime_handle_distinguishes_contract_and_extension_fields(): "runtime_binding", "agent_name", "harness", - "mode", "adapter_kind", "environment", ), @@ -234,7 +343,6 @@ def test_runtime_handle_requires_native_contract_fields(field): (EffectiveConfig, {"config": {}}), (DoctorReport, {}), (RunResult, {}), - (SessionInfo, {}), ), ) def test_snapshot_models_require_profiles(model, payload): @@ -251,8 +359,8 @@ def test_run_plan_requires_profiles(): def test_runtime_capabilities_reject_non_boolean_values(): - with pytest.raises(FabricConfigError, match="session capability"): - RuntimeCapabilities.from_mapping({"session": "false"}) + with pytest.raises(FabricConfigError, match="streaming capability"): + RuntimeCapabilities.from_mapping({"streaming": "false"}) def test_doctor_report_and_errors_expose_typed_contract_fields(): @@ -263,7 +371,7 @@ def test_doctor_report_and_errors_expose_typed_contract_fields(): "status": "warn", "checks": [ { - "name": "runtime.mode", + "name": "runtime.adapter", "status": "warn", "message": "not implemented", } @@ -278,7 +386,7 @@ def test_doctor_report_and_errors_expose_typed_contract_fields(): details={"adapter_id": "test.fabric.shim"}, ) - assert report.checks[0].name == "runtime.mode" + assert report.checks[0].name == "runtime.adapter" assert error.stage == "invoke" assert error.code == "adapter_failed" assert error.retryable is True @@ -290,8 +398,6 @@ def _plan() -> dict[str, Any]: "metadata": {"name": "demo"}, "harness": {"adapter_id": "test.fabric.shim"}, "runtime": { - "mode": "session", - "transport": "library", "input_schema": "chat", "output_schema": "message", }, @@ -316,12 +422,10 @@ def _plan() -> dict[str, Any]: } }, "capabilities": { - "session": True, "service": False, "streaming": False, "updates": False, "cancellation": False, - "concurrent_invocations": False, }, } @@ -332,7 +436,6 @@ def _runtime() -> dict[str, Any]: "runtime_binding": "fabric-runtime-binding-test", "agent_name": "demo", "harness": "hermes", - "mode": "session", "adapter_kind": "python", "adapter_id": "test.fabric.shim", "environment": { @@ -367,7 +470,7 @@ def _fabric_config() -> FabricConfig: return FabricConfig( metadata=MetadataConfig(name="demo"), harness=HarnessConfig(adapter_id="test.fabric.shim"), - runtime=RuntimeConfig(mode="session"), + runtime=RuntimeConfig(), ) @@ -375,6 +478,7 @@ class NativeRecorder: def __init__(self) -> None: self.requests: list[dict[str, Any]] = [] self.path_profile_calls: list[Any] = [] + self.config_profile_calls: list[Any] = [] self.stopped = 0 self.fail_invoke = False @@ -395,6 +499,9 @@ def resolve_config( base_dir: str | None = None, ) -> str: assert json.loads(config_json)["metadata"]["name"] == "demo" + self.config_profile_calls.append( + None if profiles_json is None else json.loads(profiles_json) + ) return json.dumps(_plan()["effective_config"]) def plan_config( @@ -404,6 +511,9 @@ def plan_config( base_dir: str | None = None, ) -> str: assert json.loads(config_json)["metadata"]["name"] == "demo" + self.config_profile_calls.append( + None if profiles_json is None else json.loads(profiles_json) + ) return json.dumps(_plan()) def start_runtime(self, plan_json: str) -> str: @@ -454,7 +564,7 @@ def stop_runtime(self, plan_json: str, runtime_json: str) -> str: return json.dumps([]) -class NativeClient(FabricClient): +class NativeClient(Fabric): def __init__(self, native: NativeRecorder) -> None: super().__init__() self.native = native @@ -466,7 +576,7 @@ def _require_native_module(self, method: str) -> NativeRecorder: return self.native -def test_run_request_is_mapping_compatible_and_json_safe(): +def test_run_request_is_validated_and_json_safe(): context = {"run_id": "run-1", "labels": ["sdk"]} overrides = {"temperature": 0, "limits": {"turns": 1}} request = RunRequest( @@ -478,7 +588,6 @@ def test_run_request_is_mapping_compatible_and_json_safe(): context["labels"].append("mutated") overrides["limits"]["turns"] = 2 - assert request["request_id"] == "request-1" assert request.request_id == "request-1" assert request.to_mapping()["input"] == { "messages": [{"role": "user", "content": "hello"}] @@ -489,7 +598,7 @@ def test_run_request_is_mapping_compatible_and_json_safe(): "limits": {"turns": 1}, } - copied = request.to_dict() + copied = request.to_mapping() copied["context"]["run_id"] = "changed" assert request.to_mapping()["context"] == {"run_id": "run-1", "labels": ["sdk"]} @@ -507,24 +616,24 @@ def test_run_request_from_mapping_copies_and_validates_context(): assert request.input == "hello" assert request.context == {"job_id": "job-1"} - with pytest.raises(FabricConfigError, match="request context"): + with pytest.raises(ValidationError, match="request context"): RunRequest.from_mapping({"input": "bad", "context": "not-a-mapping"}) def test_run_request_constructor_validates_context_and_overrides(): - with pytest.raises(FabricConfigError, match="request context"): + with pytest.raises(ValidationError, match="request context"): RunRequest(input="bad", context="not-a-mapping") # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="request overrides"): + with pytest.raises(ValidationError, match="request overrides"): RunRequest(input="bad", overrides="not-a-mapping") # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="request context"): + with pytest.raises(ValidationError, match="request context"): RunRequest(input="bad", context=[]) # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="request extra_fields"): - RunRequest(input="bad", extra_fields=[]) # type: ignore[arg-type] + with pytest.raises(ValidationError, match="JSON-compatible"): + RunRequest(input="bad", future_request=object()) - with pytest.raises(FabricConfigError, match="finite"): + with pytest.raises(ValidationError, match="finite"): RunRequest(input=float("nan")) @@ -536,6 +645,30 @@ def test_run_request_constructor_generates_request_metadata(): assert request.context == {} +def test_run_request_preserves_extension_fields(): + request = RunRequest( + input={"messages": [{"role": "user", "content": "hello"}]}, + request_id="request-1", + context={"job_id": "job-1"}, + future_request={"enabled": True}, + ) + + assert request.to_mapping()["input"] == { + "messages": [{"role": "user", "content": "hello"}] + } + assert request.context == {"job_id": "job-1"} + assert request.extra_fields["future_request"] == {"enabled": True} + + +@pytest.mark.parametrize("value", [{}, []]) +def test_run_request_preserves_empty_structured_input(value): + assert RunRequest(input=value).to_mapping()["input"] == value + + +def test_run_request_defaults_missing_input_to_empty_text(): + assert RunRequest().to_mapping()["input"] == "" + + def test_run_result_wraps_nested_error_and_keeps_mapping_access(): result = RunResult.from_mapping( _run_result( @@ -643,13 +776,6 @@ async def test_run_accepts_full_run_request_on_native_path(): native = NativeRecorder() client = NativeClient(native) - with pytest.raises(FabricConfigError, match="complete request"): - await client.run( - "agent", - request=RunRequest(input="hello"), - context={"turn_id": "turn-4"}, - ) - result = await client.run( "agent", request=RunRequest( @@ -670,15 +796,17 @@ async def test_run_accepts_full_run_request_on_native_path(): } -async def test_typed_source_accepts_granular_request_fields_and_returns_result(): +async def test_typed_source_accepts_run_request_and_returns_result(): native = NativeRecorder() client = NativeClient(native) result = await client.run( _fabric_config(), - input="hello", - request_id="request-1", - context={"job_id": "job-1"}, - overrides={"max_iterations": 1}, + request=RunRequest( + input="hello", + request_id="request-1", + context={"job_id": "job-1"}, + overrides={"max_iterations": 1}, + ), ) assert isinstance(result, RunResult) @@ -692,20 +820,6 @@ async def test_typed_source_accepts_granular_request_fields_and_returns_result() } -async def test_invalid_request_context_raises_config_error(): - native = NativeRecorder() - client = NativeClient(native) - - with pytest.raises(FabricConfigError, match="request context"): - await client.run( - _fabric_config(), - input="hello", - context="not-a-mapping", # type: ignore[arg-type] - ) - - assert native.requests == [] - - async def test_native_runtime_errors_use_typed_exception_and_stop_runtime(): native = NativeRecorder() native.fail_invoke = True @@ -719,27 +833,6 @@ async def test_native_runtime_errors_use_typed_exception_and_stop_runtime(): assert native.stopped == 1 -async def test_start_service_reports_capability_failure_contract(): - client = NativeClient(NativeRecorder()) - - with pytest.raises(FabricCapabilityError) as caught: - await client.start_service("agent", service_id="service-1") - - assert caught.value.stage == "start" - assert caught.value.code == "service_not_supported" - assert caught.value.details == {"service": False, "service_id": "service-1"} - - -async def test_start_service_validates_overrides_before_planning(): - native = NativeRecorder() - client = NativeClient(native) - - with pytest.raises(FabricConfigError, match="service overrides"): - await client.start_service("agent", overrides=[]) # type: ignore[arg-type] - - assert native.path_profile_calls == [] - - def test_public_sdk_exceptions_share_a_common_base(): assert issubclass(FabricConfigError, FabricError) assert issubclass(FabricRuntimeError, FabricError) @@ -748,17 +841,16 @@ def test_public_sdk_exceptions_share_a_common_base(): assert issubclass(FabricNativeUnavailableError, FabricError) -async def test_session_invoke_accepts_run_request_and_turn_fields(): +async def test_runtime_invoke_accepts_run_request(): native = NativeRecorder() - session = Session( + runtime = Runtime( client=NativeClient(native), plan=_plan(), runtime=_runtime(), - overrides={"session": True, "limits": {"session": 1}}, - session_id="session-1", + overrides={"runtime": True, "limits": {"runtime": 1}}, ) - result = await session.invoke( + result = await runtime.invoke( request=RunRequest( input="hello", request_id="request-2", @@ -772,53 +864,31 @@ async def test_session_invoke_accepts_run_request_and_turn_fields(): assert native.requests[0] == { "input": "hello", "request_id": "request-2", - "context": { - "job_id": "job-2", - "session_id": "session-1", - }, + "context": {"job_id": "job-2"}, "overrides": { - "session": True, + "runtime": True, "request": True, - "limits": {"session": 1, "request": 1}, + "limits": {"runtime": 1, "request": 1}, }, } - with pytest.raises(FabricConfigError, match="complete request"): - await session.invoke( - request=RunRequest(input="hello"), - context={"turn_id": "turn-1"}, - ) - - -async def test_session_info_stream_and_capability_errors_are_typed(): - session = Session( +async def test_runtime_handle_is_typed_and_detached(): + runtime = Runtime( client=NativeClient(NativeRecorder()), plan=RunPlan.from_mapping(_plan()), runtime=_runtime(), - session_id="session-1", ) - assert isinstance(session.info, SessionInfo) - assert session.info.profiles == ("typed",) - assert session.info.harness == "hermes" - assert session.info.adapter_id == "test.fabric.shim" - - streamed = [item async for item in session.stream(input="hello")] - assert streamed[0].kind == "invocation_end" - assert isinstance(streamed[-1], RunResult) - - with pytest.raises(FabricCapabilityError, match="cancellation"): - await session.cancel() - assert session.info.status == "active" - - with pytest.raises(FabricCapabilityError, match="updates"): - await session.update(RuntimeUpdate.from_mapping({"overrides": {"x": 1}})) + assert isinstance(runtime.handle, RuntimeHandle) + assert runtime.handle.harness == "hermes" + assert runtime.handle.adapter_id == "test.fabric.shim" + assert runtime.handle is not runtime.handle async def test_run_rejects_multiple_primary_input_sources(): client = NativeClient(NativeRecorder()) - with pytest.raises(FabricConfigError, match="at most one input source"): + with pytest.raises(FabricConfigError, match="mutually exclusive"): await client.run( _fabric_config(), input="hello", @@ -826,14 +896,23 @@ async def test_run_rejects_multiple_primary_input_sources(): ) +async def test_run_rejects_raw_mapping_request(): + client = NativeClient(NativeRecorder()) + + with pytest.raises(FabricConfigError, match="request must be a RunRequest"): + await client.run( + _fabric_config(), + request={"input": "request"}, # type: ignore[arg-type] + ) + + async def test_unified_agent_source_dispatches_fabric_config_to_runtime_path(): native = NativeRecorder() client = NativeClient(native) result = await client.run( _fabric_config(), - input="hello", - request_id="request-5", + request=RunRequest(input="hello", request_id="request-5"), ) assert result.request_id == "request-5" @@ -850,12 +929,13 @@ async def test_lifecycle_methods_reject_raw_mapping_agent_source(): assert native.requests == [] -def test_config_methods_reject_raw_mappings_and_pydantic_like_objects(): +def test_config_methods_accept_real_pydantic_models_and_reject_lookalikes(): class ModelDumpLike: def model_dump(self, *, mode: str, exclude_none: bool) -> dict[str, Any]: return {"metadata": {"name": "demo"}} - client = NativeClient(NativeRecorder()) + native = NativeRecorder() + client = NativeClient(native) with pytest.raises(FabricConfigError, match="FabricConfig.from_mapping"): client.plan({"metadata": {"name": "demo"}}) @@ -863,26 +943,45 @@ def model_dump(self, *, mode: str, exclude_none: bool) -> dict[str, Any]: with pytest.raises(FabricConfigError, match="FabricConfig"): client.plan(ModelDumpLike()) + config = FabricConfig( + metadata={"name": "demo"}, + harness={"adapter_id": "test.fabric.shim"}, + ) + client.plan(config, profiles=[FabricProfileConfig(name="typed")]) + + assert native.config_profile_calls == [[{"schema_version": "fabric.profile/v1alpha1", "name": "typed"}]] -def test_profile_configs_require_explicit_profile_config_conversion(): + +def test_typed_config_profiles_require_profile_models(): client = NativeClient(NativeRecorder()) - with pytest.raises(FabricConfigError, match="FabricProfileConfig values"): + client.plan( + _fabric_config(), + profiles=[FabricProfileConfig(name="typed_relay")], + ) + + with pytest.raises(FabricConfigError, match="FabricProfileConfig"): + client.plan( + _fabric_config(), + profiles=[{"name": "typed_relay"}], # type: ignore[list-item] + ) + + with pytest.raises(FabricConfigError, match="FabricProfileConfig"): client.plan(_fabric_config(), profiles="typed_relay") # type: ignore[arg-type] - with pytest.raises(FabricConfigError, match="FabricProfileConfig.from_mapping"): + with pytest.raises(FabricConfigError, match="FabricProfileConfig"): client.plan( _fabric_config(), - profiles=[{"name": "typed_relay"}], + profiles=["typed_relay"], # type: ignore[list-item] ) def test_path_source_accepts_single_profile_name(): native = NativeRecorder() - NativeClient(native).plan("agent", profiles="hermes_session") + NativeClient(native).plan("agent", profiles="hermes_runtime") - assert native.path_profile_calls == [["hermes_session"]] + assert native.path_profile_calls == [["hermes_runtime"]] def test_path_source_rejects_mapping_profiles_before_native_planning(): @@ -891,7 +990,7 @@ def test_path_source_rejects_mapping_profiles_before_native_planning(): with pytest.raises(FabricConfigError, match="profile names"): NativeClient(native).plan( "agent", - profiles={"name": "hermes_session"}, # type: ignore[arg-type] + profiles={"name": "hermes_runtime"}, # type: ignore[arg-type] ) assert native.path_profile_calls == [] @@ -906,8 +1005,6 @@ def test_fabric_config_constructors_emit_schema_shaped_mappings(): settings={"workspace": "./ws"}, ), runtime=RuntimeConfig( - mode="oneshot", - transport="cli", input_schema="chat", output_schema="message", ), @@ -915,17 +1012,14 @@ def test_fabric_config_constructors_emit_schema_shaped_mappings(): copied = config.to_mapping() copied["harness"]["settings"]["workspace"] = "mutated" - assert config["schema_version"] == "fabric.agent/v1alpha1" - assert config["metadata"] == {"name": "demo"} - assert config["harness"]["adapter_id"] == "test.fabric.shim" - assert config["runtime"]["mode"] == "oneshot" - assert config["harness"]["settings"]["workspace"] == "./ws" + assert config.schema_version == "fabric.agent/v1alpha1" + assert config.metadata.to_mapping() == {"name": "demo"} + assert config.harness.adapter_id == "test.fabric.shim" + assert config.runtime.input_schema == "chat" + assert config.harness.settings["workspace"] == "./ws" - profile = FabricProfileConfig.from_mapping({"name": "typed_relay"}) - assert profile.to_mapping() == { - "schema_version": "fabric.profile/v1alpha1", - "name": "typed_relay", - } + client = NativeClient(NativeRecorder()) + client.plan(config, profiles=[FabricProfileConfig(name="typed_relay")]) def test_resolve_accepts_path_and_fabric_config_sources(): @@ -934,27 +1028,24 @@ def test_resolve_accepts_path_and_fabric_config_sources(): path_config = client.resolve("agent") typed_config = client.resolve(_fabric_config()) - assert path_config["config"]["runtime"]["mode"] == "session" - assert typed_config["config"]["runtime"]["mode"] == "session" + assert path_config["config"]["runtime"]["input_schema"] == "chat" + assert typed_config["config"]["runtime"]["input_schema"] == "chat" -async def test_start_session_alias_returns_session_and_info_includes_session_id(): - session = await NativeClient(NativeRecorder()).start_session( - "agent", - session_id="session-1", - ) +async def test_start_runtime_returns_runtime_with_typed_handle(): + runtime = await NativeClient(NativeRecorder()).start_runtime("agent") - assert session.session_id == "session-1" - assert session.info["session_id"] == "session-1" + assert runtime.runtime_id == "runtime-1" + assert isinstance(runtime.handle, RuntimeHandle) -async def test_session_state_errors_use_sdk_error_hierarchy(): - session = Session( +async def test_runtime_state_errors_use_sdk_error_hierarchy(): + runtime = Runtime( client=NativeClient(NativeRecorder()), plan=_plan(), runtime=_runtime(), ) - await session.stop() + await runtime.stop() - with pytest.raises(FabricStateError, match="cannot invoke a stopped session"): - await session.invoke(input="hello") + with pytest.raises(FabricStateError, match="cannot invoke a stopped runtime"): + await runtime.invoke(input="hello") diff --git a/tests/python/test_sdk_sessions.py b/tests/python/test_sdk_runtimes.py similarity index 67% rename from tests/python/test_sdk_sessions.py rename to tests/python/test_sdk_runtimes.py index bb16a4607..501f95dcb 100644 --- a/tests/python/test_sdk_sessions.py +++ b/tests/python/test_sdk_runtimes.py @@ -1,22 +1,14 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Smoke: the SDK Session boundary over the native RuntimeHandle lifecycle.""" +"""Smoke: the SDK Runtime boundary over the native runtime lifecycle.""" from __future__ import annotations import json from typing import Any -from nemo_fabric import ( - FabricCapabilityError, - FabricClient, - FabricStateError, - RunRequest, - RunResult, - Session, - SessionStatus, -) +from nemo_fabric import Fabric, FabricStateError, RunRequest, RunResult, Runtime, RuntimeStatus def _plan() -> dict[str, Any]: @@ -24,8 +16,6 @@ def _plan() -> dict[str, Any]: "metadata": {"name": "demo"}, "harness": {"adapter_id": "test.fabric.shim"}, "runtime": { - "mode": "session", - "transport": "library", "input_schema": "chat", "output_schema": "message", }, @@ -50,12 +40,10 @@ def _plan() -> dict[str, Any]: } }, "capabilities": { - "session": True, "service": False, "streaming": False, "updates": False, "cancellation": False, - "concurrent_invocations": False, }, } @@ -66,7 +54,6 @@ def _runtime() -> dict[str, Any]: "runtime_binding": "fabric-runtime-binding-test", "agent_name": "demo", "harness": "hermes", - "mode": "session", "adapter_kind": "python", "adapter_id": "test.fabric.shim", "environment": { @@ -86,6 +73,7 @@ def __init__(self) -> None: def invoke_runtime( self, plan_json: str, runtime_json: str, request_json: str ) -> str: + assert json.loads(plan_json)["agent_name"] == "demo" request = json.loads(request_json) self.requests.append(request) turn = len(self.requests) @@ -131,7 +119,7 @@ def stop_runtime(self, plan_json: str, runtime_json: str) -> str: return "[]" -class NativeClient(FabricClient): +class NativeClient(Fabric): def __init__(self, native: MockNative) -> None: super().__init__() self.native = native @@ -140,92 +128,71 @@ def _require_native_module(self, method: str) -> MockNative: return self.native -def _session(native: MockNative) -> Session: - return Session(client=NativeClient(native), plan=_plan(), runtime=_runtime()) +def _runtime_wrapper(native: MockNative) -> Runtime: + return Runtime(client=NativeClient(native), plan=_plan(), runtime=_runtime()) async def stable_runtime_across_turns() -> None: native = MockNative() - session = _session(native) - assert session.status is SessionStatus.ACTIVE - assert session.runtime_id == "runtime-1" - assert session.session_id == "runtime-1" - assert session.info["session_id"] == "runtime-1" - assert not hasattr(session, "id") - - first = await session.invoke( + runtime = _runtime_wrapper(native) + assert runtime.status is RuntimeStatus.ACTIVE + assert runtime.runtime_id == "runtime-1" + assert runtime.handle.runtime_id == "runtime-1" + + first = await runtime.invoke( request=RunRequest( input="My name is Robin.", - request_id="session-request-1", + request_id="runtime-request-1", context={"job_id": "job-1", "turn_id": "turn-1"}, ), ) - await session.invoke(input="What's my name?") + await runtime.invoke(input="What's my name?") assert isinstance(first, RunResult) - assert first.request_id == "session-request-1" - assert [inv["runtime_id"] for inv in session.invocations] == [ - "runtime-1", - "runtime-1", - ] + assert first.request_id == "runtime-request-1" + assert [inv["runtime_id"] for inv in runtime.invocations] == ["runtime-1", "runtime-1"] assert native.requests[0]["context"]["job_id"] == "job-1" assert native.requests[0]["context"]["turn_id"] == "turn-1" - assert native.requests[0]["context"]["session_id"] == "runtime-1" - assert native.requests[1]["context"]["session_id"] == "runtime-1" assert "history" not in native.requests[0]["context"] assert "history" not in native.requests[1]["context"] - assert session.runtime_id == "runtime-1" + assert runtime.runtime_id == "runtime-1" -async def stream_and_lifecycle() -> None: +async def runtime_lifecycle() -> None: native = MockNative() - session = _session(native) - items = [item async for item in session.stream(input="hello")] - assert items[-1].status == "succeeded" - assert items[:-1] and all(event.kind == "log" for event in items[:-1]) - - await session.stop() - await session.stop() - assert session.status is SessionStatus.STOPPED + runtime = _runtime_wrapper(native) + result = await runtime.invoke(input="hello") + assert result.status == "succeeded" + assert result.events and all(event.kind == "log" for event in result.events) + + await runtime.stop() + await runtime.stop() + assert runtime.status is RuntimeStatus.STOPPED assert native.stopped == 1 try: - await session.invoke(input="too late") + await runtime.invoke(input="too late") except FabricStateError: pass else: raise AssertionError("invoke after stop should raise") -async def unsupported_cancel_leaves_session_active() -> None: - native = MockNative() - session = _session(native) - try: - await session.cancel() - except FabricCapabilityError: - pass - else: - raise AssertionError("unsupported cancellation should raise") - assert session.status is SessionStatus.ACTIVE - await session.stop() - - async def failed_result_exposes_structured_error() -> None: native = MockNative() - session = _session(native) - result = await session.invoke(input="fail") + runtime = _runtime_wrapper(native) + result = await runtime.invoke(input="fail") assert isinstance(result, RunResult) assert result.status == "failed" assert result.error.stage == "invoke" assert result.error.code == "adapter_failed" assert result.error.retryable is False - await session.stop() - assert session.status is SessionStatus.STOPPED + await runtime.stop() + assert runtime.status is RuntimeStatus.STOPPED assert native.stopped == 1 -async def test_sdk_sessions(): +async def test_sdk_runtimes(): await stable_runtime_across_turns() - await stream_and_lifecycle() - await unsupported_cancel_leaves_session_active() + await runtime_lifecycle() await failed_result_exposes_structured_error() diff --git a/tests/python/test_typed_config.py b/tests/python/test_typed_config.py index 08de7bcf7..5e2537369 100644 --- a/tests/python/test_typed_config.py +++ b/tests/python/test_typed_config.py @@ -25,7 +25,7 @@ import yaml from nemo_fabric import ( - FabricClient, + Fabric, FabricConfig, FabricProfileConfig, RunRequest, @@ -58,8 +58,6 @@ def _repository_adapter_config() -> FabricConfig: } }, "runtime": { - "mode": "oneshot", - "transport": "library", "input_schema": "chat", "output_schema": "message", "artifacts": "./artifacts", @@ -90,7 +88,7 @@ def _shim_adapter_config() -> FabricConfig: return FabricConfig.from_mapping(config) -async def resolves_and_diagnoses_without_a_directory(client: FabricClient) -> None: +async def resolves_and_diagnoses_without_a_directory(client: Fabric) -> None: """plan / doctor resolve a maintained adapter with no package.""" config = _repository_adapter_config() @@ -116,7 +114,7 @@ async def resolves_and_diagnoses_without_a_directory(client: FabricClient) -> No assert report["status"] in {"pass", "warn", "fail"}, report["status"] -async def runs_without_an_agent_package(client: FabricClient) -> None: +async def runs_without_an_agent_package(client: Fabric) -> None: """run drives a core run with only an adapter dir (no agent.yaml).""" config = _shim_adapter_config() @@ -146,15 +144,15 @@ async def runs_without_an_agent_package(client: FabricClient) -> None: assert result["output"]["received"] == "hello typed" -def sdk_and_cli_profile_stacks_match(client: FabricClient) -> None: +def sdk_and_cli_profile_stacks_match(client: Fabric) -> None: """The same config/profile stack plans identically through CLI and SDK.""" config = FabricConfig.from_mapping(_load_yaml(SHIM_AGENT / "agent.yaml")) profiles = [ - FabricProfileConfig.from_mapping( + FabricProfileConfig.model_validate( _load_yaml(SHIM_AGENT / "profiles" / "env-local.yaml") ), - FabricProfileConfig.from_mapping( + FabricProfileConfig.model_validate( _load_yaml(SHIM_AGENT / "profiles" / "mcp-github.yaml") ), ] @@ -182,10 +180,10 @@ def sdk_and_cli_profile_stacks_match(client: FabricClient) -> None: async def test_typed_config(): - async with FabricClient() as client: - sdk_and_cli_profile_stacks_match(client) - await resolves_and_diagnoses_without_a_directory(client) - await runs_without_an_agent_package(client) + client = Fabric() + sdk_and_cli_profile_stacks_match(client) + await resolves_and_diagnoses_without_a_directory(client) + await runs_without_an_agent_package(client) def _load_yaml(path: Path) -> dict: diff --git a/uv.lock b/uv.lock index d95e472e8..f0566e187 100644 --- a/uv.lock +++ b/uv.lock @@ -1741,6 +1741,16 @@ requires-dist = [ [[package]] name = "nemo-fabric-runtime" source = { directory = "python" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.10,<3" }, + { name = "typing-extensions", specifier = ">=4.12" }, +] [[package]] name = "nemo-relay"