LangGraph
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:
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)graph-call streaming through
runner.stream(...)/runner.astream(...), which forwards best-effortlanggraph.stream.*live events while still returning a durableLangGraphRunResultoptional 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 warningsa
build_resume_request(...)helper that turns an interrupted result into aCommand(resume=...)-backed resume requesta
wait_for_interrupt(...)bridge that pauses the Kitaru flow throughkitaru.wait(...)and produces the resume requestpreservation of the LangGraph
thread_idacross start and resume callsKitaru 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:
That 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:
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:
Migrating an existing LangGraph, LangChain agent, or Deep Agents-style project? The zenml-io/kitaru-skills package includes /kitaru:kitaru-langgraph-migration for choosing between the outer graph_call boundary and middleware-backed calls checkpoints. See Agent Skills.
Minimal graph_call flow pattern
graph_call flow patternThis pattern has no LLM and no interactive wait prompt. It just shows the default adapter shape.
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.
Graph-call streaming
Use runner.stream(...) or runner.astream(...) when you want to watch LangGraph progress while the outer graph call is running:
The shape is deliberately simple: Kitaru drains LangGraph's .stream(..., version="v2") output inside the graph-call checkpoint, publishes safe live events while chunks arrive, and then returns the same durable LangGraphRunResult shape as invoke(...).
Picture it as two lanes:
The live lane is for watching. The saved lane is what replay and later workflow steps should trust.
By default, Kitaru asks LangGraph for messages, updates, and custom stream modes, plus an internal values mode so it can reconstruct the final result without calling the graph a second time. Kitaru does not publish that internal values state unless you explicitly request stream_mode="values" or include it in a mode list.
Safe defaults matter because LangGraph stream payloads can contain prompts, state, tool results, or SDK internals. Message chunks are summarized as text deltas plus safe metadata. Updates and custom events are summarized and made JSON-safe. values, checkpoints, tasks, and debug are summarized by default; raw payloads require explicit policy opt-in, and debug requires allow_debug=True.
Streaming is currently graph-call only. checkpoint_strategy="calls" rejects stream(...) / astream(...) because a stream event only says "something happened". It does not wrap the actual LangChain handler call. Kitaru would not be physically around the model/tool side effect, so pretending those stream chunks are replay checkpoints would be unsafe.
Cache and replay have the same live-event behavior as other checkpoint live events:
if the graph-call checkpoint body runs, it may publish live stream events;
if replay re-executes the body, it may publish those events again;
if a cached graph-call result is reused, fresh stream events may not appear because the body did not run.
For the general live-event API and watcher behavior, see Checkpoint Live Events.
Minimal calls flow pattern
calls flow patternCalls mode needs a graph or agent built with Kitaru's LangChain middleware:
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
thread_id mattersLangGraph 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.
If 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:
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:
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:
There is also wait_for_interrupt(...), which bridges an interrupted LangGraph result to kitaru.wait(...):
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
graph_callgraph_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 name stays "graph_call" because LangGraph still owns graph-internal state and replay. Kitaru is making the outer graph invocation durable; it is not replacing LangGraph's own checkpointer or claiming every node is a Kitaru replay boundary.
The outer graph-call checkpoint defaults are conservative:
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
callscalls 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=...:
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.
This is why graph-call streaming above returns one durable LangGraphRunResult: the stream is live observability, while the outer graph-call checkpoint remains the replay boundary.
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.
Important defaults:
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).
Usage and cost statistics
When save_usage=True (the default), the graph-call adapter logs one canonical llm_usage_v1 record for the graph run. If calls-mode model events contain usage metadata, Kitaru sums those completed model-call usage payloads and uses that total for the graph-level record. In a concrete run with two model calls — 30 tokens, then 70 tokens — the final graph record reports 100 tokens rather than only the first message it happens to find.
If there is no usable event-level usage, Kitaru falls back to the graph output and looks for credible token usage there. It can aggregate multiple message-level usage payloads, but it does not combine event-derived usage and output-derived usage for the same graph run. That avoids the bad outcome where the same model response is counted once from a model event and a second time from the final graph state.
If neither events nor the graph output expose token usage, Kitaru still records the graph call with empty token fields. That makes the summary’s usage_record_count mean “a Kitaru usage record exists for this graph call,” not “the adapter found token metadata.” If you pass a cost_calculator= to KitaruGraphRunner, Kitaru stores the calculator result as estimated_cost_usd; calculator failures become warnings and do not fail the graph call. LangGraph records do not include provider-reported actual cost in this adapter path.
save_model_usage=True is separate and narrower: in calls mode, it controls whether model-call event payloads include usage metadata when individual LangChain responses expose it. If you set it to False, event-level usage is unavailable, so the graph-level record can only use the output fallback when save_usage=True. The execution-level LLM usage summary comes from the canonical llm_usage_v1 records, which are written by the shared adapter finalization path and roll up after FlowHandle.wait() or FlowHandle.get() observes the terminal execution.
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.Publish best-effort
langgraph.stream.*live events fromrunner.stream(...)/runner.astream(...)ingraph_callmode while returning a durableLangGraphRunResult.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.
Stream in
checkpoint_strategy="calls"mode; streams observe activity but do not own the LangChain handler call that would need a replay boundary.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.
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:
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 examples have two provider-neutral graph-call paths and one provider-backed calls path. The graph_call path is local and needs no provider API key:
The streaming path is also local and needs no provider API key:
It builds a small StateGraph with InMemorySaver, emits custom progress from graph nodes, watches langgraph.stream.* events with KitaruClient().executions.events(...) when the backend supports live watching, and then prints the durable LangGraphRunResult from handle.wait().
The calls path uses a real OpenAI-backed LangChain agent with deterministic local ticket tools:
--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.
langgraph_streaming.py runs the streaming demo:
Builds a local graph with two nodes.
Calls
runner.stream(...)inside a Kitaru flow submitted withcache=False.Emits LangGraph custom progress from inside the graph.
Watches
langgraph.stream.started, mode events, and terminal events when the backend supports live event watching.Prints the final durable
LangGraphRunResultafter the stream finishes.
--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:
for the streaming demo:
or, for a typical calls-mode run where the model follows the lookup instruction:
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.
stream(...)says calls mode is unsupported — expected. LangGraph streaming is supported forcheckpoint_strategy="graph_call"only. Use calls mode for synchronous middleware-wrapped model/tool checkpoints, not for stream chunks.You do not see live stream events — check that you are connected to a backend with live-event streaming enabled. The graph result is still durable even when event watching is unavailable. Also remember that cache hits may skip fresh live events because the graph body did not run.
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.
Related docs
Last updated
Was this helpful?