Install the canvas extension
Download the .vsix from GitHub Releases or install from Open VSX. Open any *.langstitch.json file to design graphs.
Documentation
Visual LangGraph IDE — canvas, asset designers, Python 3.13 export, and platform deployment. Install via downloadable VSX or LangTailor desktop.
Download the .vsix from GitHub Releases or install from Open VSX. Open any *.langstitch.json file to design graphs.
docker run -p 8787:8787 \
ghcr.io/langstitch/langstitch-api:latest
Platform API at http://localhost:8787
pip install langstitch-sdk
langstitch new my-agent
Build LangGraph apps with the Python SDK
A complete, production-oriented guide to the langstitch Python SDK. The three
core steps — Requirements,
First Project,
Specification — get you running; the
Production sections cover configuration, secrets, observability,
resilience, security, testing, deployment, and CI you need before shipping.
PyYAML); heavy libraries are opt-in extras.python -m venv .venv) so builds are reproducible.OPENAI_API_KEY — only if you call a
real LLM. Provided via the environment (see §4), never in code.| Extra | Pulls in | Enables |
|---|---|---|
(core) | PyYAML | decorators, config, registries, context, CLI scaffold |
server | fastapi, uvicorn | langstitch run, create_app() (/health, /info, /invoke) |
graph | langgraph, langchain-core | GraphBuilder.compile() → real StateGraph |
llm | langchain | get_llm_provider() (+ a provider pkg, e.g. langchain-openai) |
http | httpx | get_http_client() / external services |
all | all of the above | everything |
# pick what you need
pip install langstitch-sdk # core only
pip install "langstitch-sdk[server,graph,llm,http]" # typical agent service
pip install "langstitch-sdk[all]" # everything
Design note: importing langstitch never imports FastAPI, LangGraph,
LangChain, or httpx. Each helper imports its dependency lazily and raises a clear, actionable
error if the extra is missing — so the core stays light and safe to import in tooling/CI.
langstitch new my-agent # scaffold
cd my-agent
python -m venv .venv && . .venv/bin/activate # (Windows: .venv\Scripts\activate)
pip install -e ".[server,graph,llm,http]" # install with extras
python -m app # bootstrap + print registered components (JSON)
pytest -q # run the generated smoke tests
langstitch run # start the API server
Generated layout — every concept gets its own package:
my-agent/
application.yaml # application config (or precompiled application.json)
env.yaml # runtime environment variables (gitignored in real projects)
pyproject.toml # depends on langstitch; your package is "app"
app/
__init__.py # imports submodules so decorators register on import
graphs/ # @graph — main graph (main.py) + subgraphs
nodes/ # @graph_node — node handlers (state -> dict)
skills/ # @skill
guardrails/ # @input_guardrail / @output_guardrail
policies/ # @business_policy
personas/ # @persona
tools/ # @tool
agents/ # @worker_agent
mcp/ # @langstitch_mcp_server + @mcp_tool/resource/prompt
config.py # @configuration — typed application.yaml sections
state.py # graph state schema (TypedDict)
main.py # @langstitch_graph_server — server + bootstrap()
tests/ # smoke tests
How registration works: decorators record a spec on a process-global registry
at import time. app/__init__.py imports every submodule, so a single
import app wires up the whole application. Keep decorated modules import-safe (no
network/IO at module scope) so import stays fast and deterministic.
| Command | What it does |
|---|---|
langstitch new <name> [--dir P] [--force] | scaffold a project |
langstitch info [--root P] | load config & list all registered components |
langstitch run [--root P] [--host] [--port] | start the API server |
langstitch compile [--root P] | application.yaml → application.json (fast startup) |
langstitch get <json.path> [--root P] | resolve a config path, print as JSON |
langstitch version | print the SDK version |
Describe the agent declaratively: configuration in application.yaml
(or application.json), components via decorators.
app:
name: my_agent
version: 0.1.0
description: Support agent built with LangStitch.
model:
provider: openai # passed to LangChain init_chat_model
name: gpt-4o-mini
temperature: 0.2
server:
host: 0.0.0.0
port: 8000
http:
timeout: 30 # default for get_http_client() (no service)
external_services: # see §5
billing:
serverUrl: https://api.billing.com
basePath: /v1
propagate_headers: [x-request-id, authorization]
auth:
type: bearer
token: ${BILLING_TOKEN}
from langstitch import (
graph, graph_node, skill, persona,
input_guardrail, output_guardrail, business_policy,
tool, worker_agent, configuration,
langstitch_graph_server, GraphBuilder, END,
)
@persona(role="assistant", tone="helpful, concise")
def assistant() -> str:
return "You are a helpful LangStitch support assistant."
@tool(tags=["billing"], roles=["agent"])
def lookup_invoice(invoice_id: str) -> dict:
"""Fetch an invoice by id."""
...
@input_guardrail(description="Reject empty/oversized input.", action="block")
def non_empty(text: str) -> bool:
return bool(text and 0 < len(text) <= 8000)
@business_policy(priority=100, description="Deny refunds over policy limit.")
def refund_limit(context: dict) -> dict:
amount = context.get("amount", 0)
return {"decision": "deny" if amount > 1000 else "allow"}
@graph_node(description="Answer the latest message.")
def respond(state: dict) -> dict:
...
return {"response": "...", "messages": [...]}
@graph(name="main", entrypoint=True)
def main_graph() -> GraphBuilder:
g = GraphBuilder("main")
g.add_node("respond", respond)
g.set_entry_point("respond")
g.add_edge("respond", END)
return g
@langstitch_graph_server(name="my-agent", protocol="http", port=8000)
class Server:
"""Graph API server."""
Two files drive an app, both resolved from the project root.
load_config() runs once at bootstrap and becomes the active in-memory store.
application.yaml — declarative app configuration. Prefer a
precompiled application.json in production (built with
langstitch compile); when present it loads directly with no YAML parse and takes
precedence.env.yaml — runtime environment variables, flattened to
UPPER_SNAKE and exported into os.environ at startup
(openai.api_key → OPENAI_API_KEY). Existing env vars win unless
override=True, so real deployments can set secrets via the platform and keep
env.yaml for local dev.from langstitch import get_config
get_config() # whole AppConfig (cached)
get_config("server.port") # -> 8000 (scalar)
get_config("model") # -> {...} (nested object)
get_config("external_services.billing.auth.type")
get_config("items[0].name") # array index, negative allowed
get_config("missing.key", default="x") # safe default
get_config("server", as_json=True) # -> '{"host": ...}'
from dataclasses import dataclass
from langstitch import configuration
@configuration(section="server")
@dataclass
class ServerConfig:
host: str = "0.0.0.0"
port: int = 8000
# after load_config(): values are bound + type-coerced
cfg = ServerConfig._langstitch_instance # ServerConfig(host="0.0.0.0", port=8000)
env.yaml out
of version control (the scaffold gitignores it) and inject secrets through your platform/orchestrator
in production. Use ${ENV_VAR} interpolation in application.yaml so the file
references secrets without containing them.
from langstitch import get_llm_provider
# reads model.provider / model.name / model.temperature from config;
# API key comes from the environment (populated by env.yaml)
llm = get_llm_provider() # or get_llm_provider("gpt-4o", temperature=0)
result = llm.invoke([{"role": "user", "content": "hi"}])
from langstitch import get_http_client, set_request_headers
# In server middleware, record inbound headers once per request:
set_request_headers(request.headers) # enables header propagation
api = get_http_client("billing") # base_url, timeout, auth, propagated headers wired in
resp = api.get("/invoices/{id}", path_params={"id": 42},
params={"expand": "lines"}, headers={"X-Trace": "1"})
api.post("/invoices", json={"amount": 10})
api.put("/invoices/{id}", path_params={"id": 42}, json={...})
api.patch("/invoices/{id}", path_params={"id": 42}, json={...})
api.delete("/invoices/{id}", path_params={"id": 42})
# also: head/options, api.request("GET", ...), set_header/add_headers/remove_header
# async: get_async_http_client("billing") -> await api.get(...); raw=True for bare httpx
Supported auth.type values:
| type | options | effect |
|---|---|---|
none | — | no credentials |
basic | username, password | Authorization: Basic <b64> |
bearer | token | Authorization: Bearer <token> |
api_key | name (def. X-API-Key), value, in (header|query) | header or query param |
oauth2 | token_url, client_id, client_secret, scope?, audience? | client-credentials; token cached + refreshed |
Hierarchical context keeps parent state clean: every LLM/sub-agent call runs in an isolated child scope and only its final output merges back. Tools are lazily materialized — a node pulls only the subset it needs.
from langstitch import Context, run_llm, run_worker_agent
ctx = Context(data={"question": q}, messages=[{"role": "user", "content": q}])
def call_model(llm_ctx):
# llm_ctx.system = resolved persona; llm_ctx.tools = materialized just for this call
return get_llm_provider().invoke(
[{"role": "system", "content": llm_ctx.system}] + llm_ctx.messages,
)
answer = run_llm(ctx, call_model, persona="assistant",
tool_tags=["billing"], key="answer", as_message="assistant")
# ctx.data["answer"] set; the child's tool traffic + scratch were discarded.
# Delegate to a sub-agent (runs with only its allowed tools, isolated context):
findings = run_worker_agent(ctx, "researcher", carry=["question"])
Dynamic registries are refreshable and lazy — they index decorator specs plus optional external loaders, auto-detect changes, and never eagerly instantiate everything. The graph server exposes them for introspection:
Server.get_all_tools(); Server.get_all_worker_agents()
Server.get_input_guardrails(); Server.get_output_guardrails()
Server.get_skills(); Server.get_policies(); Server.get_personas()
Server.get_tool("lookup_invoice"); Server.refresh_registries()
# module-level equivalents: langstitch.get_all_tools(), ... , refresh_registries()
from langstitch import get_logger
log = get_logger(__name__) # level from LOG_LEVEL env (default INFO)
log.info("handled request", extra={"intent": intent})
LOG_LEVEL via env.yaml / platform env. Use structured fields for
machine-readable logs.X-Request-ID on inbound requests, register it with
set_request_headers(), and list it in each service's propagate_headers
so it flows downstream.get_input_guardrails() before the LLM and get_output_guardrails()
after; honor each spec's action (block/warn/log)
and severity.get_policies() in priority order (highest first) and
short-circuit on a non-allow decision.RuntimeError with an
install hint — fail fast at startup rather than per request. Wrap LLM/tool calls with retries and
timeouts; get_http_client honors the configured timeout.${ENV_VAR}; env.yaml gitignored.roles=[...] and select by role at context-build time.authorization to third-party hosts — only list headers a
service should receive in its propagate_headers.pip audit in CI; rebuild images on base updates.from langstitch import get_registry, reset_registry, Context, run_worker_agent
def test_components_register():
import app # triggers registration
reg = get_registry()
assert "respond" in reg.nodes and reg.server is not None
def test_context_isolation():
parent = Context(data={"topic": "x"})
out = run_worker_agent(parent, "researcher", carry=["topic"])
assert "researcher" in parent.data and "messages" not in parent.data # no leakage
Use reset_registry() / reset_config_cache() between tests for isolation.
Mock external HTTP with httpx MockTransport; the SDK's own suite runs on Python
3.10–3.13 in CI.
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml ./
RUN pip install --no-cache-dir ".[server,graph,llm,http]"
COPY . .
RUN langstitch compile # precompile application.json for fast startup
EXPOSE 8000
CMD ["langstitch", "run", "--host", "0.0.0.0", "--port", "8000"]
Provide secrets at runtime (-e OPENAI_API_KEY=... / orchestrator secrets), not in the
image. The server exposes /health (liveness), /info (registered
components), and /invoke.
python -m build + twine check
validate the distribution.langstitch-sdk>=0.1.0 to pyproject.toml.| Symbol | Kind | Purpose |
|---|---|---|
@graph_node | decorator | node handler state → dict |
@graph | decorator | graph builder (entrypoint=, parent=) |
@skill | decorator | reusable capability |
@input_guardrail / @output_guardrail | decorator | inbound/outbound validation |
@business_policy | decorator | priority-ordered org rule |
@persona | decorator | identity / system prompt |
@configuration | decorator | bind a config section to a dataclass |
@tool / @worker_agent | decorator | LLM tool / delegatable sub-agent |
@langstitch_graph_server | decorator | graph API server (protocol, port, name, properties) |
@langstitch_mcp_server + @mcp_tool/resource/prompt | decorator | MCP server & primitives |
GraphBuilder, START, END | class/const | declarative wiring → LangGraph |
LangStitchApp | class | runtime: bootstrap(), build_graph(), invoke(), info() |
load_config, get_config, compile_config, query_json | fn | config load + JSON-path access |
get_llm_provider, get_http_client, get_async_http_client | fn | configured clients |
get_logger, get_env, get_secret | fn | logging & env access |
set_request_headers, load_service_config, ServiceClient | fn/class | external services + header propagation |
Context, ContextScope, ContextBuilder, run_llm, run_worker_agent | class/fn | hierarchical context |
get_all_tools, get_all_worker_agents, refresh_registries, … | fn | dynamic registries |
Exhaustive signatures and options live in the
SDK docs
and the langstitch package on PyPI.
| Symptom | Cause & fix |
|---|---|
RuntimeError: … requires <pkg> | missing extra — pip install "langstitch-sdk[server|graph|llm|http]" |
No graph registered | mark the root graph @graph(entrypoint=True) and ensure its module is imported |
Component missing from info | its module isn't imported — add it to app/__init__.py |
external service … not configured | add it under external_services in config (or pass the right name) |
| Secret is empty downstream | set the env var; check ${VAR} spelling and env.yaml flattening |
| Config edits not picked up | a precompiled application.json wins — re-run langstitch compile |
Model, prompts, temperature, bound tools/agents.
Inline, registry, or MCP Studio tools.
Sub-agent, remote, A2A, or registry delegation.
Retrieval-augmented node bound to a RAG pipeline.
Special decision node — route by classified intents.
Conditional edges, Python transforms, nested graphs.
Open the sidebar Assets tab to configure reusable project artifacts. All export to dedicated Python modules.
skills/guardrails/rules/personas/rag/The Components designer tab lets you build reusable custom components without editing source. A component is a declarative manifest — identity, ports, a typed config-field schema, a theme, and a safe Python codegen template — that renders on the canvas alongside the built-in nodes and survives the project round-trip.
node / connector /
adaptor), description, icon, version.string, number,
boolean, select, code, secret, json —
with validation and defaults. These render as an auto-generated property form.
Templates use whitelisted placeholder substitution — there is no eval or arbitrary code
execution in the IDE. Values are escaped per field kind, and secret fields are emitted as
os.environ.get(...) rather than inlined.
# Template (authored in the designer)
def {{nodeName}}(state: State) -> dict:
"""{{label}} — {{description}}"""
import httpx
url = {{field.url}} # string → safe Python literal
timeout = {{field.timeout}} # number
api_key = {{field.api_key}} # secret → os.environ.get("...")
resp = httpx.request({{field.method}}, url, timeout=timeout)
return {"{{outputKey}}": resp.text}
| Placeholder | Resolves to |
|---|---|
{{nodeName}} | generated function name for the placed node |
{{label}} / {{description}} | node label / description (escaped for docstrings) |
{{outputKey}} | the node's output state key |
{{field.<id>}} | a config value, escaped by kind (secret → os.environ.get) |
{{field.<id>.raw}} | verbatim injection — allowed for code fields only |
graphs/main.py with
imports hoisted and deduped; langsmith.json records them under
registries.components (schema version 1.2)..component.json and import it
into another project (with replace / import-as-copy collision handling), or publish it to the
marketplace.componentRegistry on the
project document (GraphDocument v1.2); older .langstitch.json projects load
unchanged. The built-in node kinds are untouched.
Platform → Export → Python produces a buildable multi-module ZIP:
src/<package>/
graphs/ # StateGraph definitions
nodes/ # Per-node handlers
skills/ # Skill modules
prompts/ # Prompt templates
tools/ # Tool registry
resources/ # Static resources
connections/ # MCP, remote, A2A
guardrails/ # Guardrail policies
rules/ # Business rules
personas/ # Agent personas
rag/ # RAG pipelines
langsmith.json # LangSmith + LangStitch IDE metadata (incl. registries.components)
langstitch.project.json # full project incl. componentRegistry (custom components)
pip install -e ".[dev]"
pytest
python -m <package>
.langstitch.json or exported ZIPWorkspaces: ~/.langstitch/workspaces/<project-id>/
.vsix and publish to Open VSXghcr.io/langstitch/langstitch-apinpx playwright install chromium
npm run test:e2e
npm run agent:run