diff --git a/README.md b/README.md index b498cd675..aff63ea55 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Fabric provides: - a versioned typed config contract, with `agent.yaml` as the portable file format; - profile-based config variation for evaluation and ablation runs; -- adapter descriptors for harness-specific launch and control; +- adapter descriptors for harness-specific launch, lifecycle control, and + supported execution strategies; - a Rust core with a CLI and Python bindings; - JSON Schema snapshots for the public config and runtime contract; - normalized run results, artifact manifests, and telemetry references. diff --git a/adapters/claude/README.md b/adapters/claude/README.md index a0d159936..8cc3b9cb8 100644 --- a/adapters/claude/README.md +++ b/adapters/claude/README.md @@ -64,11 +64,18 @@ for other supported installation methods. ## Execution Model -Each `invoke` starts a fresh adapter process. The adapter persists the terminal -Claude session ID under the Fabric artifact root, keyed by `runtime_id`, and -passes it as `ClaudeAgentOptions.resume` on the next invocation. One Fabric -runtime therefore maps to one Claude session even though no adapter process -stays resident. +The compatibility default, `process_per_invocation`, starts a fresh adapter +process for each `invoke`. Set +`harness.settings.runtime_strategy="persistent_local_host"` to keep one +adapter host for the Fabric runtime and process invocations in order. Both +strategies persist the terminal Claude session ID under the Fabric artifact +root, keyed by `runtime_id`, and pass it as `ClaudeAgentOptions.resume` on the +next invocation. One Fabric runtime therefore maps to one Claude session +independently of adapter-process lifetime. + +The adapter does not declare `remote_service`. The Claude Agent SDK still uses +a local Claude Code control process, even when the selected model is remotely +hosted. ## Configuration @@ -87,6 +94,7 @@ Configure portable capabilities through the normalized `FabricConfig` fields: Only Claude-specific controls belong in `harness.settings`: +- `runtime_strategy`: `process_per_invocation` or `persistent_local_host` - `system_prompt`, `allowed_tools`, and `permission_mode` - `max_turns`, `max_budget_usd`, and `timeout_seconds` - `setting_sources` (defaults to `[]` for deterministic isolation) diff --git a/adapters/claude/fabric-adapter.json b/adapters/claude/fabric-adapter.json index ed36b146a..86fd7ee5b 100644 --- a/adapters/claude/fabric-adapter.json +++ b/adapters/claude/fabric-adapter.json @@ -10,6 +10,10 @@ "config": { "accepts": ["models", "tools", "tools.blocked", "mcp", "skills", "telemetry"] }, + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + }, "telemetry": { "providers": { "relay": { diff --git a/adapters/claude/src/nemo_fabric_adapters/claude/adapter.py b/adapters/claude/src/nemo_fabric_adapters/claude/adapter.py index 2a04a5b6f..541b730aa 100644 --- a/adapters/claude/src/nemo_fabric_adapters/claude/adapter.py +++ b/adapters/claude/src/nemo_fabric_adapters/claude/adapter.py @@ -32,6 +32,7 @@ from claude_agent_sdk._errors import MessageParseError from nemo_fabric_adapters.common import relay_gateway from nemo_fabric_adapters.common import relay_hooks +from nemo_fabric_adapters.common import lifecycle from nemo_fabric_adapters.common import utils as common_utils LOGGER = logging.getLogger(__name__) @@ -885,6 +886,9 @@ def run(payload: dict[str, Any]) -> dict[str, Any]: def main() -> None: + if lifecycle.is_lifecycle_host(os.environ): + lifecycle.serve(run) + return try: payload = common_utils.load_payload() except ( diff --git a/adapters/codex/README.md b/adapters/codex/README.md index 4275e9c76..c18977f91 100644 --- a/adapters/codex/README.md +++ b/adapters/codex/README.md @@ -62,12 +62,19 @@ to the Fabric config root. Fabric passes the resolved path through ## Execution Model -Each Fabric invocation starts a fresh SDK client and closes its app-server -transport before returning. The first invocation creates a Codex thread and +The compatibility default, `process_per_invocation`, starts a fresh adapter +process for each Fabric invocation. Set +`harness.settings.runtime_strategy="persistent_local_host"` to keep one +adapter host for the Fabric runtime and process invocations in order. The +current SDK integration creates and closes its app-server client for each turn +under either strategy. The first invocation creates a Codex thread and persists its ID under the Fabric artifact root. Later invocations for the same Fabric runtime resume that exact thread. Codex owns the transcript; Fabric owns runtime-to-thread correlation, timeout, cancellation, and cleanup. +The adapter does not declare `remote_service`. The Codex SDK still uses a local +app-server control process, even when model inference is remotely hosted. + The result includes the SDK's typed terminal response, turn status, token usage, timing, and completed thread items. It does not expose CLI commands, return codes, stdout, or stderr. @@ -83,6 +90,7 @@ Use normalized `FabricConfig` fields for portable configuration: Codex-specific controls belong in `harness.settings`: +- `runtime_strategy`: `process_per_invocation` or `persistent_local_host` - `sandbox`: `read-only`, `workspace-write`, or `danger-full-access` - `approval_mode`: `auto_review` or `deny_all` - `base_instructions` and `developer_instructions` @@ -157,4 +165,3 @@ For Phoenix, native Codex OpenTelemetry targets the OTLP collector at Relay OpenInference provides the semantic chain, LLM, and tool hierarchy with decoded prompt, response, and token attributes. Prefer Relay OpenInference for agent-turn inspection. - diff --git a/adapters/codex/fabric-adapter.json b/adapters/codex/fabric-adapter.json index 9cbc7f147..81b563073 100644 --- a/adapters/codex/fabric-adapter.json +++ b/adapters/codex/fabric-adapter.json @@ -10,6 +10,10 @@ "config": { "accepts": ["models", "telemetry"] }, + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + }, "telemetry": { "providers": { "relay": { diff --git a/adapters/codex/src/nemo_fabric_adapters/codex/adapter.py b/adapters/codex/src/nemo_fabric_adapters/codex/adapter.py index 17959350c..d547cb3e4 100644 --- a/adapters/codex/src/nemo_fabric_adapters/codex/adapter.py +++ b/adapters/codex/src/nemo_fabric_adapters/codex/adapter.py @@ -29,6 +29,7 @@ import nemo_fabric_adapters.common.relay_gateway as relay_gateway import nemo_fabric_adapters.common.relay_hooks as relay_hooks +import nemo_fabric_adapters.common.lifecycle as lifecycle import nemo_fabric_adapters.common.utils as common_utils @@ -911,6 +912,9 @@ def run(payload: dict[str, Any]) -> dict[str, Any]: def main() -> None: + if lifecycle.is_lifecycle_host(os.environ): + lifecycle.serve(run) + return try: payload = common_utils.load_payload() except Exception: diff --git a/adapters/common/src/nemo_fabric_adapters/common/lifecycle.py b/adapters/common/src/nemo_fabric_adapters/common/lifecycle.py new file mode 100644 index 000000000..76b1f3e6b --- /dev/null +++ b/adapters/common/src/nemo_fabric_adapters/common/lifecycle.py @@ -0,0 +1,238 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Versioned lifecycle host for adapters that support persistent runtimes.""" + +from __future__ import annotations + +import json +import os +import sys +import traceback +from collections.abc import Callable +from collections.abc import Iterator +from collections.abc import Mapping +from contextlib import contextmanager +from contextlib import redirect_stdout +from typing import Any +from typing import TextIO + + +CONTRACT_VERSION = "fabric.adapter.lifecycle/v1alpha1" +CONTRACT_ENV = "FABRIC_ADAPTER_LIFECYCLE_CONTRACT" + +AdapterRun = Callable[[dict[str, Any]], dict[str, Any]] + + +def is_lifecycle_host(environ: Mapping[str, str]) -> bool: + """Return whether Fabric requested the versioned lifecycle host protocol.""" + + return CONTRACT_ENV in environ + + +def _error(stage: str, code: str, message: str) -> dict[str, Any]: + return { + "stage": stage, + "code": code, + "message": message, + "retryable": False, + } + + +def _response( + operation: str, + *, + output: Any = None, + error: dict[str, Any] | None = None, +) -> dict[str, Any]: + outcome = ( + {"status": "succeeded", "output": output} + if error is None + else {"status": "failed", "error": error} + ) + return { + "contract_version": CONTRACT_VERSION, + "operation": operation, + "outcome": outcome, + } + + +def _runtime_id(message: dict[str, Any]) -> str | None: + operation = message.get("operation") + payload = message.get("payload") or {} + if operation == "start": + value = (payload.get("runtime") or {}).get("runtime_id") + elif operation == "invoke": + value = (payload.get("runtime_context") or {}).get("runtime_id") + else: + value = payload.get("runtime_id") + return value if isinstance(value, str) and value else None + + +@contextmanager +def _invocation_environment(payload: dict[str, Any]) -> Iterator[None]: + telemetry = (payload.get("runtime_context") or {}).get("telemetry") or {} + overlay = telemetry.get("env") if isinstance(telemetry, dict) else None + if not isinstance(overlay, dict) or any( + not isinstance(key, str) or not isinstance(value, str) + for key, value in overlay.items() + ): + overlay = {} + previous = {key: os.environ.get(key) for key in overlay} + os.environ.update(overlay) + try: + yield + finally: + for key, value in previous.items(): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + + +def _handle_message( + message: dict[str, Any], + *, + run: AdapterRun, + active_runtime_id: str | None, +) -> tuple[dict[str, Any], str | None, bool]: + operation = message.get("operation") + if operation not in {"start", "invoke", "stop"}: + return ( + _response( + "start", + error=_error( + "start", "lifecycle_invalid_operation", "Unknown lifecycle operation" + ), + ), + active_runtime_id, + False, + ) + if message.get("contract_version") != CONTRACT_VERSION: + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_contract_mismatch", + f"Expected lifecycle contract {CONTRACT_VERSION}", + ), + ), + active_runtime_id, + False, + ) + + runtime_id = _runtime_id(message) + if runtime_id is None: + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_invalid_runtime", + "Lifecycle payload is missing a runtime ID", + ), + ), + active_runtime_id, + False, + ) + + if operation == "start": + if active_runtime_id is not None: + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_already_started", + "Lifecycle host already owns a runtime", + ), + ), + active_runtime_id, + False, + ) + return _response(operation), runtime_id, False + + if active_runtime_id != runtime_id: + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_runtime_mismatch", + "Lifecycle payload does not match the active runtime", + ), + ), + active_runtime_id, + False, + ) + if operation == "invoke": + payload = message.get("payload") + if not isinstance(payload, dict): + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_invalid_payload", + "Invoke payload must be a mapping", + ), + ), + active_runtime_id, + False, + ) + # Protocol stdout is reserved for one JSON response per line. Preserve + # incidental adapter/library output as diagnostics instead. + try: + with _invocation_environment(payload), redirect_stdout(sys.stderr): + output = run(payload) + except Exception: + traceback.print_exc(file=sys.stderr) + return ( + _response( + operation, + error=_error( + operation, + "lifecycle_adapter_failure", + "Adapter failed while processing the invocation", + ), + ), + active_runtime_id, + False, + ) + return _response(operation, output=output), active_runtime_id, False + + return _response(operation), None, True + + +def serve( + run: AdapterRun, + *, + input_stream: TextIO = sys.stdin, + output_stream: TextIO = sys.stdout, +) -> None: + """Serve ordered lifecycle requests for exactly one Fabric runtime.""" + + active_runtime_id: str | None = None + for line in input_stream: + try: + message = json.loads(line) + if not isinstance(message, dict): + raise TypeError("lifecycle request must be a mapping") + response, active_runtime_id, should_stop = _handle_message( + message, + run=run, + active_runtime_id=active_runtime_id, + ) + except Exception as error: # Protocol boundary must retain diagnostics. + print(f"Invalid lifecycle request: {error}", file=sys.stderr, flush=True) + response = _response( + "start", + error=_error( + "start", "lifecycle_invalid_request", "Invalid lifecycle request" + ), + ) + should_stop = False + print(json.dumps(response, sort_keys=True), file=output_stream, flush=True) + if should_stop: + break diff --git a/adapters/deepagents/fabric-adapter.json b/adapters/deepagents/fabric-adapter.json index 78a09fe35..bfd5b3fe8 100644 --- a/adapters/deepagents/fabric-adapter.json +++ b/adapters/deepagents/fabric-adapter.json @@ -11,6 +11,9 @@ "config": { "accepts": ["models", "tools", "tools.blocked", "mcp", "skills", "telemetry"] }, + "execution": { + "strategies": ["process_per_invocation"] + }, "telemetry": { "providers": { "relay": { diff --git a/adapters/hermes/fabric-adapter.json b/adapters/hermes/fabric-adapter.json index f28574707..3111f12c3 100644 --- a/adapters/hermes/fabric-adapter.json +++ b/adapters/hermes/fabric-adapter.json @@ -22,6 +22,9 @@ "telemetry" ] }, + "execution": { + "strategies": ["process_per_invocation"] + }, "telemetry": { "providers": { "relay": { diff --git a/crates/fabric-core/src/config.rs b/crates/fabric-core/src/config.rs index 37d83d06c..4dbbe28f1 100644 --- a/crates/fabric-core/src/config.rs +++ b/crates/fabric-core/src/config.rs @@ -17,6 +17,8 @@ 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"; +/// Adapter lifecycle contract version supported by this core. +pub const ADAPTER_LIFECYCLE_CONTRACT_VERSION: &str = "fabric.adapter.lifecycle/v1alpha1"; /// A loaded Fabric document with resolved source path and agent root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] @@ -156,6 +158,9 @@ pub struct AdapterDescriptor { /// Telemetry support declared by this adapter. #[serde(default)] pub telemetry: AdapterTelemetrySupport, + /// Runtime execution strategies implemented by this adapter. + #[serde(default, skip_serializing_if = "AdapterExecutionSupport::is_empty")] + pub execution: AdapterExecutionSupport, /// Runtime lifecycle operations supported by this adapter. #[serde(default)] pub capabilities: RuntimeCapabilities, @@ -394,6 +399,35 @@ pub struct AdapterTelemetryProviderSupport { pub extensions: BTreeMap, } +/// Execution strategies implemented by an adapter. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AdapterExecutionSupport { + /// Version of the external start/invoke/stop contract used by persistent strategies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lifecycle_contract_version: Option, + /// Execution strategies implemented by this adapter. + #[serde(default, skip_serializing_if = "BTreeSet::is_empty")] + pub strategies: BTreeSet, + /// Additive execution-support fields. + #[serde(default, flatten)] + pub extensions: BTreeMap, +} + +impl AdapterExecutionSupport { + fn is_empty(&self) -> bool { + self.lifecycle_contract_version.is_none() + && self.strategies.is_empty() + && self.extensions.is_empty() + } + + fn effective_strategies(&self) -> BTreeSet { + if self.strategies.is_empty() { + return BTreeSet::from([ExecutionStrategy::ProcessPerInvocation]); + } + self.strategies.clone() + } +} + /// Profile config applied on top of a Fabric config. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct ProfileConfig { @@ -500,6 +534,32 @@ pub enum AdapterKind { NativePlugin, } +/// How the selected adapter executes harness work for one Fabric runtime. +#[derive( + Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum ExecutionStrategy { + /// Launch a fresh adapter process for each invocation. + #[default] + ProcessPerInvocation, + /// Start one adapter-owned local host and reuse it for the runtime. + PersistentLocalHost, + /// Allocate or connect to an adapter-owned remote harness service. + RemoteService, +} + +impl ExecutionStrategy { + /// Stable serialized strategy name. + pub fn as_str(self) -> &'static str { + match self { + Self::ProcessPerInvocation => "process_per_invocation", + Self::PersistentLocalHost => "persistent_local_host", + Self::RemoteService => "remote_service", + } + } +} + /// Model configuration. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct ModelConfig { @@ -1315,6 +1375,7 @@ pub fn resolve_run_plan_from_effective_config( .as_ref() .map(|adapter| &adapter.descriptor); let resolution = resolve_resolution(&config, descriptor)?; + let execution_strategy = resolve_execution_strategy(&config, descriptor)?; let environment_plan = resolve_environment_plan(&config, &config_root); validate_control_location(descriptor, environment_plan.as_ref())?; let capability_plan = @@ -1326,6 +1387,7 @@ pub fn resolve_run_plan_from_effective_config( profiles: effective_config.profiles.clone(), adapter_descriptor, resolution, + execution_strategy, environment_plan, capability_plan, capabilities, @@ -1489,6 +1551,28 @@ fn validate_adapter_descriptor_shape(descriptor: &AdapterDescriptor, path: &Path if descriptor.harness.trim().is_empty() { return invalid_adapter_descriptor(path, "harness must not be empty"); } + if let Some(version) = descriptor.execution.lifecycle_contract_version.as_deref() + && version != ADAPTER_LIFECYCLE_CONTRACT_VERSION + { + return Err(FabricError::AdapterDescriptorUnsupported { + adapter_id: descriptor.adapter_id.clone(), + field: "execution.lifecycle_contract_version", + value: version.to_string(), + }); + } + if descriptor.execution.strategies.iter().any(|strategy| { + matches!( + strategy, + ExecutionStrategy::PersistentLocalHost | ExecutionStrategy::RemoteService + ) + }) && descriptor.execution.lifecycle_contract_version.as_deref() + != Some(ADAPTER_LIFECYCLE_CONTRACT_VERSION) + { + return invalid_adapter_descriptor( + path, + "execution.lifecycle_contract_version is required for persistent_local_host and remote_service", + ); + } Ok(()) } @@ -1506,6 +1590,32 @@ fn validate_control_location( Ok(()) } +fn resolve_execution_strategy( + config: &FabricConfig, + adapter_descriptor: Option<&AdapterDescriptor>, +) -> Result { + let requested = match config.harness.settings.get("runtime_strategy") { + Some(value) => serde_json::from_value(value.clone()).map_err(|_| { + FabricError::InvalidRuntimeStrategy { + adapter_id: config.harness.adapter_id.clone(), + value: value.to_string(), + } + })?, + None => ExecutionStrategy::ProcessPerInvocation, + }; + let supported = adapter_descriptor + .map(|descriptor| descriptor.execution.effective_strategies()) + .unwrap_or_else(|| BTreeSet::from([ExecutionStrategy::ProcessPerInvocation])); + if supported.contains(&requested) { + return Ok(requested); + } + Err(FabricError::UnsupportedExecutionStrategy { + adapter_id: config.harness.adapter_id.clone(), + requested, + supported: supported.into_iter().collect(), + }) +} + fn resolve_runtime_capabilities( _config: &FabricConfig, descriptor: Option<&AdapterDescriptor>, @@ -1847,6 +1957,8 @@ pub struct RunPlan { /// Selected install or availability strategy. #[serde(default, skip_serializing_if = "Option::is_none")] pub resolution: Option, + /// Adapter execution strategy selected during planning. + pub execution_strategy: ExecutionStrategy, /// Resolved environment plan. #[serde(default, skip_serializing_if = "Option::is_none")] pub environment_plan: Option, @@ -3013,4 +3125,180 @@ environment: let _ = std::fs::remove_dir_all(root); } + + #[test] + fn legacy_adapter_defaults_to_process_per_invocation() { + let plan = resolve_run_plan(file_config_agent_dir(), None).expect("run plan"); + + assert_eq!( + plan.execution_strategy, + ExecutionStrategy::ProcessPerInvocation + ); + } + + #[test] + fn planning_selects_supported_persistent_local_host() { + let root = execution_strategy_agent_dir( + "persistent_local_host", + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.lifecycle", + "harness": "lifecycle", + "adapter_kind": "python", + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + } +}"#, + ); + + let plan = resolve_run_plan(&root, None).expect("run plan"); + + assert_eq!( + plan.execution_strategy, + ExecutionStrategy::PersistentLocalHost + ); + let _ = std::fs::remove_dir_all(root); + } + + #[test] + fn planning_rejects_unsupported_execution_strategy() { + let root = execution_strategy_agent_dir( + "persistent_local_host", + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.lifecycle", + "harness": "lifecycle", + "adapter_kind": "python", + "execution": { + "strategies": ["process_per_invocation"] + } +}"#, + ); + + let error = resolve_run_plan(&root, None).expect_err("unsupported strategy"); + + assert!(matches!( + error, + FabricError::UnsupportedExecutionStrategy { + adapter_id, + requested: ExecutionStrategy::PersistentLocalHost, + supported, + } if adapter_id == "acme.fabric.lifecycle" + && supported == vec![ExecutionStrategy::ProcessPerInvocation] + )); + let _ = std::fs::remove_dir_all(root); + } + + #[test] + fn planning_rejects_unsupported_remote_service_strategy() { + let root = execution_strategy_agent_dir( + "remote_service", + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.lifecycle", + "harness": "lifecycle", + "adapter_kind": "python", + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + } +}"#, + ); + + let error = resolve_run_plan(&root, None).expect_err("unsupported strategy"); + + assert!(matches!( + error, + FabricError::UnsupportedExecutionStrategy { + adapter_id, + requested: ExecutionStrategy::RemoteService, + supported, + } if adapter_id == "acme.fabric.lifecycle" + && supported == vec![ + ExecutionStrategy::ProcessPerInvocation, + ExecutionStrategy::PersistentLocalHost, + ] + )); + let _ = std::fs::remove_dir_all(root); + } + + #[test] + fn planning_rejects_unknown_runtime_strategy_value() { + let root = execution_strategy_agent_dir( + "future_strategy", + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.lifecycle", + "harness": "lifecycle", + "adapter_kind": "python" +}"#, + ); + + let error = resolve_run_plan(&root, None).expect_err("invalid strategy"); + + assert!(matches!( + error, + FabricError::InvalidRuntimeStrategy { adapter_id, value } + if adapter_id == "acme.fabric.lifecycle" && value == "\"future_strategy\"" + )); + let _ = std::fs::remove_dir_all(root); + } + + #[test] + fn descriptor_rejects_persistent_host_without_lifecycle_contract() { + let root = execution_strategy_agent_dir( + "process_per_invocation", + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.lifecycle", + "harness": "lifecycle", + "adapter_kind": "python", + "execution": { + "strategies": ["persistent_local_host"] + } +}"#, + ); + + let error = resolve_run_plan(&root, None).expect_err("invalid descriptor"); + + assert!(matches!( + error, + FabricError::InvalidAdapterDescriptor { message, .. } + if message.contains("lifecycle_contract_version") + )); + let _ = std::fs::remove_dir_all(root); + } + + fn execution_strategy_agent_dir(execution_strategy: &str, descriptor: &str) -> PathBuf { + static NEXT_FIXTURE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + let fixture_id = NEXT_FIXTURE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let root = std::env::temp_dir().join(format!( + "fabric-execution-strategy-test-{}-{fixture_id}-{execution_strategy}", + std::process::id(), + )); + let _ = std::fs::remove_dir_all(&root); + std::fs::create_dir_all(root.join("adapters/lifecycle")).expect("create adapter directory"); + std::fs::write( + root.join("agent.yaml"), + format!( + r#"schema_version: fabric.agent/v1alpha1 +metadata: + name: lifecycle-agent +harness: + adapter_id: acme.fabric.lifecycle + settings: + runtime_strategy: {execution_strategy} +runtime: +"# + ), + ) + .expect("write agent config"); + std::fs::write( + root.join("adapters/lifecycle/fabric-adapter.json"), + descriptor, + ) + .expect("write adapter descriptor"); + root + } } diff --git a/crates/fabric-core/src/doctor.rs b/crates/fabric-core/src/doctor.rs index c4a397677..eea11b8a5 100644 --- a/crates/fabric-core/src/doctor.rs +++ b/crates/fabric-core/src/doctor.rs @@ -59,6 +59,7 @@ pub fn doctor_plan(plan: &RunPlan) -> DoctorReport { let mut checks = Vec::new(); checks.push(check_adapter_descriptor(plan)); checks.push(check_resolution(plan)); + checks.push(check_execution_strategy(plan)); checks.extend(check_runtime_execution_surface(plan)); checks.push(check_environment_context(plan)); checks.extend(check_capability_routes(plan)); @@ -74,6 +75,35 @@ pub fn doctor_plan(plan: &RunPlan) -> DoctorReport { } } +fn check_execution_strategy(plan: &RunPlan) -> DoctorCheck { + let mut metadata = BTreeMap::new(); + metadata.insert( + "strategy".to_string(), + Value::String(plan.execution_strategy.as_str().to_string()), + ); + if let Some(version) = plan.adapter_descriptor.as_ref().and_then(|adapter| { + adapter + .descriptor + .execution + .lifecycle_contract_version + .as_ref() + }) { + metadata.insert( + "lifecycle_contract_version".to_string(), + Value::String(version.clone()), + ); + } + check_with_metadata( + "execution_strategy", + DoctorStatus::Pass, + format!( + "selected execution strategy `{}`", + plan.execution_strategy.as_str() + ), + metadata, + ) +} + fn check_adapter_descriptor(plan: &RunPlan) -> DoctorCheck { if let Some(adapter) = &plan.adapter_descriptor { let mut metadata = BTreeMap::new(); diff --git a/crates/fabric-core/src/error.rs b/crates/fabric-core/src/error.rs index dbed6db8f..b20ed18c0 100644 --- a/crates/fabric-core/src/error.rs +++ b/crates/fabric-core/src/error.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; -use crate::config::AdapterKind; +use crate::config::{AdapterKind, ExecutionStrategy}; /// Core Fabric result type. pub type Result = std::result::Result; @@ -72,6 +72,42 @@ pub enum FabricError { /// Unsupported value. value: String, }, + /// The selected adapter does not implement the requested execution strategy. + #[error( + "adapter `{adapter_id}` does not support execution strategy `{}`; supported strategies: {supported:?}", + requested.as_str() + )] + UnsupportedExecutionStrategy { + /// Adapter id selected by the config. + adapter_id: String, + /// Requested execution strategy. + requested: ExecutionStrategy, + /// Strategies implemented by the selected adapter. + supported: Vec, + }, + /// The runtime strategy setting is not part of the shared strategy vocabulary. + #[error( + "invalid harness.settings.runtime_strategy for adapter `{adapter_id}`: expected `process_per_invocation`, `persistent_local_host`, or `remote_service`, found {value}" + )] + InvalidRuntimeStrategy { + /// Adapter id selected by the config. + adapter_id: String, + /// Invalid JSON value supplied by the consumer. + value: String, + }, + /// The selected adapter declares a strategy whose runtime transport is unavailable. + #[error( + "runtime strategy `{}` is not executable for adapter `{adapter_id}`: {reason}", + strategy.as_str() + )] + RuntimeStrategyUnavailable { + /// Adapter id selected by the config. + adapter_id: String, + /// Strategy selected during planning. + strategy: ExecutionStrategy, + /// Missing adapter/runtime contract. + reason: &'static str, + }, /// An adapter descriptor is malformed. #[error("invalid adapter descriptor in {path}: {message}")] InvalidAdapterDescriptor { @@ -109,6 +145,27 @@ pub enum FabricError { /// Adapter kind. adapter_kind: AdapterKind, }, + /// A versioned persistent-host lifecycle operation failed. + #[error( + "adapter lifecycle {operation} failed for runtime `{runtime_id}` ({code}): {message}{diagnostics_suffix}", + diagnostics_suffix = if diagnostics.is_empty() { + String::new() + } else { + format!("; diagnostics: {diagnostics}") + } + )] + AdapterLifecycleOperation { + /// Lifecycle operation that failed. + operation: &'static str, + /// Runtime whose host failed. + runtime_id: String, + /// Stable failure code. + code: String, + /// Human-readable failure message. + message: String, + /// Bounded adapter-host diagnostics. + diagnostics: String, + }, /// The selected harness cannot enforce the configured blocked-tools policy. #[error("harness `{harness}` cannot enforce configured blocked tools: {reason}")] UnsupportedToolsPolicy { diff --git a/crates/fabric-core/src/lib.rs b/crates/fabric-core/src/lib.rs index 80e78f511..1037fc2bb 100644 --- a/crates/fabric-core/src/lib.rs +++ b/crates/fabric-core/src/lib.rs @@ -10,10 +10,11 @@ pub mod runtime; pub mod schema; pub use config::{ - ADAPTER_CONTRACT_VERSION, AdapterConfigSupport, AdapterDescriptor, AdapterDescriptorSource, - AdapterKind, AdapterRequirements, AdapterTelemetryProviderSupport, AdapterTelemetrySupport, - CapabilityPlan, ControlLocation, EffectiveConfig, EnvironmentConfig, EnvironmentOwnership, - EnvironmentPlan, FabricConfig, FabricDocument, HarnessConfig, McpConfig, McpExposure, + ADAPTER_CONTRACT_VERSION, ADAPTER_LIFECYCLE_CONTRACT_VERSION, AdapterConfigSupport, + AdapterDescriptor, AdapterDescriptorSource, AdapterExecutionSupport, AdapterKind, + AdapterRequirements, AdapterTelemetryProviderSupport, AdapterTelemetrySupport, CapabilityPlan, + ControlLocation, EffectiveConfig, EnvironmentConfig, EnvironmentOwnership, EnvironmentPlan, + ExecutionStrategy, FabricConfig, FabricDocument, HarnessConfig, McpConfig, McpExposure, McpServerPlan, MetadataConfig, ModelConfig, ProfileConfig, ResolutionStrategy, ResolveContext, ResolvedAdapterDescriptor, RunPlan, RuntimeCapabilities, RuntimeConfig, SkillConfig, TelemetryConfig, TelemetryPlan, TelemetryProvider, TelemetryProviderConfig, @@ -25,7 +26,9 @@ pub use config::{ pub use doctor::{DoctorCheck, DoctorReport, DoctorStatus, doctor_plan}; pub use error::{FabricError, Result}; pub use runtime::{ - AdapterInvocation, ArtifactManifest, ArtifactRef, EnvironmentHandle, ErrorInfo, ErrorStage, + AdapterInvocation, AdapterLifecycleOperation, AdapterLifecycleOutcome, AdapterLifecycleRequest, + AdapterLifecycleRequestKind, AdapterLifecycleResponse, AdapterLifecycleStart, + AdapterLifecycleStop, ArtifactManifest, ArtifactRef, EnvironmentHandle, ErrorInfo, ErrorStage, FabricEvent, InvocationHandle, RunRequest, RunResult, RunStatus, RuntimeContext, RuntimeHandle, RuntimeTelemetryContext, TelemetryRef, invoke_runtime, prepare_environment, run_plan, start_runtime, stop_runtime, diff --git a/crates/fabric-core/src/runtime.rs b/crates/fabric-core/src/runtime.rs index 4443c75e7..e06b8c406 100644 --- a/crates/fabric-core/src/runtime.rs +++ b/crates/fabric-core/src/runtime.rs @@ -5,27 +5,33 @@ use std::collections::BTreeMap; use std::ffi::OsString; -use std::io::{ErrorKind, Write}; +use std::fs::File; +use std::io::{BufRead, BufReader, ErrorKind, Write}; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -#[cfg(test)] -use std::sync::Mutex; +use std::process::{Child, ChildStdin, Command, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; +use std::sync::{Arc, LazyLock, Mutex}; +use std::thread; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use crate::config::{ - AdapterKind, CapabilityKind, CapabilityPlan, CapabilityTarget, ControlLocation, - EffectiveConfig, EnvironmentOwnership, RunPlan, TelemetryPlan, + ADAPTER_LIFECYCLE_CONTRACT_VERSION, AdapterKind, CapabilityKind, CapabilityPlan, + CapabilityTarget, ControlLocation, EffectiveConfig, EnvironmentOwnership, ExecutionStrategy, + RunPlan, RuntimeCapabilities, TelemetryPlan, }; use crate::error::{FabricError, Result}; static NEXT_ID: AtomicU64 = AtomicU64::new(1); const ADAPTER_PYTHON_ENV: &str = "ADAPTER_PYTHON"; const VIRTUAL_ENV_ENV: &str = "VIRTUAL_ENV"; +const PERSISTENT_HOST_START_TIMEOUT: Duration = Duration::from_secs(10); +const PERSISTENT_HOST_STOP_TIMEOUT: Duration = Duration::from_secs(5); +const PERSISTENT_HOST_DIAGNOSTIC_LIMIT: usize = 16 * 1024; #[cfg(not(windows))] const VENV_BIN_DIR: &str = "bin"; @@ -45,6 +51,8 @@ const DEFAULT_PYTHON: &str = "python3"; const DEFAULT_PYTHON: &str = "python.exe"; #[cfg(test)] static TEST_STOPPED_AGENTS: Mutex> = Mutex::new(Vec::new()); +static PERSISTENT_HOSTS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); /// A request passed to a Fabric-managed harness runtime. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default)] @@ -259,6 +267,8 @@ pub struct RuntimeHandle { /// Adapter implementation id. #[serde(default, skip_serializing_if = "Option::is_none")] pub adapter_id: Option, + /// Execution strategy selected for this runtime. + pub execution_strategy: ExecutionStrategy, /// Prepared environment. pub environment: EnvironmentHandle, } @@ -313,6 +323,8 @@ pub struct RuntimeTelemetryContext { pub struct AdapterInvocation { /// Merged agent config and provenance. pub effective_config: EffectiveConfig, + /// Execution strategy selected during planning. + pub execution_strategy: ExecutionStrategy, /// Per-runtime/per-invocation execution context. pub runtime_context: RuntimeContext, /// Per-invocation request. @@ -325,6 +337,135 @@ pub struct AdapterInvocation { pub telemetry_plan: Option, } +/// Operation exchanged over the versioned persistent-host adapter protocol. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AdapterLifecycleOperation { + /// Initialize one adapter-owned host for a Fabric runtime. + Start, + /// Execute one invocation against an initialized host. + Invoke, + /// Release the host and all runtime-owned resources. + Stop, +} + +impl AdapterLifecycleOperation { + /// Stable serialized operation name. + pub fn as_str(self) -> &'static str { + match self { + Self::Start => "start", + Self::Invoke => "invoke", + Self::Stop => "stop", + } + } + + fn error_stage(self) -> ErrorStage { + match self { + Self::Start => ErrorStage::Start, + Self::Invoke => ErrorStage::Invoke, + Self::Stop => ErrorStage::Stop, + } + } +} + +/// Start payload sent once when Fabric creates a persistent adapter host. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AdapterLifecycleStart { + /// Runtime identity and prepared environment owned by this host. + pub runtime: RuntimeHandle, + /// Merged agent config and provenance for this runtime. + pub effective_config: EffectiveConfig, + /// Capability routing selected during planning. + #[serde(default)] + pub capability_plan: CapabilityPlan, + /// Lifecycle capabilities selected during planning. + #[serde(default)] + pub capabilities: RuntimeCapabilities, + /// Telemetry routing selected during planning. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub telemetry_plan: Option, +} + +/// Stop payload sent once when Fabric releases a persistent adapter host. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AdapterLifecycleStop { + /// Runtime being stopped. + pub runtime_id: String, +} + +/// Typed operation payload carried by an adapter lifecycle request. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "operation", content = "payload", rename_all = "snake_case")] +pub enum AdapterLifecycleRequestKind { + /// Initialize the host. + Start(AdapterLifecycleStart), + /// Execute one invocation. + Invoke(AdapterInvocation), + /// Stop the host. + Stop(AdapterLifecycleStop), +} + +impl AdapterLifecycleRequestKind { + fn operation(&self) -> AdapterLifecycleOperation { + match self { + Self::Start(_) => AdapterLifecycleOperation::Start, + Self::Invoke(_) => AdapterLifecycleOperation::Invoke, + Self::Stop(_) => AdapterLifecycleOperation::Stop, + } + } +} + +/// One newline-delimited request sent to a persistent adapter host. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AdapterLifecycleRequest { + /// Lifecycle protocol version. + pub contract_version: String, + /// Typed lifecycle operation and payload. + #[serde(flatten)] + pub request: AdapterLifecycleRequestKind, +} + +impl AdapterLifecycleRequest { + fn new(request: AdapterLifecycleRequestKind) -> Self { + Self { + contract_version: ADAPTER_LIFECYCLE_CONTRACT_VERSION.to_string(), + request, + } + } + + fn operation(&self) -> AdapterLifecycleOperation { + self.request.operation() + } +} + +/// Outcome returned by a persistent adapter host lifecycle operation. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "status", rename_all = "snake_case")] +pub enum AdapterLifecycleOutcome { + /// The operation completed successfully. + Succeeded { + /// Operation output. Only invoke normally returns a non-null value. + #[serde(default)] + output: Value, + }, + /// The operation failed with normalized lifecycle diagnostics. + Failed { + /// Structured failure reported by the adapter host. + error: ErrorInfo, + }, +} + +/// One newline-delimited response returned by a persistent adapter host. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct AdapterLifecycleResponse { + /// Lifecycle protocol version. + pub contract_version: String, + /// Operation completed by this response. + pub operation: AdapterLifecycleOperation, + /// Normalized success or failure outcome. + pub outcome: AdapterLifecycleOutcome, +} + trait RuntimeAdapter { fn start(&self, plan: &RunPlan, environment: EnvironmentHandle) -> Result; fn invoke( @@ -338,6 +479,7 @@ trait RuntimeAdapter { struct ProcessAdapter; struct PythonAdapter; +struct PersistentHostAdapter; #[derive(Debug, Clone)] struct RelayRuntimeConfig { @@ -345,6 +487,15 @@ struct RelayRuntimeConfig { env: BTreeMap, } +struct PersistentHost { + child: Child, + stdin: ChildStdin, + responses: Receiver>, + command: String, + runtime_dir: PathBuf, + stderr_path: PathBuf, +} + /// Invoke a Fabric run plan. pub fn run_plan(plan: &RunPlan, request: RunRequest) -> Result { let runtime = start_runtime(plan)?; @@ -428,6 +579,13 @@ pub fn prepare_environment(plan: &RunPlan) -> Result { pub fn start_runtime(plan: &RunPlan) -> Result { validate_blocked_tools_support(plan)?; let environment = prepare_environment(plan)?; + match plan.execution_strategy { + ExecutionStrategy::PersistentLocalHost => { + return PersistentHostAdapter.start(plan, environment); + } + ExecutionStrategy::RemoteService => return remote_service_unavailable(plan), + ExecutionStrategy::ProcessPerInvocation => {} + } match adapter_kind(plan) { AdapterKind::Process => ProcessAdapter.start(plan, environment), AdapterKind::Python => PythonAdapter.start(plan, environment), @@ -446,6 +604,13 @@ pub fn invoke_runtime( ) -> Result { validate_blocked_tools_support(plan)?; validate_runtime_handle(plan, runtime)?; + match plan.execution_strategy { + ExecutionStrategy::PersistentLocalHost => { + return PersistentHostAdapter.invoke(plan, runtime, request); + } + ExecutionStrategy::RemoteService => return remote_service_unavailable(plan), + ExecutionStrategy::ProcessPerInvocation => {} + } match adapter_kind(plan) { AdapterKind::Process => ProcessAdapter.invoke(plan, runtime, request), AdapterKind::Python => PythonAdapter.invoke(plan, runtime, request), @@ -471,6 +636,11 @@ fn validate_blocked_tools_support(plan: &RunPlan) -> Result<()> { /// Stop or detach from a harness runtime. pub fn stop_runtime(plan: &RunPlan, runtime: &RuntimeHandle) -> Result> { validate_runtime_handle(plan, runtime)?; + match plan.execution_strategy { + ExecutionStrategy::PersistentLocalHost => return PersistentHostAdapter.stop(runtime), + ExecutionStrategy::RemoteService => return remote_service_unavailable(plan), + ExecutionStrategy::ProcessPerInvocation => {} + } match runtime.adapter_kind { AdapterKind::Process => ProcessAdapter.stop(runtime), AdapterKind::Python => PythonAdapter.stop(runtime), @@ -481,6 +651,14 @@ pub fn stop_runtime(plan: &RunPlan, runtime: &RuntimeHandle) -> Result(plan: &RunPlan) -> Result { + Err(FabricError::RuntimeStrategyUnavailable { + adapter_id: adapter_id(plan).unwrap_or_else(|| plan.config.harness.adapter_id.clone()), + strategy: ExecutionStrategy::RemoteService, + reason: "the adapter does not provide a remote lifecycle transport", + }) +} + fn validate_runtime_handle(plan: &RunPlan, runtime: &RuntimeHandle) -> Result<()> { let expected_binding = runtime_binding(&runtime.runtime_id, plan, &runtime.environment)?; expect_runtime_field( @@ -503,6 +681,12 @@ fn validate_runtime_handle(plan: &RunPlan, runtime: &RuntimeHandle) -> Result<() &optional_runtime_value(adapter_id(plan).as_deref()), &optional_runtime_value(runtime.adapter_id.as_deref()), )?; + expect_runtime_field( + runtime, + "execution_strategy", + plan.execution_strategy.as_str(), + runtime.execution_strategy.as_str(), + )?; Ok(()) } @@ -676,6 +860,7 @@ impl RuntimeAdapter for ProcessAdapter { harness: harness(plan), adapter_kind: adapter_kind(plan), adapter_id: adapter_id(plan), + execution_strategy: plan.execution_strategy, environment, }) } @@ -724,6 +909,7 @@ impl RuntimeAdapter for PythonAdapter { harness: harness(plan), adapter_kind: adapter_kind(plan), adapter_id: adapter_id(plan), + execution_strategy: plan.execution_strategy, environment, }) } @@ -754,6 +940,676 @@ impl RuntimeAdapter for PythonAdapter { } } +impl RuntimeAdapter for PersistentHostAdapter { + fn start(&self, plan: &RunPlan, environment: EnvironmentHandle) -> Result { + if environment.provider != "local" { + return Err(FabricError::UnsupportedEnvironmentProvider { + provider: environment.provider, + adapter_kind: adapter_kind(plan), + }); + } + match adapter_kind(plan) { + AdapterKind::Python => preflight_python_adapter(plan)?, + AdapterKind::Process => {} + adapter_kind => { + return Err(FabricError::UnsupportedRuntimeAdapter { + harness: harness(plan), + adapter_kind, + }); + } + } + + let runtime_id = new_id("runtime"); + let runtime_binding = runtime_binding(&runtime_id, plan, &environment)?; + let runtime = RuntimeHandle { + runtime_id, + runtime_binding, + agent_name: plan.agent_name.clone(), + harness: harness(plan), + adapter_kind: adapter_kind(plan), + adapter_id: adapter_id(plan), + execution_strategy: plan.execution_strategy, + environment, + }; + let mut host = spawn_persistent_host(plan, &runtime)?; + let request = AdapterLifecycleRequest::new(AdapterLifecycleRequestKind::Start( + AdapterLifecycleStart { + runtime: runtime.clone(), + effective_config: adapter_effective_config(plan)?, + capability_plan: plan.capability_plan.clone(), + capabilities: plan.capabilities.clone(), + telemetry_plan: plan.telemetry_plan.clone(), + }, + )); + if let Err(error) = exchange_lifecycle_message( + &mut host, + &runtime.runtime_id, + &request, + Some(PERSISTENT_HOST_START_TIMEOUT), + ) { + terminate_persistent_host(&mut host); + remove_persistent_host_files(&host); + return Err(error); + } + + persistent_hosts().insert(runtime.runtime_id.clone(), Arc::new(Mutex::new(host))); + Ok(runtime) + } + + fn invoke( + &self, + plan: &RunPlan, + runtime: &RuntimeHandle, + request: RunRequest, + ) -> Result { + run_persistent_host_adapter(plan, runtime, request) + } + + fn stop(&self, runtime: &RuntimeHandle) -> Result> { + let Some(host) = persistent_hosts().remove(&runtime.runtime_id) else { + return Ok(vec![persistent_host_stop_event(runtime, true)]); + }; + let mut host = host.lock().unwrap_or_else(|error| error.into_inner()); + let request = + AdapterLifecycleRequest::new(AdapterLifecycleRequestKind::Stop(AdapterLifecycleStop { + runtime_id: runtime.runtime_id.clone(), + })); + let result = exchange_lifecycle_message( + &mut host, + &runtime.runtime_id, + &request, + Some(PERSISTENT_HOST_STOP_TIMEOUT), + ); + terminate_persistent_host(&mut host); + remove_persistent_host_files(&host); + result?; + + #[cfg(test)] + TEST_STOPPED_AGENTS + .lock() + .expect("stop tracker") + .push(runtime.agent_name.clone()); + Ok(vec![persistent_host_stop_event(runtime, false)]) + } +} + +fn run_persistent_host_adapter( + plan: &RunPlan, + runtime: &RuntimeHandle, + mut request: RunRequest, +) -> Result { + if request.request_id.is_empty() { + request.request_id = new_id("request"); + } + let invocation = InvocationHandle { + invocation_id: new_id("invocation"), + request_id: request.request_id.clone(), + runtime_id: runtime.runtime_id.clone(), + }; + let mut artifacts = artifact_manifest(plan)?; + 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_invocation = adapter_invocation( + plan, + runtime, + &invocation, + &request, + &artifacts, + relay_config.as_ref(), + )?; + let adapter_payload = + serde_json::to_string_pretty(&adapter_invocation).map_err(FabricError::SerializeJson)?; + let fabric_invocation = write_fabric_invocation(&fabric_home, &adapter_payload)?; + let lifecycle_request = + AdapterLifecycleRequest::new(AdapterLifecycleRequestKind::Invoke(adapter_invocation)); + + let host = persistent_hosts() + .get(&runtime.runtime_id) + .cloned() + .ok_or_else(|| { + lifecycle_error( + AdapterLifecycleOperation::Invoke, + &runtime.runtime_id, + "host_unavailable", + "persistent adapter host is not active", + "", + ) + })?; + let (output, host_command) = { + let mut host = host.lock().unwrap_or_else(|error| error.into_inner()); + let output = + exchange_lifecycle_message(&mut host, &runtime.runtime_id, &lifecycle_request, None)?; + (output, host.command.clone()) + }; + + let mut events = vec![event_with_metadata( + "runtime_start", + format!("started runtime {}", runtime.runtime_id), + BTreeMap::from([ + ( + "runtime_id".to_string(), + Value::String(runtime.runtime_id.clone()), + ), + ( + "environment_id".to_string(), + Value::String(runtime.environment.environment_id.clone()), + ), + ( + "environment_provider".to_string(), + Value::String(runtime.environment.provider.clone()), + ), + ( + "execution_strategy".to_string(), + Value::String(runtime.execution_strategy.as_str().to_string()), + ), + ]), + )]; + events.push(event_with_metadata( + "invocation_start", + format!("invoking persistent adapter host for {}", harness(plan)), + BTreeMap::from([ + ( + "runtime_id".to_string(), + Value::String(runtime.runtime_id.clone()), + ), + ( + "invocation_id".to_string(), + Value::String(invocation.invocation_id.clone()), + ), + ]), + )); + let (status, error) = adapter_output_status(&output); + events.push(event_with_metadata( + "invocation_end", + format!("persistent adapter host completed with status {status:?}"), + BTreeMap::from([ + ( + "runtime_id".to_string(), + Value::String(runtime.runtime_id.clone()), + ), + ( + "invocation_id".to_string(), + Value::String(invocation.invocation_id.clone()), + ), + ]), + )); + collect_workspace_artifacts(&mut artifacts, &fabric_home, runtime, &mut events)?; + promote_relay_artifacts_to_manifest(&output, &mut artifacts); + + let metadata = BTreeMap::from([ + ( + "adapter_runner".to_string(), + Value::String("persistent_local_host".to_string()), + ), + ("host_command".to_string(), Value::String(host_command)), + ( + "fabric_home".to_string(), + Value::String(fabric_home.to_string_lossy().into_owned()), + ), + ( + "fabric_invocation".to_string(), + Value::String(fabric_invocation.to_string_lossy().into_owned()), + ), + ( + "environment_provider".to_string(), + Value::String(runtime.environment.provider.clone()), + ), + ]); + Ok(RunResult { + agent_name: plan.agent_name.clone(), + profiles: plan.profiles.clone(), + harness: harness(plan), + adapter_kind: adapter_kind(plan), + adapter_id: adapter_id(plan), + runtime_id: invocation.runtime_id, + invocation_id: invocation.invocation_id, + request_id: request.request_id, + status, + output, + error, + artifacts, + telemetry: telemetry_ref(plan, relay_config.as_ref()), + events, + metadata, + }) +} + +fn adapter_output_status(output: &Value) -> (RunStatus, Option) { + let failed = output + .as_object() + .and_then(|output| output.get("failed")) + .and_then(Value::as_bool) + .unwrap_or(false); + if !failed { + return (RunStatus::Succeeded, None); + } + + let reported = output + .as_object() + .and_then(|output| output.get("error")) + .and_then(Value::as_object); + let code = reported + .and_then(|error| error.get("code")) + .and_then(Value::as_str) + .unwrap_or("adapter_reported_failure") + .to_string(); + let message = reported + .and_then(|error| error.get("message")) + .and_then(Value::as_str) + .unwrap_or("adapter reported an invocation failure") + .to_string(); + let retryable = reported + .and_then(|error| error.get("retryable")) + .and_then(Value::as_bool) + .unwrap_or(false); + let metadata = reported + .and_then(|error| error.get("metadata")) + .and_then(Value::as_object) + .map(|metadata| { + metadata + .iter() + .map(|(key, value)| (key.clone(), value.clone())) + .collect() + }) + .unwrap_or_default(); + ( + RunStatus::Failed, + Some(ErrorInfo { + stage: ErrorStage::Invoke, + code, + message, + retryable, + metadata, + }), + ) +} + +fn persistent_host_stop_event(runtime: &RuntimeHandle, already_stopped: bool) -> FabricEvent { + event_with_metadata( + "runtime_stop", + format!("stopped runtime {}", runtime.runtime_id), + BTreeMap::from([ + ( + "runtime_id".to_string(), + Value::String(runtime.runtime_id.clone()), + ), + ("already_stopped".to_string(), Value::Bool(already_stopped)), + ]), + ) +} + +fn persistent_hosts() -> std::sync::MutexGuard<'static, BTreeMap>>> +{ + PERSISTENT_HOSTS + .lock() + .unwrap_or_else(|error| error.into_inner()) +} + +fn spawn_persistent_host(plan: &RunPlan, runtime: &RuntimeHandle) -> Result { + let runtime_dir = std::env::temp_dir() + .join("nemo-fabric") + .join(&runtime.runtime_id); + std::fs::create_dir_all(&runtime_dir).map_err(|source| FabricError::Write { + path: runtime_dir.clone(), + source, + })?; + let stderr_path = runtime_dir.join("host.stderr.log"); + let stderr = File::create(&stderr_path).map_err(|source| FabricError::Write { + path: stderr_path.clone(), + source, + })?; + let (mut command, command_display) = match persistent_host_command(plan, runtime) { + Ok(command) => command, + Err(error) => { + let _ = std::fs::remove_dir_all(&runtime_dir); + return Err(error); + } + }; + command + .env( + "FABRIC_ADAPTER_LIFECYCLE_CONTRACT", + ADAPTER_LIFECYCLE_CONTRACT_VERSION, + ) + .env("FABRIC_RUNTIME_ID", &runtime.runtime_id) + .env("FABRIC_HOME", &runtime_dir) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::from(stderr)); + let mut child = match command.spawn() { + Ok(child) => child, + Err(source) => { + let _ = std::fs::remove_dir_all(&runtime_dir); + return Err(FabricError::ProcessRunner { + command: command_display, + source, + }); + } + }; + let Some(stdin) = child.stdin.take() else { + let _ = child.kill(); + let _ = child.wait(); + let _ = std::fs::remove_dir_all(&runtime_dir); + return Err(lifecycle_error( + AdapterLifecycleOperation::Start, + &runtime.runtime_id, + "host_io", + "persistent adapter host stdin was not available", + "", + )); + }; + let Some(stdout) = child.stdout.take() else { + let _ = child.kill(); + let _ = child.wait(); + let _ = std::fs::remove_dir_all(&runtime_dir); + return Err(lifecycle_error( + AdapterLifecycleOperation::Start, + &runtime.runtime_id, + "host_io", + "persistent adapter host stdout was not available", + "", + )); + }; + let (sender, responses) = mpsc::channel(); + if let Err(source) = thread::Builder::new() + .name(format!("fabric-host-{}", runtime.runtime_id)) + .spawn(move || { + let mut stdout = BufReader::new(stdout); + loop { + let mut line = String::new(); + match stdout.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + while line.ends_with(['\n', '\r']) { + line.pop(); + } + if sender.send(Ok(line)).is_err() { + break; + } + } + Err(error) => { + let _ = sender.send(Err(error.to_string())); + break; + } + } + } + }) + { + let _ = child.kill(); + let _ = child.wait(); + let _ = std::fs::remove_dir_all(&runtime_dir); + return Err(FabricError::ProcessRunner { + command: command_display, + source, + }); + } + Ok(PersistentHost { + child, + stdin, + responses, + command: command_display, + runtime_dir, + stderr_path, + }) +} + +fn persistent_host_command(plan: &RunPlan, runtime: &RuntimeHandle) -> Result<(Command, String)> { + let cwd = |configured: Option<&Path>| { + configured + .map(|path| resolve_path(&plan.config_root, path)) + .or_else(|| runtime.environment.workspace.clone()) + .unwrap_or_else(|| plan.agent_root.clone()) + }; + match adapter_kind(plan) { + AdapterKind::Python => { + let settings = parse_python_settings(plan)?; + let python = resolve_python_command(&plan.config_root, &settings).path; + let mut command = Command::new(&python); + command + .arg("-m") + .arg(&settings.module) + .args(&settings.args) + .current_dir(cwd(settings.cwd.as_deref())) + .envs(&settings.env); + Ok(( + command, + format!("{} -m {}", python.to_string_lossy(), settings.module), + )) + } + AdapterKind::Process => { + let settings = parse_process_settings(plan)?; + let command_path = resolve_command_path( + adapter_setting_root(plan, "command"), + Path::new(&settings.command), + ); + let command_args = process_command_args(plan, &settings); + let mut command = Command::new(&command_path); + command + .args(&command_args) + .current_dir(cwd(settings.cwd.as_deref())) + .envs(&settings.env); + Ok((command, command_path.to_string_lossy().into_owned())) + } + adapter_kind => Err(FabricError::UnsupportedRuntimeAdapter { + harness: harness(plan), + adapter_kind, + }), + } +} + +fn exchange_lifecycle_message( + host: &mut PersistentHost, + runtime_id: &str, + request: &AdapterLifecycleRequest, + timeout: Option, +) -> Result { + let operation = request.operation(); + if let Some(status) = host.child.try_wait().map_err(|source| { + lifecycle_error( + operation, + runtime_id, + "host_io", + format!("failed to inspect persistent adapter host: {source}"), + persistent_host_diagnostics(host), + ) + })? { + return Err(lifecycle_error( + operation, + runtime_id, + "host_crashed", + format!( + "persistent adapter host exited before {} ({status})", + operation.as_str() + ), + persistent_host_diagnostics(host), + )); + } + let mut message = serde_json::to_string(request).map_err(FabricError::SerializeJson)?; + message.push('\n'); + if let Err(source) = host + .stdin + .write_all(message.as_bytes()) + .and_then(|()| host.stdin.flush()) + { + let code = match host.child.try_wait() { + Ok(Some(_)) => "host_crashed", + _ => "host_io", + }; + return Err(lifecycle_error( + operation, + runtime_id, + code, + format!( + "failed to send {} to persistent adapter host: {source}", + operation.as_str() + ), + persistent_host_diagnostics(host), + )); + } + + let line = match timeout { + Some(timeout) => match host.responses.recv_timeout(timeout) { + Ok(line) => line, + Err(RecvTimeoutError::Timeout) => { + return Err(lifecycle_error( + operation, + runtime_id, + "host_timeout", + format!( + "persistent adapter host did not complete {} within {} ms", + operation.as_str(), + timeout.as_millis() + ), + persistent_host_diagnostics(host), + )); + } + Err(RecvTimeoutError::Disconnected) => { + return Err(lifecycle_error( + operation, + runtime_id, + "host_crashed", + format!( + "persistent adapter host exited while processing {}", + operation.as_str() + ), + persistent_host_diagnostics(host), + )); + } + }, + None => host.responses.recv().map_err(|_| { + lifecycle_error( + operation, + runtime_id, + "host_crashed", + format!( + "persistent adapter host exited while processing {}", + operation.as_str() + ), + persistent_host_diagnostics(host), + ) + })?, + } + .map_err(|message| { + lifecycle_error( + operation, + runtime_id, + "host_io", + message, + persistent_host_diagnostics(host), + ) + })?; + let response: AdapterLifecycleResponse = serde_json::from_str(&line).map_err(|source| { + lifecycle_error( + operation, + runtime_id, + "protocol_error", + format!("invalid lifecycle response: {source}"), + persistent_host_diagnostics(host), + ) + })?; + if response.contract_version != ADAPTER_LIFECYCLE_CONTRACT_VERSION { + return Err(lifecycle_error( + operation, + runtime_id, + "protocol_version_mismatch", + format!( + "expected lifecycle contract `{ADAPTER_LIFECYCLE_CONTRACT_VERSION}` but host returned `{}`", + response.contract_version + ), + persistent_host_diagnostics(host), + )); + } + if response.operation != operation { + return Err(lifecycle_error( + operation, + runtime_id, + "protocol_error", + format!( + "expected `{}` response but host returned `{}`", + operation.as_str(), + response.operation.as_str() + ), + persistent_host_diagnostics(host), + )); + } + match response.outcome { + AdapterLifecycleOutcome::Succeeded { output } => Ok(output), + AdapterLifecycleOutcome::Failed { error } => { + if error.stage != operation.error_stage() { + return Err(lifecycle_error( + operation, + runtime_id, + "protocol_error", + format!( + "{} failure reported the wrong lifecycle stage `{:?}`", + operation.as_str(), + error.stage + ), + persistent_host_diagnostics(host), + )); + } + let mut diagnostics = persistent_host_diagnostics(host); + if !error.metadata.is_empty() + && let Ok(metadata) = serde_json::to_string(&error.metadata) + { + if !diagnostics.is_empty() { + diagnostics.push('\n'); + } + diagnostics.push_str("adapter metadata: "); + diagnostics.push_str(&metadata); + } + Err(lifecycle_error( + operation, + runtime_id, + error.code, + error.message, + diagnostics, + )) + } + } +} + +fn lifecycle_error( + operation: AdapterLifecycleOperation, + runtime_id: &str, + code: impl Into, + message: impl Into, + diagnostics: impl Into, +) -> FabricError { + FabricError::AdapterLifecycleOperation { + operation: operation.as_str(), + runtime_id: runtime_id.to_string(), + code: code.into(), + message: message.into(), + diagnostics: diagnostics.into(), + } +} + +fn persistent_host_diagnostics(host: &PersistentHost) -> String { + let Ok(bytes) = std::fs::read(&host.stderr_path) else { + return String::new(); + }; + let start = bytes.len().saturating_sub(PERSISTENT_HOST_DIAGNOSTIC_LIMIT); + String::from_utf8_lossy(&bytes[start..]).trim().to_string() +} + +fn terminate_persistent_host(host: &mut PersistentHost) { + if matches!(host.child.try_wait(), Ok(None)) { + let _ = host.child.kill(); + } + let _ = host.child.wait(); +} + +fn remove_persistent_host_files(host: &PersistentHost) { + let _ = std::fs::remove_dir_all(&host.runtime_dir); +} + fn run_process_adapter( plan: &RunPlan, runtime: &RuntimeHandle, @@ -1320,12 +2176,9 @@ fn adapter_invocation( artifacts: &ArtifactManifest, relay_config: Option<&RelayRuntimeConfig>, ) -> Result { - let mut effective_config = plan.effective_config.clone(); - effective_config.agent_root = absolute_path(effective_config.agent_root)?; - effective_config.config_path = absolute_path(effective_config.config_path)?; - effective_config.config_root = absolute_path(effective_config.config_root)?; Ok(AdapterInvocation { - effective_config, + effective_config: adapter_effective_config(plan)?, + execution_strategy: plan.execution_strategy, runtime_context: RuntimeContext { runtime_id: runtime.runtime_id.clone(), invocation_id: invocation.invocation_id.clone(), @@ -1340,6 +2193,14 @@ fn adapter_invocation( }) } +fn adapter_effective_config(plan: &RunPlan) -> Result { + let mut effective_config = plan.effective_config.clone(); + effective_config.agent_root = absolute_path(effective_config.agent_root)?; + effective_config.config_path = absolute_path(effective_config.config_path)?; + effective_config.config_root = absolute_path(effective_config.config_root)?; + Ok(effective_config) +} + fn runtime_telemetry_context( plan: &RunPlan, relay_config: Option<&RelayRuntimeConfig>, @@ -2070,6 +2931,125 @@ runtime: }"# } + fn persistent_host_agent_dir(mode: &str) -> PathBuf { + let root = std::env::temp_dir().join(new_id("fabric-persistent-host-test")); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("adapters/persistent")).expect("create adapters dir"); + fs::write( + root.join("agent.yaml"), + format!( + r#"schema_version: fabric.agent/v1alpha1 +metadata: + name: persistent-host-test-agent +harness: + adapter_id: acme.fabric.persistent + settings: + runtime_strategy: persistent_local_host + command: python3 + script: ./fake_host.py + env: + FABRIC_FAKE_HOST_MODE: {mode} +models: + default: + provider: test + model: test-model +runtime: + input_schema: text + output_schema: text + artifacts: ./artifacts +"# + ), + ) + .expect("write config"); + fs::write( + root.join("adapters/persistent/fabric-adapter.json"), + r#"{ + "contract_version": "fabric.adapter/v1alpha1", + "adapter_id": "acme.fabric.persistent", + "harness": "persistent-test", + "adapter_kind": "process", + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + } +}"#, + ) + .expect("write adapter descriptor"); + fs::write( + root.join("fake_host.py"), + r#"import json +import os +import sys + +VERSION = "fabric.adapter.lifecycle/v1alpha1" +MODE = os.environ.get("FABRIC_FAKE_HOST_MODE", "success") +invocations = 0 + +def response(operation, *, output=None, error=None): + if error is None: + outcome = {"status": "succeeded", "output": output} + else: + outcome = {"status": "failed", "error": error} + print(json.dumps({ + "contract_version": VERSION, + "operation": operation, + "outcome": outcome, + }), flush=True) + +def failure(stage, code, message): + return { + "stage": stage, + "code": code, + "message": message, + "retryable": False, + } + +for line in sys.stdin: + message = json.loads(line) + operation = message["operation"] + if operation == "start": + if MODE == "start_failure": + print("start diagnostic", file=sys.stderr, flush=True) + response("start", error=failure("start", "fake_start", "start rejected")) + continue + response("start") + if MODE == "crash_after_start": + print("host crashed intentionally", file=sys.stderr, flush=True) + sys.exit(17) + elif operation == "invoke": + invocations += 1 + if MODE == "invoke_failure": + response("invoke", error=failure("invoke", "fake_invoke", "invoke rejected")) + continue + invocation = message["payload"] + output = { + "host_pid": os.getpid(), + "invocation_count": invocations, + "input": invocation["request"]["input"], + } + if MODE == "adapter_reported_failure": + output.update({ + "failed": True, + "error": { + "code": "fake_adapter_failure", + "message": "adapter rejected the invocation", + "retryable": True, + "metadata": {"source": "fake-host"}, + }, + }) + response("invoke", output=output) + elif operation == "stop": + if MODE == "stop_failure": + response("stop", error=failure("stop", "fake_stop", "stop rejected")) + sys.exit(18) + response("stop") + break +"#, + ) + .expect("write fake host"); + root + } + fn stopped_agents() -> Vec { TEST_STOPPED_AGENTS.lock().expect("stop tracker").clone() } @@ -2320,6 +3300,10 @@ relay: let payload = serde_json::to_value(payload).expect("adapter payload json"); assert!(relay.is_none()); + assert_eq!( + payload["execution_strategy"], + serde_json::json!("process_per_invocation") + ); assert!( !artifacts .artifacts @@ -2379,6 +3363,128 @@ relay: let _ = fs::remove_dir_all(root); } + #[test] + fn persistent_host_reuses_one_process_and_stops_idempotently() { + let root = persistent_host_agent_dir("success"); + let plan = resolve_run_plan(&root, None).expect("run plan"); + let runtime = start_runtime(&plan).expect("start persistent host"); + + let first = + invoke_runtime(&plan, &runtime, RunRequest::text("first")).expect("first invocation"); + let second = + invoke_runtime(&plan, &runtime, RunRequest::text("second")).expect("second invocation"); + + assert_eq!(first.output["host_pid"], second.output["host_pid"]); + assert_eq!(first.output["invocation_count"], serde_json::json!(1)); + assert_eq!(second.output["invocation_count"], serde_json::json!(2)); + assert_eq!(first.output["input"], serde_json::json!("first")); + assert_eq!(second.output["input"], serde_json::json!("second")); + assert_eq!( + first.metadata["adapter_runner"], + serde_json::json!("persistent_local_host") + ); + + let first_stop = stop_runtime(&plan, &runtime).expect("first stop"); + let second_stop = stop_runtime(&plan, &runtime).expect("idempotent stop"); + assert_eq!(first_stop[0].metadata["already_stopped"], false); + assert_eq!(second_stop[0].metadata["already_stopped"], true); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn persistent_host_start_failure_preserves_stage_and_diagnostics() { + let root = persistent_host_agent_dir("start_failure"); + let plan = resolve_run_plan(&root, None).expect("run plan"); + + let error = start_runtime(&plan).expect_err("start must fail"); + let message = error.to_string(); + assert!(message.contains("lifecycle start"), "{message}"); + assert!(message.contains("fake_start"), "{message}"); + assert!(message.contains("start diagnostic"), "{message}"); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn persistent_host_invoke_failure_is_stopped_by_run_plan() { + let root = persistent_host_agent_dir("invoke_failure"); + let mut plan = resolve_run_plan(&root, None).expect("run plan"); + plan.agent_name = new_id("persistent-invoke-error-agent"); + plan.effective_config.agent_name = plan.agent_name.clone(); + let agent_name = plan.agent_name.clone(); + + let error = run_plan(&plan, RunRequest::text("fail")).expect_err("invoke must fail"); + let message = error.to_string(); + assert!(message.contains("lifecycle invoke"), "{message}"); + assert!(message.contains("fake_invoke"), "{message}"); + assert!( + stopped_agents().contains(&agent_name), + "run_plan must stop the persistent host after invocation failure" + ); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn persistent_host_preserves_normalized_adapter_failure() { + let root = persistent_host_agent_dir("adapter_reported_failure"); + let plan = resolve_run_plan(&root, None).expect("run plan"); + + let result = run_plan(&plan, RunRequest::text("fail")).expect("normalized result"); + + assert_eq!(result.status, RunStatus::Failed); + assert_eq!(result.output["failed"], true); + assert_eq!( + result.error, + Some(ErrorInfo { + stage: ErrorStage::Invoke, + code: "fake_adapter_failure".to_string(), + message: "adapter rejected the invocation".to_string(), + retryable: true, + metadata: BTreeMap::from([("source".to_string(), serde_json::json!("fake-host"),)]), + }) + ); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn persistent_host_crash_rejects_new_invocations() { + let root = persistent_host_agent_dir("crash_after_start"); + let plan = resolve_run_plan(&root, None).expect("run plan"); + let runtime = start_runtime(&plan).expect("start persistent host"); + + let first = invoke_runtime(&plan, &runtime, RunRequest::text("first")) + .expect_err("crashed host must reject invocation"); + let second = invoke_runtime(&plan, &runtime, RunRequest::text("second")) + .expect_err("dead runtime handle must remain unusable"); + assert!(first.to_string().contains("host_crashed"), "{first}"); + assert!(second.to_string().contains("host_crashed"), "{second}"); + + let stop = stop_runtime(&plan, &runtime).expect_err("crashed host cannot acknowledge stop"); + assert!(stop.to_string().contains("lifecycle stop"), "{stop}"); + stop_runtime(&plan, &runtime).expect("cleanup remains idempotent"); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn persistent_host_stop_failure_cleans_up_before_retry() { + let root = persistent_host_agent_dir("stop_failure"); + let plan = resolve_run_plan(&root, None).expect("run plan"); + let runtime = start_runtime(&plan).expect("start persistent host"); + + let error = stop_runtime(&plan, &runtime).expect_err("stop must fail"); + let message = error.to_string(); + assert!(message.contains("lifecycle stop"), "{message}"); + assert!(message.contains("fake_stop"), "{message}"); + let retry = stop_runtime(&plan, &runtime).expect("cleanup retry is idempotent"); + assert_eq!(retry[0].metadata["already_stopped"], true); + + let _ = fs::remove_dir_all(root); + } + #[test] fn runtime_handle_exposes_single_opaque_binding() { let root = temp_process_agent_dir(); @@ -2387,12 +3493,34 @@ relay: let value = serde_json::to_value(&runtime).expect("runtime json"); assert!(value.get("runtime_binding").is_some()); + assert_eq!( + value["execution_strategy"], + serde_json::json!("process_per_invocation") + ); assert!(value.get("plan_fingerprint").is_none()); assert!(value.get("environment_fingerprint").is_none()); let _ = fs::remove_dir_all(root); } + #[test] + fn remote_service_never_falls_back_to_per_invocation_execution() { + let root = temp_process_agent_dir(); + let mut plan = resolve_run_plan(&root, None).expect("run plan"); + plan.execution_strategy = ExecutionStrategy::RemoteService; + + let error = start_runtime(&plan).expect_err("remote transport is required"); + + assert!(matches!( + error, + FabricError::RuntimeStrategyUnavailable { + strategy: ExecutionStrategy::RemoteService, + .. + } + )); + let _ = fs::remove_dir_all(root); + } + #[test] fn runtime_handle_without_binding_is_rejected_during_deserialization() { let root = temp_process_agent_dir(); @@ -2536,6 +3664,20 @@ relay: let _ = fs::remove_dir_all(root); } + #[test] + fn invoke_runtime_rejects_mutated_execution_strategy() { + let root = temp_process_agent_dir(); + let plan = resolve_run_plan(&root, None).expect("run plan"); + let mut runtime = start_runtime(&plan).expect("runtime"); + runtime.execution_strategy = ExecutionStrategy::PersistentLocalHost; + + let error = invoke_runtime(&plan, &runtime, RunRequest::text("hello fabric")) + .expect_err("runtime mismatch"); + + assert!(error.to_string().contains("execution_strategy"), "{error}"); + let _ = fs::remove_dir_all(root); + } + #[test] fn stop_runtime_rejects_runtime_handle_from_different_plan() { let root = temp_process_agent_dir(); diff --git a/crates/fabric-core/src/schema.rs b/crates/fabric-core/src/schema.rs index b5ea09354..44fc3128d 100644 --- a/crates/fabric-core/src/schema.rs +++ b/crates/fabric-core/src/schema.rs @@ -12,8 +12,9 @@ use serde_json::Value; use crate::config::{AdapterDescriptor, EffectiveConfig, FabricConfig, ProfileConfig, RunPlan}; use crate::error::{FabricError, Result}; use crate::runtime::{ - AdapterInvocation, ArtifactManifest, EnvironmentHandle, ErrorInfo, FabricEvent, - InvocationHandle, RunRequest, RunResult, RuntimeContext, RuntimeHandle, + AdapterInvocation, AdapterLifecycleRequest, AdapterLifecycleResponse, ArtifactManifest, + EnvironmentHandle, ErrorInfo, FabricEvent, InvocationHandle, RunRequest, RunResult, + RuntimeContext, RuntimeHandle, }; /// Public schema snapshots generated by Fabric. @@ -31,6 +32,10 @@ pub enum SchemaName { RunPlan, /// Adapter-facing invocation payload schema. AdapterInvocation, + /// Persistent-host adapter lifecycle request schema. + AdapterLifecycleRequest, + /// Persistent-host adapter lifecycle response schema. + AdapterLifecycleResponse, /// Runtime context schema. RuntimeContext, /// Environment handle schema. @@ -53,13 +58,15 @@ pub enum SchemaName { impl SchemaName { /// All public schemas in stable output order. - pub const ALL: [Self; 15] = [ + pub const ALL: [Self; 17] = [ Self::Agent, Self::Profile, Self::AdapterDescriptor, Self::EffectiveConfig, Self::RunPlan, Self::AdapterInvocation, + Self::AdapterLifecycleRequest, + Self::AdapterLifecycleResponse, Self::RuntimeContext, Self::EnvironmentHandle, Self::RuntimeHandle, @@ -80,6 +87,8 @@ impl SchemaName { Self::EffectiveConfig => "effective-config", Self::RunPlan => "run-plan", Self::AdapterInvocation => "adapter-invocation", + Self::AdapterLifecycleRequest => "adapter-lifecycle-request", + Self::AdapterLifecycleResponse => "adapter-lifecycle-response", Self::RuntimeContext => "runtime-context", Self::EnvironmentHandle => "environment-handle", Self::RuntimeHandle => "runtime-handle", @@ -106,6 +115,12 @@ impl SchemaName { "effective-config" | "effective_config" => Ok(Self::EffectiveConfig), "run-plan" | "run_plan" => Ok(Self::RunPlan), "adapter-invocation" | "adapter_invocation" => Ok(Self::AdapterInvocation), + "adapter-lifecycle-request" | "adapter_lifecycle_request" => { + Ok(Self::AdapterLifecycleRequest) + } + "adapter-lifecycle-response" | "adapter_lifecycle_response" => { + Ok(Self::AdapterLifecycleResponse) + } "runtime-context" | "runtime_context" => Ok(Self::RuntimeContext), "environment-handle" | "environment_handle" => Ok(Self::EnvironmentHandle), "runtime-handle" | "runtime_handle" => Ok(Self::RuntimeHandle), @@ -135,6 +150,8 @@ pub fn generate_schema(schema: SchemaName) -> Result { SchemaName::EffectiveConfig => to_value(schema_for!(EffectiveConfig)), SchemaName::RunPlan => to_value(schema_for!(RunPlan)), SchemaName::AdapterInvocation => to_value(schema_for!(AdapterInvocation)), + SchemaName::AdapterLifecycleRequest => to_value(schema_for!(AdapterLifecycleRequest)), + SchemaName::AdapterLifecycleResponse => to_value(schema_for!(AdapterLifecycleResponse)), SchemaName::RuntimeContext => to_value(schema_for!(RuntimeContext)), SchemaName::EnvironmentHandle => to_value(schema_for!(EnvironmentHandle)), SchemaName::RuntimeHandle => to_value(schema_for!(RuntimeHandle)), 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 6d70a2676..e4f719299 100644 --- a/docs/reference/api/python-library-reference/nemo_fabric.types.md +++ b/docs/reference/api/python-library-reference/nemo_fabric.types.md @@ -237,6 +237,7 @@ Immutable execution plan produced before a runtime is started. - `agent_name`: Resolved agent name. - `profiles`: Applied profile names in caller order. - `adapter`: Resolved adapter identity. + - `execution_strategy`: Adapter execution strategy selected by planning. - `capabilities`: Operations declared by the resolved runtime. @@ -796,6 +797,7 @@ Applications should treat ``runtime_binding`` as opaque. Fabric validates the ha - `harness`: Stable harness identifier. - `adapter_kind`: Adapter execution mechanism. - `adapter_id`: Optional Fabric adapter identifier. + - `execution_strategy`: Adapter execution strategy selected for this runtime. - `environment`: Prepared environment snapshot. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/constant-adapter-lifecycle-contract-version.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/constant-adapter-lifecycle-contract-version.mdx new file mode 100644 index 000000000..040a3d607 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/constant-adapter-lifecycle-contract-version.mdx @@ -0,0 +1,14 @@ +--- +title: "Constant ADAPTER_LIFECYCLE_CONTRACT_VERSION" +sidebar-title: "ADAPTER_LIFECYCLE_CONTRACT_VERSION" +description: "Adapter lifecycle contract version supported by this core." +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 nemo-fabric-core`. + +
str = \"fabric.adapter.lifecycle/v1alpha1\";"}} />
+ +Adapter lifecycle contract version supported by this core. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterdescriptorsource.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterdescriptorsource.mdx index 57ada140b..3d35cda8f 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterdescriptorsource.mdx +++ b/docs/reference/api/rust-library-reference/nemo-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: 4 +position: 5 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterkind.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterkind.mdx index c786a2439..423d44f85 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterkind.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-adapterkind.mdx @@ -2,7 +2,7 @@ title: "Enum Adapter Kind" sidebar-title: "AdapterKind" description: "Adapter implementation kind." -position: 5 +position: 7 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitykind.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitykind.mdx index 9209ea0f1..3676ff3cc 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitykind.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitykind.mdx @@ -2,7 +2,7 @@ title: "Enum Capability Kind" sidebar-title: "CapabilityKind" description: "Capability kind." -position: 42 +position: 43 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitytarget.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitytarget.mdx index 4727c3d29..199a96ecc 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitytarget.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitytarget.mdx @@ -2,7 +2,7 @@ title: "Enum Capability Target" sidebar-title: "CapabilityTarget" description: "Capability routing target." -position: 43 +position: 44 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-controllocation.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-controllocation.mdx index 84d27e315..46088e426 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-controllocation.mdx +++ b/docs/reference/api/rust-library-reference/nemo-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: 10 +position: 12 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-environmentownership.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-environmentownership.mdx index 56f121751..eb7988d16 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-environmentownership.mdx +++ b/docs/reference/api/rust-library-reference/nemo-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: 13 +position: 15 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-executionstrategy.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-executionstrategy.mdx new file mode 100644 index 000000000..2411d51bd --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-executionstrategy.mdx @@ -0,0 +1,186 @@ +--- +title: "Enum Execution Strategy" +sidebar-title: "ExecutionStrategy" +description: "How the selected adapter executes harness work for one Fabric runtime." +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 nemo-fabric-core`. + +```rust +pub enum ExecutionStrategy { + ProcessPerInvocation, + PersistentLocalHost, + RemoteService, +} +``` + +How the selected adapter executes harness work for one Fabric runtime. + +## Variants + +### `ProcessPerInvocation` + +
+ +Launch a fresh adapter process for each invocation. + +### `PersistentLocalHost` + +
+ +Start one adapter-owned local host and reuse it for the runtime. + +### `RemoteService` + +
+ +Allocate or connect to an adapter-owned remote harness service. + +## Implementations + +### `impl ExecutionStrategy` + +
ExecutionStrategy"}} />
+ +#### `as_str` + +
str"}} />
+ +Stable serialized strategy name. + +## Trait Implementations + +### `impl Clone for ExecutionStrategy` + +
Clone for ExecutionStrategy"}} />
+ +#### `clone` + +
clone(&self) -> ExecutionStrategy"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for ExecutionStrategy` + +
Debug for ExecutionStrategy"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl Default for ExecutionStrategy` + +
Default for ExecutionStrategy"}} />
+ +#### `default` + +
default() -> ExecutionStrategy"}} />
+ +### `impl<'de> Deserialize<'de> for ExecutionStrategy` + +
Deserialize<'de> for ExecutionStrategy"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for ExecutionStrategy` + +
ExecutionStrategy"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl Ord for ExecutionStrategy` + +
Ord for ExecutionStrategy"}} />
+ +#### `cmp` + +
cmp(&self, other: &ExecutionStrategy) -> Ordering"}} />
+ +#### `max` + +
max(self, other: Self) -> Selfwhere\n    Self: Sized,"}} />
+ +#### `min` + +
min(self, other: Self) -> Selfwhere\n    Self: Sized,"}} />
+ +#### `clamp` + +
clamp(self, min: Self, max: Self) -> Selfwhere\n    Self: Sized,"}} />
+ +### `impl PartialEq for ExecutionStrategy` + +
PartialEq for ExecutionStrategy"}} />
+ +#### `eq` + +
eq(&self, other: &ExecutionStrategy) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl PartialOrd for ExecutionStrategy` + +
PartialOrd for ExecutionStrategy"}} />
+ +#### `partial_cmp` + +
partial_cmp(&self, other: &ExecutionStrategy) -> Option<Ordering>"}} />
+ +#### `lt` + +
lt(&self, other: &Rhs) -> bool"}} />
+ +#### `le` + +
le(&self, other: &Rhs) -> bool"}} />
+ +#### `gt` + +
gt(&self, other: &Rhs) -> bool"}} />
+ +#### `ge` + +
ge(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for ExecutionStrategy` + +
Serialize for ExecutionStrategy"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl Copy for ExecutionStrategy` + +
Copy for ExecutionStrategy"}} />
+ +### `impl Eq for ExecutionStrategy` + +
Eq for ExecutionStrategy"}} />
+ +### `impl StructuralPartialEq for ExecutionStrategy` + +
StructuralPartialEq for ExecutionStrategy"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-fabricdocument.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-fabricdocument.mdx index 9f52c7e1c..a1b139c53 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-fabricdocument.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-fabricdocument.mdx @@ -2,7 +2,7 @@ title: "Enum Fabric Document" sidebar-title: "FabricDocument" description: "A loaded Fabric document with resolved source path and agent root." -position: 16 +position: 19 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure.mdx index dce2481c1..12920cc58 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure.mdx @@ -2,7 +2,7 @@ title: "Enum McpExposure" sidebar-title: "McpExposure" description: "MCP exposure strategy." -position: 19 +position: 22 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx index 6e1dd321f..86fb7b408 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Atif Storage Config" sidebar-title: "RelayAtifStorageConfig" description: "Relay ATIF remote storage configuration." -position: 48 +position: 50 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointfieldnamepolicy.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointfieldnamepolicy.mdx index f145956be..9b8b214d5 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointfieldnamepolicy.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointfieldnamepolicy.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Atof Endpoint Field Name Policy" sidebar-title: "RelayAtofEndpointFieldNamePolicy" description: "Relay ATOF endpoint field-name policy." -position: 49 +position: 51 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointtransport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointtransport.mdx index ab2682f91..83b3f47db 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointtransport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofendpointtransport.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Atof Endpoint Transport" sidebar-title: "RelayAtofEndpointTransport" description: "Relay ATOF endpoint transport." -position: 50 +position: 52 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofmode.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofmode.mdx index ee3ac693a..496b48a14 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofmode.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofmode.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Atof Mode" sidebar-title: "RelayAtofMode" description: "Relay ATOF file mode." -position: 51 +position: 53 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayotlptransport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayotlptransport.mdx index 2fd040adb..6fdf069ef 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayotlptransport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayotlptransport.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Otlp Transport" sidebar-title: "RelayOtlpTransport" description: "Relay OTLP transport." -position: 52 +position: 54 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayunsupportedbehavior.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayunsupportedbehavior.mdx index 4669a0453..f0c566a0e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayunsupportedbehavior.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayunsupportedbehavior.mdx @@ -2,7 +2,7 @@ title: "Enum Relay Unsupported Behavior" sidebar-title: "RelayUnsupportedBehavior" description: "Relay unsupported/unknown config handling." -position: 53 +position: 55 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-resolutionstrategy.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-resolutionstrategy.mdx index 2f06bcd9e..bdc3b2d58 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-resolutionstrategy.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-resolutionstrategy.mdx @@ -2,7 +2,7 @@ title: "Enum Resolution Strategy" sidebar-title: "ResolutionStrategy" description: "Adapter install or availability strategy." -position: 24 +position: 27 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-telemetryprovider.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-telemetryprovider.mdx index 851ae8829..54f49fdaa 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-telemetryprovider.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-telemetryprovider.mdx @@ -2,7 +2,7 @@ title: "Enum Telemetry Provider" sidebar-title: "TelemetryProvider" description: "Telemetry runtime provider." -position: 33 +position: 36 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-adapter-descriptor.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-adapter-descriptor.mdx index 058809419..1a0c4cdc4 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-adapter-descriptor.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-adapter-descriptor.mdx @@ -2,7 +2,7 @@ title: "Function load_adapter_descriptor" sidebar-title: "load_adapter_descriptor" description: "Load an adapter descriptor from JSON package metadata." -position: 35 +position: 38 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-fabric-document.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-fabric-document.mdx index 64820dfa6..c469e0ef0 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-fabric-document.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-load-fabric-document.mdx @@ -2,7 +2,7 @@ title: "Function load_fabric_document" sidebar-title: "load_fabric_document" description: "Load a Fabric document from an agent directory or single agent config." -position: 36 +position: 39 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-from-config.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-from-config.mdx index fe82db846..046b75569 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-from-config.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-from-config.mdx @@ -2,7 +2,7 @@ title: "Function resolve_effective_config_from_config" sidebar-title: "resolve_effective_config_from_config" description: "Resolve typed config/profile overlays into merged effective config." -position: 38 +position: 41 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-with-profiles.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-with-profiles.mdx index 52fc900eb..f7f6f1079 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-with-profiles.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config-with-profiles.mdx @@ -2,7 +2,7 @@ title: "Function resolve_effective_config_with_profiles" sidebar-title: "resolve_effective_config_with_profiles" description: "Resolve an agent directory or single agent config with ordered profiles into merged effective config." -position: 39 +position: 42 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config.mdx index 8665e8029..25833e593 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-effective-config.mdx @@ -2,7 +2,7 @@ title: "Function resolve_effective_config" sidebar-title: "resolve_effective_config" description: "Resolve an agent directory or single agent config into merged effective config." -position: 37 +position: 40 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-config.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-config.mdx index 494cb76f1..ba76332f3 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-config.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-config.mdx @@ -2,7 +2,7 @@ title: "Function resolve_run_plan_from_config" sidebar-title: "resolve_run_plan_from_config" description: "Resolve a typed Fabric config and typed profile overlays into a runnable plan." -position: 41 +position: 44 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-effective-config.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-effective-config.mdx index 21299f44d..8b4fcb745 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-effective-config.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-from-effective-config.mdx @@ -2,7 +2,7 @@ title: "Function resolve_run_plan_from_effective_config" sidebar-title: "resolve_run_plan_from_effective_config" description: "Resolve execution planning metadata from merged effective config." -position: 42 +position: 45 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-with-profiles.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-with-profiles.mdx index 0b28b5fca..39915cc37 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-with-profiles.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan-with-profiles.mdx @@ -2,7 +2,7 @@ title: "Function resolve_run_plan_with_profiles" sidebar-title: "resolve_run_plan_with_profiles" description: "Resolve an agent directory or single agent config with ordered profile application." -position: 43 +position: 46 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan.mdx index fd80030da..f0fa1afa4 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-resolve-run-plan.mdx @@ -2,7 +2,7 @@ title: "Function resolve_run_plan" sidebar-title: "resolve_run_plan" description: "Resolve an agent directory or single agent config into a runnable plan." -position: 40 +position: 43 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-validate-agent-directory.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-validate-agent-directory.mdx index 7706821b3..1cae51ea6 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-validate-agent-directory.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/fn-validate-agent-directory.mdx @@ -2,7 +2,7 @@ title: "Function validate_agent_directory" sidebar-title: "validate_agent_directory" description: "Validate an agent directory or config, including discoverable profile YAMLs." -position: 44 +position: 47 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx index 45fd6b667..912c34bba 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx @@ -2,7 +2,7 @@ title: "Module config" sidebar-title: "config" description: "Fabric config models and loading helpers." -position: 76 +position: 86 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -15,6 +15,7 @@ Fabric config models and loading helpers. - [AdapterConfigSupport](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterconfigsupport): Adapter config support. - [AdapterDescriptor](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor): Language-neutral adapter descriptor for a harness integration. +- [AdapterExecutionSupport](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterexecutionsupport): Execution strategies implemented by an adapter. - [AdapterRequirements](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterrequirements): Adapter runtime requirements. - [AdapterTelemetryProviderSupport](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetryprovidersupport): Telemetry capabilities for one adapter-supported provider. - [AdapterTelemetrySupport](/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetrysupport): Adapter telemetry support. @@ -61,6 +62,7 @@ Fabric config models and loading helpers. - [CapabilityTarget](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-capabilitytarget): Capability routing target. - [ControlLocation](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-controllocation): Where Fabric control code runs relative to the environment. - [EnvironmentOwnership](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-environmentownership): Whether Fabric owns the underlying environment resource. +- [ExecutionStrategy](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-executionstrategy): How the selected adapter executes harness work for one Fabric runtime. - [FabricDocument](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-fabricdocument): A loaded Fabric document with resolved source path and agent root. - [McpExposure](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-mcpexposure): MCP exposure strategy. - [RelayAtifStorageConfig](/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig): Relay ATIF remote storage configuration. @@ -75,6 +77,7 @@ Fabric config models and loading helpers. ## Constants - [ADAPTER_CONTRACT_VERSION](/reference/api/rust-library-reference/nemo-fabric-core/config/constant-adapter-contract-version): Adapter descriptor contract version supported by this core. +- [ADAPTER_LIFECYCLE_CONTRACT_VERSION](/reference/api/rust-library-reference/nemo-fabric-core/config/constant-adapter-lifecycle-contract-version): Adapter lifecycle contract version supported by this core. ## Functions diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterconfigsupport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterconfigsupport.mdx index 4e4a85f05..d2d20b630 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterconfigsupport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterconfigsupport.mdx @@ -2,7 +2,7 @@ title: "Struct Adapter Config Support" sidebar-title: "AdapterConfigSupport" description: "Adapter config support." -position: 2 +position: 3 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx index 2d1a3357d..58639872d 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx @@ -2,14 +2,14 @@ title: "Struct Adapter Descriptor" sidebar-title: "AdapterDescriptor" description: "Language-neutral adapter descriptor for a harness integration." -position: 3 +position: 4 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p nemo-fabric-core`. -
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}"}} />
+
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 execution: AdapterExecutionSupport,\n    pub capabilities: RuntimeCapabilities,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
Language-neutral adapter descriptor for a harness integration. @@ -47,6 +47,10 @@ Fabric config areas this adapter consumes or generates. Telemetry support declared by this adapter. +### `execution: AdapterExecutionSupport` + +Runtime execution strategies implemented by this adapter. + ### `capabilities: RuntimeCapabilities` Runtime lifecycle operations supported by this adapter. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterexecutionsupport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterexecutionsupport.mdx new file mode 100644 index 000000000..b9c4849f4 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterexecutionsupport.mdx @@ -0,0 +1,110 @@ +--- +title: "Struct Adapter Execution Support" +sidebar-title: "AdapterExecutionSupport" +description: "Execution strategies implemented by an adapter." +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 nemo-fabric-core`. + +
Option<String>,\n    pub strategies: BTreeSet<ExecutionStrategy>,\n    pub extensions: BTreeMap<String, Value>,\n}"}} />
+ +Execution strategies implemented by an adapter. + +## Fields + +### `lifecycle_contract_version: Option` + +Version of the external start/invoke/stop contract used by persistent strategies. + +### `strategies: BTreeSet` + +Execution strategies implemented by this adapter. + +### `extensions: BTreeMap` + +Additive execution-support fields. + +## Trait Implementations + +### `impl Clone for AdapterExecutionSupport` + +
Clone for AdapterExecutionSupport"}} />
+ +#### `clone` + +
clone(&self) -> AdapterExecutionSupport"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterExecutionSupport` + +
Debug for AdapterExecutionSupport"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl Default for AdapterExecutionSupport` + +
Default for AdapterExecutionSupport"}} />
+ +#### `default` + +
default() -> AdapterExecutionSupport"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterExecutionSupport` + +
Deserialize<'de> for AdapterExecutionSupport"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterExecutionSupport` + +
AdapterExecutionSupport"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterExecutionSupport` + +
PartialEq for AdapterExecutionSupport"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterExecutionSupport) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterExecutionSupport` + +
Serialize for AdapterExecutionSupport"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterExecutionSupport` + +
StructuralPartialEq for AdapterExecutionSupport"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterrequirements.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterrequirements.mdx index 128e05df3..115a6dc28 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterrequirements.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterrequirements.mdx @@ -2,7 +2,7 @@ title: "Struct Adapter Requirements" sidebar-title: "AdapterRequirements" description: "Adapter runtime requirements." -position: 6 +position: 8 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetryprovidersupport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetryprovidersupport.mdx index 329db488c..8a081daf0 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetryprovidersupport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetryprovidersupport.mdx @@ -2,7 +2,7 @@ title: "Struct Adapter Telemetry Provider Support" sidebar-title: "AdapterTelemetryProviderSupport" description: "Telemetry capabilities for one adapter-supported provider." -position: 7 +position: 9 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetrysupport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetrysupport.mdx index 455d860af..90334dbf0 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetrysupport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adaptertelemetrysupport.mdx @@ -2,7 +2,7 @@ title: "Struct Adapter Telemetry Support" sidebar-title: "AdapterTelemetrySupport" description: "Adapter telemetry support." -position: 8 +position: 10 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityplan.mdx index a3e63f791..27c6b6221 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityplan.mdx @@ -2,7 +2,7 @@ title: "Struct Capability Plan" sidebar-title: "CapabilityPlan" description: "Resolved capability configuration." -position: 9 +position: 11 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityroute.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityroute.mdx index f9bc76d80..25b03aec2 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityroute.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilityroute.mdx @@ -2,7 +2,7 @@ title: "Struct Capability Route" sidebar-title: "CapabilityRoute" description: "One capability routing decision." -position: 7 +position: 8 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilitytargetplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilitytargetplan.mdx index 73c0a0b90..320d95af8 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilitytargetplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-capabilitytargetplan.mdx @@ -2,7 +2,7 @@ title: "Struct Capability Target Plan" sidebar-title: "CapabilityTargetPlan" description: "Capabilities routed to one target." -position: 8 +position: 9 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-effectiveconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-effectiveconfig.mdx index ffc2d9c9f..4109ab7f6 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-effectiveconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-effectiveconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Effective Config" sidebar-title: "EffectiveConfig" description: "Merged Fabric config after applying selected profiles." -position: 11 +position: 13 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentconfig.mdx index 4c0cc7b44..74c1764ea 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Environment Config" sidebar-title: "EnvironmentConfig" description: "Execution environment configuration." -position: 12 +position: 14 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentplan.mdx index 906e78ccc..bc223bf12 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-environmentplan.mdx @@ -2,7 +2,7 @@ title: "Struct Environment Plan" sidebar-title: "EnvironmentPlan" description: "Resolved environment plan." -position: 14 +position: 16 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-fabricconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-fabricconfig.mdx index 04b88254e..527b18cfd 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-fabricconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-fabricconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Fabric Config" sidebar-title: "FabricConfig" description: "Versioned Fabric agent config." -position: 15 +position: 18 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-harnessconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-harnessconfig.mdx index 111869755..02a8e984f 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-harnessconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-harnessconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Harness Config" sidebar-title: "HarnessConfig" description: "Harness selection." -position: 17 +position: 20 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpconfig.mdx index 33a7d5e50..f274aba3a 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpconfig.mdx @@ -2,7 +2,7 @@ title: "Struct McpConfig" sidebar-title: "McpConfig" description: "MCP capability configuration." -position: 18 +position: 21 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx index 0d3eb1f09..8e939ebd7 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverconfig.mdx @@ -2,7 +2,7 @@ title: "Struct McpServer Config" sidebar-title: "McpServerConfig" description: "MCP server configuration." -position: 15 +position: 16 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx index c51dbefb5..66491515d 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-mcpserverplan.mdx @@ -2,7 +2,7 @@ title: "Struct McpServer Plan" sidebar-title: "McpServerPlan" description: "Resolved MCP server exposure." -position: 20 +position: 23 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-metadataconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-metadataconfig.mdx index 40ba1d635..aa0f46e35 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-metadataconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-metadataconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Metadata Config" sidebar-title: "MetadataConfig" description: "Human-readable metadata." -position: 21 +position: 24 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-modelconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-modelconfig.mdx index 43673736a..9da94f9e7 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-modelconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-modelconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Model Config" sidebar-title: "ModelConfig" description: "Model configuration." -position: 22 +position: 25 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileconfig.mdx index 94b6c8443..30be04c4d 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Profile Config" sidebar-title: "ProfileConfig" description: "Profile config applied on top of a Fabric config." -position: 23 +position: 26 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileregistryconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileregistryconfig.mdx index 86d980c6d..799c77692 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileregistryconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-profileregistryconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Profile Registry Config" sidebar-title: "ProfileRegistryConfig" description: "Profile discovery config for curated package profiles." -position: 20 +position: 21 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatifconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatifconfig.mdx index 4c28fe811..04e931473 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatifconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatifconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Atif Config" sidebar-title: "RelayAtifConfig" description: "Relay ATIF export configuration." -position: 21 +position: 22 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofconfig.mdx index c667bfddf..8f7cb7067 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Atof Config" sidebar-title: "RelayAtofConfig" description: "Relay ATOF export configuration." -position: 22 +position: 23 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofendpointconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofendpointconfig.mdx index 1977b524b..4e7faf2cf 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofendpointconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayatofendpointconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Atof Endpoint Config" sidebar-title: "RelayAtofEndpointConfig" description: "Relay ATOF endpoint configuration." -position: 23 +position: 24 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relaycomponentconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relaycomponentconfig.mdx index dc0043292..9c2808556 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relaycomponentconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relaycomponentconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Component Config" sidebar-title: "RelayComponentConfig" description: "Generic NeMo Relay plugin component configuration." -position: 24 +position: 25 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfig.mdx index 1b8b6bff5..8fef83191 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Config" sidebar-title: "RelayConfig" description: "NeMo Relay integration configuration." -position: 25 +position: 26 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfigpolicy.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfigpolicy.mdx index 3011b139d..d1eb60117 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfigpolicy.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayconfigpolicy.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Config Policy" sidebar-title: "RelayConfigPolicy" description: "Relay validation policy." -position: 26 +position: 27 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayobservabilityconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayobservabilityconfig.mdx index 785fdead5..fb3f92141 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayobservabilityconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayobservabilityconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Observability Config" sidebar-title: "RelayObservabilityConfig" description: "NeMo Relay observability component configuration." -position: 27 +position: 28 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayotlpconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayotlpconfig.mdx index 2965e9161..31e50c6b3 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayotlpconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-relayotlpconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Relay Otlp Config" sidebar-title: "RelayOtlpConfig" description: "Relay OpenTelemetry/OpenInference export configuration." -position: 28 +position: 29 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvecontext.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvecontext.mdx index 42b530b0e..ded5d950e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvecontext.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvecontext.mdx @@ -2,7 +2,7 @@ title: "Struct Resolve Context" sidebar-title: "ResolveContext" description: "Source context used when resolving an in-memory Fabric config." -position: 25 +position: 28 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvedadapterdescriptor.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvedadapterdescriptor.mdx index 0fa3450f7..baa019d40 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvedadapterdescriptor.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-resolvedadapterdescriptor.mdx @@ -2,7 +2,7 @@ title: "Struct Resolved Adapter Descriptor" sidebar-title: "ResolvedAdapterDescriptor" description: "Adapter descriptor selected for a run plan." -position: 26 +position: 29 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runplan.mdx index c0c331303..16ec08209 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runplan.mdx @@ -2,14 +2,14 @@ title: "Struct RunPlan" sidebar-title: "RunPlan" description: "Resolved Fabric run plan." -position: 27 +position: 30 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p nemo-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 execution_strategy: ExecutionStrategy,\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. @@ -35,6 +35,10 @@ Adapter descriptor resolved for this plan, when configured. Selected install or availability strategy. +### `execution_strategy: ExecutionStrategy` + +Adapter execution strategy selected during planning. + ### `environment_plan: Option` Resolved environment plan. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimecapabilities.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimecapabilities.mdx index 2f73a4d20..4c4b9107e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimecapabilities.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimecapabilities.mdx @@ -2,7 +2,7 @@ title: "Struct Runtime Capabilities" sidebar-title: "RuntimeCapabilities" description: "Lifecycle behavior implemented by a resolved runtime path." -position: 28 +position: 31 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimeconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimeconfig.mdx index 8b51cafcc..86fa41d02 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimeconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-runtimeconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Runtime Config" sidebar-title: "RuntimeConfig" description: "Runtime input/output contract." -position: 29 +position: 32 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-skillconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-skillconfig.mdx index 8ea5d934d..23e67c72a 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-skillconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-skillconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Skill Config" sidebar-title: "SkillConfig" description: "Skill capability configuration." -position: 30 +position: 33 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryconfig.mdx index 9338bbfb4..685b5e73a 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Telemetry Config" sidebar-title: "TelemetryConfig" description: "Telemetry configuration." -position: 31 +position: 34 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryplan.mdx index eee9a11e3..97b2fa24f 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryplan.mdx @@ -2,7 +2,7 @@ title: "Struct Telemetry Plan" sidebar-title: "TelemetryPlan" description: "Resolved telemetry plan." -position: 32 +position: 35 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryproviderconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryproviderconfig.mdx index 67483880a..df71e6256 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryproviderconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-telemetryproviderconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Telemetry Provider Config" sidebar-title: "TelemetryProviderConfig" description: "Provider-specific telemetry configuration." -position: 34 +position: 37 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsconfig.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsconfig.mdx index 1d39c422e..68bc1d939 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsconfig.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsconfig.mdx @@ -2,7 +2,7 @@ title: "Struct Tools Config" sidebar-title: "ToolsConfig" description: "Harness-neutral tool capability configuration." -position: 38 +position: 39 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsplan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsplan.mdx index fa1ed8399..c30b0975e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsplan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-toolsplan.mdx @@ -2,7 +2,7 @@ title: "Struct Tools Plan" sidebar-title: "ToolsPlan" description: "Normalized tool policy for a run." -position: 39 +position: 40 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/enum-doctorstatus.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/enum-doctorstatus.mdx index 4bdf73ab3..54aa667f2 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/enum-doctorstatus.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/enum-doctorstatus.mdx @@ -2,7 +2,7 @@ title: "Enum Doctor Status" sidebar-title: "DoctorStatus" description: "Diagnostic status." -position: 47 +position: 50 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/fn-doctor-plan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/fn-doctor-plan.mdx index 64841c884..eeb802e45 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/fn-doctor-plan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/fn-doctor-plan.mdx @@ -2,7 +2,7 @@ title: "Function doctor_plan" sidebar-title: "doctor_plan" description: "Inspect a resolved run plan without mutating the environment." -position: 48 +position: 51 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx index c409cd201..9349a651e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx @@ -2,7 +2,7 @@ title: "Module doctor" sidebar-title: "doctor" description: "Plan diagnostics for Fabric." -position: 77 +position: 87 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorcheck.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorcheck.mdx index ade547253..a69e8f1fb 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorcheck.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorcheck.mdx @@ -2,7 +2,7 @@ title: "Struct Doctor Check" sidebar-title: "DoctorCheck" description: "Diagnostic check result." -position: 45 +position: 48 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorreport.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorreport.mdx index 14a0150e4..c78aab104 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorreport.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/struct-doctorreport.mdx @@ -2,7 +2,7 @@ title: "Struct Doctor Report" sidebar-title: "DoctorReport" description: "Diagnostic report for a resolved run plan." -position: 46 +position: 49 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx index 8f6cdeecd..cfd3d47bc 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx @@ -2,14 +2,14 @@ title: "Enum Fabric Error" sidebar-title: "FabricError" description: "Errors raised by Fabric config loading and validation." -position: 49 +position: 52 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p nemo-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    UnsupportedToolsPolicy {\n        harness: String,\n        reason: String,\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    PythonInterpreterUnavailable {\n        path: PathBuf,\n        origin: String,\n        reason: String,\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    UnsupportedExecutionStrategy {\n        adapter_id: String,\n        requested: ExecutionStrategy,\n        supported: Vec<ExecutionStrategy>,\n    },\n    InvalidRuntimeStrategy {\n        adapter_id: String,\n        value: String,\n    },\n    RuntimeStrategyUnavailable {\n        adapter_id: String,\n        strategy: ExecutionStrategy,\n        reason: &'static str,\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    AdapterLifecycleOperation {\n        operation: &'static str,\n        runtime_id: String,\n        code: String,\n        message: String,\n        diagnostics: String,\n    },\n    UnsupportedToolsPolicy {\n        harness: String,\n        reason: String,\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    PythonInterpreterUnavailable {\n        path: PathBuf,\n        origin: String,\n        reason: String,\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. @@ -129,6 +129,62 @@ Unsupported field. Unsupported value. +### `UnsupportedExecutionStrategy` + +
+ +The selected adapter does not implement the requested execution strategy. + +#### Fields + +### `adapter_id: String` + +Adapter id selected by the config. + +### `requested: ExecutionStrategy` + +Requested execution strategy. + +### `supported: Vec` + +Strategies implemented by the selected adapter. + +### `InvalidRuntimeStrategy` + +
+ +The runtime strategy setting is not part of the shared strategy vocabulary. + +#### Fields + +### `adapter_id: String` + +Adapter id selected by the config. + +### `value: String` + +Invalid JSON value supplied by the consumer. + +### `RuntimeStrategyUnavailable` + +
+ +The selected adapter declares a strategy whose runtime transport is unavailable. + +#### Fields + +### `adapter_id: String` + +Adapter id selected by the config. + +### `strategy: ExecutionStrategy` + +Strategy selected during planning. + +### `reason: &'static str` + +Missing adapter/runtime contract. + ### `InvalidAdapterDescriptor`
@@ -199,6 +255,34 @@ Harness type. Adapter kind. +### `AdapterLifecycleOperation` + +
+ +A versioned persistent-host lifecycle operation failed. + +#### Fields + +### `operation: &'static str` + +Lifecycle operation that failed. + +### `runtime_id: String` + +Runtime whose host failed. + +### `code: String` + +Stable failure code. + +### `message: String` + +Human-readable failure message. + +### `diagnostics: String` + +Bounded adapter-host diagnostics. + ### `UnsupportedToolsPolicy`
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx index 569e37226..4ab4e7b4a 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx @@ -2,7 +2,7 @@ title: "Module error" sidebar-title: "error" description: "Error types for Fabric core." -position: 78 +position: 88 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/type-result.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/type-result.mdx index 64e9cf199..435e84bb2 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/error/type-result.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/error/type-result.mdx @@ -2,7 +2,7 @@ title: "Type Alias Result" sidebar-title: "Result" description: "Core Fabric result type." -position: 50 +position: 53 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx index b677a330b..9dae180bc 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx @@ -2,7 +2,7 @@ title: "Function version" sidebar-title: "version" description: "Returns the crate version compiled into this build." -position: 81 +position: 91 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx index c52d13b64..c4d67bde3 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx @@ -14,9 +14,11 @@ Core config and runtime contract for NeMo Fabric. ## Re-exports - `pub use config::ADAPTER_CONTRACT_VERSION;` +- `pub use config::ADAPTER_LIFECYCLE_CONTRACT_VERSION;` - `pub use config::AdapterConfigSupport;` - `pub use config::AdapterDescriptor;` - `pub use config::AdapterDescriptorSource;` +- `pub use config::AdapterExecutionSupport;` - `pub use config::AdapterKind;` - `pub use config::AdapterRequirements;` - `pub use config::AdapterTelemetryProviderSupport;` @@ -27,6 +29,7 @@ Core config and runtime contract for NeMo Fabric. - `pub use config::EnvironmentConfig;` - `pub use config::EnvironmentOwnership;` - `pub use config::EnvironmentPlan;` +- `pub use config::ExecutionStrategy;` - `pub use config::FabricConfig;` - `pub use config::FabricDocument;` - `pub use config::HarnessConfig;` @@ -64,6 +67,13 @@ Core config and runtime contract for NeMo Fabric. - `pub use error::FabricError;` - `pub use error::Result;` - `pub use runtime::AdapterInvocation;` +- `pub use runtime::AdapterLifecycleOperation;` +- `pub use runtime::AdapterLifecycleOutcome;` +- `pub use runtime::AdapterLifecycleRequest;` +- `pub use runtime::AdapterLifecycleRequestKind;` +- `pub use runtime::AdapterLifecycleResponse;` +- `pub use runtime::AdapterLifecycleStart;` +- `pub use runtime::AdapterLifecycleStop;` - `pub use runtime::ArtifactManifest;` - `pub use runtime::ArtifactRef;` - `pub use runtime::EnvironmentHandle;` diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoperation.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoperation.mdx new file mode 100644 index 000000000..38e2dd040 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoperation.mdx @@ -0,0 +1,134 @@ +--- +title: "Enum Adapter Lifecycle Operation" +sidebar-title: "AdapterLifecycleOperation" +description: "Operation exchanged over the versioned persistent-host adapter protocol." +position: 18 +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +Generated from `cargo doc --no-deps -p nemo-fabric-core`. + +```rust +pub enum AdapterLifecycleOperation { + Start, + Invoke, + Stop, +} +``` + +Operation exchanged over the versioned persistent-host adapter protocol. + +## Variants + +### `Start` + +
+ +Initialize one adapter-owned host for a Fabric runtime. + +### `Invoke` + +
+ +Execute one invocation against an initialized host. + +### `Stop` + +
+ +Release the host and all runtime-owned resources. + +## Implementations + +### `impl AdapterLifecycleOperation` + +
AdapterLifecycleOperation"}} />
+ +#### `as_str` + +
str"}} />
+ +Stable serialized operation name. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleOperation` + +
Clone for AdapterLifecycleOperation"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleOperation"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleOperation` + +
Debug for AdapterLifecycleOperation"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleOperation` + +
Deserialize<'de> for AdapterLifecycleOperation"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleOperation` + +
AdapterLifecycleOperation"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleOperation` + +
PartialEq for AdapterLifecycleOperation"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleOperation) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleOperation` + +
Serialize for AdapterLifecycleOperation"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl Copy for AdapterLifecycleOperation` + +
Copy for AdapterLifecycleOperation"}} />
+ +### `impl Eq for AdapterLifecycleOperation` + +
Eq for AdapterLifecycleOperation"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleOperation` + +
StructuralPartialEq for AdapterLifecycleOperation"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoutcome.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoutcome.mdx new file mode 100644 index 000000000..c129639c7 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoutcome.mdx @@ -0,0 +1,114 @@ +--- +title: "Enum Adapter Lifecycle Outcome" +sidebar-title: "AdapterLifecycleOutcome" +description: "Outcome returned by a persistent adapter host lifecycle operation." +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 nemo-fabric-core`. + +
Value,\n    },\n    Failed {\n        error: ErrorInfo,\n    },\n}"}} />
+ +Outcome returned by a persistent adapter host lifecycle operation. + +## Variants + +### `Succeeded` + +
+ +The operation completed successfully. + +#### Fields + +### `output: Value` + +Operation output. Only invoke normally returns a non-null value. + +### `Failed` + +
+ +The operation failed with normalized lifecycle diagnostics. + +#### Fields + +### `error: ErrorInfo` + +Structured failure reported by the adapter host. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleOutcome` + +
Clone for AdapterLifecycleOutcome"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleOutcome"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleOutcome` + +
Debug for AdapterLifecycleOutcome"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleOutcome` + +
Deserialize<'de> for AdapterLifecycleOutcome"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleOutcome` + +
AdapterLifecycleOutcome"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleOutcome` + +
PartialEq for AdapterLifecycleOutcome"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleOutcome) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleOutcome` + +
Serialize for AdapterLifecycleOutcome"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleOutcome` + +
StructuralPartialEq for AdapterLifecycleOutcome"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecyclerequestkind.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecyclerequestkind.mdx new file mode 100644 index 000000000..b40f8e585 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecyclerequestkind.mdx @@ -0,0 +1,108 @@ +--- +title: "Enum Adapter Lifecycle Request Kind" +sidebar-title: "AdapterLifecycleRequestKind" +description: "Typed operation payload carried by an adapter lifecycle request." +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 nemo-fabric-core`. + +
AdapterLifecycleStart),\n    Invoke(AdapterInvocation),\n    Stop(AdapterLifecycleStop),\n}"}} />
+ +Typed operation payload carried by an adapter lifecycle request. + +## Variants + +### `Start(AdapterLifecycleStart)` + +
AdapterLifecycleStart)"}} />
+ +Initialize the host. + +### `Invoke(AdapterInvocation)` + +
AdapterInvocation)"}} />
+ +Execute one invocation. + +### `Stop(AdapterLifecycleStop)` + +
AdapterLifecycleStop)"}} />
+ +Stop the host. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleRequestKind` + +
Clone for AdapterLifecycleRequestKind"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleRequestKind"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleRequestKind` + +
Debug for AdapterLifecycleRequestKind"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleRequestKind` + +
Deserialize<'de> for AdapterLifecycleRequestKind"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleRequestKind` + +
AdapterLifecycleRequestKind"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleRequestKind` + +
PartialEq for AdapterLifecycleRequestKind"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleRequestKind) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleRequestKind` + +
Serialize for AdapterLifecycleRequestKind"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleRequestKind` + +
StructuralPartialEq for AdapterLifecycleRequestKind"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage.mdx index 16442abed..5ce972c9d 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage.mdx @@ -2,7 +2,7 @@ title: "Enum Error Stage" sidebar-title: "ErrorStage" description: "Fabric lifecycle stage associated with an error." -position: 14 +position: 21 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus.mdx index d07a6e868..97bba799c 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus.mdx @@ -2,7 +2,7 @@ title: "Enum RunStatus" sidebar-title: "RunStatus" description: "Runtime completion status." -position: 15 +position: 22 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-runtime.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-runtime.mdx index 9d6e3e4f1..18d8072c0 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-runtime.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-runtime.mdx @@ -2,7 +2,7 @@ title: "Function invoke_runtime" sidebar-title: "invoke_runtime" description: "Invoke a started harness runtime." -position: 16 +position: 23 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-prepare-environment.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-prepare-environment.mdx index 79b8e615f..2bf6bff62 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-prepare-environment.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-prepare-environment.mdx @@ -2,7 +2,7 @@ title: "Function prepare_environment" sidebar-title: "prepare_environment" description: "Resolve or attach to the execution environment context for a run plan." -position: 17 +position: 24 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-run-plan.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-run-plan.mdx index 707f8768b..8a701e209 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-run-plan.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-run-plan.mdx @@ -2,7 +2,7 @@ title: "Function run_plan" sidebar-title: "run_plan" description: "Invoke a Fabric run plan." -position: 18 +position: 25 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-start-runtime.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-start-runtime.mdx index ae49a8ea3..180c43f6f 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-start-runtime.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-start-runtime.mdx @@ -2,7 +2,7 @@ title: "Function start_runtime" sidebar-title: "start_runtime" description: "Start or connect to a harness runtime." -position: 19 +position: 26 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-stop-runtime.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-stop-runtime.mdx index 904db4138..49ad8bb1e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-stop-runtime.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-stop-runtime.mdx @@ -2,7 +2,7 @@ title: "Function stop_runtime" sidebar-title: "stop_runtime" description: "Stop or detach from a harness runtime." -position: 20 +position: 27 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx index 8e8af21f9..2468c25a3 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx @@ -2,7 +2,7 @@ title: "Module runtime" sidebar-title: "runtime" description: "Runtime invocation helpers." -position: 79 +position: 89 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} @@ -14,6 +14,10 @@ Runtime invocation helpers. ## Structs - [AdapterInvocation](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterinvocation): Adapter-facing invocation payload. +- [AdapterLifecycleRequest](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclerequest): One newline-delimited request sent to a persistent adapter host. +- [AdapterLifecycleResponse](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecycleresponse): One newline-delimited response returned by a persistent adapter host. +- [AdapterLifecycleStart](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestart): Start payload sent once when Fabric creates a persistent adapter host. +- [AdapterLifecycleStop](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestop): Stop payload sent once when Fabric releases a persistent adapter host. - [ArtifactManifest](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactmanifest): Manifest of run artifacts. - [ArtifactRef](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactref): Reference to one artifact. - [EnvironmentHandle](/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-environmenthandle): Resolved execution environment context. @@ -29,6 +33,9 @@ Runtime invocation helpers. ## Enums +- [AdapterLifecycleOperation](/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoperation): Operation exchanged over the versioned persistent-host adapter protocol. +- [AdapterLifecycleOutcome](/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecycleoutcome): Outcome returned by a persistent adapter host lifecycle operation. +- [AdapterLifecycleRequestKind](/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-adapterlifecyclerequestkind): Typed operation payload carried by an adapter lifecycle request. - [ErrorStage](/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage): Fabric lifecycle stage associated with an error. - [RunStatus](/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus): Runtime completion status. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterinvocation.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterinvocation.mdx index 4d332bd2e..3cf7ee60f 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterinvocation.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterinvocation.mdx @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 */} Generated from `cargo doc --no-deps -p nemo-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 execution_strategy: ExecutionStrategy,\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. @@ -19,6 +19,10 @@ Adapter-facing invocation payload. Merged agent config and provenance. +### `execution_strategy: ExecutionStrategy` + +Execution strategy selected during planning. + ### `runtime_context: RuntimeContext` Per-runtime/per-invocation execution context. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclerequest.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclerequest.mdx new file mode 100644 index 000000000..15b3adc80 --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclerequest.mdx @@ -0,0 +1,98 @@ +--- +title: "Struct Adapter Lifecycle Request" +sidebar-title: "AdapterLifecycleRequest" +description: "One newline-delimited request sent to a persistent adapter host." +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 nemo-fabric-core`. + +
String,\n    pub request: AdapterLifecycleRequestKind,\n}"}} />
+ +One newline-delimited request sent to a persistent adapter host. + +## Fields + +### `contract_version: String` + +Lifecycle protocol version. + +### `request: AdapterLifecycleRequestKind` + +Typed lifecycle operation and payload. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleRequest` + +
Clone for AdapterLifecycleRequest"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleRequest"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleRequest` + +
Debug for AdapterLifecycleRequest"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleRequest` + +
Deserialize<'de> for AdapterLifecycleRequest"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleRequest` + +
AdapterLifecycleRequest"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleRequest` + +
PartialEq for AdapterLifecycleRequest"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleRequest) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleRequest` + +
Serialize for AdapterLifecycleRequest"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleRequest` + +
StructuralPartialEq for AdapterLifecycleRequest"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecycleresponse.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecycleresponse.mdx new file mode 100644 index 000000000..2a6473e3f --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecycleresponse.mdx @@ -0,0 +1,102 @@ +--- +title: "Struct Adapter Lifecycle Response" +sidebar-title: "AdapterLifecycleResponse" +description: "One newline-delimited response returned by a persistent adapter host." +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 nemo-fabric-core`. + +
String,\n    pub operation: AdapterLifecycleOperation,\n    pub outcome: AdapterLifecycleOutcome,\n}"}} />
+ +One newline-delimited response returned by a persistent adapter host. + +## Fields + +### `contract_version: String` + +Lifecycle protocol version. + +### `operation: AdapterLifecycleOperation` + +Operation completed by this response. + +### `outcome: AdapterLifecycleOutcome` + +Normalized success or failure outcome. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleResponse` + +
Clone for AdapterLifecycleResponse"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleResponse"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleResponse` + +
Debug for AdapterLifecycleResponse"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleResponse` + +
Deserialize<'de> for AdapterLifecycleResponse"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleResponse` + +
AdapterLifecycleResponse"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleResponse` + +
PartialEq for AdapterLifecycleResponse"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleResponse) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleResponse` + +
Serialize for AdapterLifecycleResponse"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleResponse` + +
StructuralPartialEq for AdapterLifecycleResponse"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestart.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestart.mdx new file mode 100644 index 000000000..f55ca018a --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestart.mdx @@ -0,0 +1,110 @@ +--- +title: "Struct Adapter Lifecycle Start" +sidebar-title: "AdapterLifecycleStart" +description: "Start payload sent once when Fabric creates a persistent adapter host." +position: 4 +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +Generated from `cargo doc --no-deps -p nemo-fabric-core`. + +
RuntimeHandle,\n    pub effective_config: EffectiveConfig,\n    pub capability_plan: CapabilityPlan,\n    pub capabilities: RuntimeCapabilities,\n    pub telemetry_plan: Option<TelemetryPlan>,\n}"}} />
+ +Start payload sent once when Fabric creates a persistent adapter host. + +## Fields + +### `runtime: RuntimeHandle` + +Runtime identity and prepared environment owned by this host. + +### `effective_config: EffectiveConfig` + +Merged agent config and provenance for this runtime. + +### `capability_plan: CapabilityPlan` + +Capability routing selected during planning. + +### `capabilities: RuntimeCapabilities` + +Lifecycle capabilities selected during planning. + +### `telemetry_plan: Option` + +Telemetry routing selected during planning. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleStart` + +
Clone for AdapterLifecycleStart"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleStart"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleStart` + +
Debug for AdapterLifecycleStart"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleStart` + +
Deserialize<'de> for AdapterLifecycleStart"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleStart` + +
AdapterLifecycleStart"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleStart` + +
PartialEq for AdapterLifecycleStart"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleStart) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleStart` + +
Serialize for AdapterLifecycleStart"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleStart` + +
StructuralPartialEq for AdapterLifecycleStart"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestop.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestop.mdx new file mode 100644 index 000000000..bd049391d --- /dev/null +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-adapterlifecyclestop.mdx @@ -0,0 +1,94 @@ +--- +title: "Struct Adapter Lifecycle Stop" +sidebar-title: "AdapterLifecycleStop" +description: "Stop payload sent once when Fabric releases a persistent adapter host." +position: 5 +--- +{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 */} + +Generated from `cargo doc --no-deps -p nemo-fabric-core`. + +
String,\n}"}} />
+ +Stop payload sent once when Fabric releases a persistent adapter host. + +## Fields + +### `runtime_id: String` + +Runtime being stopped. + +## Trait Implementations + +### `impl Clone for AdapterLifecycleStop` + +
Clone for AdapterLifecycleStop"}} />
+ +#### `clone` + +
clone(&self) -> AdapterLifecycleStop"}} />
+ +#### `clone_from` + +
clone_from(&mut self, source: &Self)"}} />
+ +### `impl Debug for AdapterLifecycleStop` + +
Debug for AdapterLifecycleStop"}} />
+ +#### `fmt` + +
fmt(&self, f: &mut Formatter<'_>) -> Result"}} />
+ +### `impl<'de> Deserialize<'de> for AdapterLifecycleStop` + +
Deserialize<'de> for AdapterLifecycleStop"}} />
+ +#### `deserialize` + +
deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where\n    __D: Deserializer<'de>,"}} />
+ +### `impl JsonSchema for AdapterLifecycleStop` + +
AdapterLifecycleStop"}} />
+ +#### `schema_name` + +
Cow<'static, str>"}} />
+ +#### `schema_id` + +
Cow<'static, str>"}} />
+ +#### `json_schema` + +
+ +#### `inline_schema` + +
bool"}} />
+ +### `impl PartialEq for AdapterLifecycleStop` + +
PartialEq for AdapterLifecycleStop"}} />
+ +#### `eq` + +
eq(&self, other: &AdapterLifecycleStop) -> bool"}} />
+ +#### `ne` + +
ne(&self, other: &Rhs) -> bool"}} />
+ +### `impl Serialize for AdapterLifecycleStop` + +
Serialize for AdapterLifecycleStop"}} />
+ +#### `serialize` + +
serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where\n    __S: Serializer,"}} />
+ +### `impl StructuralPartialEq for AdapterLifecycleStop` + +
StructuralPartialEq for AdapterLifecycleStop"}} />
diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactmanifest.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactmanifest.mdx index 08fa09016..3cc147a62 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactmanifest.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactmanifest.mdx @@ -2,7 +2,7 @@ title: "Struct Artifact Manifest" sidebar-title: "ArtifactManifest" description: "Manifest of run artifacts." -position: 2 +position: 6 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactref.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactref.mdx index 682072afd..25bf19b77 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactref.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-artifactref.mdx @@ -2,7 +2,7 @@ title: "Struct Artifact Ref" sidebar-title: "ArtifactRef" description: "Reference to one artifact." -position: 3 +position: 7 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-environmenthandle.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-environmenthandle.mdx index 18dffa0be..2ab2454ce 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-environmenthandle.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-environmenthandle.mdx @@ -2,7 +2,7 @@ title: "Struct Environment Handle" sidebar-title: "EnvironmentHandle" description: "Resolved execution environment context." -position: 4 +position: 8 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-errorinfo.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-errorinfo.mdx index 2ac7f2184..eaafac4cc 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-errorinfo.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-errorinfo.mdx @@ -2,7 +2,7 @@ title: "Struct Error Info" sidebar-title: "ErrorInfo" description: "Normalized error metadata." -position: 5 +position: 9 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-fabricevent.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-fabricevent.mdx index f8677facb..ab0a2d1ec 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-fabricevent.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-fabricevent.mdx @@ -2,7 +2,7 @@ title: "Struct Fabric Event" sidebar-title: "FabricEvent" description: "Fabric lifecycle or progress event." -position: 6 +position: 10 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-invocationhandle.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-invocationhandle.mdx index 57f8879fa..98e08dc72 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-invocationhandle.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-invocationhandle.mdx @@ -2,7 +2,7 @@ title: "Struct Invocation Handle" sidebar-title: "InvocationHandle" description: "One request sent to a runtime." -position: 7 +position: 11 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runrequest.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runrequest.mdx index f6f5fa597..bddd9a804 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runrequest.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runrequest.mdx @@ -2,7 +2,7 @@ title: "Struct RunRequest" sidebar-title: "RunRequest" description: "A request passed to a Fabric-managed harness runtime." -position: 8 +position: 12 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runresult.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runresult.mdx index 3f0018cbb..9443bd543 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runresult.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runresult.mdx @@ -2,7 +2,7 @@ title: "Struct RunResult" sidebar-title: "RunResult" description: "Result from a Fabric-managed harness invocation." -position: 9 +position: 13 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimecontext.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimecontext.mdx index 3b27d3591..7579ceaee 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimecontext.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimecontext.mdx @@ -2,7 +2,7 @@ title: "Struct Runtime Context" sidebar-title: "RuntimeContext" description: "Per-run/per-invocation context passed to harness adapters." -position: 10 +position: 14 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimehandle.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimehandle.mdx index 58e9dfa2c..23dfb4b24 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimehandle.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimehandle.mdx @@ -2,14 +2,14 @@ title: "Struct Runtime Handle" sidebar-title: "RuntimeHandle" description: "Active or resumable harness runtime." -position: 11 +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 nemo-fabric-core`. -
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}"}} />
+
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 execution_strategy: ExecutionStrategy,\n    pub environment: EnvironmentHandle,\n}"}} />
Active or resumable harness runtime. @@ -39,6 +39,10 @@ Adapter kind. Adapter implementation id. +### `execution_strategy: ExecutionStrategy` + +Execution strategy selected for this runtime. + ### `environment: EnvironmentHandle` Prepared environment. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimetelemetrycontext.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimetelemetrycontext.mdx index b8b2ff3a3..51d2a8f95 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimetelemetrycontext.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimetelemetrycontext.mdx @@ -2,7 +2,7 @@ title: "Struct Runtime Telemetry Context" sidebar-title: "RuntimeTelemetryContext" description: "Runtime telemetry config passed to adapters." -position: 12 +position: 16 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-telemetryref.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-telemetryref.mdx index 1b18679e6..2cf267bc3 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-telemetryref.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-telemetryref.mdx @@ -2,7 +2,7 @@ title: "Struct Telemetry Ref" sidebar-title: "TelemetryRef" description: "Reference to telemetry emitted by Relay or another configured telemetry path." -position: 13 +position: 17 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/enum-schemaname.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/enum-schemaname.mdx index 97a1e7f7c..56d7ba00e 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/enum-schemaname.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/enum-schemaname.mdx @@ -11,12 +11,14 @@ Generated from `cargo doc --no-deps -p nemo-fabric-core`. ```rust pub enum SchemaName { -Show 15 variants Agent, +Show 17 variants Agent, Profile, AdapterDescriptor, EffectiveConfig, RunPlan, AdapterInvocation, + AdapterLifecycleRequest, + AdapterLifecycleResponse, RuntimeContext, EnvironmentHandle, RuntimeHandle, @@ -69,6 +71,18 @@ Resolved run plan schema. Adapter-facing invocation payload schema. +### `AdapterLifecycleRequest` + +
+ +Persistent-host adapter lifecycle request schema. + +### `AdapterLifecycleResponse` + +
+ +Persistent-host adapter lifecycle response schema. + ### `RuntimeContext`
@@ -131,7 +145,7 @@ Fabric lifecycle event schema. #### `ALL` -
15]"}} />
+
17]"}} />
All public schemas in stable output order. diff --git a/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx b/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx index 88d03afa2..a62c03e26 100644 --- a/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx +++ b/docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx @@ -2,7 +2,7 @@ title: "Module schema" sidebar-title: "schema" description: "JSON Schema generation for the public Fabric contract." -position: 80 +position: 90 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} diff --git a/docs/sdk/python.mdx b/docs/sdk/python.mdx index 7689ceebd..5d7412e74 100644 --- a/docs/sdk/python.mdx +++ b/docs/sdk/python.mdx @@ -125,6 +125,69 @@ objects. Fabric provides the runtime contract. Applications own scheduling, queues, retries, worker scaling, and the number of runtimes to run. +### Select An Adapter Execution Strategy + +`harness.settings.runtime_strategy` requests how the selected adapter is +hosted. Fabric validates the requested value against the selected adapter +descriptor. The resolved value is recorded in `RunPlan`, `RuntimeHandle`, and +the adapter invocation payload so execution cannot silently use a different +strategy. + +The shared contract defines three values: + +| Strategy | Behavior | +| --- | --- | +| `process_per_invocation` | Start a fresh adapter process for each invocation. This is the compatibility default, including for descriptors that omit execution support. | +| `persistent_local_host` | Start one local adapter host for a Fabric runtime, reuse it for ordered invocations, and stop it deterministically. | +| `remote_service` | Allocate or connect to an adapter-owned remote harness service. Endpoint resolution, authentication, connection ownership, health checks, and release behavior remain adapter-owned. | + +Request a persistent host only when its adapter descriptor declares support: + +```python +config = FabricConfig( + metadata=MetadataConfig(name="review-agent"), + harness=HarnessConfig( + adapter_id="example.fabric.adapter", + settings={"runtime_strategy": "persistent_local_host"}, + ), +) +``` + +```json +{ + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + } +} +``` + +Planning fails when the requested strategy is not declared by the selected +adapter. Unknown values also fail during planning. Fabric never falls back to +`process_per_invocation` after another strategy has been selected. + +The Claude and Codex adapters currently declare `process_per_invocation` and +`persistent_local_host`. Their SDK calls still depend on local harness control +processes, so they do not declare `remote_service`. A remote-capable adapter +must provide an actual remote harness lifecycle transport; calling a remotely +hosted model from a local harness does not satisfy that contract. + +Persistent hosts exchange one JSON request and response per line over standard +input and output. The protocol uses the `adapter-lifecycle-request` and +`adapter-lifecycle-response` schemas and has three operations: `start`, +`invoke`, and `stop`. Standard output is reserved for protocol responses; +adapter diagnostics belong on standard error. + +Fabric owns the host process, runtime identifier, protocol files, and teardown. +The adapter owns harness-native session or conversation identifiers and keeps +them private inside the host. One persistent host belongs to one Fabric +runtime. A host crash makes that runtime handle unusable; Fabric does not +silently create a replacement or replay an invocation. Stop removes the host +from the active registry before requesting shutdown, force-releases the child +after either a success or failure response, and is idempotent on retry. Hosts +must exit after a successful `stop` response and when protocol input reaches +end-of-file. + ## Configure Agents In Code Build the complete nested `FabricConfig` directly, or start with a base @@ -455,6 +518,7 @@ reference for exact Python signatures. New application code should prefer | 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` | +| Persistent-host adapter protocol | `schemas/adapter-lifecycle-request.schema.json`, `schemas/adapter-lifecycle-response.schema.json` | | Artifacts and errors | `schemas/artifact-manifest.schema.json`, `schemas/error-info.schema.json` | ### Versioning @@ -466,6 +530,8 @@ maintained artifacts cross package boundaries: and profile files. - `contract_version` identifies the adapter descriptor contract implemented by a `fabric-adapter.json` file. +- `execution.lifecycle_contract_version` identifies the stateful + start/invoke/stop protocol implemented by an adapter. - Python and Rust package versions identify the installed SDK/core release. Fabric versions top-level persisted documents and independently maintained diff --git a/examples/harbor/swebench/adapters/claude/fabric-adapter.json b/examples/harbor/swebench/adapters/claude/fabric-adapter.json index ed36b146a..86fd7ee5b 100644 --- a/examples/harbor/swebench/adapters/claude/fabric-adapter.json +++ b/examples/harbor/swebench/adapters/claude/fabric-adapter.json @@ -10,6 +10,10 @@ "config": { "accepts": ["models", "tools", "tools.blocked", "mcp", "skills", "telemetry"] }, + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"] + }, "telemetry": { "providers": { "relay": { diff --git a/examples/harbor/swebench/adapters/hermes/fabric-adapter.json b/examples/harbor/swebench/adapters/hermes/fabric-adapter.json index f28574707..3111f12c3 100644 --- a/examples/harbor/swebench/adapters/hermes/fabric-adapter.json +++ b/examples/harbor/swebench/adapters/hermes/fabric-adapter.json @@ -22,6 +22,9 @@ "telemetry" ] }, + "execution": { + "strategies": ["process_per_invocation"] + }, "telemetry": { "providers": { "relay": { diff --git a/python/src/nemo_fabric/types.py b/python/src/nemo_fabric/types.py index 1298b71af..3384474bb 100644 --- a/python/src/nemo_fabric/types.py +++ b/python/src/nemo_fabric/types.py @@ -981,6 +981,7 @@ class RunPlan(FabricMapping): agent_name: Resolved agent name. profiles: Applied profile names in caller order. adapter: Resolved adapter identity. + execution_strategy: Adapter execution strategy selected by planning. capabilities: Operations declared by the resolved runtime. """ @@ -988,8 +989,18 @@ class RunPlan(FabricMapping): agent_name: str profiles: Sequence[str] adapter: AdapterInfo + execution_strategy: str capabilities: RuntimeCapabilities - _fields = frozenset({"effective_config", "agent_name", "profiles", "adapter", "capabilities"}) + _fields = frozenset( + { + "effective_config", + "agent_name", + "profiles", + "adapter", + "execution_strategy", + "capabilities", + } + ) @classmethod def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: @@ -999,6 +1010,9 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: data["effective_config"] = EffectiveConfig.from_mapping(data["effective_config"]) data["profiles"] = _required_profiles(data, "RunPlan") data["adapter"] = AdapterInfo.from_mapping(descriptor) + data["execution_strategy"] = _required_text( + data.get("execution_strategy"), "execution strategy" + ) data["capabilities"] = RuntimeCapabilities.from_mapping(data.get("capabilities", {})) return data @@ -1199,6 +1213,7 @@ class RuntimeHandle(FabricMapping): harness: Stable harness identifier. adapter_kind: Adapter execution mechanism. adapter_id: Optional Fabric adapter identifier. + execution_strategy: Adapter execution strategy selected for this runtime. environment: Prepared environment snapshot. """ @@ -1208,6 +1223,7 @@ class RuntimeHandle(FabricMapping): harness: str adapter_kind: str adapter_id: str | None + execution_strategy: str environment: Mapping[str, Any] _fields = frozenset( { @@ -1217,6 +1233,7 @@ class RuntimeHandle(FabricMapping): "harness", "adapter_kind", "adapter_id", + "execution_strategy", "environment", } ) @@ -1230,6 +1247,7 @@ def _normalize(cls, data: dict[str, Any]) -> dict[str, Any]: "agent_name", "harness", "adapter_kind", + "execution_strategy", ): data[field] = _required_text(data.get(field), field.replace("_", " ")) if data.get("adapter_id") is not None: diff --git a/schemas/SCHEMA.md b/schemas/SCHEMA.md index ef262463c..a6bee6f29 100644 --- a/schemas/SCHEMA.md +++ b/schemas/SCHEMA.md @@ -31,6 +31,10 @@ schema-alignment tests in the same change. - `adapter-invocation`: adapter-facing payload sent to inline or process adapters. +- `adapter-lifecycle-request`: versioned `start`, `invoke`, or `stop` request + sent over an adapter-owned stateful lifecycle transport. +- `adapter-lifecycle-response`: normalized success or failure returned for one + stateful lifecycle operation. - `runtime-context`: per-run/per-invocation context included in adapter invocations. - `run-request`: per-invocation request/input. diff --git a/schemas/adapter-descriptor.schema.json b/schemas/adapter-descriptor.schema.json index 7eb806702..12b22bad5 100644 --- a/schemas/adapter-descriptor.schema.json +++ b/schemas/adapter-descriptor.schema.json @@ -21,6 +21,28 @@ }, "type": "object" }, + "AdapterExecutionSupport": { + "additionalProperties": true, + "description": "Execution strategies implemented by an adapter.", + "properties": { + "lifecycle_contract_version": { + "description": "Version of the external start/invoke/stop contract used by persistent strategies.", + "type": [ + "string", + "null" + ] + }, + "strategies": { + "description": "Execution strategies implemented by this adapter.", + "items": { + "$ref": "#/$defs/ExecutionStrategy" + }, + "type": "array", + "uniqueItems": true + } + }, + "type": "object" + }, "AdapterKind": { "description": "Adapter implementation kind.", "oneOf": [ @@ -123,6 +145,26 @@ }, "type": "object" }, + "ExecutionStrategy": { + "description": "How the selected adapter executes harness work for one Fabric runtime.", + "oneOf": [ + { + "const": "process_per_invocation", + "description": "Launch a fresh adapter process for each invocation.", + "type": "string" + }, + { + "const": "persistent_local_host", + "description": "Start one adapter-owned local host and reuse it for the runtime.", + "type": "string" + }, + { + "const": "remote_service", + "description": "Allocate or connect to an adapter-owned remote harness service.", + "type": "string" + } + ] + }, "RuntimeCapabilities": { "description": "Lifecycle behavior implemented by a resolved runtime path.", "properties": { @@ -188,6 +230,10 @@ "minLength": 1, "type": "string" }, + "execution": { + "$ref": "#/$defs/AdapterExecutionSupport", + "description": "Runtime execution strategies implemented by this adapter." + }, "harness": { "description": "Stable machine-readable harness identifier implemented by this adapter.", "minLength": 1, @@ -217,4 +263,4 @@ ], "title": "AdapterDescriptor", "type": "object" -} +} \ No newline at end of file diff --git a/schemas/adapter-invocation.schema.json b/schemas/adapter-invocation.schema.json index d1624c1ca..08764fe05 100644 --- a/schemas/adapter-invocation.schema.json +++ b/schemas/adapter-invocation.schema.json @@ -377,6 +377,26 @@ } ] }, + "ExecutionStrategy": { + "description": "How the selected adapter executes harness work for one Fabric runtime.", + "oneOf": [ + { + "const": "process_per_invocation", + "description": "Launch a fresh adapter process for each invocation.", + "type": "string" + }, + { + "const": "persistent_local_host", + "description": "Start one adapter-owned local host and reuse it for the runtime.", + "type": "string" + }, + { + "const": "remote_service", + "description": "Allocate or connect to an adapter-owned remote harness service.", + "type": "string" + } + ] + }, "FabricConfig": { "additionalProperties": true, "description": "Versioned Fabric agent config.", @@ -1552,6 +1572,10 @@ "$ref": "#/$defs/EffectiveConfig", "description": "Merged agent config and provenance." }, + "execution_strategy": { + "$ref": "#/$defs/ExecutionStrategy", + "description": "Execution strategy selected during planning." + }, "request": { "$ref": "#/$defs/RunRequest", "description": "Per-invocation request." @@ -1574,6 +1598,7 @@ }, "required": [ "effective_config", + "execution_strategy", "runtime_context", "request" ], diff --git a/schemas/adapter-lifecycle-request.schema.json b/schemas/adapter-lifecycle-request.schema.json new file mode 100644 index 000000000..84125bae6 --- /dev/null +++ b/schemas/adapter-lifecycle-request.schema.json @@ -0,0 +1,1848 @@ +{ + "$defs": { + "AdapterInvocation": { + "description": "Adapter-facing invocation payload.", + "properties": { + "capability_plan": { + "$ref": "#/$defs/CapabilityPlan", + "default": { + "managed": { + "tools_configured": false + }, + "native": { + "tools_configured": false + }, + "tools": {}, + "tools_configured": false, + "unsupported": { + "tools_configured": false + } + }, + "description": "Derived capability routing plan for the selected adapter." + }, + "effective_config": { + "$ref": "#/$defs/EffectiveConfig", + "description": "Merged agent config and provenance." + }, + "execution_strategy": { + "$ref": "#/$defs/ExecutionStrategy", + "description": "Execution strategy selected during planning." + }, + "request": { + "$ref": "#/$defs/RunRequest", + "description": "Per-invocation request." + }, + "runtime_context": { + "$ref": "#/$defs/RuntimeContext", + "description": "Per-runtime/per-invocation execution context." + }, + "telemetry_plan": { + "anyOf": [ + { + "$ref": "#/$defs/TelemetryPlan" + }, + { + "type": "null" + } + ], + "description": "Derived telemetry plan for the selected adapter." + } + }, + "required": [ + "effective_config", + "execution_strategy", + "runtime_context", + "request" + ], + "type": "object" + }, + "AdapterKind": { + "description": "Adapter implementation kind.", + "oneOf": [ + { + "const": "process", + "description": "Launch and supervise a CLI process.", + "type": "string" + }, + { + "const": "http", + "description": "Connect to a service or HTTP-backed harness.", + "type": "string" + }, + { + "const": "python", + "description": "Call a Python SDK/plugin adapter.", + "type": "string" + }, + { + "const": "native_plugin", + "description": "Delegate to a harness-native plugin package.", + "type": "string" + } + ] + }, + "AdapterLifecycleStart": { + "description": "Start payload sent once when Fabric creates a persistent adapter host.", + "properties": { + "capabilities": { + "$ref": "#/$defs/RuntimeCapabilities", + "default": { + "cancellation": false, + "service": false, + "streaming": false, + "updates": false + }, + "description": "Lifecycle capabilities selected during planning." + }, + "capability_plan": { + "$ref": "#/$defs/CapabilityPlan", + "default": { + "managed": { + "tools_configured": false + }, + "native": { + "tools_configured": false + }, + "tools": {}, + "tools_configured": false, + "unsupported": { + "tools_configured": false + } + }, + "description": "Capability routing selected during planning." + }, + "effective_config": { + "$ref": "#/$defs/EffectiveConfig", + "description": "Merged agent config and provenance for this runtime." + }, + "runtime": { + "$ref": "#/$defs/RuntimeHandle", + "description": "Runtime identity and prepared environment owned by this host." + }, + "telemetry_plan": { + "anyOf": [ + { + "$ref": "#/$defs/TelemetryPlan" + }, + { + "type": "null" + } + ], + "description": "Telemetry routing selected during planning." + } + }, + "required": [ + "runtime", + "effective_config" + ], + "type": "object" + }, + "AdapterLifecycleStop": { + "description": "Stop payload sent once when Fabric releases a persistent adapter host.", + "properties": { + "runtime_id": { + "description": "Runtime being stopped.", + "type": "string" + } + }, + "required": [ + "runtime_id" + ], + "type": "object" + }, + "ArtifactManifest": { + "description": "Manifest of run artifacts.", + "properties": { + "artifacts": { + "description": "Artifact entries.", + "items": { + "$ref": "#/$defs/ArtifactRef" + }, + "type": "array" + }, + "root": { + "description": "Artifact root directory.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "ArtifactRef": { + "description": "Reference to one artifact.", + "properties": { + "kind": { + "description": "Artifact kind.", + "type": "string" + }, + "media_type": { + "description": "Optional media type.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Logical artifact name.", + "type": "string" + }, + "path": { + "description": "Artifact path.", + "type": "string" + } + }, + "required": [ + "name", + "kind", + "path" + ], + "type": "object" + }, + "CapabilityKind": { + "description": "Capability kind.", + "oneOf": [ + { + "const": "tools", + "description": "Tool config.", + "type": "string" + }, + { + "const": "skills", + "description": "Skill paths.", + "type": "string" + }, + { + "const": "mcp", + "description": "MCP server.", + "type": "string" + } + ] + }, + "CapabilityPlan": { + "description": "Resolved capability configuration.", + "properties": { + "managed": { + "$ref": "#/$defs/CapabilityTargetPlan", + "default": { + "tools_configured": false + }, + "description": "Capabilities that Fabric must expose or manage outside the native harness config." + }, + "mcp_servers": { + "additionalProperties": { + "$ref": "#/$defs/McpServerPlan" + }, + "description": "MCP server exposure plan.", + "type": "object" + }, + "native": { + "$ref": "#/$defs/CapabilityTargetPlan", + "default": { + "tools_configured": false + }, + "description": "Capabilities mapped into the harness-native surface." + }, + "routes": { + "description": "Routing decisions made while resolving the effective config.", + "items": { + "$ref": "#/$defs/CapabilityRoute" + }, + "type": "array" + }, + "skill_paths": { + "description": "Resolved skill paths.", + "items": { + "type": "string" + }, + "type": "array" + }, + "tools": { + "$ref": "#/$defs/ToolsPlan", + "default": {}, + "description": "Normalized tool policy." + }, + "tools_configured": { + "default": false, + "description": "Whether tool configuration was provided.", + "type": "boolean" + }, + "unsupported": { + "$ref": "#/$defs/CapabilityTargetPlan", + "default": { + "tools_configured": false + }, + "description": "Capabilities that are configured but not executable by this Fabric build." + } + }, + "type": "object" + }, + "CapabilityRoute": { + "description": "One capability routing decision.", + "properties": { + "kind": { + "$ref": "#/$defs/CapabilityKind", + "description": "Capability kind." + }, + "name": { + "description": "Capability name.", + "type": "string" + }, + "reason": { + "description": "Human-readable reason for the selected route.", + "type": "string" + }, + "target": { + "$ref": "#/$defs/CapabilityTarget", + "description": "Routing target." + } + }, + "required": [ + "kind", + "name", + "target", + "reason" + ], + "type": "object" + }, + "CapabilityTarget": { + "description": "Capability routing target.", + "oneOf": [ + { + "const": "harness_native", + "description": "Adapter maps the capability into harness-native config.", + "type": "string" + }, + { + "const": "fabric_managed", + "description": "Fabric exposes or manages the capability around the harness.", + "type": "string" + }, + { + "const": "unsupported", + "description": "Capability is configured but no executable surface exists.", + "type": "string" + } + ] + }, + "CapabilityTargetPlan": { + "description": "Capabilities routed to one target.", + "properties": { + "mcp_servers": { + "additionalProperties": { + "$ref": "#/$defs/McpServerPlan" + }, + "description": "MCP servers for this target.", + "type": "object" + }, + "skill_paths": { + "description": "Resolved skill paths for this target.", + "items": { + "type": "string" + }, + "type": "array" + }, + "tools_configured": { + "default": false, + "description": "Whether tool configuration was provided for this target.", + "type": "boolean" + } + }, + "type": "object" + }, + "ControlLocation": { + "description": "Where Fabric control code runs relative to the environment.", + "oneOf": [ + { + "const": "external_control", + "description": "Fabric runs on the host/control plane and starts or connects to the harness in the environment.", + "type": "string" + }, + { + "const": "in_env_control", + "description": "Fabric runs inside the prepared environment with the harness.", + "type": "string" + } + ] + }, + "EffectiveConfig": { + "description": "Merged Fabric config after applying selected profiles.", + "properties": { + "agent_name": { + "description": "Stable agent name.", + "type": "string" + }, + "agent_root": { + "description": "Root used to resolve agent package paths.", + "type": "string" + }, + "config": { + "$ref": "#/$defs/FabricConfig", + "description": "Merged Fabric config with authoring-time profile discovery removed." + }, + "config_path": { + "description": "Resolved Fabric config path.", + "type": "string" + }, + "config_root": { + "description": "Root used to resolve config-local paths.", + "type": "string" + }, + "profiles": { + "description": "Ordered selected profiles.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "agent_name", + "profiles", + "agent_root", + "config_path", + "config_root", + "config" + ], + "type": "object" + }, + "EnvironmentConfig": { + "additionalProperties": true, + "description": "Execution environment configuration.", + "properties": { + "artifacts": { + "description": "Artifact path inside or outside the provider.", + "type": [ + "string", + "null" + ] + }, + "connection": { + "additionalProperties": true, + "description": "Provider connection metadata, such as server URL, credential reference, or namespace.", + "type": "object" + }, + "control_location": { + "$ref": "#/$defs/ControlLocation", + "default": "in_env_control", + "description": "Where Fabric control code runs relative to the environment." + }, + "metadata": { + "additionalProperties": true, + "description": "Consumer-provided environment metadata.", + "type": "object" + }, + "ownership": { + "$ref": "#/$defs/EnvironmentOwnership", + "default": "caller_owned", + "description": "Whether Fabric owns the environment resource." + }, + "provider": { + "description": "Environment provider, for example `local`, `docker`, `opensandbox`, or `k8s`.", + "type": "string" + }, + "settings": { + "additionalProperties": true, + "description": "Provider-specific settings.", + "type": "object" + }, + "workspace": { + "description": "Workspace path inside or outside the provider.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "provider" + ], + "type": "object" + }, + "EnvironmentHandle": { + "description": "Resolved execution environment context.", + "properties": { + "artifacts": { + "description": "Artifact root visible to the harness runtime.", + "type": [ + "string", + "null" + ] + }, + "connection": { + "additionalProperties": true, + "description": "Provider connection metadata.", + "type": "object" + }, + "control_location": { + "$ref": "#/$defs/ControlLocation", + "description": "Where Fabric control code runs." + }, + "environment_id": { + "description": "Environment handle id.", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "description": "Provider-specific metadata.", + "type": "object" + }, + "ownership": { + "$ref": "#/$defs/EnvironmentOwnership", + "description": "Whether Fabric owns the environment resource." + }, + "provider": { + "description": "Environment provider.", + "type": "string" + }, + "workspace": { + "description": "Workspace visible to the harness runtime.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "environment_id", + "provider", + "control_location", + "ownership" + ], + "type": "object" + }, + "EnvironmentOwnership": { + "description": "Whether Fabric owns the underlying environment resource.", + "oneOf": [ + { + "const": "caller_owned", + "description": "The caller or a surrounding system owns the environment resource.", + "type": "string" + }, + { + "const": "fabric_owned", + "description": "Fabric created or leased the environment resource and may release it.", + "type": "string" + } + ] + }, + "ExecutionStrategy": { + "description": "How the selected adapter executes harness work for one Fabric runtime.", + "oneOf": [ + { + "const": "process_per_invocation", + "description": "Launch a fresh adapter process for each invocation.", + "type": "string" + }, + { + "const": "persistent_local_host", + "description": "Start one adapter-owned local host and reuse it for the runtime.", + "type": "string" + }, + { + "const": "remote_service", + "description": "Allocate or connect to an adapter-owned remote harness service.", + "type": "string" + } + ] + }, + "FabricConfig": { + "additionalProperties": true, + "description": "Versioned Fabric agent config.", + "properties": { + "environment": { + "anyOf": [ + { + "$ref": "#/$defs/EnvironmentConfig" + }, + { + "type": "null" + } + ], + "description": "Environment where the harness or its tools execute." + }, + "harness": { + "$ref": "#/$defs/HarnessConfig", + "description": "Harness selection and harness-specific settings." + }, + "mcp": { + "anyOf": [ + { + "$ref": "#/$defs/McpConfig" + }, + { + "type": "null" + } + ], + "description": "MCP capability configuration." + }, + "metadata": { + "$ref": "#/$defs/MetadataConfig", + "description": "Human-readable metadata." + }, + "models": { + "additionalProperties": { + "$ref": "#/$defs/ModelConfig" + }, + "description": "Model aliases.", + "type": "object" + }, + "profiles": { + "$ref": "#/$defs/ProfileRegistryConfig", + "description": "Optional profile discovery config." + }, + "relay": { + "anyOf": [ + { + "$ref": "#/$defs/RelayConfig" + }, + { + "type": "null" + } + ], + "description": "First-class NeMo Relay integration configuration." + }, + "runtime": { + "$ref": "#/$defs/RuntimeConfig", + "description": "Runtime input/output contract." + }, + "schema_version": { + "description": "Config schema version.", + "type": "string" + }, + "skills": { + "anyOf": [ + { + "$ref": "#/$defs/SkillConfig" + }, + { + "type": "null" + } + ], + "description": "Skill capability configuration." + }, + "telemetry": { + "anyOf": [ + { + "$ref": "#/$defs/TelemetryConfig" + }, + { + "type": "null" + } + ], + "description": "Telemetry configuration." + }, + "tools": { + "anyOf": [ + { + "$ref": "#/$defs/ToolsConfig" + }, + { + "type": "null" + } + ], + "description": "Tool capability configuration." + } + }, + "required": [ + "schema_version", + "metadata", + "harness", + "runtime" + ], + "type": "object" + }, + "HarnessConfig": { + "additionalProperties": true, + "description": "Harness selection.", + "properties": { + "adapter_id": { + "description": "Adapter implementation id.", + "type": "string" + }, + "resolution": { + "anyOf": [ + { + "$ref": "#/$defs/ResolutionStrategy" + }, + { + "type": "null" + } + ], + "description": "Selected install or availability strategy." + }, + "settings": { + "additionalProperties": true, + "description": "Harness-specific settings.", + "type": "object" + } + }, + "required": [ + "adapter_id" + ], + "type": "object" + }, + "McpConfig": { + "additionalProperties": true, + "description": "MCP capability configuration.", + "properties": { + "servers": { + "additionalProperties": { + "$ref": "#/$defs/McpServerConfig" + }, + "description": "Named MCP servers.", + "type": "object" + } + }, + "type": "object" + }, + "McpExposure": { + "description": "MCP exposure strategy.", + "oneOf": [ + { + "const": "harness_native", + "description": "Map into harness-native MCP config through the selected adapter.", + "type": "string" + }, + { + "const": "fabric_managed", + "description": "Fabric manages MCP and exposes basic tools/actions.", + "type": "string" + } + ] + }, + "McpServerConfig": { + "additionalProperties": true, + "description": "MCP server configuration.", + "properties": { + "exposure": { + "$ref": "#/$defs/McpExposure", + "description": "How Fabric exposes the MCP capability to the harness." + }, + "transport": { + "description": "MCP transport.", + "type": "string" + }, + "url": { + "description": "MCP server URL or process command, depending on transport.", + "type": "string" + } + }, + "required": [ + "transport", + "url", + "exposure" + ], + "type": "object" + }, + "McpServerPlan": { + "description": "Resolved MCP server exposure.", + "properties": { + "exposure": { + "$ref": "#/$defs/McpExposure", + "description": "Exposure strategy." + }, + "transport": { + "description": "MCP transport.", + "type": "string" + }, + "url": { + "description": "MCP URL or command.", + "type": "string" + } + }, + "required": [ + "transport", + "url", + "exposure" + ], + "type": "object" + }, + "MetadataConfig": { + "additionalProperties": true, + "description": "Human-readable metadata.", + "properties": { + "description": { + "description": "Optional description.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Agent/config name.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "ModelConfig": { + "additionalProperties": true, + "description": "Model configuration.", + "properties": { + "api_key_env": { + "description": "Optional environment variable containing an API key.", + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Provider model identifier.", + "type": "string" + }, + "provider": { + "description": "Model provider name.", + "type": "string" + }, + "settings": { + "additionalProperties": true, + "description": "Provider-specific settings.", + "type": "object" + }, + "temperature": { + "description": "Optional temperature.", + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "provider", + "model" + ], + "type": "object" + }, + "ProfileRegistryConfig": { + "additionalProperties": true, + "description": "Profile discovery config for curated package profiles.", + "properties": { + "directories": { + "description": "Directories searched when a caller selects a profile by name.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "RelayAtifConfig": { + "additionalProperties": true, + "description": "Relay ATIF export configuration.", + "properties": { + "agent_name": { + "default": "NeMo Relay", + "description": "Agent name written into ATIF.", + "type": "string" + }, + "agent_version": { + "description": "Agent version written into ATIF.", + "type": [ + "string", + "null" + ] + }, + "enabled": { + "default": false, + "description": "Whether ATIF export is enabled.", + "type": "boolean" + }, + "extra": { + "description": "Extra ATIF metadata." + }, + "filename_template": { + "default": "nemo-relay-atif-{session_id}.json", + "description": "ATIF file name template.", + "type": "string" + }, + "model_name": { + "default": "unknown", + "description": "Model name written into ATIF.", + "type": "string" + }, + "output_directory": { + "description": "Directory used for ATIF files.", + "type": [ + "string", + "null" + ] + }, + "storage": { + "description": "Optional ATIF remote storage.", + "items": { + "$ref": "#/$defs/RelayAtifStorageConfig" + }, + "type": [ + "array", + "null" + ] + }, + "tool_definitions": { + "description": "Tool definitions written into ATIF.", + "items": true, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, + "RelayAtifStorageConfig": { + "description": "Relay ATIF remote storage configuration.", + "oneOf": [ + { + "additionalProperties": true, + "description": "Upload ATIF artifacts to HTTP storage.", + "properties": { + "endpoint": { + "default": "", + "description": "HTTP storage endpoint.", + "type": "string" + }, + "header_env": { + "additionalProperties": { + "type": "string" + }, + "description": "Environment-variable-backed HTTP headers.", + "type": "object" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Static HTTP headers.", + "type": "object" + }, + "timeout_millis": { + "default": 3000, + "description": "Request timeout in milliseconds.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "type": { + "const": "http", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + { + "additionalProperties": true, + "description": "Upload ATIF artifacts to S3-compatible storage.", + "properties": { + "access_key_id": { + "description": "AWS access key id.", + "type": [ + "string", + "null" + ] + }, + "allow_http": { + "description": "Allow HTTP endpoints for S3-compatible storage.", + "type": [ + "boolean", + "null" + ] + }, + "bucket": { + "default": "", + "description": "S3 bucket name.", + "type": "string" + }, + "endpoint_url": { + "description": "S3-compatible endpoint URL.", + "type": [ + "string", + "null" + ] + }, + "key_prefix": { + "description": "Optional S3 object key prefix.", + "type": [ + "string", + "null" + ] + }, + "region": { + "description": "AWS region.", + "type": [ + "string", + "null" + ] + }, + "secret_access_key_var": { + "description": "Environment variable containing the AWS secret access key.", + "type": [ + "string", + "null" + ] + }, + "session_token_var": { + "description": "Environment variable containing the AWS session token.", + "type": [ + "string", + "null" + ] + }, + "type": { + "const": "s3", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + } + ] + }, + "RelayAtofConfig": { + "additionalProperties": true, + "description": "Relay ATOF export configuration.", + "properties": { + "enabled": { + "default": false, + "description": "Whether ATOF export is enabled.", + "type": "boolean" + }, + "endpoints": { + "description": "Optional remote ATOF endpoints.", + "items": { + "$ref": "#/$defs/RelayAtofEndpointConfig" + }, + "type": "array" + }, + "filename": { + "description": "ATOF file name.", + "type": [ + "string", + "null" + ] + }, + "mode": { + "$ref": "#/$defs/RelayAtofMode", + "default": "append", + "description": "File write mode." + }, + "output_directory": { + "description": "Directory used for ATOF files.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "RelayAtofEndpointConfig": { + "additionalProperties": true, + "description": "Relay ATOF endpoint configuration.", + "properties": { + "field_name_policy": { + "$ref": "#/$defs/RelayAtofEndpointFieldNamePolicy", + "default": "preserve", + "description": "Field-name handling policy." + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Endpoint headers.", + "type": "object" + }, + "timeout_millis": { + "default": 3000, + "description": "Request timeout in milliseconds.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "transport": { + "$ref": "#/$defs/RelayAtofEndpointTransport", + "default": "http_post", + "description": "Endpoint transport." + }, + "url": { + "description": "Endpoint URL.", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + }, + "RelayAtofEndpointFieldNamePolicy": { + "description": "Relay ATOF endpoint field-name policy.", + "oneOf": [ + { + "const": "preserve", + "description": "Preserve field names.", + "type": "string" + }, + { + "const": "replace_dots", + "description": "Replace dots in field names.", + "type": "string" + } + ] + }, + "RelayAtofEndpointTransport": { + "description": "Relay ATOF endpoint transport.", + "oneOf": [ + { + "const": "http_post", + "description": "HTTP POST transport.", + "type": "string" + }, + { + "const": "websocket", + "description": "WebSocket transport.", + "type": "string" + }, + { + "const": "ndjson", + "description": "NDJSON transport.", + "type": "string" + } + ] + }, + "RelayAtofMode": { + "description": "Relay ATOF file mode.", + "oneOf": [ + { + "const": "append", + "description": "Append to an existing ATOF file.", + "type": "string" + }, + { + "const": "overwrite", + "description": "Overwrite an existing ATOF file.", + "type": "string" + } + ] + }, + "RelayComponentConfig": { + "additionalProperties": true, + "description": "Generic NeMo Relay plugin component configuration.", + "properties": { + "config": { + "additionalProperties": true, + "description": "Component-local Relay plugin config.", + "type": "object" + }, + "enabled": { + "default": true, + "description": "Whether this Relay component should be activated.", + "type": "boolean" + }, + "kind": { + "description": "Registered Relay plugin kind.", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "RelayConfig": { + "additionalProperties": true, + "description": "NeMo Relay integration configuration.", + "properties": { + "components": { + "description": "Additional Relay plugin components.", + "items": { + "$ref": "#/$defs/RelayComponentConfig" + }, + "type": "array" + }, + "observability": { + "anyOf": [ + { + "$ref": "#/$defs/RelayObservabilityConfig" + }, + { + "type": "null" + } + ], + "description": "Relay observability component configuration." + }, + "output_dir": { + "description": "Optional Relay output directory.", + "type": [ + "string", + "null" + ] + }, + "policy": { + "anyOf": [ + { + "$ref": "#/$defs/RelayConfigPolicy" + }, + { + "type": "null" + } + ], + "description": "Relay plugin validation policy." + }, + "project": { + "description": "Optional project name for Relay backends.", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "RelayConfigPolicy": { + "description": "Relay validation policy.", + "properties": { + "unknown_component": { + "$ref": "#/$defs/RelayUnsupportedBehavior", + "default": "warn", + "description": "Policy for unknown components." + }, + "unknown_field": { + "$ref": "#/$defs/RelayUnsupportedBehavior", + "default": "warn", + "description": "Policy for unknown fields." + }, + "unsupported_value": { + "$ref": "#/$defs/RelayUnsupportedBehavior", + "default": "error", + "description": "Policy for unsupported values." + } + }, + "type": "object" + }, + "RelayObservabilityConfig": { + "additionalProperties": true, + "description": "NeMo Relay observability component configuration.", + "properties": { + "atif": { + "anyOf": [ + { + "$ref": "#/$defs/RelayAtifConfig" + }, + { + "type": "null" + } + ], + "description": "ATIF export configuration." + }, + "atof": { + "anyOf": [ + { + "$ref": "#/$defs/RelayAtofConfig" + }, + { + "type": "null" + } + ], + "description": "ATOF export configuration." + }, + "openinference": { + "anyOf": [ + { + "$ref": "#/$defs/RelayOtlpConfig" + }, + { + "type": "null" + } + ], + "description": "OpenInference export configuration." + }, + "opentelemetry": { + "anyOf": [ + { + "$ref": "#/$defs/RelayOtlpConfig" + }, + { + "type": "null" + } + ], + "description": "OpenTelemetry export configuration." + }, + "policy": { + "anyOf": [ + { + "$ref": "#/$defs/RelayConfigPolicy" + }, + { + "type": "null" + } + ], + "description": "Relay config validation policy." + }, + "version": { + "default": 1, + "description": "Relay observability config version.", + "format": "uint32", + "minimum": 0, + "type": "integer" + } + }, + "type": "object" + }, + "RelayOtlpConfig": { + "additionalProperties": true, + "description": "Relay OpenTelemetry/OpenInference export configuration.", + "properties": { + "enabled": { + "default": false, + "description": "Whether OTLP export is enabled.", + "type": "boolean" + }, + "endpoint": { + "description": "OTLP endpoint.", + "type": [ + "string", + "null" + ] + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "OTLP headers.", + "type": "object" + }, + "instrumentation_scope": { + "description": "OTLP instrumentation scope.", + "type": [ + "string", + "null" + ] + }, + "resource_attributes": { + "additionalProperties": { + "type": "string" + }, + "description": "OTLP resource attributes.", + "type": "object" + }, + "service_name": { + "default": "nemo-relay", + "description": "OTLP service name.", + "type": "string" + }, + "service_namespace": { + "description": "OTLP service namespace.", + "type": [ + "string", + "null" + ] + }, + "service_version": { + "description": "OTLP service version.", + "type": [ + "string", + "null" + ] + }, + "timeout_millis": { + "default": 3000, + "description": "Request timeout in milliseconds.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "transport": { + "$ref": "#/$defs/RelayOtlpTransport", + "default": "http_binary", + "description": "OTLP transport." + } + }, + "type": "object" + }, + "RelayOtlpTransport": { + "description": "Relay OTLP transport.", + "oneOf": [ + { + "const": "http_binary", + "description": "OTLP HTTP binary transport.", + "type": "string" + }, + { + "const": "grpc", + "description": "OTLP gRPC transport.", + "type": "string" + } + ] + }, + "RelayUnsupportedBehavior": { + "description": "Relay unsupported/unknown config handling.", + "oneOf": [ + { + "const": "ignore", + "description": "Ignore the unsupported or unknown value.", + "type": "string" + }, + { + "const": "warn", + "description": "Warn on the unsupported or unknown value.", + "type": "string" + }, + { + "const": "error", + "description": "Error on the unsupported or unknown value.", + "type": "string" + } + ] + }, + "ResolutionStrategy": { + "description": "Adapter install or availability strategy.", + "oneOf": [ + { + "const": "preinstalled", + "description": "Harness is already available in the prepared environment.", + "type": "string" + }, + { + "const": "image_provided", + "description": "Environment image already contains the harness and dependencies.", + "type": "string" + }, + { + "const": "pip_uv", + "description": "Adapter may install a Python package with pip or uv.", + "type": "string" + }, + { + "const": "npm", + "description": "Adapter may install a Node package.", + "type": "string" + }, + { + "const": "source", + "description": "Adapter may install from source.", + "type": "string" + }, + { + "const": "service", + "description": "Adapter connects to an already-running service.", + "type": "string" + }, + { + "const": "native_plugin", + "description": "Adapter is installed through a harness-native plugin manager.", + "type": "string" + } + ] + }, + "RunRequest": { + "description": "A request passed to a Fabric-managed harness runtime.", + "properties": { + "context": { + "additionalProperties": true, + "description": "Runtime context such as task, rollout, workflow, or caller metadata.", + "type": "object" + }, + "input": { + "default": null, + "description": "Request payload for the harness." + }, + "overrides": { + "description": "Per-invocation overrides allowed by the resolved profile." + }, + "request_id": { + "description": "Request id.", + "type": "string" + } + }, + "required": [ + "request_id" + ], + "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" + }, + "RuntimeConfig": { + "additionalProperties": true, + "description": "Runtime input/output contract.", + "properties": { + "artifacts": { + "description": "Artifact directory.", + "type": [ + "string", + "null" + ] + }, + "input_schema": { + "default": "text", + "description": "Input schema label.", + "type": "string" + }, + "output_schema": { + "default": "text", + "description": "Output schema label.", + "type": "string" + } + }, + "type": "object" + }, + "RuntimeContext": { + "description": "Per-run/per-invocation context passed to harness adapters.", + "properties": { + "artifacts": { + "$ref": "#/$defs/ArtifactManifest", + "description": "Artifact manifest visible to the adapter at invocation start." + }, + "environment": { + "$ref": "#/$defs/EnvironmentHandle", + "description": "Prepared execution environment." + }, + "invocation_id": { + "description": "Invocation handle id.", + "type": "string" + }, + "request_id": { + "description": "Request id.", + "type": "string" + }, + "runtime_id": { + "description": "Runtime handle id.", + "type": "string" + }, + "telemetry": { + "anyOf": [ + { + "$ref": "#/$defs/RuntimeTelemetryContext" + }, + { + "type": "null" + } + ], + "description": "Runtime telemetry context generated for this invocation." + } + }, + "required": [ + "runtime_id", + "invocation_id", + "request_id", + "environment", + "artifacts" + ], + "type": "object" + }, + "RuntimeHandle": { + "description": "Active or resumable harness runtime.", + "properties": { + "adapter_id": { + "description": "Adapter implementation id.", + "type": [ + "string", + "null" + ] + }, + "adapter_kind": { + "$ref": "#/$defs/AdapterKind", + "description": "Adapter kind." + }, + "agent_name": { + "description": "Agent name.", + "type": "string" + }, + "environment": { + "$ref": "#/$defs/EnvironmentHandle", + "description": "Prepared environment." + }, + "execution_strategy": { + "$ref": "#/$defs/ExecutionStrategy", + "description": "Execution strategy selected for this runtime." + }, + "harness": { + "description": "Stable machine-readable harness identifier.", + "type": "string" + }, + "runtime_binding": { + "description": "Fabric-owned opaque binding for this runtime handle.", + "type": "string" + }, + "runtime_id": { + "description": "Runtime handle id.", + "type": "string" + } + }, + "required": [ + "runtime_id", + "runtime_binding", + "agent_name", + "harness", + "adapter_kind", + "execution_strategy", + "environment" + ], + "type": "object" + }, + "RuntimeTelemetryContext": { + "description": "Runtime telemetry config passed to adapters.", + "properties": { + "config_path": { + "description": "Generated Relay config path for this invocation.", + "type": [ + "string", + "null" + ] + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "description": "Environment variables Fabric applies while invoking the adapter.", + "type": "object" + }, + "metadata": { + "additionalProperties": true, + "description": "Additional telemetry metadata surfaced to consumers and adapters.", + "type": "object" + }, + "relay_enabled": { + "description": "Whether Relay is enabled for this invocation.", + "type": "boolean" + } + }, + "required": [ + "relay_enabled" + ], + "type": "object" + }, + "SkillConfig": { + "additionalProperties": true, + "description": "Skill capability configuration.", + "properties": { + "paths": { + "description": "Skill paths relative to the agent root.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "TelemetryConfig": { + "additionalProperties": true, + "description": "Telemetry configuration.", + "properties": { + "providers": { + "additionalProperties": { + "$ref": "#/$defs/TelemetryProviderConfig" + }, + "description": "Telemetry providers enabled for this run.", + "type": "object" + } + }, + "type": "object" + }, + "TelemetryPlan": { + "description": "Resolved telemetry plan.", + "properties": { + "adapter_outputs": { + "description": "Telemetry outputs declared by the selected adapter descriptor.", + "items": { + "type": "string" + }, + "type": "array" + }, + "native_config": { + "description": "Native telemetry pass-through config." + }, + "providers": { + "description": "Telemetry providers selected for this run.", + "items": { + "$ref": "#/$defs/TelemetryProvider" + }, + "type": "array" + }, + "relay_config": { + "description": "Relay pass-through config." + }, + "relay_enabled": { + "description": "Whether Relay is enabled.", + "type": "boolean" + }, + "relay_output_dir": { + "description": "Relay output directory, when configured.", + "type": [ + "string", + "null" + ] + }, + "relay_project": { + "description": "Relay project, when configured.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "providers", + "relay_enabled" + ], + "type": "object" + }, + "TelemetryProvider": { + "description": "Telemetry runtime provider.", + "oneOf": [ + { + "const": "relay", + "description": "Use NeMo Relay for telemetry integration.", + "type": "string" + }, + { + "const": "native", + "description": "Let the selected adapter handle telemetry natively.", + "type": "string" + } + ] + }, + "TelemetryProviderConfig": { + "additionalProperties": true, + "description": "Provider-specific telemetry configuration.", + "properties": { + "config": { + "description": "Provider-specific pass-through config." + } + }, + "type": "object" + }, + "ToolsConfig": { + "additionalProperties": true, + "description": "Harness-neutral tool capability configuration.", + "properties": { + "blocked": { + "description": "Adapter-native tool names or toolset names to block.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "ToolsPlan": { + "description": "Normalized tool policy for a run.", + "properties": { + "blocked": { + "description": "Adapter-native tool names or toolset names to block.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "One newline-delimited request sent to a persistent adapter host.", + "oneOf": [ + { + "description": "Initialize the host.", + "properties": { + "operation": { + "const": "start", + "type": "string" + }, + "payload": { + "$ref": "#/$defs/AdapterLifecycleStart" + } + }, + "required": [ + "operation", + "payload" + ], + "type": "object" + }, + { + "description": "Execute one invocation.", + "properties": { + "operation": { + "const": "invoke", + "type": "string" + }, + "payload": { + "$ref": "#/$defs/AdapterInvocation" + } + }, + "required": [ + "operation", + "payload" + ], + "type": "object" + }, + { + "description": "Stop the host.", + "properties": { + "operation": { + "const": "stop", + "type": "string" + }, + "payload": { + "$ref": "#/$defs/AdapterLifecycleStop" + } + }, + "required": [ + "operation", + "payload" + ], + "type": "object" + } + ], + "properties": { + "contract_version": { + "description": "Lifecycle protocol version.", + "type": "string" + } + }, + "required": [ + "contract_version" + ], + "title": "AdapterLifecycleRequest", + "type": "object" +} \ No newline at end of file diff --git a/schemas/adapter-lifecycle-response.schema.json b/schemas/adapter-lifecycle-response.schema.json new file mode 100644 index 000000000..1c62f2b18 --- /dev/null +++ b/schemas/adapter-lifecycle-response.schema.json @@ -0,0 +1,165 @@ +{ + "$defs": { + "AdapterLifecycleOperation": { + "description": "Operation exchanged over the versioned persistent-host adapter protocol.", + "oneOf": [ + { + "const": "start", + "description": "Initialize one adapter-owned host for a Fabric runtime.", + "type": "string" + }, + { + "const": "invoke", + "description": "Execute one invocation against an initialized host.", + "type": "string" + }, + { + "const": "stop", + "description": "Release the host and all runtime-owned resources.", + "type": "string" + } + ] + }, + "AdapterLifecycleOutcome": { + "description": "Outcome returned by a persistent adapter host lifecycle operation.", + "oneOf": [ + { + "description": "The operation completed successfully.", + "properties": { + "output": { + "default": null, + "description": "Operation output. Only invoke normally returns a non-null value." + }, + "status": { + "const": "succeeded", + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + { + "description": "The operation failed with normalized lifecycle diagnostics.", + "properties": { + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Structured failure reported by the adapter host." + }, + "status": { + "const": "failed", + "type": "string" + } + }, + "required": [ + "status", + "error" + ], + "type": "object" + } + ] + }, + "ErrorInfo": { + "description": "Normalized error metadata.", + "properties": { + "code": { + "description": "Stable error code.", + "type": "string" + }, + "message": { + "description": "Human-readable error message.", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "description": "Adapter or runtime metadata useful for diagnostics.", + "type": "object" + }, + "retryable": { + "default": false, + "description": "Whether Fabric considers this failure safe for a consumer-level retry.", + "type": "boolean" + }, + "stage": { + "$ref": "#/$defs/ErrorStage", + "description": "Fabric lifecycle stage where the failure surfaced." + } + }, + "required": [ + "stage", + "code", + "message" + ], + "type": "object" + }, + "ErrorStage": { + "description": "Fabric lifecycle stage associated with an error.", + "oneOf": [ + { + "const": "config", + "description": "Configuration or profile loading failed.", + "type": "string" + }, + { + "const": "plan", + "description": "Effective config planning failed.", + "type": "string" + }, + { + "const": "prepare", + "description": "Environment preparation failed.", + "type": "string" + }, + { + "const": "start", + "description": "Runtime start/connect failed.", + "type": "string" + }, + { + "const": "invoke", + "description": "Runtime invocation failed.", + "type": "string" + }, + { + "const": "stop", + "description": "Runtime stop/detach failed.", + "type": "string" + }, + { + "const": "release", + "description": "Environment release failed.", + "type": "string" + }, + { + "const": "artifact", + "description": "Artifact collection or writing failed.", + "type": "string" + } + ] + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "One newline-delimited response returned by a persistent adapter host.", + "properties": { + "contract_version": { + "description": "Lifecycle protocol version.", + "type": "string" + }, + "operation": { + "$ref": "#/$defs/AdapterLifecycleOperation", + "description": "Operation completed by this response." + }, + "outcome": { + "$ref": "#/$defs/AdapterLifecycleOutcome", + "description": "Normalized success or failure outcome." + } + }, + "required": [ + "contract_version", + "operation", + "outcome" + ], + "title": "AdapterLifecycleResponse", + "type": "object" +} \ No newline at end of file diff --git a/schemas/run-plan.schema.json b/schemas/run-plan.schema.json index a5f6efd4e..4fbbe6c86 100644 --- a/schemas/run-plan.schema.json +++ b/schemas/run-plan.schema.json @@ -54,6 +54,10 @@ "minLength": 1, "type": "string" }, + "execution": { + "$ref": "#/$defs/AdapterExecutionSupport", + "description": "Runtime execution strategies implemented by this adapter." + }, "harness": { "description": "Stable machine-readable harness identifier implemented by this adapter.", "minLength": 1, @@ -98,6 +102,28 @@ } ] }, + "AdapterExecutionSupport": { + "additionalProperties": true, + "description": "Execution strategies implemented by an adapter.", + "properties": { + "lifecycle_contract_version": { + "description": "Version of the external start/invoke/stop contract used by persistent strategies.", + "type": [ + "string", + "null" + ] + }, + "strategies": { + "description": "Execution strategies implemented by this adapter.", + "items": { + "$ref": "#/$defs/ExecutionStrategy" + }, + "type": "array", + "uniqueItems": true + } + }, + "type": "object" + }, "AdapterKind": { "description": "Adapter implementation kind.", "oneOf": [ @@ -527,6 +553,26 @@ ], "type": "object" }, + "ExecutionStrategy": { + "description": "How the selected adapter executes harness work for one Fabric runtime.", + "oneOf": [ + { + "const": "process_per_invocation", + "description": "Launch a fresh adapter process for each invocation.", + "type": "string" + }, + { + "const": "persistent_local_host", + "description": "Start one adapter-owned local host and reuse it for the runtime.", + "type": "string" + }, + { + "const": "remote_service", + "description": "Allocate or connect to an adapter-owned remote harness service.", + "type": "string" + } + ] + }, "FabricConfig": { "additionalProperties": true, "description": "Versioned Fabric agent config.", @@ -1706,6 +1752,10 @@ ], "description": "Resolved environment plan." }, + "execution_strategy": { + "$ref": "#/$defs/ExecutionStrategy", + "description": "Adapter execution strategy selected during planning." + }, "profiles": { "description": "Ordered selected profiles.", "items": { @@ -1740,6 +1790,7 @@ "effective_config", "agent_name", "profiles", + "execution_strategy", "capabilities", "agent_root", "config_path", @@ -1748,4 +1799,4 @@ ], "title": "RunPlan", "type": "object" -} +} \ No newline at end of file diff --git a/schemas/runtime-handle.schema.json b/schemas/runtime-handle.schema.json index 85ba2296f..b0a108b85 100644 --- a/schemas/runtime-handle.schema.json +++ b/schemas/runtime-handle.schema.json @@ -106,6 +106,26 @@ "type": "string" } ] + }, + "ExecutionStrategy": { + "description": "How the selected adapter executes harness work for one Fabric runtime.", + "oneOf": [ + { + "const": "process_per_invocation", + "description": "Launch a fresh adapter process for each invocation.", + "type": "string" + }, + { + "const": "persistent_local_host", + "description": "Start one adapter-owned local host and reuse it for the runtime.", + "type": "string" + }, + { + "const": "remote_service", + "description": "Allocate or connect to an adapter-owned remote harness service.", + "type": "string" + } + ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -130,6 +150,10 @@ "$ref": "#/$defs/EnvironmentHandle", "description": "Prepared environment." }, + "execution_strategy": { + "$ref": "#/$defs/ExecutionStrategy", + "description": "Execution strategy selected for this runtime." + }, "harness": { "description": "Stable machine-readable harness identifier.", "type": "string" @@ -149,6 +173,7 @@ "agent_name", "harness", "adapter_kind", + "execution_strategy", "environment" ], "title": "RuntimeHandle", diff --git a/tests/adapters/test_adapters_common_lifecycle.py b/tests/adapters/test_adapters_common_lifecycle.py new file mode 100644 index 000000000..1c1812fc4 --- /dev/null +++ b/tests/adapters/test_adapters_common_lifecycle.py @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import io +import json +import os +from typing import Any + +from nemo_fabric_adapters.common import lifecycle + + +def _request(operation: str, payload: dict[str, Any]) -> dict[str, Any]: + return { + "contract_version": lifecycle.CONTRACT_VERSION, + "operation": operation, + "payload": payload, + } + + +def test_lifecycle_host_orders_one_runtime_and_preserves_adapter_results(): + runtime_id = "runtime-1" + requests = [ + _request("start", {"runtime": {"runtime_id": runtime_id}}), + _request( + "invoke", + { + "runtime_context": {"runtime_id": runtime_id}, + "request": {"input": "first"}, + }, + ), + _request( + "invoke", + { + "runtime_context": {"runtime_id": runtime_id}, + "request": {"input": "second"}, + }, + ), + _request("stop", {"runtime_id": runtime_id}), + ] + input_stream = io.StringIO("".join(f"{json.dumps(item)}\n" for item in requests)) + output_stream = io.StringIO() + seen: list[str] = [] + + def run(payload: dict[str, Any]) -> dict[str, Any]: + prompt = payload["request"]["input"] + seen.append(prompt) + return {"failed": prompt == "second", "response": prompt} + + lifecycle.serve(run, input_stream=input_stream, output_stream=output_stream) + + responses = [json.loads(line) for line in output_stream.getvalue().splitlines()] + assert [item["operation"] for item in responses] == [ + "start", + "invoke", + "invoke", + "stop", + ] + assert all(item["outcome"]["status"] == "succeeded" for item in responses) + assert responses[2]["outcome"]["output"] == { + "failed": True, + "response": "second", + } + assert seen == ["first", "second"] + + +def test_lifecycle_host_rejects_runtime_mismatch_without_invoking_adapter(): + requests = [ + _request("start", {"runtime": {"runtime_id": "runtime-1"}}), + _request( + "invoke", + { + "runtime_context": {"runtime_id": "runtime-2"}, + "request": {"input": "do not run"}, + }, + ), + _request("stop", {"runtime_id": "runtime-1"}), + ] + input_stream = io.StringIO("".join(f"{json.dumps(item)}\n" for item in requests)) + output_stream = io.StringIO() + + lifecycle.serve( + lambda payload: (_ for _ in ()).throw(AssertionError(payload)), + input_stream=input_stream, + output_stream=output_stream, + ) + + responses = [json.loads(line) for line in output_stream.getvalue().splitlines()] + assert responses[1]["outcome"]["status"] == "failed" + assert responses[1]["outcome"]["error"]["code"] == "lifecycle_runtime_mismatch" + + +def test_lifecycle_host_keeps_adapter_stdout_out_of_protocol(capsys): + runtime_id = "runtime-1" + requests = [ + _request("start", {"runtime": {"runtime_id": runtime_id}}), + _request( + "invoke", + { + "runtime_context": {"runtime_id": runtime_id}, + "request": {"input": "hello"}, + }, + ), + _request("stop", {"runtime_id": runtime_id}), + ] + input_stream = io.StringIO("".join(f"{json.dumps(item)}\n" for item in requests)) + output_stream = io.StringIO() + + def run(_payload: dict[str, Any]) -> dict[str, Any]: + print("adapter diagnostic") + return {"failed": False} + + lifecycle.serve(run, input_stream=input_stream, output_stream=output_stream) + + assert "adapter diagnostic" not in output_stream.getvalue() + assert "adapter diagnostic" in capsys.readouterr().err + + +def test_lifecycle_host_scopes_invocation_telemetry_environment(monkeypatch): + runtime_id = "runtime-1" + variable = "FABRIC_TEST_LIFECYCLE_ENV" + monkeypatch.setenv(variable, "host-value") + requests = [ + _request("start", {"runtime": {"runtime_id": runtime_id}}), + _request( + "invoke", + { + "runtime_context": { + "runtime_id": runtime_id, + "telemetry": {"env": {variable: "invocation-value"}}, + }, + "request": {"input": "hello"}, + }, + ), + _request("stop", {"runtime_id": runtime_id}), + ] + input_stream = io.StringIO("".join(f"{json.dumps(item)}\n" for item in requests)) + output_stream = io.StringIO() + + lifecycle.serve( + lambda _payload: {"value": os.environ[variable]}, + input_stream=input_stream, + output_stream=output_stream, + ) + + responses = [json.loads(line) for line in output_stream.getvalue().splitlines()] + assert responses[1]["outcome"]["output"] == {"value": "invocation-value"} + assert os.environ[variable] == "host-value" diff --git a/tests/adapters/test_claude_adapter.py b/tests/adapters/test_claude_adapter.py index 26376b1b0..84f003998 100644 --- a/tests/adapters/test_claude_adapter.py +++ b/tests/adapters/test_claude_adapter.py @@ -62,6 +62,10 @@ def test_claude_descriptor_is_narrow_and_versioned(): "telemetry", ] }, + "execution": { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"], + }, "telemetry": { "providers": { "relay": { @@ -963,3 +967,16 @@ def test_main_normalizes_payload_load_failure(monkeypatch, capsys): output = json.loads(capsys.readouterr().out) assert output["error"]["code"] == "claude_adapter_internal_error" assert "secret" not in json.dumps(output) + + +def test_main_serves_lifecycle_protocol_when_requested(monkeypatch): + serve = MagicMock() + monkeypatch.setenv( + adapter.lifecycle.CONTRACT_ENV, + adapter.lifecycle.CONTRACT_VERSION, + ) + monkeypatch.setattr(adapter.lifecycle, "serve", serve) + + adapter.main() + + serve.assert_called_once_with(adapter.run) diff --git a/tests/adapters/test_codex_adapter.py b/tests/adapters/test_codex_adapter.py index 991605de0..2929923bc 100644 --- a/tests/adapters/test_codex_adapter.py +++ b/tests/adapters/test_codex_adapter.py @@ -15,6 +15,22 @@ from openai_codex.types import TurnStatus +ROOT = Path(__file__).resolve().parents[2] + + +def test_codex_descriptor_declares_supported_runtime_strategies(): + descriptor = json.loads( + (ROOT / "adapters" / "codex" / "fabric-adapter.json").read_text( + encoding="utf-8" + ) + ) + + assert descriptor["execution"] == { + "lifecycle_contract_version": "fabric.adapter.lifecycle/v1alpha1", + "strategies": ["process_per_invocation", "persistent_local_host"], + } + + @pytest.fixture(name="codex_payload") def codex_payload_fixture(tmp_path): workspace = tmp_path / "workspace" @@ -565,6 +581,19 @@ def test_adapter_rejects_structured_input(codex_payload): assert output["error"]["code"] == "codex_invalid_request" +def test_main_serves_lifecycle_protocol_when_requested(monkeypatch): + serve = MagicMock() + monkeypatch.setenv( + adapter.lifecycle.CONTRACT_ENV, + adapter.lifecycle.CONTRACT_VERSION, + ) + monkeypatch.setattr(adapter.lifecycle, "serve", serve) + + adapter.main() + + serve.assert_called_once_with(adapter.run) + + def test_descriptor_has_no_codex_binary_requirement(): descriptor = json.loads( ( diff --git a/tests/python/test_code_review_example.py b/tests/python/test_code_review_example.py index 80d5a80d9..8dd65681d 100644 --- a/tests/python/test_code_review_example.py +++ b/tests/python/test_code_review_example.py @@ -93,6 +93,27 @@ def test_variants_plan_without_file_profiles(): assert plan.adapter.adapter_id == config.harness.adapter_id +def test_claude_and_codex_plan_persistent_local_hosts(): + client = Fabric() + + for config in (claude_config(), codex_config()): + config.harness.settings["runtime_strategy"] = "persistent_local_host" + + plan = client.plan(config, base_dir=BASE_DIR) + + assert plan.execution_strategy == "persistent_local_host" + + +async def test_claude_and_codex_persistent_hosts_start_and_stop(): + client = Fabric() + + for config in (claude_config(), codex_config()): + config.harness.settings["runtime_strategy"] = "persistent_local_host" + + async with await client.start_runtime(config, base_dir=BASE_DIR) as runtime: + assert runtime.handle.execution_strategy == "persistent_local_host" + + def test_example_entrypoint_plans_without_starting_a_runtime(): cases = ( ([], "nvidia.fabric.hermes", False), diff --git a/tests/python/test_runtime.py b/tests/python/test_runtime.py index e354b0249..954d48aed 100644 --- a/tests/python/test_runtime.py +++ b/tests/python/test_runtime.py @@ -57,6 +57,7 @@ def _plan() -> dict[str, Any]: "adapter_kind": "python", } }, + "execution_strategy": "process_per_invocation", "capabilities": { "service": False, "streaming": False, @@ -74,6 +75,7 @@ def _runtime(runtime_id: str = "runtime-1") -> dict[str, Any]: "harness": "hermes", "adapter_kind": "python", "adapter_id": "test.fabric.shim", + "execution_strategy": "process_per_invocation", "environment": { "environment_id": "environment-1", "provider": "local", diff --git a/tests/python/test_sdk_contract.py b/tests/python/test_sdk_contract.py index f66f93692..df8f3f4c8 100644 --- a/tests/python/test_sdk_contract.py +++ b/tests/python/test_sdk_contract.py @@ -405,6 +405,7 @@ def test_inspection_models_are_typed_read_only_mappings(): "future": "value", } }, + "execution_strategy": "process_per_invocation", "capabilities": { "service": False, "streaming": False, @@ -419,6 +420,7 @@ def test_inspection_models_are_typed_read_only_mappings(): assert isinstance(plan.adapter, AdapterInfo) assert isinstance(plan.capabilities, RuntimeCapabilities) assert plan.profiles == ("runtime", "telemetry") + assert plan.execution_strategy == "process_per_invocation" assert plan.adapter.harness == "hermes" assert "harness_type" not in plan.adapter assert plan.adapter.extra_fields["future"] == "value" @@ -439,6 +441,7 @@ def test_runtime_handle_distinguishes_contract_and_extension_fields(): "harness": "hermes", "adapter_kind": "python", "adapter_id": "test.fabric.shim", + "execution_strategy": "process_per_invocation", "environment": { "environment_id": "environment-1", "provider": "local", @@ -449,6 +452,7 @@ def test_runtime_handle_distinguishes_contract_and_extension_fields(): } ) + assert handle.execution_strategy == "process_per_invocation" assert handle.extra_fields == {"future_handle_field": "value"} @@ -460,6 +464,7 @@ def test_runtime_handle_distinguishes_contract_and_extension_fields(): "agent_name", "harness", "adapter_kind", + "execution_strategy", "environment", ), ) @@ -492,6 +497,14 @@ def test_run_plan_requires_profiles(): RunPlan.from_mapping(raw) +def test_run_plan_requires_selected_execution_strategy(): + raw = _plan() + del raw["execution_strategy"] + + with pytest.raises(FabricConfigError, match="execution strategy"): + RunPlan.from_mapping(raw) + + def test_run_plan_config_enable_relay_preserves_existing_relay_fields(): config = _ResolvedFabricConfig.from_mapping(_plan()["config"]) @@ -582,6 +595,7 @@ def _plan() -> dict[str, Any]: "harness": "hermes", } }, + "execution_strategy": "process_per_invocation", "capabilities": { "service": False, "streaming": False, @@ -599,6 +613,7 @@ def _runtime() -> dict[str, Any]: "harness": "hermes", "adapter_kind": "python", "adapter_id": "test.fabric.shim", + "execution_strategy": "process_per_invocation", "environment": { "environment_id": "environment-1", "provider": "local", diff --git a/tests/python/test_sdk_runtimes.py b/tests/python/test_sdk_runtimes.py index ff442c387..88bf9e573 100644 --- a/tests/python/test_sdk_runtimes.py +++ b/tests/python/test_sdk_runtimes.py @@ -39,6 +39,7 @@ def _plan() -> dict[str, Any]: "harness": "hermes", } }, + "execution_strategy": "process_per_invocation", "capabilities": { "service": False, "streaming": False, @@ -56,6 +57,7 @@ def _runtime() -> dict[str, Any]: "harness": "hermes", "adapter_kind": "python", "adapter_id": "test.fabric.shim", + "execution_strategy": "process_per_invocation", "environment": { "environment_id": "environment-1", "provider": "local",