1. Initialize in every process
initialize() is per process. A queue worker, forked child, or subprocess never inherits the parent’s initialization: its LLM calls produce nothing, and flush() in that process silently no-ops instead of erroring. Call initialize() at each worker process’s startup.
For prefork servers (gunicorn with --preload, multiprocessing with the fork start method), initialize after the fork, in the worker’s own startup hook.
2. Pass the session id as data, and re-open the session
The session context followsawait within the same task, but it never crosses a thread, a process, or a queue hop: an LLM call made inside a Celery task, Temporal activity, or ThreadPoolExecutor job arrives session-less even when the job was enqueued from inside session().
The fix is one line on each side. A session id is just a string: carry it in the job payload (or the activity’s arguments), and re-open the session inside the worker:
x-belvedir-session-from: trace promotes each trace’s id to a session id, but that makes one session per trace and fragments multi-step work, so pass the real id whenever you control the code.
3. Flush before the job ends
Callflush() at the end of each job (long-lived workers) and before exit (short-lived ones): os._exit, process.exit(), and SIGKILL skip exit hooks, so an unflushed batch is silently lost. In Python flush() is synchronous, so calling it right before os._exit() is safe; in Node it is async, so await it. Details on Flush on serverless.
Reporting outcomes from workers
The job is where you learn whether the work succeeded, so callreport_outcome(session_id, ...) there, after flush(); see Outcomes.