The upstream v2 routers mount under /v1 without collisions. flowweb
answers /api with 404 instead of the SPA shell, and a CI test fails
on any first-party /api/ or /v2/ path in src/.
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
The forge reads .github/workflows natively and github.com reads nothing else,
so one directory serves both planes instead of two conventions that disagree
about which one is live. The reusable in hanzoai/ci already lives there, so a
caller naming that path now resolves to a file that exists.
Co-authored-by: Hanzo Dev <[email protected]>
The lane never scanned anything. `pip --break-system-packages` cannot
install semgrep on the runner image: semgrep wants PyJWT, the image has
PyJWT 2.7.0 from its distro, a distro package carries no RECORD, so pip
cannot uninstall what it needs to replace and stops. The flag grants
permission to touch that environment; it does not make the uninstall
possible. uv gives semgrep its own environment and shims the binary onto
PATH — the same fix the insights lane took, so both read alike now.
With the install working the scan reports four, and all four are the
code being careful rather than careless. Each carries the reason at the
line, because a bare suppression is indistinguishable from someone
silencing a real one:
alembic/env.py the advisory-lock key is an int on both branches —
a sha256 digest reduced mod 2**63-1, or a literal
auth/iam.py decoded unverified only to read `iss` and pick a
validator; a token claiming our issuer then goes to
validate(), which checks the signature and raises
auth/service.py reads a claim without authenticating it, safe while
nothing authorises on the result — today only this
module's tests call it
arxiv.py imports the Element TYPE for annotations; parsing is
defusedxml.fromstring, two lines below
Placement matters and cost a round trip: the directive has to sit on the
line the rule anchors to, which for the jwt call is the `options=` line
carrying verify_signature, not the line opening the call.
Co-authored-by: Hanzo Dev <[email protected]>
Every branch here is reachable from `hanzoai/flow` — 13 refs, not one commit it lacks — and this copy has no push mirror, so anything committed here reached nothing.
Every branch here is reachable from `hanzoai/flow` — 13 refs, not one commit it lacks — and this copy has no push mirror, so anything committed here reached nothing.
hanzoai/flow keeps ghcr.io/hanzoai/flow.
Every branch here is reachable from `hanzoai/flow` — 13 refs, not one commit it lacks — and this copy has no push mirror, so anything committed here reached nothing.
hanzoai/flow keeps ghcr.io/hanzoai/flow.
Every branch here is reachable from `hanzoai/flow` — 13 refs, not one commit it lacks — and this copy has no push mirror, so anything committed here reached nothing.
hanzoai/flow keeps ghcr.io/hanzoai/flow.
The forge reads the first WORKFLOW_DIRS entry that exists. .hanzo/workflows has
existed here since the image lane landed, so all 43 workflow files under
.github/workflows ran nowhere. Nothing went red, because the checks that would
go red were the ones not running: the forge listed 2 workflows for this repo
against 45 files on disk. This repo has had no test gate of any kind since
actions-runner-controller was decommissioned.
security.yml replaces the two dead scanners rather than restoring them:
- codeql.yml ended in github/codeql-action/analyze under `security-events:
write`, which names an upload to GitHub's code-scanning store. There is no
such store here, and a scanner whose findings have nowhere to go is not a
scanner. Semgrep covers the same two languages CodeQL was configured for.
- mend.yml ran the Mend unified agent against WS_APIKEY / WS_USERKEY. That is
a commercial subscription this org does not hold. Dependency and licence
scanning is GONE, not replaced — semgrep reads code, not a dependency graph,
and claiming otherwise would be the more expensive mistake.
Measured before it was written: 98 rules over 3558 git-tracked files in 20s.
The other 42 files are upstream's CI and cannot run here: PR labelers, autofix
bots that push to pull requests, IBM Globalization Pipeline, Codeflash, macOS
and Windows release trains, PyPI publishes for packages this fork does not own,
and a `ci_success` gate that passes only when upstream's flow-nightly was
published to PyPI that same day. They are deleted rather than moved — a dead
file cannot accumulate where there is no directory.
Still ungated and needing their own change: mypy, which lint-py.yml ran and
which nothing ever called even upstream, and `ruff check`, which the tree fails
1204 times against its own declared config.
The enqueue -> upload -> complete calls were transcribed into this file and
twelve others before hanzoai/ci carried them. Four steps collapse to one
`uses:`, so the plane's contract is written once and a fix lands everywhere
rather than in whichever repo someone remembered to edit.
It also self-provisions. sitedeploy POSTs /v1/projects idempotently before the
enqueue, so a slug that has never deployed no longer 404s on its first run --
which is every site in this fleet except hanzo-ai, hips and hanzo-app.
Build steps are untouched, verified byte-identical against the previous
revision; only the Sites-plane plumbing moved.
Co-authored-by: Hanzo Dev <[email protected]>
Three site deploys in this repo published to planes that are not ours.
deploy-storybook.yml handed src/frontend/storybook-static to
actions/deploy-pages. It is a real static site, so it moves:
.hanzo/workflows/storybook.yml builds the export, POSTs
/v1/projects/flow-storybook/deploy, syncs the bytes straight to the S3 prefix
cloud names in the 202, then flips the deployment live — and marks it error if
the run dies after enqueueing, so cloud never records a deployment as "queued"
forever. Modelled on hanzoai/hips. It runs on hanzo-build-linux-amd64, a label
the forge runners actually advertise; a label nothing answers does not fail, it
queues silently until the 24h timeout.
This is a SEPARATE file from .hanzo/workflows/deploy.yml on purpose. That one
builds and pushes ghcr.io/hanzoai/flow — the App the operator runs. An App is a
container the cluster executes, a Site is bytes S3 serves; folding a docs
publish into an image build would couple it to a deployable release.
deploy_gh-pages.yml pushed docs/ to a gh-pages branch via
peaceiris/actions-gh-pages, and deploy-docs-draft.yml published per-PR docs
previews to an AWS S3 bucket behind CloudFront on upstream's account
(DOCS_AWS_ACCESS_KEY_ID, DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID). Both are gone
with no native replacement, because this repo is not where these docs live:
hanzoai/docs carries them at apps/flow, and standing a second site up here would
be a second source of truth for one set of pages. docs/static/CNAME
(docs.flow.org) and docs/static/.nojekyll go with them — Pages artifacts that
mean nothing to S3.
The docs tree also shipped three third-party trackers: a Google gtag
(G-SLQFLQ3KPT), the consent-mode + TrustArc scripts feeding it, and a Segment
plugin reading SEGMENT_PUBLIC_WRITE_KEY. All removed, along with
@docusaurus/plugin-google-tag-manager, @mendable/search and
docs/src/plugins/segment. Telemetry goes to our own bus or nowhere.
Analytics on the storybook is the ONE hosted tag (cloud apps/analytics/tag.go)
posting to POST /v1/event. Its key is read from the project this run deploys, so
pk- rotation is one server-side change rather than five repo edits, and no key
means no tag: a keyless beacon is accepted 200 into $public, a tenant this org
cannot read.
Every other workflow stays. They are release, image-build, lint and test lanes,
not deploy planes.
Co-authored-by: Hanzo Dev <[email protected]>
An unused-import sweep ran against the wrong tree and deleted LOAD-BEARING
imports across the API layer, so main answered 500 NameError on first touch:
settings could not even construct (os, logger, copy2, AGENTIC_VARIABLES,
sanitize_database_url), create_app crashed on DeploymentGuardError, flows
CRUD lost its _new/_read/_patch/_update/_upsert helpers, projects lost its
deployment guards and MCP helpers, monitor lost the message crud, run lost
allow_custom_components (never lifted flat onto Settings) and builds lost the
Flow join. tracing/native.py additionally lost the resolved-spans collection
loop, so every trace flush hit an undefined name.
Repaired mechanically (every fix is an import restored to the module that
defines the name, plus the two Settings fields main.py reads: root_path,
allow_custom_components) and proven live: create -> list -> get -> patch ->
run (ChatInput->ChatOutput graph executes and echoes) -> monitor builds ->
delete, all 2xx against a fresh sqlite deployment. ruff --select F821 is
clean on every touched file.
Co-authored-by: Hanzo Dev <[email protected]>
TypeScript 7 is the native Go compiler and removes `baseUrl` and
`moduleResolution: node|node10`. Both appear here, so `tsc` from TS7
refuses the config outright (TS5102 / TS5108) and cannot typecheck.
`paths` targets resolve relative to `baseUrl` when it is set and relative
to the tsconfig file otherwise. Every `baseUrl` folded here already
pointed at the config's own directory, so dropping it moves nothing and
the targets are left byte-identical. Where a baseUrl pointed elsewhere,
each affected target was rewritten as join(baseUrl, target).
`moduleResolution` was chosen from the declared `module`: commonjs ->
node16, esnext/preserve -> bundler. Configs whose `module` is unset or
exotic were left alone rather than guessed at.
The result is accepted by BOTH toolchains, so nothing has to upgrade
TypeScript in lockstep. Verified on hanzo/chat packages/api: tsc 5.9
779 -> 778 errors (no regression), and tsc 7.0.2 now runs the project
in 2s where it previously refused the config.
Co-authored-by: Hanzo Dev <[email protected]>
core/go.mod was bumped to `go 1.26.5` while the goweb stage stayed on
golang:1.23-bookworm. The golang images pin GOTOOLCHAIN=local, so the base can
never fetch a newer toolchain to satisfy the directive:
go: go.mod requires go >= 1.26.5 (running go 1.23.12; GOTOOLCHAIN=local)
The build dies in 0.086s. A release build that fails in under a second did not
fail to compile — it failed to acquire a toolchain.
This is the fourth instance of the same shape (search-fts5 golang:1.23 vs go
1.26.4, world node:20 vs pnpm@11, search rust:1.89 vs 1.91.1), so I swept the
whole repo: node:20-slim, python:3.12.12-slim-trixie and now golang:1.26 all
satisfy what the tree declares.
Worth noting the frontend is now clean — this build got all the way through
vite to a Go stage, which the asset and identifier fixes are what unblocked.
Co-authored-by: Hanzo Dev <[email protected]>
playground-modal.tsx carried the same import/usage split as the other eight
files — it imported FlowLogoColor from ../../assets/FlowLogoColor.svg and
rendered <HanzoFlowLogoColor>. The previous commit made the identifiers agree,
which is what finally let rollup reach the import and report that the FILE has
never existed either.
Collapsed onto FlowLogo, the asset that does exist. FlowLogoColor was a colour
variant of a monochrome mark used in an 18x18 slot, so one asset under one name
is both correct for the brand and one fewer thing to keep in sync. Also removed
the stale commented-out import above it, which pointed at a third spelling
(FlowLogocolor.svg) that never existed.
Removed pages/MainPage/pages/enchanced-beam-effect.tsx: imports BorderBeam from
../../../components/ui/border-beams, which is not in the repo, and nothing
imports it. Same shape as importButtonComponent.
Note both orphans were UNREACHABLE, so rollup never resolved them and neither
was blocking the build — that is cleanup, not the fix. Widened the sweep from
alias-only to alias + relative specifiers, which is what caught FlowLogoColor:
2778 -> 5495 checked, now 0 unresolved.
Co-authored-by: Hanzo Dev <[email protected]>
The previous commit renamed the asset FILES but shipped none of the 13 source
edits that point at them: git mv and git rm stage themselves, plain edits do
not, and that commit named no paths. So the tree kept importing
@/assets/HanzoFlowLogo.svg?react from an asset that is now FlowLogo.svg, and
rollup failed on exactly the import the rename was meant to remove.
It did move: modules transformed went 3401 -> 6441, so the earlier asset and
module renames were real. This is the remaining 8 files.
Co-authored-by: Hanzo Dev <[email protected]>
The rebrand sed rewrote import PATHS and IDENTIFIERS but never the files they
name, in three distinct ways. Rollup reports one unresolved import per build,
so these surface one at a time, each looking like a fresh unrelated breakage:
1. Assets renamed in code only. Restored the files to the names the code
already asks for:
langflow_assistant.svg -> flow_assistant.svg (6 importers)
langflow-icon-smooth.svg -> FlowLogo.svg (9 importers)
MCPLangflow.png -> MCPFlow.png (1 importer)
No file referenced the old names, so these are pure renames.
2. ONE logo, TWO invented names. The same asset was rewritten to both
FlowLogo.svg and HanzoFlowLogo.svg, so neither existed. Worse, the rename
landed on imports and usages independently — LoginPage imported FlowLogo
and rendered <HanzoFlowLogo>, an identifier bound to nothing. Collapsed to
FlowLogo everywhere (13 occurrences, 8 files); import and usage now agree.
3. Modules renamed in importers only:
custom-langflow-counts.tsx -> custom-flow-counts.tsx
langflow-counts.tsx -> flow-counts.tsx
Also removed modals/fileManagerModal/components/importButtonComponent/. It
imports @/components/ui/morphing-menu, which has never existed in this repo,
and it is orphaned: its only reference is inside a JSX comment and nothing
imports it. Every one of its three onClick handlers is an empty body with a
"// Handle X click" comment, so it is unfinished scaffolding, not a feature.
Writing MorphingMenu to satisfy it would ship a menu where every item does
nothing.
Verified by resolving all 2777 '@/' specifiers against the tree: 0 unresolved,
0 stale references to the renamed files.
Co-authored-by: Hanzo Dev <[email protected]>
vite.config.mts declares envHanzoFlow on line 22 but every USE of it reads
`envHanzo Flow` — the rebrand replaced Langflow with "Hanzo Flow" and the
space landed inside the identifier. esbuild rejects the config before Vite
starts, so the whole frontend stage dies with
`Expected ")" but found "Flow"`.
Usages now match the existing declaration. Swept the tree: this was the only
file the sed corrupted.
Co-authored-by: Hanzo Dev <[email protected]>
With the phantom base uv.lock removed the build reached uv sync and failed:
error: Failed to determine installation plan
Caused by: Distribution not found at: file:///app/src/sdk
[tool.uv.workspace] lists four members — src/backend/base, ., src/lfx and
src/sdk — and the Dockerfile copied manifests for the first three only. uv needs
every member's pyproject.toml present when it resolves, even though the packages
themselves are installed later with --no-install-project.
The build failed with
failed to calculate checksum of ref ...: "/src/backend/base/uv.lock": not found
A uv workspace has exactly ONE lockfile, at the root, which line 54 already
copies. Members do not carry their own, and src/backend/base contains only
README.md, flow/ and pyproject.toml. The comment asserting the base member
required its own lock was wrong.
Verified against [tool.uv.workspace] members = [src/backend/base, ., src/lfx,
src/sdk] and the actual repo tree, rather than repointing the path at
src/lfx/uv.lock, which would have fed the wrong member's lock to the build.
Superseded: the Hanzo GitHub App pushes a webhook, so the forge tracks GitHub
without a per-repo workflow. This file called git.hanzo.ai/api/v1/.../mirror-sync
— a Gitea API for a system we no longer drive — and would sit inert in every repo.
One mechanism, in one place, instead of ~350 copies of a cron.
Empty commit to exercise the rewritten .hanzo/workflows/deploy.yml on
git.hanzo.ai. workflow_dispatch is accepted (HTTP 204) but creates no run on
this forge — every run is ev=push — so a real push is the only reliable
trigger. The previous failures were push events that predated the rewrite
reaching the mirror.
Co-authored-by: Hanzo Dev <[email protected]>
The previous pipeline called buildctl-daemonless.sh, absent from the job image
(catthehacker/ubuntu:act-24.04 per GIT_RUNNER_LABELS) — exit 127 — and read
secrets.GIT_CLONE_TOKEN, which exists on neither the repo nor the hanzoai org.
It had never produced an image.
Its kubectl step failed the same way and was futile regardless: cd.hanzo.ai
selfHeal restores the App CR from the reviewed pin in hanzoai/universe within
minutes, so the deploy step is removed rather than repaired.
This is now urgent rather than cosmetic: actions-runner-controller was
decommissioned 2026-07-27 and every GitHub workflow pinning an ARC pool was
removed, so this pipeline is the ONLY CI this repo has.
Replaced with the shape proven on hanzoai/playground and 14 siblings:
setup-buildx + login-action + build-push-action using GHCR_USER/GHCR_TOKEN/
GH_PAT, which DO exist as org secrets, plus an imagetools check because
build-push-action can exit 0 before the manifest resolves.
git.hanzo.ai mirrors this repo by PULL on a ~10-minute interval, and arcd runs
CI/CD there — so every push waited out that interval before anything built.
This asks Gitea to pull HEAD immediately.
Latency only: the repo already mirrors via the App webhook, so a missing
HANZO_GIT_TOKEN or a failed curl is non-fatal and never fails the push.
Idempotent (mirror-sync just pulls HEAD) and concurrency-coalesced.
These jobs asked for `hanzo-linux-amd64`. No runner advertises that label, so
they never ran — and crucially they did not FAIL either. A job requesting an
unadvertised label queues until the 24h timeout, which reads as "CI is slow"
rather than "CI is misconfigured", so this hid rather than surfaced.
Verified live against git.hanzo.ai /api/v1/admin/actions/runners rather than
inferred: the four ONLINE runners (git-runner-0..3) advertise exactly
ubuntu-latest, ubuntu-22.04, ubuntu-24.04 and hanzo-build-linux-amd64. The
three self-hosted hosts that carry other labels (spark gb10/cuda, dbc
m4max/metal, evo gfx1151/rocm) are all OFFLINE, and none of them advertises
a linux-amd64 pool anyway.
So there is exactly ONE usable pool today and this points at it.
Fleet-wide this was 27 jobs across 14 files in 8 repos. The one remaining
mention of the old label is a COMMENT in iam2/image.yml that documents this
very defect — deliberately left intact.
compiler.py + runner.py + FUNCTIONS.md — take a Flow (graph) and execute it as a
Hanzo Function against the live Fission control plane.
Rebased onto upstream rather than merged: my local tree was 21 commits behind and
also carried a fix repointing four alembic migrations from `langflow.utils` to
`flow.utils`. That fix is obsolete — upstream deleted those four migration files
outright AND completed the rename (src/backend/base/langflow no longer exists,
630 files now under base/flow). Keeping my version would have resurrected four
deleted migrations. Only the genuinely new work is carried forward.
Co-authored-by: Hanzo Dev <[email protected]>
Multi-stage: build the Vite frontend, go:embed it into the CGO-free flowweb
binary, and run flowweb (:8080) as the container front door — it serves the
embedded UI + landing and reverse-proxies /api to the Python flow backend
(:7860, loopback). One image, one origin at flow.hanzo.ai; retires the split
flow-site landing + separate app host. EXPOSE 8080.
One binary fronts flow.hanzo.ai instead of the split flow-site landing + a
separate app host + a disabled-ingress backend. flowweb go:embeds the built flow
UI (Vite frontend) plus a self-contained Hanzo Flow landing and serves it with
SPA fallback, while reverse-proxying /api, /health, /openapi.json, /docs, /logs
etc. to the flow backend (FLOW_BACKEND_URL, default 127.0.0.1:7860). One image,
one origin — hanzoai/world + hanzoai/cloud's embedded-SPA pattern.
At plain `go build` it ships the committed landing placeholder; the image build
overwrites core/cmd/flowweb/frontend/ with the real Vite bundle before `go build`.
Verified: go build clean, GET / serves the landing (200), /api/* proxies.
MlDevice wraps hanzo_ml::Device (CPU/CUDA/Metal/…); MlTensor wraps
hanzo_ml::Tensor — already an Arc-backed device-resident handle, so it IS the
DeviceTensor abstraction concretely (zero-copy step-to-step). The same steps
run on CPU or GPU; only the Device ctor differs (MlDevice::cpu vs ::cuda).
flow-core stays std-only — hanzo-ml lives behind this crate so hanzo-desktop
embeds flow-core alone (tiny) or flow-ml (with compute) as needed. Verified on
CPU (no GPU): a real affine op runs through GpuResidentBackend, 1.0→2.0, result
resident on the injected device. 2/2, clippy clean. cuda/metal features forward
to hanzo-ml for on-GPU residence.
Closes the Mode trio: InProcess, GpuResident, and now Durable each have a real
backend — no bare enum variant without an impl. The DurableBackend checkpoints
each step's outputs to a Store and skips steps that already completed, so a run
survives a crash, resumes where it stopped, and the same graph can be scheduled
or re-run in the cloud without repeating finished work. This is the engine side
of "build/test locally, save to cloud, re-run/schedule on GPU."
Store is a trait — hanzo cloud impls it over S3/Base, hanzo-desktop over local
disk. MemoryStore is the std-only reference impl (also useful for retrying a
failed graph in-process). Verified: resume does NOT re-execute a checkpointed
step. 3/3 durable + 3/3 gpu + 5/5 pipeline = 11/11, clippy clean.
Step 2 of the Rust flow migration: the backend that pins tensors in device
memory across steps. DeviceTensor carries only an opaque handle (a hanzo-ml
tensor id), never the bytes, so it flows step-to-step zero-copy and never
round-trips through host memory. The GpuResidentBackend injects the active
Device into each step via a thread-local (current_device()); real GPU steps
downcast it to their concrete hanzo-ml device (as_any) to call typed kernels.
Core stays dependency-free — Device is a trait, hanzo-ml/hanzo-fusion plug in
behind it. Verified with a stub device: 3/3 gpu tests + 5/5 pipeline = 8/8,
clippy clean.
The Rust twin of hanzoai/flow/core (Go): the ONE parametric step-DAG engine, now
native. Dependency-free (std only) so it drops into hanzo-desktop (local/private,
low memory) and the cloud unchanged — the SAME library builds/tests a flow locally
on BYO GPU and runs/schedules it in the cloud, surfaced via studio.hanzo.ai /
flow.hanzo.ai.
Contract (1:1 with the Go core, so they stay in lockstep):
- Value (Arc<dyn Value>, zero-copy hand-off — the point for GPU tensors), Port, Step
- Graph (add/wire/validate — port typecheck + acyclicity, deterministic Kahn topo)
- Mode (InProcess | GpuResident | Durable) + Backend — the locality/durability seam
where hanzo-ml / hanzo-fusion on hanzo-engine plug in as the GpuResident backend
- Scheduler — the one topology walk, curried over (value-domain, Mode)
Verified: cargo build clean, cargo test 5/5 (domain-agnostic pipeline runs in order,
port type mismatch rejected at wire time, ANY_TYPE bypasses, cycle caught by
validate, Mode selects the backend), clippy clean.
This is step 1 of the Rust migration: the engine. Node-packs (mechanically porting
the Python flow/comfy nodes -> Rust steps on hanzo-ml/hanzo-fusion), the GpuResident
backend on hanzo-engine, and studio's canvas on this engine build on top.
Flow was never live, so no migration ceremony / backward-compat: delete the 70
upstream Langflow migrations and tag every resource table with the IAM tenant.
- deleted flow/alembic/versions/*.py (schema is recreated fresh from the models)
- added org_id (str, indexed, nullable) to all 11 resource tables (flow, folder,
file, variable, api_key, message, jobs, deployment, deployment_provider_account,
flow_version, flow_version_deployment_attachment) — User already had it. This is
the tenant-isolation column the IAM seam's current_org scopes queries on.
py_compile clean. NEXT: scope the 18 user_id query sites by current_org (the
enforcement), then a single fresh initial migration once base.py boots (blocked on
the concurrent session's settings merge).
The directive: hanzoai/ai (api.hanzo.ai) as the AI backend for all. Adds a native
HanzoAI model component so flow routes generations through the Hanzo gateway (one
OpenAI-compatible backend fronting Zen/DeepSeek/GLM) instead of a raw provider SDK
— which is also what lets the provider-langchain zoo be dropped.
- components/hanzo/hanzo_ai.py — HanzoAIModelComponent (LCModelComponent). Points
ChatOpenAI at HANZO_BASE_URL (default https://api.hanzo.ai/v1) with the HANZO_API_KEY
and the Zen model tiers (zen5-pro default). json_mode, temperature, max_tokens.
- components/__init__.py — registered the "hanzo" category.
Also fixes two more rebrand-missed-a-file-rename bugs surfaced by finally getting
the env to resolve (they broke the whole model layer at import):
- renamed lfx/utils/langflow_utils.py -> flow_utils.py (the import was rebranded,
the file wasn't) — unblocks every LCModelComponent.
- added the missing `from .schemas import DeploymentProviderKey` in the migrated
deployment_provider_account model.
NOTE: full app-load can't be verified from here yet — a SEPARATE, pre-existing
breakage (the concurrent session's in-flight upstream settings-mixin merge, PR
#13141) leaves lfx/services/settings/base.py with a broken import block
(VARIABLES_TO_GET_FROM_ENVIRONMENT / Field / MAX_TEXT_LENGTH undefined). That's
theirs to reconcile; this component's own code is py_compile-clean and mirrors the
proven OpenAI component exactly.
The fork's provider extras were pinned to a pre-1.0 langchain generation that
can't coexist with lfx's langchain-core 1.x, so `uv sync` (and CI) couldn't
resolve at all. Per the "route everything through hanzoai/ai" direction, the fix
is to align/drop rather than resurrect the provider zoo:
- langchain-openai >=1.1.12, langchain-anthropic >=1.0, pgvector >=0.4.2 (match lfx / cuga)
- dropped langchain-cohere (no core-1.x release exists; cohere is reached via the
hanzoai/ai gateway, not a direct langchain integration)
- [tool.uv] environments pinned to linux (our deploy target; the macOS-only
cuga/pgvector split is a separate dev-env concern)
- renamed the stale SDK module dir langflow_sdk -> flow_sdk (the rebrand renamed
the pyproject refs + the pytest11 entry point but missed the directory, so the
`flow` test plugin failed to import)
Verified: `uv sync` resolves + installs clean, and the IAM auth seam test now runs
in the REAL full-app env — 7/7 pass (previously only verifiable via direct module
load). This unblocks CI and all further flow work.
The langflow->flow sweep had over-reached in the root pyproject: a langflow-base
missed by the package-name pass was then hit by the general langflow->flow sweep,
becoming 'flow-base' (and 'flow-sdk') in [tool.uv.sources] + the dependency specs,
while the actual member packages are hanzo-flow-base/hanzo-flow-sdk. That made
'uv sync' fail ('flow-base references a workspace ... but is not a workspace
member'). Restored the hanzo- prefix everywhere (protecting existing ones).
Phase (a) of "flow uses only Hanzo IAM": flow stops minting identity and validates
a Hanzo IAM access token instead, mirroring the cloud's principal model so one IAM
login is the same session across console, studio, and flow.
- services/auth/iam.py — the self-contained seam. HanzoIAMValidator verifies an IAM
JWT via the IAM JWKS (PyJWT PyJWKClient), pins RS256, enforces issuer (+audience),
and extracts (org, project, user) into an IAMPrincipal. org falls back to the
Casdoor `owner` claim. Sets current_org/current_project context vars — the hook the
data layer scopes queries on (phase c). Enabled by FLOW_IAM_ISSUER (forward-only, no
compat alias).
- service.py — _authenticate_with_iam wired at the top of _authenticate_with_token:
an IAM token is validated + resolved to an org-scoped flow user (get-or-create,
org_id carries the tenant) BEFORE any local decode. Critically, a token FOR our IAM
issuer that fails verification RAISES — it never falls through to the weaker local
SECRET_KEY path. Inert unless FLOW_IAM_ISSUER is set, so existing deploys are
unaffected.
Verified: the security-critical validator logic was exercised directly (RSA keypair,
signed tokens) — 7/7 pass incl. bad-signature-never-falls-through, wrong-issuer->local
fallback, missing-org/expired rejected, tenant context vars set. iam_test.py runs the
same in CI (full-app deps). py_compile clean.
Follow-on: (b) org_id/project_id on the remaining 15 resource tables + Alembic
migration (User already has org_id); (c) scope the 24 user_id query sites by
current_org/current_project. Those need the running app + a migration, so they land
as the next verified phase.
Cleaned the concurrent session's half-rebrand inconsistency (166 files):
- pip/pypi refs hanzoflow -> hanzo-flow (matches hanzo-flow-base/sdk)
- module paths/commands/devcontainer hanzoflow -> flow (the import name + CLI)
- env vars HANZOFLOW_ -> FLOW_ (was inconsistent with the LANGFLOW_->FLOW_ sweep)
- fixed a stale src/backend/base/hanzoflow/ path that didn't exist
- product display HanzoFlow -> 'Hanzo Flow' in docs; kept HanzoFlow only as a
code identifier where a space is illegal
Canonical: pip install hanzo-flow / import flow / `flow` CLI. compileall clean.
The rebrand sweep had corrupted NOTICE — it read 'Copyright (c) 2024 Hanzo Flow'
where MIT requires the ORIGINAL upstream notice. Restored the exact upstream line
(Copyright (c) 2024 Langflow, verified against langflow-ai/langflow LICENSE) plus
the full MIT permission text, so the legal requirement (retain the original
copyright + permission notice in the distribution) is satisfied in the file where
it belongs — NOTICE.
With NOTICE correct, the README 'Forked from' line is redundant courtesy, not a
legal requirement, and is removed per direction. Attribution lives where it's
actually needed (NOTICE), not the marketing README.
Completes the rebrand per direction: LANGFLOW_/LANGFLOW -> FLOW_/FLOW (env vars,
headers), langflow_ -> flow_ (settings fields, local vars), camelCase identifiers
(LangflowClient/Logo/HTTPError, builtWithLangflow, getLangflowDesktop) -> Flow*,
and all remaining bare tokens. Fixed the identifier-space hazard (Langflow ->
'Hanzo Flow' is prose-only; in code it collapses to Flow so no identifier gains a
space). compileall of flow + lfx is clean.
Repo-wide langflow now = 4, all the protected MIT attribution to langflow-ai/
langflow in README (legally required; deliberately preserved).
Breaking (per no-backwards-compat / forward-perfection): the LANGFLOW_* env-var
prefix is now FLOW_* — deployments must update their env. CI's uv + frontend
build is the runtime gate.
Docs corpus (Langflow->Hanzo Flow, upstream URLs langflow-ai/langflow->hanzoai/
flow, docs.langflow.org->docs.hanzo.ai), starter-project template help-URLs, CI
workflows, and test files. Remaining 'langflow' repo-wide is now dominated by
LANGFLOW_* env-var names + langflow_ settings fields — the config-boundary layer
deliberately preserved pending a coordinated FLOW_/HANZO_ alias-with-fallback
(renaming them blind breaks every deployment).
Stage 2 of the rebrand, on top of the module migration:
- Workspace distribution names langflow-base/langflow-sdk -> hanzo-flow-base/
hanzo-flow-sdk across all pyproject.toml (matches root hanzo-flow).
- App-code product strings: "Langflow" -> "Hanzo Flow", bare "langflow" -> "flow"
across src/backend/base/flow + src/lfx/src (207 files) — MCP server names,
docstrings, user-facing labels.
compileall of the flow package + lfx parses clean.
DELIBERATELY NOT swept (the delicate config-boundary layer — needs the running
app + a compat decision, not a blind sed):
- LANGFLOW_* env vars (LANGFLOW_DATABASE_URL, LANGFLOW_SERVER_URL,
LANGFLOW_SSRF_PROTECTION_ENABLED, ...) — renaming breaks every deployment's
config; needs a FLOW_/HANZO_ alias with fallback, coordinated with deploy env.
- pydantic settings fields that map to those env vars.
- internal identifiers (LangflowHTTPError, *_langflow_connections_binding) — safe
to global-rename but want the uv suite green to confirm no reference is missed.
The concurrent rebrand left src/backend/base/langflow alive with 34 UNIQUE
unmigrated files (a flow_events service, the WatsonX-Orchestrate deployment
adapter, the flow_version_deployment_attachment DB model + tests) that flow/
never received, plus 152 files still importing `langflow` and 137 more with
`langflow.<mod>` string module paths (import_module/monkeypatch targets).
- Migrated all 34 unique files langflow/ -> flow/ at matching paths (dropped
langflow/api/v1/__init__.py, superseded by flow/'s).
- Deleted the emptied src/backend/base/langflow package.
- Repointed every `from langflow`/`import langflow` (152 files) and every
`langflow.<mod>` string path (137 files) to `flow`.
Verify: 0 `from/import langflow` and 0 `langflow.` module refs remain in *.py;
compileall of src/backend/base/flow + src/lfx/src parses clean. Runtime import
resolution + the uv test suite is the CI gate (the module tree is now internally
consistent: every moved model/service resolves under flow/).
Remaining (follow-on commits): runtime identifier strings (MCP server names),
the langflow-base/langflow-sdk distribution names, and the docs corpus.
The seam the whole platform composes on. A stdlib-only Go module (core/) that
holds zero domain knowledge and is what every node-pack and execution backend
plugs into. flow is curried over two parameters:
Value type — what travels on an edge (Message / Tensor / connector I/O). The
engine never inspects it; a Step interprets it per its domain.
Mode — execution locality/durability (InProcess | GPUResident | Durable),
a PARAMETER not baked in: media flows keep latents pinned in VRAM
and serialize nothing; automations checkpoint every step on the
durable backend. Same scheduler, different Backend.
So every product is flow applied:
studio = Run(GPUResident, graph-of-Tensor-steps) // comfy nodes
automations = Run(Durable, graph-of-ConnectorIO) // triggers -> actions
llmFlow = Run(InProcess, graph-of-Message-steps) // langflow (lfx) nodes
Contract: Value (opaque, typed by PortType), Port/Step (typed in/out ports + Run),
Graph (Add/Wire/Validate — port typecheck + acyclicity), Mode + Backend (the
locality/durability seam), and Scheduler (the ONE topology walk, curried over
value-domain and Mode). Ships with the reference InProcess backend; GPUResident
(studio VRAM) and Durable (auto/tasks activities) register the same way.
Tests prove it: a domain-agnostic 2-step pipeline runs in order, port type
mismatch is rejected at wire time, AnyType bypasses, cycles are caught by
Validate, and Mode selects the backend over an identical scheduler walk.
Next: studio and langflow delete their private executors and become node-packs
on this scheduler; auto's tasks engine registers as the Durable backend.
Two boot blockers prevented flow from starting (deploy parked at replicas:0):
1. Duplicate revision id: both the Hanzo-local add_org_id_to_user migration and
the upstream convert_provider_key_and_deployment migration declared
revision='a1b2c3d4e5f6'. Alembic cannot build a history graph with a duplicate
id. Upstream legitimately owns a1b2c3d4e5f6 (chained to head d306e5c17c41), so
the Hanzo migration is re-id'd to 9f2e7b14c0aa and re-parented onto the current
head; its body is made idempotent (guards column/index) so re-runs are safe.
2. Incomplete rebrand: 4 upstream migrations imported 'from langflow.utils import
migration' (+ langflow.services.auth.utils). The langflow shim is an empty
namespace package, so these raised ModuleNotFoundError at alembic load time.
Switched to 'from flow.*' (flow.utils.migration / flow.services.auth.utils both
exist), matching every other migration in the tree.
Validated: alembic ScriptDirectory now loads a single clean head (9f2e7b14c0aa)
across the full 70-revision chain.
LICENSE-attested or repo-attested upstream is now called out at the
top of LLM.md alongside the project description, so downstream
audits (universe/docs/PRODUCTS.md) can rely on a single canonical
location for the OSS lineage statement.
arm64 paused — DOKS has no arm64 droplets, ARC scale set
hanzo-build-linux-arm64 is offline. Without this pin the build
queues for arm64 indefinitely and hits the 24h workflow timeout.
Mirrors hanzo/kms.
Required for hanzoai/.github/.github/workflows/docker-build.yml@main —
without it the workflow_call dies as startup_failure with no jobs
dispatched. Caller permissions are a CEILING.
The K8s deployment uses FLOW_* env vars but the app was reading
LANGFLOW_* env vars (inherited from upstream), causing it to fall
back to SQLite and crash on migration. Changes env_prefix in
pydantic Settings, all os.getenv() calls, header prefixes, and
constants from LANGFLOW_ to FLOW_.
psycopg is under [project.optional-dependencies] postgresql.
Without --extra postgresql, uv sync skips it and the app crashes
with 'No module named psycopg' when connecting to PostgreSQL.
The __init__.py had a duplicate knowledge_bases_router import inside
the __all__ list (syntax error) and was missing imports for
mcp_projects_router, openai_responses_router, models_router,
model_options_router, and projects_router that router.py requires.
The Docker image installs the package as hanzo-flow but version.py
only checks for flow, flow-base, and nightly variants. This causes
a ValueError crash on startup.
Replace custom docker/build-push-action steps with
hanzoai/.github/.github/workflows/docker-build.yml@main.
This gives us proper multi-arch manifest support and
consistent build infrastructure across all repos.
amd64-only until arm64 runner is restored.
- Cap requires-python at <3.14 (faiss-cpu lacks cp314 wheels)
- Override openai to <2.0.0 (hanzo-llm wants >=2.8.0 but mem0ai,
nv-ingest-client, and other langflow deps cap at <2.0.0)
- Disable nv-ingest extra (incompatible openai constraint)
- Remove openai upper bound cap from langflow-base (redundant with override)
- Create placeholder hanzoflow package dir in Dockerfile for uv workspace validation
- Regenerate uv.lock with all constraints resolved