LangGraph Adapter
Run LangGraph graphs inside Kitaru flows with either coarse graph-call checkpoints or granular LangChain call checkpoints
The LangGraph framework gives you a graph-based agent runtime: nodes execute, state flows between them, the graph can pause for human input through interrupt(...), and LangGraph checkpointers persist that paused state so the same conversation can be resumed later. LangChain agents built with create_agent(...) are LangGraph runnables underneath. Kitaru does not replace any of that.
Kitaru adds an outer durable execution boundary around the graph invocation:
one completed graph.invoke(...) = one Kitaru checkpointThat boundary is useful when a LangGraph call is one part of a larger workflow. Imagine this flow:
load ticket → run LangGraph triage agent → write report → notify customerIf the agent finishes its work and the later write report checkpoint fails, Kitaru can replay the flow and reuse the completed graph result instead of running the agent again. The graph might have called paid model APIs and sent a real Slack message; replaying the whole thing from scratch would burn money and risk a duplicate notification. The Kitaru boundary lets you say "that graph already finished, here is its output, move on."
The adapter focuses on the completed graph invocation as the durable unit: an input enters the graph, the graph finishes or interrupts for human input, and Kitaru stores what came out plus a small capture envelope describing the call.
The mental model
Think of LangGraph as the graph engine and Kitaru as the trip recorder and checkpoint gate around the whole graph call.
By default, Kitaru puts one shipping label on the whole LangGraph box:
Kitaru flow
├─ Kitaru checkpoint: review_graph_langgraph_call
│ └─ LangGraph graph.invoke(..., thread_id="ticket-42")
│ ├─ LangGraph node A
│ ├─ LangGraph node B
│ └─ LangGraph checkpoint/state snapshot
└─ Kitaru checkpoint: persist_summaryKitaru can see and record the box: when it started, whether it completed or interrupted, which thread_id was used, and which latest LangGraph checkpoint ID was observed. LangGraph controls what happens inside the box: node execution, graph state, checkpoint history, and where resume should continue. This is the graph_call strategy. It is the default, and it works for any compatible LangGraph graph or LangChain-agent runnable.
There is also a narrower opt-in strategy, calls, for when you want Kitaru checkpoints around the synchronous LangChain model and tool calls inside an agent graph:
Kitaru flow
└─ graph.invoke(...)
├─ LangGraph / LangChain agent logic
├─ Kitaru checkpoint: model_call__...
│ └─ LangChain model handler(request)
├─ Kitaru checkpoint: tool_call__approve_ticket_...
│ └─ LangChain tool handler(request)
├─ Kitaru checkpoint: model_call__...
│ └─ LangChain model handler(request)
└─ Kitaru checkpoint: langgraph_summary__...That second picture is the key. Kitaru is not magically seeing through LangGraph. The calls strategy works because KitaruLangGraphMiddleware is physically wrapped around the real LangChain model/tool handler call, so Kitaru can open a true checkpoint at that exact seam.
This boundary discipline avoids a dangerous double-replay problem. Imagine a graph node sends a Slack message, the process crashes, LangGraph resumes from its last checkpoint, and Kitaru also retries the same node. The message might be sent twice. The default graph_call strategy avoids that by using one Kitaru boundary around the whole graph call. The calls strategy is narrower: it only checkpoints calls where Kitaru middleware is actually wrapped around the model/tool handler. Outside those middleware-wrapped calls, LangGraph's own replay logic remains the source of truth.
So the high-level rule is:
- LangGraph keeps owning graph state:
thread_id, checkpointers, super-step snapshots, interrupts, stores, and graph-local replay. - Kitaru records the Kitaru flow around the graph: checkpoints, run metadata, artifacts, deployment/runtime placement, and Kitaru-friendly observability.
Kitaru is not replacing LangGraph persistence. It is adding Kitaru durability and observability at the places where Kitaru can safely stand.
What you get
The adapter gives existing LangGraph users:
- one durable Kitaru checkpoint around each completed
graph.invoke(...)/graph.ainvoke(...)call (the defaultgraph_callstrategy) - optional granular Kitaru checkpoints around synchronous LangChain model and tool calls (the opt-in
callsstrategy withKitaruLangGraphMiddleware) - a typed
LangGraphRunResultwith status, output, observed LangGraphthread_idand latest checkpoint ID, interrupt summaries, pending-state metadata, and warnings - a
build_resume_request(...)helper that turns an interrupted result into aCommand(resume=...)-backed resume request - a
wait_for_interrupt(...)bridge that pauses the Kitaru flow throughkitaru.wait(...)and produces the resume request - preservation of the LangGraph
thread_idacross start and resume calls - Kitaru event-log and run-summary artifacts summarizing the graph run
- redacted config/context metadata captured by default, plus opt-in deeper capture through
LangGraphCapturePolicy
LangGraph's own docs are still the source of truth for graph-internal behavior:
Install
Add the provider-neutral langgraph extra — and local if you want the local dashboard/server:
uv sync --extra local --extra langgraphThat is enough for raw LangGraph graphs and the local graph_call example. If you want the OpenAI-backed calls example, install the OpenAI provider extra and set an API key:
uv sync --extra local --extra langgraph-openai
export OPENAI_API_KEY='sk-...'
# Optional: override the default model used by the example.
export LANGGRAPH_AGENT_MODEL='gpt-5-nano'The base langgraph extra does not install a model provider. Use langgraph-openai for OpenAI-backed LangChain agents, or langgraph-anthropic when you are building Anthropic-backed LangChain agents.
Initialize the project once:
kitaru init
kitaru login # local server; add a URL to connect to a deployed one
kitaru statusMinimal graph_call flow pattern
This pattern has no LLM and no interactive wait prompt. It just shows the default adapter shape.
import kitaru
from kitaru import checkpoint, flow
from kitaru.adapters.langgraph import (
KitaruGraphRunner,
LangGraphRunRequest,
build_resume_request,
)
runner = KitaruGraphRunner(graph, name="review_graph")
@checkpoint
def persist_summary(summary: dict) -> dict:
kitaru.save("review_summary", summary, type="context")
return summary
@flow
def review(ticket: str) -> None:
first = runner.invoke(
LangGraphRunRequest.start(
{"ticket": ticket},
thread_id=ticket,
)
)
if first.status == "interrupted":
second = runner.invoke(
build_resume_request(first, {"approved": True})
)
_ = persist_summary(
{
"thread_id": second.thread_id,
"first_status": first.status,
"second_status": second.status,
"latest_checkpoint_id": second.latest_checkpoint_id,
"output": second.output,
}
)There are three important details in this small example:
thread_id=ticketgives LangGraph a stable conversation key.runner.invoke(...)is called from flow scope, so Kitaru can create graph-call checkpoints.kitaru.save(...)happens inside a normal@checkpoint, so the summary becomes a Kitaru artifact.
Minimal calls flow pattern
Calls mode needs a graph or agent built with Kitaru's LangChain middleware:
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from kitaru.adapters.langgraph import KitaruGraphRunner
from kitaru.adapters.langgraph.langchain import KitaruLangGraphMiddleware
agent_graph = create_agent(
model=model,
tools=[lookup_ticket, approve_ticket],
middleware=[KitaruLangGraphMiddleware()],
checkpointer=InMemorySaver(),
)
runner = KitaruGraphRunner(
agent_graph,
name="ticket_agent",
checkpoint_strategy="calls",
)In this setup:
- the runner sets the active Kitaru tracking context for the graph invocation;
- the middleware wraps synchronous LangChain model/tool handlers;
- each eligible sync handler call can become a true Kitaru checkpoint;
- the runner writes a summary checkpoint for the event log and run summary when it is in flow scope.
If you use checkpoint_strategy="calls" without KitaruLangGraphMiddleware or a future Kitaru call wrapper, the graph still runs, but Kitaru has no model/tool call boundary to checkpoint. You will get graph-level trace metadata, not granular Kitaru call checkpoints.
Why thread_id matters
LangGraph uses thread_id to find the same in-progress graph state later. You can think of it as the label on a folder of LangGraph checkpoints.
start call -> thread_id="ticket-42" -> graph pauses
resume call -> thread_id="ticket-42" -> graph continues the paused threadIf the resume call uses a different ID, LangGraph sees a different folder and cannot continue the paused work you expected.
Kitaru requires a non-empty thread_id on LangGraphRunRequest so this identity is explicit. The adapter merges it into LangGraph's config["configurable"] before calling the graph.
Checkpointers: local learning vs restart durability
LangGraph persistence depends on the checkpointer you compile the graph with. The runnable example uses InMemorySaver because it is simple and local:
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(checkpointer=InMemorySaver())That is good for learning, tests, and short local demos. It is not durable across process or container restarts, because the checkpoints live in memory.
This matters even more on Kubernetes. A Kitaru flow can resume or replay in a different pod from the one that ran the first graph call. If your graph used InMemorySaver, the paused LangGraph state stayed inside the old Python process. The new pod has the same code and the same Kitaru flow checkpoint, but it does not have the old process memory. LangGraph opens the thread_id folder and finds nothing useful.
For restart durability, use a persistent LangGraph checkpointer/store such as the ones documented in the LangGraph persistence guide, and keep the same stable thread_id for start and resume calls. Kitaru records the Kitaru execution; LangGraph's checkpointer remains the thing that stores graph-internal state, graph replay state, stores, and interrupts.
You can ask the adapter to be stricter with LangGraphDurabilityPolicy:
from kitaru.adapters.langgraph import KitaruGraphRunner, LangGraphDurabilityPolicy
runner = KitaruGraphRunner(
graph,
name="review_graph",
durability=LangGraphDurabilityPolicy(require_checkpointer=True),
)By default, the adapter warns when it can detect missing or obviously ephemeral checkpointers instead of failing local examples.
Interrupt and resume
LangGraph's native human-in-the-loop primitive is interrupt(...). When a graph interrupts, the adapter returns a LangGraphRunResult with:
status="interrupted"interrupts— JSON-safe summaries of pending interrupt payloadspending_state— thethread_id, checkpoint namespace, next nodes, and warnings needed to build a resume request
The resume helper creates a LangGraph Command(resume=...) for you:
from kitaru.adapters.langgraph import build_resume_request
first = runner.invoke(
LangGraphRunRequest.start({"ticket": "ticket-42"}, thread_id="ticket-42")
)
if first.status == "interrupted":
resume_request = build_resume_request(first, {"approved": True})
second = runner.invoke(resume_request)There is also wait_for_interrupt(...), which bridges an interrupted LangGraph result to kitaru.wait(...):
from kitaru.adapters.langgraph import wait_for_interrupt
if first.status == "interrupted":
resume_request = wait_for_interrupt(
first,
schema=bool,
question="Approve this ticket escalation?",
)
second = runner.invoke(resume_request)wait_for_interrupt(...) must be called from the flow body, not from inside a checkpoint. That is the same Kitaru rule as regular waits: a flow can pause safely, but a checkpoint body should either complete or fail.
If you pass metadata=..., the adapter attaches it in two places: under user_metadata on the Kitaru wait record, and as metadata on the LangGraphRunRequest returned for the resume call. The wait record also gets adapter metadata such as interrupt_index, task_id, and node_name so you can trace which LangGraph interrupt produced the pause without user metadata overwriting those adapter keys.
Checkpoint strategy
graph_call
KitaruGraphRunner(graph, name="review_graph", checkpoint_strategy="graph_call")graph_call is the universal, coarse strategy. It means one Kitaru checkpoint is placed around each outer graph invocation. It works for raw LangGraph graphs, LangChain agents that behave like LangGraph runnables, and any compatible object with invoke(...) / ainvoke(...).
The outer graph-call checkpoint defaults are conservative:
| Setting | Default | Why |
|---|---|---|
cache | False | A cached outer graph call could skip LangGraph's own resume/state logic. |
retries | 0 | Retrying a graph call can repeat external side effects if your graph node already performed them before the last LangGraph checkpoint. |
runtime | "inline" | Adapter-managed graph objects are live Python objects and are not sent to isolated runtime workers by default. |
type | "graph_call" | The dashboard groups these as graph-call checkpoints. |
You can override these through run_checkpoint_config=..., but only do so when your graph nodes are idempotent and you understand the replay implications.
calls
KitaruGraphRunner(
agent_graph,
name="ticket_agent",
checkpoint_strategy="calls",
)calls is granular, but only at real call boundaries. Today that means synchronous LangChain middleware hooks from KitaruLangGraphMiddleware.
A practical story:
- LangGraph starts an agent run.
- LangChain is about to call the model.
- Kitaru middleware receives
requestandhandler. - Kitaru opens a
model_call__...checkpoint. - Inside that checkpoint, the middleware calls
handler(request). - LangChain later calls a tool, and the same thing happens around the tool handler.
Because the middleware owns the moment when handler(request) is called, Kitaru can make sync model/tool calls true replay boundaries.
Calls mode uses call_checkpoint_policy=..., not run_checkpoint_config=...:
from kitaru.adapters.langgraph import LangGraphCallCheckpointPolicy
runner = KitaruGraphRunner(
agent_graph,
name="ticket_agent",
checkpoint_strategy="calls",
call_checkpoint_policy=LangGraphCallCheckpointPolicy(
tool_checkpoint_config_by_name={"send_email": False},
),
)The default call checkpoint types are model_call, tool_call, and langgraph_summary. Adapter-created call checkpoints run inline and default to no cache/no retries. Model-input checkpoint inputs are structural by default: message and system-message free text is omitted before persistence. Tool-argument checkpoint inputs are redacted before persistence. If caching is enabled for a model or tool checkpoint, Kitaru hashes a separate raw-enough cache identity so different calls do not collapse into the same cache entry.
Async calls mode
runner.ainvoke(...) and async LangChain middleware hooks currently record call metadata only. They do not open true async model/tool checkpoints yet.
LangGraphCallCheckpointPolicy.async_checkpoint_policy exists to make that boundary explicit, but it only accepts "metadata_only" today. It is not a hidden switch for enabling async checkpoints.
The reason is safety. A true Kitaru checkpoint needs to be wrapped around the actual handler execution in a way that Kitaru can replay cleanly. Sync middleware is proven for this PR. Async call checkpointing is deliberately metadata-only until that replay boundary is proven safe.
Callbacks and event streams are trace-only
LangChain callbacks, LangChain event streams, and LangGraph streams are useful for timelines. They are not Kitaru replay boundaries.
Here is the concrete difference:
- Middleware is handed
handler(request). It can decide, "Open a Kitaru checkpoint, then call the handler inside it." - A callback or stream event is told, "Something happened" or "something is happening." It observes the run, but it does not own the handler call.
So callbacks and streams can enrich event logs, dashboards, and debugging traces. They cannot create true Kitaru checkpoints for model/tool replay, because Kitaru is not physically around the side-effecting call.
Capture policy
LangGraphCapturePolicy controls what the adapter records for observability. Defaults are metadata-first: useful for debugging, but cautious about full graph state.
from kitaru.adapters.langgraph import LangGraphCapturePolicy, KitaruGraphRunner
runner = KitaruGraphRunner(
graph,
name="review_graph",
capture=LangGraphCapturePolicy(
save_input=True,
save_output=True,
save_state_values=False, # default: do not persist full graph values
),
)Important defaults:
| Option | Default | Meaning |
|---|---|---|
save_input | True | Include the start input, or resume command payload, in the adapter run summary. In calls mode, message-shaped start inputs omit raw message/system text by default. |
save_output | True | Include completed graph output in the adapter run summary. Interrupted and failed runs do not store output. |
save_config | True | Include redacted config metadata, including thread_id. Secret-like keys are redacted. |
save_context | False | Do not persist arbitrary runtime context by default. |
save_state_snapshot | True | Inspect graph.get_state(config) when available and save a summary. Set this to False to skip get_state(...) entirely. |
save_state_values | False | Do not save full LangGraph state values unless you opt in. |
save_state_tasks | True | Save safe task metadata summaries, not raw task internals. |
save_usage | True | Try to extract token usage from graph output. |
emit_call_events | True | In calls mode, record model_call and tool_call events when middleware observes them. |
save_model_input | True | In sync calls mode, store a redacted structural model-input envelope when a true model checkpoint is opened. Raw message and system-message text is omitted by default. |
save_model_response | True | In sync calls mode, include the model checkpoint output in event artifact references. Setting this to False removes that event reference only; the true checkpoint still stores its return value for replay. |
save_model_usage | True | Include model usage metadata when the response exposes it. |
save_tool_args | True | In sync calls mode, store redacted tool arguments as a structural checkpoint input when a true tool checkpoint is opened. Secret-like nested keys are redacted. |
save_tool_result | True | In sync calls mode, include the tool checkpoint output in event artifact references. Setting this to False removes that event reference only; the true checkpoint still stores its return value for replay. |
fail_on_event_persistence_error | False | Best-effort by default: event/run-summary persistence failures do not fail the graph call. Set to True when missing observability artifacts should fail the run. |
capture_mode | "metadata" | Metadata mode summarizes task IDs, node names, paths, interrupt counts, and error labels. "full" opts into raw JSON-safe task serialization. |
Here is the practical safety story. By default, Kitaru records enough to answer, "Which graph call ran? Which thread did it use? Did it finish, fail, or interrupt? What checkpoint ID did LangGraph report? Which model/tool calls did the middleware observe?" It does not dump full LangGraph task objects by default, because those task objects can contain prompts, tool outputs, customer data, or SDK internals. If you set capture_mode="full", treat the run summaries as potentially sensitive.
If your graph state contains prompts, tool outputs, customer data, or secrets, be careful with save_state_values=True and capture_mode="full".
Observability artifacts
When the adapter persists from checkpoint scope, it saves two Kitaru context artifacts for each graph run:
event_log__<graph>_<run_label>— ordered LangGraph adapter events, such asgraph_call_started,model_call,tool_call,graph_call_completed,graph_interrupted, orgraph_call_failed.run_summary__<graph>_<run_label>— the run summary: thread ID, status, captured config/context fields, output or failure details, warnings, call counters, and observed LangGraph checkpoint metadata.
The checkpoint shape depends on the strategy:
- In
graph_callmode, these artifacts are saved from the outer graph-call checkpoint. - In
callsmode, model/tool checkpoints are separate, and the aggregate event/run artifacts are saved from alanggraph_summary__<graph>_<run_label>checkpoint when possible. SetLangGraphCallCheckpointPolicy(persist_run_artifacts=False)to suppress that calls-mode event/run-summary persistence; in that case the run result does not advertise event-log or run-summary artifact names.
Inside checkpoint scope, Kitaru logs lightweight metadata pointers to those artifacts for search and debugging.
If the graph call is inside a Kitaru flow body but outside an active checkpoint, the adapter cannot call kitaru.save(...) for these context artifacts. In that case it may log the event/run-summary metadata payloads directly as flow metadata instead.
If you call the runner outside any Kitaru flow, the graph runs normally, but there is no Kitaru execution context where the adapter can persist artifacts or log Kitaru metadata.
By default, event persistence is best-effort. A graph result should not disappear just because the observability write had a problem. If you want strict behavior, set LangGraphCapturePolicy(fail_on_event_persistence_error=True).
What Kitaru does and does not do
Kitaru does
- Run your graph calls inside Kitaru flows.
- Create one outer Kitaru checkpoint per
runner.invoke(...)/runner.ainvoke(...)call ingraph_callmode. - Create true sync model/tool checkpoints in
callsmode whenKitaruLangGraphMiddlewarewraps LangChain handlers inside flow scope. - Preserve and record the LangGraph
thread_idused for the call. - Record status, interrupt summaries, latest checkpoint ID, call events, and run-summary metadata.
- Save event logs and run summaries as role-first Kitaru context artifacts when persistence is available.
- Persist failure summaries for graph calls that raise, including the exception type/message and the safe run metadata captured before the failure.
- Bridge LangGraph interrupts into resume requests, and optionally into
kitaru.wait(...). - Let you deploy the flow using the same Kitaru stacks as other workflows.
Kitaru does not do
- Replace LangGraph's checkpointer or store.
- Replay arbitrary LangGraph nodes as Kitaru checkpoints.
- Create call checkpoints from callbacks or event streams alone.
- Open true async model/tool call checkpoints yet; async calls mode is metadata-only.
- Snapshot arbitrary Python process memory.
- Snapshot Deep Agents sandbox files, local filesystem writes, or external volumes.
- Make non-idempotent tool side effects exactly-once.
- Provide
stream/astreamas a supported adapter contract yet. Streaming support is deferred until Kitaru/ZenML streaming support lands.
LangChain and Deep Agents
The adapter is named langgraph because LangGraph is the runtime seam. LangChain agents and Deep Agents are built on top of LangGraph-style execution, but they add their own higher-level concepts.
For this adapter, the contract is:
| Layer | Kitaru support story |
|---|---|
| Raw LangGraph compiled graph | First-class target for graph_call. |
LangChain create_agent(...) | Compatible with graph_call when the returned object behaves like a LangGraph runnable. Compatible with calls when you add KitaruLangGraphMiddleware. |
| Deep Agents | Invocation can be wrapped if compatible, but Deep Agents filesystem, sandbox, and backend semantics remain Deep Agents-owned. |
If you are using Deep Agents' virtual filesystem or sandbox backends, Kitaru records the graph call or the LangChain calls that middleware can see. It does not automatically snapshot the sandbox filesystem. See the official Deep Agents backends guide for how those files are stored.
Runnable example
The included example has two strategy paths. The graph_call path is local and needs no provider API key:
uv sync --extra local --extra langgraph
uv run examples/integrations/langgraph_agent/langgraph_adapter.py --strategy graph_callThe calls path uses a real OpenAI-backed LangChain agent with deterministic local ticket tools:
uv sync --extra local --extra langgraph-openai
export OPENAI_API_KEY='sk-...'
# Optional: override the default gpt-5-nano model.
export LANGGRAPH_AGENT_MODEL='gpt-5-nano'
uv run examples/integrations/langgraph_agent/langgraph_adapter.py --strategy calls--strategy graph_call runs the interrupt/resume demo:
- Builds a tiny graph with two nodes.
- Starts the graph with
thread_id="langgraph-local-demo-thread". - The graph interrupts and asks whether to approve a ticket escalation.
- The flow resumes the graph with
build_resume_request(...). - Kitaru records two
langgraph_local_interrupt_demo_langgraph_call...checkpoints. - The flow saves a
summary__langgraph_demoartifact.
--strategy calls runs the LangChain middleware demo:
- Builds an OpenAI-backed LangChain support agent.
- The model is instructed to call the local
lookup_tickettool first. - If the ticket needs escalation, the model is instructed to call the local
approve_tickettool. KitaruLangGraphMiddlewarecreates sync call checkpoints around the real model/tool handlers.- Kitaru records the model-call checkpoints it observes and writes a
langgraph_summary__...checkpoint. - When the model follows the lookup instruction, Kitaru records
tool_call__lookup_ticket_...; if it also follows the escalation instruction, Kitaru recordstool_call__approve_ticket_.... - The flow saves the same user-facing
summary__langgraph_demoartifact.
You should see output like:
Kitaru: Checkpoint `langgraph_local_interrupt_demo_langgraph_call` started.
Kitaru: Checkpoint `persist_summary` started.
LangGraph adapter demo summary (graph_call):
- strategy: graph_call
- first_status: interrupted
- resume_status: completedor, for a typical calls-mode run where the model follows the lookup instruction:
Kitaru: Checkpoint `model_call__...` started.
Kitaru: Checkpoint `tool_call__lookup_ticket_...` started.
Kitaru: Checkpoint `langgraph_summary__...` started.
LangGraph adapter demo summary (calls):
- strategy: calls
- model: gpt-5-nano
- final_message: <OpenAI model response summarizing the ticket and next step>If the model follows the escalation instruction, you should also see a tool_call__approve_ticket_... checkpoint.
For the full catalog, see Examples.
Troubleshooting
- "requires optional dependency
langgraph" — install withuv sync --extra langgraph, or includelocaltoo if you want the local Kitaru server. - "Missing LangChain OpenAI provider" or "No module named
langchain_openai" — install withuv sync --extra local --extra langgraph-openaibefore running the OpenAI-backedcallsexample. - "Missing OPENAI_API_KEY" — set
OPENAI_API_KEYbefore running--strategy calls. The local--strategy graph_callpath does not need it. - "requires a stable non-empty
thread_id" — pass a stable application key such as a ticket ID, user conversation ID, or workflow session ID. - The graph resumes from the beginning — check that start and resume use the same
thread_id, and that the graph was compiled with a checkpointer. - Restart durability does not work with
InMemorySaver— use a persistent LangGraph checkpointer/store.InMemorySaveris only in-memory, so a new process or Kubernetes pod cannot see the old graph state. callsmode produced no model/tool checkpoints — check that your graph usesKitaruLangGraphMiddlewareand that the observed calls are synchronous LangChain model/tool handlers inside a Kitaru flow.- Async calls only show metadata — expected for now. Async model/tool handlers do not create true Kitaru checkpoints yet.
- You need streaming — streaming is intentionally deferred from this adapter contract until Kitaru/ZenML streaming support is available.
- You expected Deep Agents files to appear as Kitaru artifacts — Deep Agents owns its filesystem backends. Save important outputs explicitly with
kitaru.save(...)if you want them as Kitaru artifacts.