Context Propagation

A setup guide for ensuring traces and spans remain connected across async boundaries.

Context propagation ensures that all enforcement calls, traces, and spans are correctly correlated within a single request or user session. Without it, your traces will fragment, preventing you from diagnosing what caused a policy block.

Why Context Matters

If you skip context propagation, you lose:

  • Trace continuity: Spans from the same request appear disconnected, making timeline debugging impossible.
  • Audit correlation: You cannot link a blocked enforcePost decision back to the originating prompt or user ID.
  • Support triage: You cannot filter the Console logs by user_id or org_id when investigating customer tickets.

Operational IDs

The following fields must be injected at the absolute edge of your application (usually the API middleware):

Field Purpose Example
requestId Essential for grouping spans within an immediate application execution lifecycle. req_abc123
traceId Essential for distributed tracing across multiple microservices (e.g., from your API gateway to your agent runner). trace_xyz789
orgId The tenant identifier. Mandatory for correct multi-tenant policy resolution. org_acme
userId The end-user identifier, used for incident triage and support tickets. user_42
tags Arbitrary key-value metadata. { env: "prod" }

Standard Middleware Setup

The most robust way to guarantee context is to use the provided SDK middlewares at your application entrypoint. The middleware manages the requestId generation, reads distributed headers (x-njira-trace-id), binds the context to the thread, and flushes traces on exit.

Express / Node.js

import { njiraMiddleware } from "@njiraai/sdk/express";
// Place this strictly before your LLM/Agent route handlers
app.use(njiraMiddleware(njira));

FastAPI / Python

from njiraai.fastapi import NjiraMiddleware
# Binds contextvars automatically per-request
app.add_middleware(NjiraMiddleware, njira=njira)

Next.js

// middleware.ts
export { njiraMiddleware as middleware } from "@njiraai/sdk/next";

Manual Context (Worker/Job Queues)

If you are running agents inside background workers (e.g., Celery, BullMQ, Temporal) where HTTP middleware does not apply, you must initialize context manually.

TypeScript (AsyncLocalStorage)

// Wrap the entire job processor execution
njira.runWithContext(
  { 
    requestId: job.id, 
    orgId: job.data.tenant_id, 
    userId: job.data.user_id,
  },
  async () => {
    // All SDK and span calls inside this closure will inherit the IDs above
    await processAgentWorkflow(job.data);
  }
);

Python (contextvars)

ctx = {
    "request_id": job.id, 
    "org_id": job.tenant_id, 
    "user_id": job.user_id,
}

async def worker_handler():
    # Will automatically attach the scoped ctx
    await execute_agent_chain()

await njira.run_with_context(ctx, worker_handler)

Triage: Missing or Disconnected Traces

If you investigate a Block event in the Njira Console but cannot see the parent llm_request span, run through this troubleshooting checklist:

  1. Verify Middleware Order: Ensure the Njira middleware is mounted before body parsers and framework-specific LLM adapters. Use njira.getContext() manually within the controller to verify that fields populated correctly.
  2. Check the Async Boundary: If your application spins off isolated fire-and-forget promises (Promise.all() without waiting, or asyncio.create_task()), localized context may be dropped. Ensure SDK boundaries are within the awaited call stack.
  3. Console Log Dump: Inject console.log(njira.getContext()) immediately prior to the tool boundary call. If the object prints undefined or lacks a requestId, your context scope has failed.
  4. Third-Party Callbacks: Some legacy libraries (e.g., older Postgres callback drivers) destroy AsyncLocalStorage state. Prefer modern async/await drivers.