Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

catwalk for LangGraph

see your LangGraph from above

catwalk for LangGraph is a self-contained ReactFlow viewer for compiled LangGraph topologies: nodes, edges, conditional-edge labels, and optional per-node "wiring annotation" status. It ships as a single ~300KB static HTML file with no server and no external requests.

→ Live demo — the shipped dist/index.html, served as-is. No sign-up, no upload, nothing to install.

On the name. There is an unrelated npm package called catwalk, and an unrelated charmbracelet/catwalk. This project is catwalk for LangGraph — use the qualified name when referring to it, since the bare word is ambiguous in exactly the audience that would search for this.

What it is

dist/index.html is one file. Open it in a browser and it renders the graph baked into it at build time -- no server, no CDN script, no network call. That graph is a demo: a fictional research assistant (a supervisor routing to five specialists, two tool-calling loops, a self-critique cycle, and a human-approval interrupt), not real data. Every node in it is a stub -- only the wiring is meaningful. Point the build at your own exported graph and rebuild to view your own topology.

Quickstart

# view the shipped demo
open dist/index.html          # or just double-click it

# view your own graph
#  1. produce graph.json for your compiled LangGraph (see "Schema" and
#     export.py below)
#  2. drop it at export/graph.json
#  3. rebuild
./build.sh
open dist/index.html

build.sh needs Node.js + npm (it runs npm install and esbuild inside app/). It refuses to build without export/graph.json present -- no silent fallback to demo data.

Producing your own graph.json

export.py is a standalone exporter: stdlib + langgraph only, no other dependencies. Given a compiled graph (your_graph.compile()), it walks .get_graph() into the schema below.

python3 export.py mymodule:compiled_graph --out export/graph.json

or as a library:

from export import write_document

compiled = my_state_graph.compile()
write_document(compiled, "export/graph.json", source_ref="deadbeef")

export.py only ever emits topology (nodes/edges) -- it has no opinion about a rule/annotation system, so annotations is always [] and annotations_status is always "not_built". If you track your own per-node rules or invariants, write annotation objects matching the schema below yourself and set annotations_status to "collected".

Schema

graph.json is a single JSON object:

{
  "schema_version": "1.0.0",
  "source": {
    "langgraph_version": "0.2.6",  // string or null, best-effort metadata
    "spine_ref": "demo-0000000"    // string -- see note below, should not be null
  },
  "nodes": [
    { "id": "__start__", "kind": "start" },  // kind: "start" | "end" | "node"
    { "id": "validate",  "kind": "node" }
  ],
  "edges": [
    {
      "source": "validate",
      "target": "escalate",
      "conditional": true,     // get_graph()'s own edge.conditional
      "label": "flagged"       // get_graph()'s own edge.data, or null
    }
  ],
  "annotations_status": "collected",  // "not_built" | "collected"
  "annotations": [
    {
      "rule_id": "OP-3",
      "normative_text": "Escalated orders must be resolved before they can close.",
      "status": "BUILT",              // any string; the viewer renders it verbatim
      "anchor": "pipeline.py::escalate_order",
      "node_id": "escalate",          // the node this attaches to, or null (topology-level)
      "planted_failure": "test_pipeline.py::test_op3_escalate_requires_resolution",
      "proof_seam_note": null,
      "gap": null
    }
  ]
}

Notes:

  • nodes and edges are the direct shape of LangGraph's own CompiledGraph.get_graph() -- kind is derived only from the two special ids __start__/__end__, everything else is "node". conditional/label are independent fields; don't assume one implies the other.
  • annotations_status is the source of truth for whether annotation data exists at all. The viewer reads it directly rather than inferring presence from annotations.length -- a "collected" export with zero annotations for a given node and a "not_built" export both produce an empty list for that node, but they are different facts, and the panel displays them differently ("no annotations attached to this node" vs. "no annotation data available yet").
  • node_id on an annotation is the exporter's own resolved attachment, not something the viewer re-derives from anchor text. If you write your own annotations, resolve node_id yourself (or leave it null for a whole-graph/topology-level annotation) -- do not rely on the viewer to guess it from a substring match, since a node id can appear inside a longer identifier in anchor without actually naming that node (e.g. escalate inside check_requires_escalate_gate).
  • source.spine_ref is rendered with .slice(0, 12) in the header with no null check, so keep it a string (export.py defaults to "unknown" rather than emitting null).

Scope

catwalk is a static viewer. It renders one exported snapshot of a graph's topology (and, optionally, annotation data you supply) -- it does not connect to a running LangGraph process, does not show live execution state, and does not re-derive anything the export didn't already compute. Rebuilding is how you refresh it.

License

MIT, see LICENSE.

About

See your LangGraph from above — a self-contained ReactFlow viewer for compiled LangGraph topologies

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages