Rust Quick Start

View as Markdown

This quick start shows the smallest Rust workflow that emits scope, tool, and LLM events.

Choose an Install Path

Pick the installation path that matches whether you are using a published package or a local checkout.

Install from a Package Manager

Use the published crates when you are consuming a release:

$cargo add nemo-relay@0.5.0
$cargo add serde_json
$cargo add tokio --features macros,rt-multi-thread

Install the published NeMo Relay CLI separately when you need coding-agent hook and LLM gateway observability:

$cargo install nemo-relay-cli@0.5.0

Install from the Repository

Use a path dependency when your application is consuming a local checkout:

1[dependencies]
2nemo-relay = { path = "../NeMo-Relay/crates/core" }
3serde_json = "1"
4tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
  • nemo-relay is the core Rust runtime surface.
  • tokio provides the async runtime used by the managed tool and LLM helpers in this quick start.
  • nemo-relay-adaptive is a companion crate for adaptive runtime primitives and Redis-backed learning components when you need adaptive behavior later, but it is not required for this minimal direct-runtime example.
  • nemo-relay-cli is a binary crate. Use cargo install nemo-relay-cli@0.5.0 when you need the NeMo Relay CLI.

Run One Scope, One Tool Call, and One LLM Call

The example below creates a scope, records an initialization mark, runs one managed tool call, and runs one managed LLM call through the public Rust APIs. Place it in src/main.rs in the same Cargo project where you added the dependencies above, then run cargo run.

1use nemo_relay::api::llm::{llm_call_execute, LlmCallExecuteParams, LlmRequest};
2use nemo_relay::api::scope::{
3 self, EmitMarkEventParams, PopScopeParams, PushScopeParams, ScopeAttributes, ScopeType,
4};
5use nemo_relay::api::subscriber::{deregister_subscriber, flush_subscribers, register_subscriber};
6use nemo_relay::api::tool::{tool_call_execute, ToolCallExecuteParams};
7use serde_json::json;
8use std::sync::Arc;
9
10#[tokio::main]
11async fn main() -> Result<(), Box<dyn std::error::Error>> {
12 register_subscriber(
13 "quickstart-printer",
14 Arc::new(|event| {
15 println!("{} {}", event.kind(), event.name());
16 }),
17 )?;
18
19 let handle = scope::push_scope(
20 PushScopeParams::builder()
21 .name("demo-agent")
22 .scope_type(ScopeType::Agent)
23 .attributes(ScopeAttributes::empty())
24 .data(json!({"binding": "rust"}))
25 .build(),
26 )?;
27
28 scope::event(
29 EmitMarkEventParams::builder()
30 .name("initialized")
31 .parent(&handle)
32 .data(json!({"ok": true}))
33 .build(),
34 )?;
35
36 let tool_result = tool_call_execute(
37 ToolCallExecuteParams::builder()
38 .name("search")
39 .args(json!({"query": "hello"}))
40 .func(Arc::new(|args| Box::pin(async move {
41 Ok(json!({"echo": args["query"]}))
42 })))
43 .parent(handle.clone())
44 .build(),
45 )
46 .await?;
47
48 let llm_result = llm_call_execute(
49 LlmCallExecuteParams::builder()
50 .name("demo-provider")
51 .request(LlmRequest {
52 headers: Default::default(),
53 content: json!({"messages": [{"role": "user", "content": "hi"}]}),
54 })
55 .func(Arc::new(|request| Box::pin(async move {
56 Ok(json!({
57 "messages": request.content["messages"],
58 "ok": true
59 }))
60 })))
61 .parent(handle.clone())
62 .model_name("demo-model")
63 .build(),
64 )
65 .await?;
66
67 println!("{tool_result}");
68 println!("{llm_result}");
69
70 scope::pop_scope(PopScopeParams::builder().handle_uuid(&handle.uuid).build())?;
71 flush_subscribers()?;
72 let _ = deregister_subscriber("quickstart-printer")?;
73 Ok(())
74}

What Success Looks Like

You should see:

  • Event lines for the root scope, managed tool and LLM lifecycles, plus the initialized mark event
  • {"echo":"hello"} from the tool call
  • A final object containing ok: true and the echoed message payload from the LLM callback

Native subscriber delivery is asynchronous, so flush before checking subscriber output. If you only see the returned JSON values and no event lines, the tool and LLM callbacks ran but you did not verify instrumentation.

That tells you two things:

  • The direct Rust runtime APIs ran successfully.
  • Emitted events were observable through the subscriber system.

What to Learn Next

Use these links to continue from the quick start into the core runtime concepts.