Basic Guide: Adding Scopes and Marks#

Use this guide when you want NeMo Flow to identify one agent run, request, workflow, or operation before you instrument individual tool and LLM calls.

What You Build#

You will create an active scope, emit named mark events inside that scope, and validate that subscribers can observe the lifecycle. The result is a small trace boundary that later tool calls, LLM calls, middleware, and exporters can attach to.

Scopes give emitted work ownership. Marks record point-in-time checkpoints inside that ownership boundary.

Before You Start#

Complete one binding Quick Start guide first:

You should know which application boundary should own the trace. Common scope boundaries include:

  • One HTTP request

  • One agent run

  • One workflow step

  • One background job

  • One tenant-isolated experiment

Integration Pattern#

Follow this sequence to keep framework work attached to the expected runtime context.

  1. Choose the scope boundary that owns the work.

  2. Register a temporary subscriber while validating instrumentation.

  3. Push or enter the scope before tool and LLM work begins.

  4. Emit marks for important checkpoints that are not full nested calls.

  5. Close the scope when the request, agent run, or workflow ends.

  6. Confirm that the subscriber sees scope start, mark, and scope end events.

Minimal Example#

The examples below create one agent-run scope and emit two marks.

import nemo_flow


def log_event(event) -> None:
    print(f"{event.kind} {event.name}")


nemo_flow.subscribers.register("scope-check", log_event)

try:
    with nemo_flow.scope.scope(
        "agent-run",
        nemo_flow.ScopeType.Agent,
        input={"request_id": "req-123"},
    ) as handle:
        nemo_flow.scope.event("planning-started", handle=handle, data={"step": 1})
        nemo_flow.scope.event("planning-finished", handle=handle, data={"step": 2})
finally:
    nemo_flow.subscribers.deregister("scope-check")
const {
  ScopeType,
  deregisterSubscriber,
  event,
  registerSubscriber,
  withScope,
} = require("nemo-flow-node");

async function main() {
  registerSubscriber("scope-check", (runtimeEvent) => {
    console.log(`${runtimeEvent.kind} ${runtimeEvent.name}`);
  });

  try {
    await withScope(
      "agent-run",
      ScopeType.Agent,
      async (handle) => {
        event("planning-started", handle, { step: 1 }, null);
        event("planning-finished", handle, { step: 2 }, null);
      },
      null,
      null,
      null,
      null,
      { request_id: "req-123" },
    );
  } finally {
    deregisterSubscriber("scope-check");
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
use nemo_flow::api::scope::{
    self, EmitMarkEventParams, PopScopeParams, PushScopeParams, ScopeAttributes, ScopeType,
};
use nemo_flow::api::subscriber::{deregister_subscriber, register_subscriber};
use serde_json::json;
use std::sync::Arc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    register_subscriber(
        "scope-check",
        Arc::new(|event| {
            println!("{} {}", event.kind(), event.name());
        }),
    )?;

    let handle = scope::push_scope(
        PushScopeParams::builder()
            .name("agent-run")
            .scope_type(ScopeType::Agent)
            .attributes(ScopeAttributes::empty())
            .input(json!({"request_id": "req-123"}))
            .build(),
    )?;

    scope::event(
        EmitMarkEventParams::builder()
            .name("planning-started")
            .parent(&handle)
            .data(json!({"step": 1}))
            .build(),
    )?;
    scope::event(
        EmitMarkEventParams::builder()
            .name("planning-finished")
            .parent(&handle)
            .data(json!({"step": 2}))
            .build(),
    )?;

    scope::pop_scope(PopScopeParams::builder().handle_uuid(&handle.uuid).build())?;
    let _ = deregister_subscriber("scope-check")?;
    Ok(())
}

When to Add Marks#

Use marks for checkpoints that should appear in the event stream but do not wrap a callback:

  • Request accepted

  • Plan created

  • Memory loaded

  • Routing decision made

  • Final answer assembled

  • Retry scheduled

Use a nested scope instead of a mark when the work has a meaningful start and end boundary, child work, or a duration that matters.

Validate the Integration#

Check that the subscriber prints:

  • One scope start event for agent-run

  • One mark event for planning-started

  • One mark event for planning-finished

  • One scope end event for agent-run

If marks appear outside the intended trace, pass the active scope handle explicitly or make sure the mark is emitted while the scope is active.

Production Checklist#

Use this checklist before running the pattern in production traffic.

  • Keep scope names stable enough for filtering and dashboards.

  • Use marks for business checkpoints, not verbose debug logging.

  • Include only JSON-compatible data, metadata, input, and output payloads.

  • Do not attach sensitive payloads unless sanitize guardrails or exporter filters will remove them.

  • Propagate the active scope stack when work crosses thread or worker boundaries.

Next Steps#

Use these links to continue from this workflow into the next related task.