Skip to main content

Observability

Run events​

Every run emits lifecycle events. Subscribe with onEvent:

import { createGraph } from "@ailu-ai/graph-sdk";

const app = createGraph({ name: "pipeline" })
.channel("n", { type: "number", default: 0 })
.node("a", async () => ({ n: 1 }))
.node("b", async () => ({ n: 2 }))
.edge("a", "b")
.compile();

// Every run emits lifecycle events: node_started, node_completed, run_suspended, run_completed, ...
const unsubscribe = app.onEvent((event) => console.log(event.type));
await app.run();
unsubscribe();

The events and their fields are listed in Events. A channel marked noLog: true is masked in all of them.

Traces and costs​

exportTracesToOtlp sends one trace per run, with a span per node, to any OpenTelemetry (OTLP/HTTP) collector: Jaeger, Grafana Tempo, Honeycomb, Langfuse, LangSmith and others.

// Send one trace per run (a span per node, with token cost) to any OTLP/HTTP collector:
// Jaeger, Tempo, Honeycomb, Langfuse, LangSmith, ...
const stop = exportTracesToOtlp(app, {
endpoint: "http://localhost:4318/v1/traces", // or set AILU_OTEL_EXPORTER_URL
serviceName: "refund-desk",
fetchImpl: collector // omit in production: the global fetch is used
});

await app.run();
stop(); // stop exporting

It traces the runs of that app (run, resume, approveAndResume, signal). For the catalog runner, pass onEvent to runCatalogGraph and forward the events to your tracer.

Agent spans carry token usage and an estimated cost (ailu.cost.usd). The estimate uses a built-in price list; pass priceBook to use your own prices. computeCost(usage, model) gives the same estimate for one agent result's usage.

Watch a run in the browser​

serveInspector(app, input) runs the graph once and serves a live view of it on http://127.0.0.1:4517: nodes light up as they run, and a suspended run shows what it waits for. It is a development tool and listens on the local machine only.

const inspector = await serveInspector(app, { question: "What is a checkpoint?" });
console.log(inspector.url);

Debug a run​

Where is it stuck? app.explain(runId) returns the run's status, the node it stopped at, why, and the call that continues it. For a state you saved yourself, use explainRun(state).

Why did it fail? A failed run has status: "failed" and emits run_failed with the error. A node that throws is retried when it has a retryPolicy; each attempt emits node_failed.

Common errors:

ErrorFix
no API key for provider '...'Set the variable it names, or AILU_LLM_MOCK=1 to run offline.
RustEngineRequiredErrorThe native engine didn't load. See Install.
ResumeStateNotFoundErrorresume was called on another CompiledGraph, or after a restart. See Long-running runs.
GraphCompileErrorThe graph is invalid. The message lists each problem with its code.

Every SDK error has a code, a hint with the fix, and a link to its entry in Errors.

Next​