RFC-0013: LangGraph Companion Package
Generated from the repository source of truth. Source file:
/docs/RFC-0013-aioc-langgraph-companion-package.md. Status:Experimental.
- Status: Experimental
- Date: 2026-06-21
- Owners: aioc maintainers
- Depends on: RFC-0003, RFC-0009, RFC-0011, RFC-0012
- Related: RFC-0001, RFC-0002, RFC-0010
Context
Section titled “Context”aioc is intentionally governance-first and framework-light. The core runtime
owns deterministic policy gates, RunRecord, replay, comparison, and
application-owned audit artifacts. It should not absorb every orchestration,
retrieval, graph, or agent framework feature.
LangGraph is a common orchestration layer for agentic and deterministic workflows. Teams using LangGraph may still want aioc-style portable run records, comparison workflows, and governance boundaries without replacing their graph orchestrator.
The examples in examples/langchain validate three integration patterns:
- aioc-first, LangChain-extended;
- LangGraph-orchestrated, aioc-governed;
- LangGraph-orchestrated, aioc-recorded.
The third pattern is especially small:
const app = withAiocRunRecord(graph.compile(), { record: { sink },});It keeps LangGraph as the orchestrator and adds a transparent aioc recording layer around the compiled graph.
Decision
Section titled “Decision”aioc should explore a companion package for LangGraph interoperability:
@axiastudio/aioc-langgraphThe package should not be a dependency of the core @axiastudio/aioc runtime.
It should depend on aioc contracts and LangGraph runtime types through peer
dependencies:
{ "peerDependencies": { "@axiastudio/aioc": "^0.2.6", "@langchain/core": "^1.x", "@langchain/langgraph": "^1.x" }}The first official candidate API should be withAiocRunRecord(...).
withAiocGovernance(...) is a valuable direction, but should remain
experimental until the node-wrapping semantics, deny behavior, and LangGraph
internal API dependency are better understood.
- Keep
@axiastudio/aioccore free of LangGraph dependencies. - Provide a one-line way to attach
RunRecordemission to a compiled LangGraph app. - Preserve LangGraph runtime behavior: same input, same output, same error semantics.
- Make the recording layer non-invasive and failure-isolated.
- Use existing aioc
RunRecordOptionsfor sink, metadata, run id, prompt text preference, and context redaction. - Produce graph-level
RunRecordvalues that can be inspected, compared, persisted, and evaluated by downstream workflows. - Keep the companion package small enough to validate with examples before expanding the public surface.
Non-Goals
Section titled “Non-Goals”- No LangGraph dependency in aioc core.
- No broad
@axiastudio/aioc-langchainpackage in this RFC. - No generic replacement for LangChain, LangGraph, or their orchestration model.
- No hosted or managed trace/debug/eval platform in this RFC.
- No LangSmith-compatible service API.
- No claim that graph-level recording provides node-level governance.
- No automatic capture of LangGraph internal tool calls unless those calls cross an aioc boundary.
- No mutation of compiled LangGraph runtime internals after
compile().
Applications may use the emitted RunRecord values to build inspection,
comparison, offline evaluation, dashboards, or platform workflows. Those
workflows are consumers of the record contract, not responsibilities of the
LangGraph adapter itself.
Package Shape
Section titled “Package Shape”Suggested package layout:
packages/ aioc-langgraph/ src/ index.ts with-aioc-run-record.ts with-aioc-governance.ts types.ts README.md package.jsonInitial exports:
export { withAiocRunRecord } from "./with-aioc-run-record";export type { LangGraphRunRecordContext, WithAiocRunRecordOptions,} from "./types";Future experimental exports:
export { withAiocGovernance } from "./with-aioc-governance";export type { WithAiocGovernanceOptions, GovernedLangGraphNodeOptions,} from "./types";Candidate API: withAiocRunRecord
Section titled “Candidate API: withAiocRunRecord”withAiocRunRecord(...) wraps an already compiled LangGraph app.
const graph = new StateGraph(State) .addNode("answer", answerNode) .addEdge(START, "answer") .addEdge("answer", END);
const app = withAiocRunRecord(graph.compile(), { record: { sink, metadata: { workflow: "support-answer" }, contextRedactor: (context) => ({ contextSnapshot: { ...context, input: "[redacted]", }, contextRedacted: true, }), },});
const result = await app.invoke(input);The function returns a compiled-graph-like runnable wrapper, not a
StateGraph.
Conceptually:
CompiledStateGraph -> RecordedCompiledStateGraphThe returned object should preserve the operational interface of the input app as much as practical:
invoke(...)delegates to the original app and records the run.- non-intercepted methods delegate to the original app.
- successful graph output is returned unchanged.
- graph errors are recorded and then rethrown unchanged.
- sink failures must not alter LangGraph behavior.
Proposed Type
Section titled “Proposed Type”export interface LangGraphRunRecordContext< RunInput = unknown, RunOutput = unknown,> { integration: "langgraph"; runnableName: string; input: RunInput; output?: RunOutput; error?: { name: string; message: string; };}
export interface WithAiocRunRecordOptions<TContext = unknown> { record: RunRecordOptions<TContext>;}
export function withAiocRunRecord< TApp extends RunnableInterface<unknown, unknown, any>,>( app: TApp, options: WithAiocRunRecordOptions< LangGraphRunRecordContext<RunnableInput<TApp>, RunnableOutput<TApp>> >,): TApp;The adapter should use the existing aioc RunRecordOptions surface rather than
introducing parallel sink, metadata, or contextRedactor options.
RunRecord Semantics
Section titled “RunRecord Semantics”withAiocRunRecord(...) produces a graph-level RunRecord.
For a completed graph run:
status:completedproviderName:LangGraphagentName: runnable or graph name when availablequestion: serialized graph inputresponse: serialized graph outputcontextSnapshot: LangGraph wrapper context, optionally redacted throughrecord.contextRedactoritems: a minimal graph-level history containing the serialized input as ausermessage and the serialized output as anassistantmessageinputItemCount:1, so consumers can distinguish the original graph input from the output item added by the wrapperpolicyDecisions: empty unless the graph itself crosses an aioc-governed boundarypromptSnapshots: empty unless future instrumentation can capture stable prompt evidencerequestFingerprints: empty unless future instrumentation can capture stable model request evidencemetadata: user metadata plus adapter metadata
For a failed graph run:
status:faileditems: only the serialized inputusermessageinputItemCount:1errorNameanderrorMessageare populated- original error is rethrown after recording
This record is useful for inspection, persistence, comparison, regression checks, and offline judging. It should not be presented as a complete node-level LangGraph trace.
Privacy
Section titled “Privacy”The adapter should follow the existing aioc privacy posture:
- no implicit redaction by default;
record.contextRedactoris the primary minimization hook;- sink ownership remains application-side;
- recording failures are isolated from runtime behavior;
- examples should show redaction when context may contain sensitive data.
Because graph inputs and outputs may contain full message histories, documents,
tool payloads, or business data, production usage should treat
contextRedactor as mandatory before durable persistence.
Candidate API: withAiocGovernance
Section titled “Candidate API: withAiocGovernance”withAiocGovernance(...) is a separate, stronger pattern.
const governedGraph = withAiocGovernance(graph, { nodes: "all", record: { sink },});
const app = governedGraph.compile();Conceptually:
StateGraph -> GovernedStateGraph -> CompiledStateGraphUnlike withAiocRunRecord(...), this function operates before compile().
It would wrap graph nodes so selected execution boundaries can cross aioc
policy and recording logic.
Potential behavior:
- inspect
graph.nodes; - replace selected node runnables with aioc-governed wrappers;
- preserve graph edges, branches, waiting edges, schemas, and node options;
- return a compilable
StateGraph; - record policy decisions for governed nodes;
- allow node-specific deny behavior through explicit
onDenycallbacks.
This API should remain experimental because denial in a graph node must still return a valid LangGraph state update or command. The adapter cannot invent a universal deny shape for arbitrary graph state.
Relationship Between APIs
Section titled “Relationship Between APIs”The two APIs serve different purposes.
withAiocRunRecord(graph.compile(), options);- input: compiled LangGraph app;
- output: compiled-graph-like runnable wrapper;
- purpose: graph-level recording;
- enforcement: none;
- implementation: wrap
invoke(...)and delegate runtime behavior.
withAiocGovernance(graph, options);- input: uncompiled
StateGraph; - output: uncompiled
StateGraph; - purpose: node-level governance;
- enforcement: yes, for wrapped nodes;
- implementation: wrap selected nodes before LangGraph compiles them.
withAiocRunRecord(...) should be the first package API because it has a small
surface and a strong one-line value proposition. withAiocGovernance(...)
should follow only after examples validate a clear and honest state-update
contract.
Example
Section titled “Example”A minimal LLM-backed example should live in:
examples/langchain/src/langgraph-run-record.tsIt should:
- build a pure LangGraph workflow;
- call an LLM from a LangGraph node;
- wrap the compiled graph with
withAiocRunRecord(...); - invoke the wrapped app;
- print the LangGraph result and a compact
RunRecordsummary.
The current example domain is intentionally simple:
Explain photosynthesis for a 10 year old.This keeps attention on the integration pattern rather than on retrieval, tools, or multi-agent routing.
Versioning
Section titled “Versioning”The companion package should start as experimental:
0.xCompatibility should be documented around:
- aioc minor version;
- LangGraph major version;
- LangChain core major version.
Breaking changes are expected while the adapter validates:
- graph-level record shape;
- streaming behavior;
- batch behavior;
- topology fingerprinting;
- node governance semantics.
Implementation Notes
Section titled “Implementation Notes”The initial implementation can be promoted from:
examples/langchain/src/lib/aioc-langgraph.tsThe companion package should avoid depending on aioc internals that are not
exported from @axiastudio/aioc. If helper reuse is required, core should
export small stable utilities rather than asking the companion package to reach
into private files.
For withAiocRunRecord(...), a proxy or small runnable wrapper is acceptable
as long as:
invoke(...)preserves the original return value;- non-intercepted methods remain available;
- sink failures are swallowed or routed to an explicit optional error handler;
- TypeScript inference keeps graph input and output types usable.
For withAiocGovernance(...), the adapter should prefer public LangGraph
builder surfaces. If wrapping requires internal fields such as nodes, edges,
branches, or waitingEdges, the RFC should mark that part as version-fragile
until a stable extension point exists.
Open Questions
Section titled “Open Questions”- Should
withAiocRunRecord(...)also interceptbatch(...),stream(...), andstreamEvents(...)in the first companion package release? - Should the adapter produce a topology fingerprint from graph nodes and edges?
- Should topology metadata live in
RunRecord.metadataor in a future harness descriptor extension? - Should the adapter expose an optional
onRecordErrorcallback for sink failures? - Should input and output serialization be configurable, or should the first release keep strict default JSON serialization?
- Should graph-level
RunRecord.itemsremain minimal, or should it include a LangGraph-specific item type once core supports extension items? - Can
withAiocGovernance(...)be implemented without relying on LangGraph internals? - What is the minimal deny contract for governed nodes that still feels LangGraph-native?
- Should this package also expose helpers for correlating multiple aioc
node-level
RunRecordvalues under one graph run id?
Adoption Plan
Section titled “Adoption Plan”- Keep the prototype in
examples/langchain/src/lib/aioc-langgraph.ts. - Add one LLM-backed example for
withAiocRunRecord(...). - Validate the one-line API against real LangGraph usage.
- Promote the helper into
packages/aioc-langgraph. - Publish as experimental
0.x. - Add docs that clearly distinguish recording from governance.
- Explore
withAiocGovernance(...)with explicit node deny examples.
The companion package should graduate only after withAiocRunRecord(...)
proves stable enough to support real graph-level recording without surprising
LangGraph users.