feat(#126): introduce Langfuse observability for the memory pipeline#127
Open
alexosugo wants to merge 10 commits into
Open
feat(#126): introduce Langfuse observability for the memory pipeline#127alexosugo wants to merge 10 commits into
alexosugo wants to merge 10 commits into
Conversation
aa3aaad to
608f78d
Compare
- Add src/observability/index.ts — lazy singleton Langfuse client, makeLangfuseHandler factory, createTrace and flushLangfuse helpers - Thread optional langfuseHandler through FilterOptions and DistillOptions - filter.ts / distiller.ts pass the handler to chain.invoke callbacks - run-pipeline.ts creates one trace per PR with scrape span and distill-outcome score; groups all PR traces under a shared session UUID - Disable tracing in CI (LANGFUSE_ENABLED=false in unit_tests.yml) - Add Langfuse secrets to run-pipeline.yml production env - Add docs/observability.md covering setup, naming conventions, and how to instrument new agents or workflows - Update .env.example with Langfuse variables
- makeLangfuseHandler: drop LANGFUSE_ENABLED guard (SDK no-ops when enabled=false); return type narrows from CallbackHandler|undefined to CallbackHandler - export getLangfuse(); delete flushLangfuse wrapper; callers call getLangfuse().flushAsync() directly - delete resetLangfuseClient (unreachable from production code; tests mock the whole module) - remove ?? traceId fallback in processSinglePR; pass sessionId directly to both makeLangfuseHandler and createTrace
…ce()
Collapse makeLangfuseHandler()/createTrace() into a single startTrace()
returning { trace, handler }, so callers no longer juggle two related
objects built from the same trace id. Let the Langfuse SDK generate trace
ids instead of handcrafting them from PR number — the old scheme could
silently corrupt session groupings when overlapping --since windows
reprocessed the same PR. Update run-pipeline.ts wiring, docs, and add a
direct unit test for the disabled no-op path.
This is a runtime audit log appended to by local pipeline/test runs, not a file with content meant to be checked in. Truncate the stale entries that had accumulated in the repo.
Auto-empties and re-stages the file if it's non-empty at commit time, so local pipeline/test runs appending to it never leak into git history.
Match the variable name actually used in .env across the module, docs, and CI workflow.
_pending/*.md files are local/test-run output, promoted to agent-memory/domains/ via a reviewed PR — not meant to be committed directly. Only the .gitkeep placeholders stay tracked.
Installs post-commit and post-rewrite husky hooks so every commit gets automatically reviewed by roborev; ignores its local snapshot state.
Rebase conflict resolution introduced a bare optional sessionId param after two defaulted params; give it an explicit default instead.
babdb7b to
42726c5
Compare
Override withStructuredOutput() chain names via withConfig({ runName })
so triage/distill spans show as triage-classify/distill-draft instead
of the generic RunnableSequence in Langfuse traces.
Also collapse processSinglePR's 5 positional params into an options
object to satisfy SonarQube S107 (max 4 params).
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #126
Closes #103
Introduces end-to-end LLM observability for the memory distillation pipeline using Langfuse.
Note on #103 scope: #103 asked for a self-hosted Langfuse deployment via Docker Compose. This PR ships Langfuse Cloud instead — self-hosting is deferred until the agent platform has matured enough to justify the operational overhead of running our own instance. Trace capture, span structure, and the LangChain integration itself fully satisfy the eval-foundation goal of #103; only the hosting model differs from what was originally scoped.
src/observability/index.ts— shared module: lazy singleton Langfuse client (getLangfuse()) plus a singlestartTrace()primitive that returns{ trace, handler }— a trace and the LangChain callback handler rooted on it. Replaces the earlier separatemakeLangfuseHandler/createTrace/flushLangfusehelpers. All calls are no-ops whenLANGFUSE_ENABLED=false.src/types/pipeline.ts—FilterOptionsandDistillOptionsgain an optionallangfuseHandler?: BaseCallbackHandlerfield.src/scripts/filter.ts— threads the handler tochain.invoke({ callbacks })insidellmTriage.src/scripts/distiller.ts— threads the handler tochain.invoke({ callbacks })insidellmDistill.src/scripts/run-pipeline.ts—processSinglePRcreates one Langfuse trace per PR viastartTrace()(with ascrapespan and adistill-outcomescore), setstrace.update({ output }), and flushes viagetLangfuse().flushAsync(). All traces from a singlerunPipelinecall share a session UUID..github/workflows/unit_tests.yml— setsLANGFUSE_ENABLED=falseso CI tests never contact Langfuse..github/workflows/run-pipeline.yml— addsLANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY, andLANGFUSE_BASE_URLfrom repository secrets/vars.docs/observability.md— setup guide, trace structure, naming conventions, and a step-by-step recipe for instrumenting new agents/workflows, plus a "Future Work" section (prompt versioning, dashboards, alerting, eval datasets, agent instrumentation)..env.example— documents the four Langfuse variables (LANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY,LANGFUSE_BASE_URL,LANGFUSE_ENABLED).Trace shape (per PR)
The trace id is generated by Langfuse per run (not derived from the PR number), so reprocessing the same PR produces a distinct trace rather than mutating an earlier run's session. PR identity lives in
input/tags/metadataso it stays filterable.Design notes
dotenv.config()runs before the constructor readsprocess.env.LANGFUSE_ENABLED=false:startTrace()'s handler is still constructed but no-ops; callers usehandler ? [handler] : undefinedto skip callbacks entirely.LANGFUSE_HOSTwas renamed toLANGFUSE_BASE_URLacross source, docs, workflow, and.env.examplefor clarity (matches the Langfuse client constructor option name).opts.triageFn/opts.distillFnin tests still bypass the real LLM and the handler, keeping test isolation intact.distill-outcomeis the only score currently emitted, set directly in code (trace.score(...)inrun-pipeline.ts) — not an LLM-judge or human annotation. It only fires for PRs that reach thedistillstage; filtered-out PRs get an unscored trace withdecision: 'skip'in the output.Test plan
npm run build— tsc cleannpm run lint— eslint cleanLANGFUSE_ENABLED=false npm run test:coverage— 416 tests pass, all coverage thresholds met (exit 0)npm run run-pipeline -- --pr <cht-core-pr>locally with real Langfuse keys and confirmed a trace appears in the Langfuse Cloud dashboard with scrape span, LLM generations, and thedistill-outcomescore.LANGFUSE_PUBLIC_KEYandLANGFUSE_SECRET_KEYas repository secrets, andLANGFUSE_BASE_URLas a repository variable before the next scheduled pipeline run.