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:
| Error | Fix |
|---|---|
no API key for provider '...' | Set the variable it names, or AILU_LLM_MOCK=1 to run offline. |
RustEngineRequiredError | The native engine didn't load. See Install. |
ResumeStateNotFoundError | resume was called on another CompiledGraph, or after a restart. See Long-running runs. |
GraphCompileError | The 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.