CodeVetter · Repo Unpack · Contributor handoff
langflow-ai/langflow
Onboarding essentials: this is a uv workspace — always `uv run` Python commands, sync sub-package dev groups before testing inside them, run `make format_backend` before committing (ruff select=ALL, line-length 120, big per-file-ignore table), and respect the one-way dependency direction (lfx never imports langflow.*). Component class names are stable identifiers — never rename. Flow JSON shape is a contract. DB model changes require alembic revisions. The service layer is registry-driven: add services via factories + ServiceType, not globals. Highest-risk edit zones: main.py lifespan ordering, endpoints.py/build.py/chat.py auth+policy ordering, and anything that executes stored component code.
- Read first: AGENTS.md (commands + component rules), docs/agents/ARCHITECTURE.md (dependency rules + 'where does code go' decision tree), src/backend/base/langflow/main.py (app wiring + lifespan), services/deps.py + services/utils.py (service registry + init order), api/router.py (route map), api/v1/endpoints.py (run path), lfx/services/manager.py (plugin discovery).
- Commands: `make init` (uv sync --frozen --extra postgresql + npm install + pre-commit), `make backend`/`make frontend` for dev servers, `LFX_DEV=1 make backend` for dynamic component loading, `make unit_tests` / `uv run pytest path::test`, `make lint` (mypy), `uv run git commit` (pre-commit needs uv's env).
- Sub-package test trap: `uv sync` only resolves the top-level workspace; tests inside langflow-base or lfx need `uv sync --group dev --package langflow-base` (etc.) first or dev-only deps like fakeredis are missing and tests fail oddly.
- Never rename a Component class — the class name is the persisted identifier matching components in saved flows and flagging UI updates; renaming breaks existing flows. New components go in src/bundles/<provider>/ (with extension.json) or lfx/components, inheriting lfx Component.
- Dependency direction is enforced by convention: lfx MUST NOT import langflow.* (langflow-base installs the `langflow` package); langflow-base must not import vendor component modules; shared primitives go in lfx/base, never langflow/base; lfx-* bundles must not import application services.
- DB changes: models live in services/database/models/*; every change needs `make alembic-revision` + `make alembic-upgrade`; alembic/versions is excluded from ruff; note async-session traps — cascade_delete_flow lists updatable columns explicitly because getattr on unloaded relationships raises MissingGreenlet.
- Async discipline: blockbuster flags blocking calls in tests; use session_scope() (get_session raises NotImplementedError); DB tests can fail in batch but pass individually; set LANGFLOW_MODELS_DEV_REFRESH=false to keep the network-touching refresh task out of tests.
- Security invariants to preserve when editing run paths: owner-only tweaks (_enforce_owner_only_tweaks), deny->404 UUID-privacy reframes (deny_to_404_unless_readable), public-flow file namespace regex, session namespacing, and prepare_flow_build_for_user before any stored graph executes — these encode prior CVEs.
- Lint conventions: ruff select=ALL at line-length 120 with google docstrings; intentional violations are codified per-file (exec in lfx/custom/validate.py, pickle in services/cache, SLF001 for MCP/third-party internals) — match the pattern or add a justified per-file-ignore; frontend uses biome + tsc --noEmit.
- Version bumps: `make patch v=X.Y.Z` updates pyproject.toml, src/backend/base/pyproject.toml, src/frontend/package.json together; bundle lfx pins are re-synced by scripts/ci/sync_bundle_lfx_pin.py — don't hand-edit versions in only one manifest.
- Hotspot map: main.py (1277 lines) encodes fragile startup ordering documented by inline issue references; __main__.py's ensure_multi_worker_safe guards a real race; endpoints.py's auth/widening wrappers exist because raw Depends helpers once bypassed user scoping — preserve the wrapper pattern. (inferred)