@@ -26,7 +26,7 @@ for what's landed so far.
2626- ** Pluggable formatters** — ` JSONFormatter ` out of the box; implement ` format(record) -> str ` for your own
2727- ** Config from file/env** — ` load_config(dict) ` , ` logger_from_file(path) ` (JSON/YAML), ` logger_from_env() ` build a ` Logger ` from one config shape — see [ Config] ( #config )
2828- ** Plugin pipeline** — ` ContextPlugin ` , ` RedactPlugin ` (by key), ` PIIRedactPlugin ` (by pattern), ` SamplingPlugin ` (with tail-based elevation), ` TamperEvidentPlugin ` (hash-chained logs), ` TraceContextPlugin ` (cross-service trace correlation), and ` AlertingPlugin ` (` SlackAlertPlugin ` /` PagerDutyAlertPlugin ` /` EmailAlertPlugin ` , deduplicated) out of the box; a broken plugin can't crash logging; ` .use() ` also accepts a plain function, no subclassing required (see [ Plugins] ( #plugins ) )
29- - ** Agentic & harness tracing** — ` .child() ` loggers, ` RunPlugin ` , ` .thought()/.action()/.observation()/.decision() ` , ` with agent_log.span(...) ` , and framework adapters — ` LangChainAdapter ` (` pip install logquill[langchain] ` , covers LangGraph for free ), ` CrewAIAdapter ` (` pip install logquill[crewai] ` ), ` LlamaIndexAdapter ` (` pip install logquill[llamaindex] ` ), and ` AutoGenAdapter ` (` pip install logquill[autogen] ` ) — see [ Agentic & harness tracing] ( #agentic--harness-tracing )
29+ - ** Agentic & harness tracing** — ` .child() ` loggers, ` RunPlugin ` , ` .thought()/.action()/.observation()/.decision() ` , ` with agent_log.span(...) ` , and framework adapters — ` LangChainAdapter ` (` pip install logquill[langchain] ` ), ` LangGraphAdapter ` ( ` pip install logquill[langgraph] ` , adds checkpoint interrupt/resume events on top ), ` CrewAIAdapter ` (` pip install logquill[crewai] ` ), ` LlamaIndexAdapter ` (` pip install logquill[llamaindex] ` ), and ` AutoGenAdapter ` (` pip install logquill[autogen] ` ) — see [ Agentic & harness tracing] ( #agentic--harness-tracing )
3030- ** Zero required runtime dependencies** — stdlib only; ` aiohttp ` is opt-in, for async HTTP
3131- ** Typed throughout** — ` mypy --strict ` clean on the public API
3232- * (planned)* non-blocking async dispatch, ` contextvars ` -based context propagation — see ` CHANGELOG.md `
@@ -554,8 +554,10 @@ assert record["meta"]["trace_id"] == "4bf92f3577b34da6a3ce929d0e0e4736"
554554` LogQuillAdapter ` is a thin base class for mapping a framework's own event
555555callbacks onto ` .thought()/.action()/.observation()/.decision() ` and
556556` .span() ` — never a reimplementation of tracing logic per framework.
557- ` LangChainAdapter ` (covers LangGraph for free, since it shares LangChain's
558- callback system) ships behind the optional ` langchain ` extra:
557+ ` LangChainAdapter ` ships behind the optional ` langchain ` extra. LangGraph
558+ nodes run as ordinary LangChain ` Runnable ` s, so it already captures node
559+ execution with zero extra work — for LangGraph's own checkpoint
560+ interrupt/resume events too, see [ LangGraph] ( #langgraph ) below:
559561
560562``` bash
561563pip install logquill[langchain]
@@ -575,6 +577,43 @@ LangChain's own `run_id`/`parent_run_id` are written directly onto
575577field renaming, not translation. ` langchain-core ` is never imported unless
576578you import ` logquill.adapters.langchain ` yourself.
577579
580+ ### LangGraph
581+
582+ LangGraph nodes execute as ordinary LangChain ` Runnable ` s, so
583+ ` LangChainAdapter ` alone already covers everything that happens * inside* a
584+ node — ` on_chain_start ` /` on_llm_start ` /` on_tool_start ` /etc. all fire exactly
585+ as they would for a plain chain. What a plain ` BaseCallbackHandler ` can't
586+ see is LangGraph's own checkpoint lifecycle: ` on_interrupt ` /` on_resume ` ,
587+ fired when a graph pauses on an ` interrupt() ` call (e.g. for human review)
588+ and later resumes from a persisted checkpoint — LangGraph dispatches those
589+ two specifically to handlers that are instances of its own
590+ ` GraphCallbackHandler ` , which a plain ` BaseCallbackHandler ` subclass never
591+ receives. ` LangGraphAdapter ` is ` LangChainAdapter ` plus those two:
592+
593+ ``` bash
594+ pip install logquill[langgraph]
595+ ```
596+
597+ ``` python
598+ from logquill import Logger, RunPlugin
599+ from logquill.adapters.langgraph import LangGraphAdapter
600+
601+ log = Logger(" app" )
602+ handler = LangGraphAdapter(log.child(" agent" ).use(RunPlugin()))
603+ graph = builder.compile(checkpointer = checkpointer)
604+ graph.invoke(input , config = {" callbacks" : [handler], " configurable" : {" thread_id" : " 1" }})
605+ ```
606+
607+ ` on_interrupt ` becomes ` .observation("graph_interrupted", ...) ` carrying
608+ ` checkpoint_id ` , ` status ` , ` checkpoint_ns ` (the subgraph namespace path, if
609+ nested), and each pending ` Interrupt ` 's ` id ` /` value ` ; ` on_resume ` becomes
610+ ` .action("graph_resumed", ...) ` with the same checkpoint fields. Both use
611+ the event's own ` run_id ` as ` parent_span_id ` , matching the enclosing
612+ graph's still-open chain span — the graph hasn't ended, just paused.
613+ ` pip install logquill[langgraph] ` pulls in a compatible ` langchain-core `
614+ transitively, so installing it alone is enough; ` langgraph ` is never
615+ imported unless you import ` logquill.adapters.langgraph ` yourself.
616+
578617` CrewAIAdapter ` ships behind the optional ` crewai ` extra, listening on
579618CrewAI's own event bus rather than a single callback handler:
580619
0 commit comments