Files
langgraph/libs/cli/GO_MIGRATION.md
2026-04-08 04:46:31 -07:00

9.6 KiB

Go Migration Plan

Goal

Move the full langgraph CLI implementation to Go while preserving existing Python distribution and invocation flows.

Users should continue to be able to run:

  • langgraph ...
  • uv run langgraph ...
  • uvx langgraph ...

During phase 1, the Go path is gated behind a feature flag. The Python package remains the public entrypoint and launcher.

Non-Goals

This phase does not include:

  • JS migration
  • langsmith-cli integration
  • user-facing command renames
  • intentional CLI behavior changes
  • a long-lived dual implementation

Source Of Truth

There will be one implementation of CLI behavior:

  • shared Go implementation lives in the langgraph repo
  • standalone Go langgraph binary uses that implementation
  • Python langgraph-cli package is a thin launcher around that binary
  • legacy Python implementation exists only temporarily as fallback during rollout

Phase 1 Artifacts

Phase 1 ships these artifacts:

  • shared Go package(s) in langgraph
  • standalone langgraph Go binary
  • Python wheel langgraph-cli that bundles the platform-specific Go binary
  • Python launcher entrypoint that can route to legacy Python or Go

JS is explicitly out of scope for phase 1.

User-Facing Command Scope

Phase 1 scope is the whole langgraph CLI, not just deploy.

Target command coverage:

  • langgraph deploy ...
  • langgraph build ...
  • langgraph up ...
  • langgraph dockerfile ...
  • langgraph dev ...
  • langgraph new ...

The goal is full parity with the current Python CLI command surface.

Compatibility Contract

Behavior must not regress.

Required parity:

  • exact JSON output where commands emit JSON
  • exact or equivalent error semantics
  • same exit codes
  • same argument and flag behavior
  • same generated artifacts for:
    • Dockerfile output
    • docker compose / inline compose output
  • same API request semantics where mocked in tests

Human-readable output should be same or better, but not worse.

Repo Ownership

Shared implementation lives in the current langgraph repo.

Reasons:

  • current CLI spec and tests already live here
  • rollout is initially only for langgraph-cli
  • command compatibility should be driven by existing behavior in this repo

Architecture

Use process boundaries, not language FFI.

Python should not call a Go shared library directly. Instead:

  • Python launcher locates bundled langgraph Go binary
  • Python launcher execs or subprocesses into the Go binary
  • Go handles all command execution
  • for dev, Go subprocesses back into Python

This keeps the boundary simple and cross-platform.

Go Package Structure

Recommended structure:

  • pkg/cli/config
    • parse and validate langgraph.json
    • normalize config model
  • pkg/cli/docker
    • docker capability detection
    • compose generation
    • Dockerfile/build plan generation
  • pkg/cli/deploy
    • deployment flows
    • host backend client
    • polling, logs, revision logic
  • pkg/cli/dev
    • dev command orchestration
    • Python subprocess handoff
  • pkg/cli/cmds
    • command runner functions with typed options/results
    • no Cobra-specific code here
  • cmd/langgraph
    • standalone Go binary wrapping shared packages

Business logic should live in shared packages, not directly in CLI adapter code.

Python Wrapper Model

The Python package remains installed as langgraph-cli, with entrypoint langgraph.

During migration, the wrapper decides whether to route to legacy Python or Go.

Wrapper behavior:

  1. inspect feature flags
  2. resolve Go binary path
  3. if Go path is active, exec into Go binary
  4. otherwise fall back to legacy Python implementation

Long-term target:

  • remove fallback
  • Python wrapper always launches bundled Go binary

Feature Flags

Temporary rollout env vars:

  • LANGGRAPH_USE_GO_CLI=1
    • route the Python wrapper to the Go binary instead of legacy Python
  • LANGGRAPH_GO_CLI_PATH=/path/to/langgraph
    • internal/dev/CI override for binary path resolution
    • not intended as a long-term public interface
  • LANGGRAPH_CALLING_PYTHON=/path/to/python
    • set by the Python wrapper before invoking Go
    • used by Go for dev

LANGGRAPH_GO_CLI_PATH is mainly for local development and CI and can be removed later.

dev Invocation Contract

dev is the main tricky area.

Design rule:

  • Go owns CLI parsing and routing
  • Python owns the actual in-process local dev server runtime

Flow for uv run langgraph dev:

  1. uv selects the Python interpreter/environment
  2. Python wrapper starts
  3. Python wrapper sets LANGGRAPH_CALLING_PYTHON=sys.executable
  4. Python wrapper launches Go binary
  5. Go receives dev
  6. Go shells out to that exact Python interpreter for the actual Python runtime behavior

This preserves the current selected Python environment.

Go Python resolution order for dev:

  1. LANGGRAPH_CALLING_PYTHON
  2. optional explicit override if added later
  3. environment-derived interpreter / active venv
  4. fallback detection
  5. clear failure

The critical constraint is: if the user entered through Python, dev should use that exact Python when possible.

Why Not FFI

Do not use:

  • cgo shared libs
  • Python-Go FFI bindings
  • embedded Python in Go
  • RPC unless absolutely necessary

Reasons:

  • packaging complexity
  • cross-platform pain
  • no advantage for a CLI architecture
  • much worse release/debug story

Process-level boundaries are the right choice here.

Packaging Constraints

The Go binary should be bundled inside Python wheels.

Preferred distribution model:

  • build platform-specific langgraph-cli wheels
  • each wheel includes the matching langgraph Go binary
  • Python launcher resolves and executes the bundled binary

Do not rely on runtime download of the binary for normal operation.

Support matrix target:

  • all OS/arch targets that are currently expected to be supported
  • at minimum, align with the practical support matrix desired for the CLI, using orjson support as a rough proxy if needed

If a platform is unsupported, fail clearly rather than silently falling back forever.

Release Constraints

Phase 1 versioning applies to:

  • shared Go implementation
  • standalone Go langgraph binary
  • PyPI langgraph-cli wrapper

They should stay on one version line.

Constraint:

  • bundled Go binary version must exactly match the Python wrapper version for the migrated surface

The wrapper should detect obvious mismatch and fail clearly if it occurs.

Migration Strategy

Use a big-bang hidden implementation change with gradual activation.

Phase 1 rollout:

  1. implement full Go path behind LANGGRAPH_USE_GO_CLI
  2. keep default behavior on legacy Python
  3. run dual CI for legacy and Go-backed paths
  4. dogfood with feature flag
  5. flip default to Go
  6. keep fallback briefly
  7. remove fallback in about two weeks

This is a big internal rewrite with gradual external activation.

CI Strategy

Dual CI is required during migration.

Run both variants:

  • legacy Python implementation
  • Python wrapper -> Go binary implementation

Required parity checks:

  • help output
  • exit code
  • stdout
  • stderr
  • generated Dockerfile output
  • generated compose output
  • mocked deployment API request semantics
  • validation errors / usage errors

Goal is not merely "both tests pass". Goal is "both implementations behave identically enough to swap by default safely".

Parity Test Philosophy

Use the current Python CLI tests as the behavioral spec.

Priority test areas:

  • config validation
  • compose/Dockerfile generation
  • deployment flows
  • error and prompt behavior
  • command help / command surface

Where practical, add golden comparisons so regressions are obvious.

Implementation Order Inside Phase 1

Even though rollout is one hidden phase, implementation should proceed in this order:

  1. wrapper contract and env contract
  2. Go command scaffolding and package boundaries
  3. config + docker/build/compose logic
  4. deploy flows
  5. remaining commands
  6. dev subprocess orchestration
  7. parity hardening in CI

This reduces risk because dev is the highest-uncertainty area.

Command Ownership Constraint

All command behavior should live in Go once ported.

Do not allow:

  • some flags parsed in Python and others in Go
  • duplicated command logic across Python and Go
  • separate behavior definitions for legacy and migrated commands

The wrapper should be thin only.

Fallback Constraint

Fallback is temporary, not a product feature.

Policy:

  • use feature flag during migration
  • flip default after parity confidence
  • remove legacy Python implementation roughly two weeks later

Do not normalize to permanent dual execution paths.

Documentation Constraint

During migration, documentation should stay conservative:

  • existing Python install flow remains primary
  • feature flag is acceptable for internal/dogfood docs
  • avoid broad external messaging about the Go implementation until default is flipped

Open Issues To Track

These are not blockers, but they need explicit implementation decisions:

  • exact bundled wheel layout for binaries
  • exact list of supported OS/arch targets
  • whether to expose a public --python override for dev
  • whether some pretty output is allowed to improve while keeping parsed output stable

Summary

Phase 1 plan:

  • move the entire langgraph CLI implementation into shared Go code in the langgraph repo
  • ship a standalone langgraph Go binary
  • keep langgraph-cli on PyPI as a thin launcher that bundles and executes that binary
  • preserve uv run / uvx behavior
  • handle dev by passing the calling Python path through the wrapper and having Go subprocess back into Python
  • gate everything behind LANGGRAPH_USE_GO_CLI
  • run dual CI until parity is proven
  • flip default
  • remove legacy fallback quickly