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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions crates/adaptive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ framework.

## Why Use It?

- ⚙️ **Install adaptive behavior through plugins**: Enable adaptive runtime
- **Install adaptive behavior through plugins**: Enable adaptive runtime
components through the same configuration path as other NeMo Relay plugins.
- 📈 **Learn from observed executions**: Derive runtime hints from scope, tool,
- **Learn from observed executions**: Derive runtime hints from scope, tool,
and LLM events without replacing the application framework.
- 💾 **Choose local or shared state**: Use in-memory state for local runs or the
- **Choose local or shared state**: Use in-memory state for local runs or the
optional Redis backend for shared persistence.
- 🧩 **Keep adaptive behavior reusable**: Package telemetry, hint injection,
- **Keep adaptive behavior reusable**: Package telemetry, hint injection,
tool parallelism, and cache-governor behavior behind stable component
settings.

## What You Get

- **`AdaptiveConfig`**: A canonical config contract for the top-level
- **`AdaptiveConfig`**: A canonical config contract for the top-level
`adaptive` plugin component.
- **Built-in component settings**: Typed config helpers for telemetry,
- **Built-in component settings**: Typed config helpers for telemetry,
adaptive hints, tool parallelism, and the Adaptive Cache Governor.
- **State backends**: In-memory state by default and Redis-backed state behind
- **State backends**: In-memory state by default and Redis-backed state behind
the `redis-backend` feature.
- **Learning primitives**: Runtime helpers and learners built on NeMo Relay
- **Learning primitives**: Runtime helpers and learners built on NeMo Relay
events.
- **Adaptive Cache Governor (ACG) module surface**: The canonical
- **Adaptive Cache Governor (ACG) module surface**: The canonical
`nemo_relay_adaptive::acg` module for PromptIR, provider plugins, stability
analysis, and cache telemetry normalization.

Expand Down
21 changes: 11 additions & 10 deletions crates/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,28 @@ with the installed `nemo-relay` command rather than link against the crate.

## Why Use It?

- 🧭 **Observe existing coding agents**: Run Claude Code, Codex, or Hermes
- **Observe existing coding agents**: Run Claude Code, Codex, or Hermes
Agent through a local NeMo Relay gateway without changing the agent
itself.
- 🛠️ **Configure hooks interactively**: Use the setup wizard to write project or
- **Configure hooks interactively**: Use the setup wizard to write project or
user config and install the hook files needed by supported agents.
- 📡 **Export local sessions**: Write ATIF trajectory files, ATOF event JSONL
- **Export local sessions**: Write ATIF trajectory files, ATOF event JSONL
streams, or OpenInference spans from one shared config model.
- 🩺 **Diagnose the machine**: Check config layers, agent binaries, hook status,
observability outputs, and shell completions with `nemo-relay doctor`.
- **Diagnose setup readiness**: Check config layers, `plugins.toml` discovery,
agent binaries, persistent host-plugin installs, hook status, observability
outputs, and shell completions with `nemo-relay doctor`.

## What You Get

- **`nemo-relay` binary**: The executable installed by the `nemo-relay-cli`
- **`nemo-relay` binary**: The executable installed by the `nemo-relay-cli`
Cargo package.
- **First-run setup**: Bare `nemo-relay` launches setup when no config exists,
- **First-run setup**: Bare `nemo-relay` launches setup when no config exists,
then runs doctor once config is present.
- **Agent shortcuts**: `nemo-relay claude`, `nemo-relay codex`, and
- **Agent shortcuts**: `nemo-relay claude`, `nemo-relay codex`, and
`nemo-relay hermes` start observed agent runs.
- **Config-driven launch**: `nemo-relay run` resolves config, environment, and
- **Config-driven launch**: `nemo-relay run` resolves config, environment, and
CLI overrides for deterministic non-interactive use.
- **Hook forwarding server**: A local gateway accepts agent hook events and
- **Hook forwarding server**: A local gateway accepts agent hook events and
provider-shaped OpenAI or Anthropic requests.

## Installation Options
Expand Down
53 changes: 45 additions & 8 deletions crates/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,11 @@ fn plugin_config_paths(
implicit_plugin_config_paths(std::env::current_dir().ok().as_deref(), user_config_dir())
}

/// Returns the implicit `plugins.toml` discovery paths used by the gateway and doctor.
pub(crate) fn default_plugin_config_paths() -> Vec<PathBuf> {
plugin_config_paths(None, None)
}

fn implicit_plugin_config_paths(
cwd: Option<&std::path::Path>,
user_config_dir: Option<PathBuf>,
Expand Down Expand Up @@ -1001,6 +1006,7 @@ struct PluginTomlConfig {
value: Option<Value>,
dynamic_plugins: Vec<ResolvedDynamicPluginConfig>,
dynamic_plugin_policy: DynamicPluginHostPolicy,
contributing_sources: Vec<PathBuf>,
}

#[derive(Debug, Clone, Default, Deserialize)]
Expand All @@ -1026,6 +1032,18 @@ fn load_plugin_toml_config(
load_plugin_toml_config_from_paths(plugin_config_paths(explicit, plugin_config_path))
}

/// Returns the physical `plugins.toml` files that contribute effective runtime or dynamic
/// plugin configuration under the default discovery rules.
pub(crate) fn effective_plugin_toml_sources() -> Result<Vec<PathBuf>, CliError> {
let Some(config) = load_plugin_toml_config(None, None)? else {
return Ok(Vec::new());
};
let mut sources = config.contributing_sources;
sources.sort();
sources.dedup();
Ok(sources)
}

Comment thread
willkill07 marked this conversation as resolved.
fn load_plugin_toml_config_from_paths<I>(paths: I) -> Result<Option<PluginTomlConfig>, CliError>
where
I: IntoIterator<Item = PathBuf>,
Expand All @@ -1034,6 +1052,7 @@ where
let mut dynamic_plugins = Vec::new();
let mut dynamic_plugin_policy = DynamicPluginHostPolicy::default();
let mut seen_plugin_ids = HashSet::new();
let mut contributing_sources = Vec::new();
let mut runtime_documents = Vec::new();

for path in &paths {
Expand All @@ -1051,6 +1070,11 @@ where
})?;
let resolved_plugins =
resolve_dynamic_plugin_refs(path, &mut parsed, &mut seen_plugin_ids)?;
if !resolved_plugins.dynamic_plugins.is_empty()
|| resolved_plugins.dynamic_plugin_policy != DynamicPluginHostPolicy::default()
{
contributing_sources.push(path.clone());
}
dynamic_plugins.extend(resolved_plugins.dynamic_plugins);
dynamic_plugin_policy.merge_from(resolved_plugins.dynamic_plugin_policy);
runtime_documents.push((
Expand All @@ -1067,17 +1091,24 @@ where
other => CliError::Config(other.to_string()),
})?;
match resolved {
Some((value, _sources)) => Ok(Some(PluginTomlConfig {
value: plugin_toml_runtime_value(value),
dynamic_plugins,
dynamic_plugin_policy,
})),
Some((value, sources)) => {
contributing_sources.extend(sources.iter().cloned());
contributing_sources.sort();
contributing_sources.dedup();
Ok(Some(PluginTomlConfig {
value: plugin_toml_runtime_value(value),
dynamic_plugins,
dynamic_plugin_policy,
contributing_sources,
}))
}
None => Ok((!dynamic_plugins.is_empty()
|| dynamic_plugin_policy != DynamicPluginHostPolicy::default())
.then_some(PluginTomlConfig {
value: None,
dynamic_plugins,
dynamic_plugin_policy,
contributing_sources,
})),
}
}
Expand Down Expand Up @@ -1137,12 +1168,18 @@ fn resolve_dynamic_plugin_refs(
for dynamic in plugins.dynamic {
let manifest_path = resolve_dynamic_manifest_path(source, &dynamic.manifest);
let (manifest, manifest_ref) = DynamicPluginManifest::load_from_path(&manifest_path)
.map_err(|error| CliError::Config(error.to_string()))?;
.map_err(|error| {
CliError::Config(format!(
"invalid dynamic plugin manifest referenced by {}: {error}",
source.display()
))
})?;
let plugin_id = manifest.plugin.id.trim().to_owned();
if !seen_plugin_ids.insert(plugin_id.clone()) {
return Err(CliError::Config(format!(
"duplicate dynamic plugin id '{}' across plugins.toml sources",
plugin_id
"duplicate dynamic plugin id '{}' in {} across plugins.toml sources",
plugin_id,
source.display()
)));
}
resolved.push(ResolvedDynamicPluginConfig {
Expand Down
Loading
Loading