docs: external authentication, RBAC, SSO - #13866
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThe PR adds and reorganizes documentation for authentication, authorization, external authentication, and JWT signing, updates sidebar and redirect navigation, adds a Kubernetes Secrets section for global variables, and documents rate-limit counter storage in multi-worker deployments. ChangesAuthentication documentation and navigation
Global variables storage
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 9✅ Passed checks (9 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/docs/Develop/api-keys-and-authentication.mdx (1)
758-760: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRemove the auto-authentication claim from the startup step.
This walkthrough sets
LANGFLOW_AUTO_LOGIN=False, and the earlier section already says users must sign in when auto-login is off. Saying server startup "automatically authenticates you" conflicts with the next/loginsteps and makes the flow misleading.Suggested fix
-Starting Langflow with a `.env` file automatically authenticates you as the superuser set in `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`. -If you don't explicitly set these variables, the default values are `langflow` and `langflow` for system auto-login. +Starting Langflow with a `.env` file applies the configured superuser bootstrap credentials. +When `LANGFLOW_AUTO_LOGIN=False`, you still sign in at `/login` with those credentials. +If you don't explicitly set these variables, the default values are `langflow` and `langflow`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/docs/Develop/api-keys-and-authentication.mdx` around lines 758 - 760, Remove the auto-authentication claim from the startup guidance in the API keys/authentication docs. In the affected paragraph, adjust the Langflow startup wording so it no longer says that launching with a .env file automatically authenticates as the superuser; instead, align it with the LANGFLOW_AUTO_LOGIN=False flow and the later /login steps. Keep the mention of LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD only as credentials/configuration context, not as an automatic login behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/docs/Develop/authentication-overview.mdx`:
- Around line 12-20: The chooser list in authentication-overview is losing its
intended hierarchy because the descriptive paragraphs after each bullet are
flush-left, so they render as standalone paragraphs instead of nested bullet
content. Reformat the text under each bullet in the authentication overview
section so the descriptions stay indented beneath the corresponding items,
keeping the list scannable in MDX. While updating that section, also correct the
wording in the second bullet from “identify provider” to “identity provider.”
In `@docs/docs/Develop/authorization.mdx`:
- Around line 179-184: The “See also” section in the authorization docs still
links to the old JWT page, so update the JWT reference to point to the merged
section under api-keys-and-authentication instead of the legacy
/jwt-authentication URL. Use the existing “See also” list in the
authorization.mdx content to locate the link and make sure any internal docs
links now target the current JWT signing section so they resolve directly
without relying on redirects.
In `@docs/docs/Develop/configuration-global-variables.mdx`:
- Around line 172-173: The Secret naming guidance is inconsistent between the
prose and the example, so update the documentation in the global variables flow
to use the same Secret name convention everywhere. Make the wording in the
section that describes Kubernetes secrets align with the name produced by
encode_user_id(user_id) and the example Secret name used in the steps, so
readers can clearly identify the actual Secret to inspect or create.
- Around line 224-235: The Kubernetes Deployment example is structurally invalid
because `serviceAccountName` and `containers` are shown directly under `spec`
instead of under `spec.template.spec`. Update the YAML snippet in the
configuration docs so the `serviceAccountName` and `LANGFLOW_VARIABLE_STORE`
container environment block are nested inside the Pod spec path used by a
Deployment, and make sure the example is valid and ready to apply as written.
- Around line 254-265: The Secret verification step in the
configuration-global-variables docs is exposing full Secret `.data` values,
which conflicts with the warning and can leak credentials. Update the
verification example to check only the stored keys, using the same Secret name
pattern and context around the UUID lookup, so readers can confirm `credential_`
and generic variable names without printing values. Keep the surrounding
guidance in sync with the warning and the existing explanation of key prefixes.
In `@docs/docs/Develop/external-authentication.mdx`:
- Around line 168-170: The external-authentication guidance is telling readers
to use jwt.io with a live access token, which is unsafe. Update the relevant
wording in the documentation to avoid recommending third-party token paste;
instead, point readers to a local JWT decoder or explicitly instruct them to
inspect only redacted tokens, while keeping the existing `aud`, `iss`, and `exp`
validation guidance in the same section.
- Around line 88-97: The external-auth example is using
langflowai/langflow-nightly:latest without explanation, so update the
walkthrough to either use the stable Langflow image/tag in the documented
current release or add a clear note in the external authentication section
stating that this example requires nightly. Make the change in the docker run
example that follows the external auth settings so readers are not switched to
nightly implicitly.
---
Outside diff comments:
In `@docs/docs/Develop/api-keys-and-authentication.mdx`:
- Around line 758-760: Remove the auto-authentication claim from the startup
guidance in the API keys/authentication docs. In the affected paragraph, adjust
the Langflow startup wording so it no longer says that launching with a .env
file automatically authenticates as the superuser; instead, align it with the
LANGFLOW_AUTO_LOGIN=False flow and the later /login steps. Keep the mention of
LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD only as
credentials/configuration context, not as an automatic login behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 239650c0-0a96-407c-a65a-6fab6aa71a15
📒 Files selected for processing (9)
docs/docs/Deployment/deployment-multi-worker.mdxdocs/docs/Develop/api-keys-and-authentication.mdxdocs/docs/Develop/authentication-overview.mdxdocs/docs/Develop/authorization.mdxdocs/docs/Develop/configuration-global-variables.mdxdocs/docs/Develop/external-authentication.mdxdocs/docs/Develop/jwt-authentication.mdxdocs/docusaurus.config.jsdocs/sidebars.js
💤 Files with no reviewable changes (1)
- docs/docs/Develop/jwt-authentication.mdx
| * To secure a Langflow server with user accounts and API keys using Langflow's built-in authentication, see [API keys and authentication](./api-keys-and-authentication). | ||
| Built-in authentication is always available and is the default setting. Users log in with a username and password, and Langflow issues a short-lived JWT session token and validates Langflow API keys against its own database. | ||
|
|
||
| * To connect Langflow to your company's SSO, OIDC, or identify provider, see [SSO and external authentication](./external-authentication). | ||
| External authentication lets an upstream identity provider, OIDC proxy, or corporate SSO gateway handle login. Langflow accepts the token the proxy forwards, validates it against the identity provider's JWKS endpoint, and provisions a local user automatically. | ||
|
|
||
| * To configure RBAC on your Langflow server, see [Authorization](./authorization). | ||
| After a user is authenticated by any of the authentication paths, the authorization layer decides what the user can do. | ||
| RBAC enforcement requires a registered authorization plugin. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Indent the path descriptions under each bullet.
Lines 13, 16, and 19 are flush-left, so MDX renders them as standalone paragraphs instead of part of the chooser list. That breaks the scan pattern here, and Line 15 still says identify provider.
Suggested fix
-* To secure a Langflow server with user accounts and API keys using Langflow's built-in authentication, see [API keys and authentication](./api-keys-and-authentication).
-Built-in authentication is always available and is the default setting. Users log in with a username and password, and Langflow issues a short-lived JWT session token and validates Langflow API keys against its own database.
+* To secure a Langflow server with user accounts and API keys using Langflow's built-in authentication, see [API keys and authentication](./api-keys-and-authentication).
+ Built-in authentication is always available and is the default setting. Users log in with a username and password, and Langflow issues a short-lived JWT session token and validates Langflow API keys against its own database.
-* To connect Langflow to your company's SSO, OIDC, or identify provider, see [SSO and external authentication](./external-authentication).
-External authentication lets an upstream identity provider, OIDC proxy, or corporate SSO gateway handle login. Langflow accepts the token the proxy forwards, validates it against the identity provider's JWKS endpoint, and provisions a local user automatically.
+* To connect Langflow to your company's SSO, OIDC, or identity provider, see [SSO and external authentication](./external-authentication).
+ External authentication lets an upstream identity provider, OIDC proxy, or corporate SSO gateway handle login. Langflow accepts the token the proxy forwards, validates it against the identity provider's JWKS endpoint, and provisions a local user automatically.
-* To configure RBAC on your Langflow server, see [Authorization](./authorization).
-After a user is authenticated by any of the authentication paths, the authorization layer decides what the user can do.
-RBAC enforcement requires a registered authorization plugin.
+* To configure RBAC on your Langflow server, see [Authorization](./authorization).
+ After a user is authenticated by any of the authentication paths, the authorization layer decides what the user can do.
+ RBAC enforcement requires a registered authorization plugin.As per coding guidelines, "keep paragraphs short and scannable."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * To secure a Langflow server with user accounts and API keys using Langflow's built-in authentication, see [API keys and authentication](./api-keys-and-authentication). | |
| Built-in authentication is always available and is the default setting. Users log in with a username and password, and Langflow issues a short-lived JWT session token and validates Langflow API keys against its own database. | |
| * To connect Langflow to your company's SSO, OIDC, or identify provider, see [SSO and external authentication](./external-authentication). | |
| External authentication lets an upstream identity provider, OIDC proxy, or corporate SSO gateway handle login. Langflow accepts the token the proxy forwards, validates it against the identity provider's JWKS endpoint, and provisions a local user automatically. | |
| * To configure RBAC on your Langflow server, see [Authorization](./authorization). | |
| After a user is authenticated by any of the authentication paths, the authorization layer decides what the user can do. | |
| RBAC enforcement requires a registered authorization plugin. | |
| * To secure a Langflow server with user accounts and API keys using Langflow's built-in authentication, see [API keys and authentication](./api-keys-and-authentication). | |
| Built-in authentication is always available and is the default setting. Users log in with a username and password, and Langflow issues a short-lived JWT session token and validates Langflow API keys against its own database. | |
| * To connect Langflow to your company's SSO, OIDC, or identity provider, see [SSO and external authentication](./external-authentication). | |
| External authentication lets an upstream identity provider, OIDC proxy, or corporate SSO gateway handle login. Langflow accepts the token the proxy forwards, validates it against the identity provider's JWKS endpoint, and provisions a local user automatically. | |
| * To configure RBAC on your Langflow server, see [Authorization](./authorization). | |
| After a user is authenticated by any of the authentication paths, the authorization layer decides what the user can do. | |
| RBAC enforcement requires a registered authorization plugin. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/authentication-overview.mdx` around lines 12 - 20, The
chooser list in authentication-overview is losing its intended hierarchy because
the descriptive paragraphs after each bullet are flush-left, so they render as
standalone paragraphs instead of nested bullet content. Reformat the text under
each bullet in the authentication overview section so the descriptions stay
indented beneath the corresponding items, keeping the list scannable in MDX.
While updating that section, also correct the wording in the second bullet from
“identify provider” to “identity provider.”
Source: Coding guidelines
| ## See also | ||
|
|
||
| - [API keys and authentication](/api-keys-and-authentication) | ||
| - [External authentication](./external-authentication) | ||
| - [JWT authentication](/jwt-authentication) | ||
| - [Security](/security) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Link current docs directly to the merged JWT section.
This page still points readers to /jwt-authentication, even though this PR moved JWT signing docs into api-keys-and-authentication. Using the current section URL avoids sending readers through a legacy redirect.
Suggested fix
-- [JWT authentication](/jwt-authentication)
+- [Configure JWT token signing](/api-keys-and-authentication#configure-jwt-token-signing)As per coding guidelines, "Test internal links to ensure they are functional and reference the correct pages."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## See also | |
| - [API keys and authentication](/api-keys-and-authentication) | |
| - [External authentication](./external-authentication) | |
| - [JWT authentication](/jwt-authentication) | |
| - [Security](/security) | |
| ## See also | |
| - [API keys and authentication](/api-keys-and-authentication) | |
| - [External authentication](./external-authentication) | |
| - [Configure JWT token signing](/api-keys-and-authentication#configure-jwt-token-signing) | |
| - [Security](/security) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/authorization.mdx` around lines 179 - 184, The “See also”
section in the authorization docs still links to the old JWT page, so update the
JWT reference to point to the merged section under api-keys-and-authentication
instead of the legacy /jwt-authentication URL. Use the existing “See also” list
in the authorization.mdx content to locate the link and make sure any internal
docs links now target the current JWT signing section so they resolve directly
without relying on redirects.
Source: Coding guidelines
| When Kubernetes secrets are enabled, each user's global variables are stored in a dedicated `Opaque` Secret in the `langflow` namespace, named after the user's UUID. | ||
| Credentials are stored with a `credential_` prefix on the secret key. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the Secret name guidance consistent.
The prose says the Secret is named after the user's UUID, but the example uses uuid-YOUR_USER_ID, and the implementation derives the name via encode_user_id(user_id). That mismatch makes step 6/7 ambiguous about which Secret name readers should actually expect.
Also applies to: 252-257
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/configuration-global-variables.mdx` around lines 172 - 173,
The Secret naming guidance is inconsistent between the prose and the example, so
update the documentation in the global variables flow to use the same Secret
name convention everywhere. Make the wording in the section that describes
Kubernetes secrets align with the name produced by encode_user_id(user_id) and
the example Secret name used in the steps, so readers can clearly identify the
actual Secret to inspect or create.
| 3. Set `serviceAccountName` and the `LANGFLOW_VARIABLE_STORE` environment variable in your Deployment: | ||
|
|
||
| ```yaml | ||
| spec: | ||
| serviceAccountName: langflow | ||
| containers: | ||
| - name: langflow | ||
| image: langflowai/langflow:latest | ||
| env: | ||
| - name: LANGFLOW_VARIABLE_STORE | ||
| value: "kubernetes" | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
Correct the YAML structure for Kubernetes Deployment
The provided YAML fragment incorrectly places serviceAccountName and containers directly under spec. In a valid Kubernetes Deployment manifest, these fields must reside under spec.template.spec (the Pod spec). Placing them at the spec level will result in an invalid resource definition or failure to apply.
Suggested fix
spec:
template:
spec:
serviceAccountName: langflow
containers:
- name: langflow
image: langflowai/langflow:latest
env:
- name: LANGFLOW_VARIABLE_STORE
value: "kubernetes"Ensure all code examples are valid and ready for immediate use by the reader.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/configuration-global-variables.mdx` around lines 224 - 235,
The Kubernetes Deployment example is structurally invalid because
`serviceAccountName` and `containers` are shown directly under `spec` instead of
under `spec.template.spec`. Update the YAML snippet in the configuration docs so
the `serviceAccountName` and `LANGFLOW_VARIABLE_STORE` container environment
block are nested inside the Pod spec path used by a Deployment, and make sure
the example is valid and ready to apply as written.
| 7. Inspect the Secret's keys to confirm your variable was stored, replacing **YOUR_USER_ID** with the user's UUID: | ||
|
|
||
| ```bash | ||
| kubectl get secret uuid-YOUR_USER_ID -n langflow -o jsonpath='{.data}' | python3 -m json.tool | ||
| ``` | ||
|
|
||
| Credential-type variables appear with a `credential_` prefix. | ||
| Generic-type variables use the variable name directly as the key. | ||
|
|
||
| :::warning | ||
| Do not read or modify Secret values directly with `kubectl`. Editing Secrets outside of Langflow can cause variables to become unreadable. | ||
| ::: |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not tell readers to print Secret .data here.
Line 257 dumps every stored Secret value, just base64-encoded, which conflicts with the warning in Lines 263-264 and unnecessarily exposes credentials during verification. If the goal is only to confirm the keys, show a key-only command instead.
Suggested fix
- kubectl get secret uuid-YOUR_USER_ID -n langflow -o jsonpath='{.data}' | python3 -m json.tool
+ kubectl get secret uuid-YOUR_USER_ID -n langflow -o go-template='{{range $k, $v := .data}}{{printf "%s\n" $k}}{{end}}'As per coding guidelines, "Verify all code examples are tested and working before committing documentation."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 7. Inspect the Secret's keys to confirm your variable was stored, replacing **YOUR_USER_ID** with the user's UUID: | |
| ```bash | |
| kubectl get secret uuid-YOUR_USER_ID -n langflow -o jsonpath='{.data}' | python3 -m json.tool | |
| ``` | |
| Credential-type variables appear with a `credential_` prefix. | |
| Generic-type variables use the variable name directly as the key. | |
| :::warning | |
| Do not read or modify Secret values directly with `kubectl`. Editing Secrets outside of Langflow can cause variables to become unreadable. | |
| ::: | |
| 7. Inspect the Secret's keys to confirm your variable was stored, replacing **YOUR_USER_ID** with the user's UUID: |
🧰 Tools
🪛 LanguageTool
[style] ~264-~264: This phrase is redundant. Consider using “outside”.
Context: ...irectly with kubectl. Editing Secrets outside of Langflow can cause variables to become ...
(OUTSIDE_OF)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/configuration-global-variables.mdx` around lines 254 - 265,
The Secret verification step in the configuration-global-variables docs is
exposing full Secret `.data` values, which conflicts with the warning and can
leak credentials. Update the verification example to check only the stored keys,
using the same Secret name pattern and context around the UUID lookup, so
readers can confirm `credential_` and generic variable names without printing
values. Keep the surrounding guidance in sync with the warning and the existing
explanation of key prefixes.
Source: Coding guidelines
| docker run -d --name langflow \ | ||
| --network container:keycloak \ | ||
| -e LANGFLOW_AUTO_LOGIN=False \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_ENABLED=true \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_PROVIDER=keycloak \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_TOKEN_HEADER=Authorization \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_JWKS_URL=http://127.0.0.1:8080/realms/langflow/protocol/openid-connect/certs \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_ISSUER=http://localhost:8080/realms/langflow \ | ||
| -e LANGFLOW_EXTERNAL_AUTH_AUDIENCE=langflow-app \ | ||
| langflowai/langflow-nightly:latest |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Avoid switching readers onto nightly without saying why.
This walkthrough is the only auth example here that pulls langflowai/langflow-nightly:latest. If external auth is documented for the current release, this should stay on the stable image/tag; otherwise the page needs an explicit note that the example requires nightly.
Suggested fix
- langflowai/langflow-nightly:latest
+ langflowai/langflow:latest📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| docker run -d --name langflow \ | |
| --network container:keycloak \ | |
| -e LANGFLOW_AUTO_LOGIN=False \ | |
| -e LANGFLOW_EXTERNAL_AUTH_ENABLED=true \ | |
| -e LANGFLOW_EXTERNAL_AUTH_PROVIDER=keycloak \ | |
| -e LANGFLOW_EXTERNAL_AUTH_TOKEN_HEADER=Authorization \ | |
| -e LANGFLOW_EXTERNAL_AUTH_JWKS_URL=http://127.0.0.1:8080/realms/langflow/protocol/openid-connect/certs \ | |
| -e LANGFLOW_EXTERNAL_AUTH_ISSUER=http://localhost:8080/realms/langflow \ | |
| -e LANGFLOW_EXTERNAL_AUTH_AUDIENCE=langflow-app \ | |
| langflowai/langflow-nightly:latest | |
| docker run -d --name langflow \ | |
| --network container:keycloak \ | |
| -e LANGFLOW_AUTO_LOGIN=False \ | |
| -e LANGFLOW_EXTERNAL_AUTH_ENABLED=true \ | |
| -e LANGFLOW_EXTERNAL_AUTH_PROVIDER=keycloak \ | |
| -e LANGFLOW_EXTERNAL_AUTH_TOKEN_HEADER=Authorization \ | |
| -e LANGFLOW_EXTERNAL_AUTH_JWKS_URL=http://127.0.0.1:8080/realms/langflow/protocol/openid-connect/certs \ | |
| -e LANGFLOW_EXTERNAL_AUTH_ISSUER=http://localhost:8080/realms/langflow \ | |
| -e LANGFLOW_EXTERNAL_AUTH_AUDIENCE=langflow-app \ | |
| langflowai/langflow:latest |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/external-authentication.mdx` around lines 88 - 97, The
external-auth example is using langflowai/langflow-nightly:latest without
explanation, so update the walkthrough to either use the stable Langflow
image/tag in the documented current release or add a clear note in the external
authentication section stating that this example requires nightly. Make the
change in the docker run example that follows the external auth settings so
readers are not switched to nightly implicitly.
| If authentication fails, mismatched `aud` or `iss` values are the most common causes. | ||
| Optionally, decode the token at [jwt.io](https://jwt.io/) and confirm `iss`, `aud`, and `exp` match the audience mapper. | ||
| The `aud` array should include `langflow-app` from the audience mapper. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Don't tell readers to paste a live access token into a third-party site.
The token in this flow is usable against Langflow. Recommend a local decoder, or explicitly tell readers to inspect only redacted tokens instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/docs/Develop/external-authentication.mdx` around lines 168 - 170, The
external-authentication guidance is telling readers to use jwt.io with a live
access token, which is unsafe. Update the relevant wording in the documentation
to avoid recommending third-party token paste; instead, point readers to a local
JWT decoder or explicitly instruct them to inspect only redacted tokens, while
keeping the existing `aud`, `iss`, and `exp` validation guidance in the same
section.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
3924fa1 to
951ea94
Compare
|
Build successful! ✅ |
* docs: add rate limiting and signup env vars * docs: configure global vars in k8s secrets * docs: per-process login rate limiting * docs: initial rbac and sso content * docs: move jwt page to auth and add redirect * docs: add keycloak example * docs: authorization plugin interface
…rve (#13818) * feat(lfx): per-request-isolated flow execution for lfx serve Run `lfx serve --workers N` flows under per-request process isolation so cross-request os.environ credential leakage is structurally impossible, while sharing the warm library + flow graphs across workers via COW. - gunicorn --preload + UvicornWorker + max_requests=1: the master builds and warms the FlowRegistry once and forks workers (copy-on-write); each worker is recycled after one request. - serve_preloaded_app.py: import-time master entrypoint (build_registry_from_env + gc.freeze) inherited by forks. - serve_gunicorn.py: LFXGunicornApp process manager. - _EXECUTE_GUARD asyncio.Semaphore(1) + guarded_execute around execute_graph_with_capture (run + stream), closing the async concurrent-overlap window that max_requests=1 alone leaves open. - commands.py: _launch_workers launches gunicorn on Unix; refuses --workers>1 on Windows (gunicorn is Unix-only). - credentials.py: gate the post-DB-miss SECRET_KEY-rotation env fallback behind is_env_fallback_disabled() so no_env_fallback is honored (A3). - FlowRegistry.get logs store reconstruction so per-request rebuild cost of uploaded flows is observable. - gunicorn>=22.0 added as a platform-gated (non-Windows) lfx dependency. Tests: process-model (preload/warm/guard/fork-safety), synchronous run contract, no_env_fallback credential regression; existing multi-worker serve tests migrated to the gunicorn launcher. 173 serve+credentials tests green. * Add pre-fork hook * feat(lfx): add opt-in --reset-environ and --sync-workers to lfx serve Two opt-in flags for per-request isolation in multi-worker lfx serve, both defaulting to the existing committed behavior: - --reset-environ: snapshot/restore os.environ around each flow run so a flow's env mutations (or request-scoped credentials) cannot leak into the next request served by the same warm worker (gated by LFX_SERVE_RESET_ENVIRON; read per request in guarded_execute). - --sync-workers: serve via gunicorn's blocking sync worker behind an a2wsgi ASGI->WSGI bridge so the kernel routes each request to an idle worker (one whole request per worker at a time) instead of queueing behind an in-flight request on a busy async worker. The bridge is built lazily post-fork; refused on Windows. Also fix LFXGunicornApp.load() to honor the app import string (it hardcoded the ASGI app, which fed the WSGI sync worker the wrong callable), and declare a2wsgi as a Unix-only dependency alongside gunicorn. * feat(lfx): add --timeout flag for lfx serve worker timeout Expose gunicorn's worker timeout (previously hardcoded at 120s) as --timeout, on both serve_command and the CLI wrapper. A worker that doesn't complete a request within --timeout seconds is killed and restarted. Matters most under --sync-workers: a blocking sync worker cannot heartbeat mid-request, so long-running flows need a higher timeout. Default stays 120s (unchanged behavior); no effect on the Windows uvicorn fallback. * fix(lfx): register VariableService in serve so request-scoped global_vars resolve lfx serve never registered a VariableService, so get_variable_service() returned None in the worker. Every credential path that resolves through it (get_api_key_for_provider, get_all_variables_for_provider, model_utils, KB connectors) therefore never consulted the request scope and fell back to os.environ -- which --no-env-fallback then blocks. As a result, request-scoped global_vars could not supply a model/agent provider API key unless the flow's api_key field was explicitly wired to load the variable. Register the minimal in-memory VariableService in create_multi_serve_app (idempotent; a real DB-backed service is left untouched), making all of those paths request-scope-aware at once. Guard the MCP component's DB-only get_all_decrypted_variables call with hasattr so the minimal service is skipped cleanly instead of raising into its broad except. Verified end-to-end: Basic Prompting and Simple Agent starter flows execute successfully under --no-env-fallback with the OpenAI key supplied only via global_vars, while a request without global_vars still fails (no env leakage). * refactor(lfx): rework lfx serve worker/concurrency flags - Remove --limit-concurrency; --use-sync-workers covers bounded concurrency - Rename --sync-workers to --use-sync-workers/--use-async-workers (default async) - Default --max-requests to periodic recycling (~1000, 10% jitter) for memory hygiene; 0 disables, explicit N overrides - Make Windows/a2wsgi flag refusals loud (typer.echo, not verbose_print) - Declare uvicorn[standard] so standalone lfx installs get uvloop/httptools - DRY: shared serve --help strings (_serve_help) + env context manager in _launch_workers - Document --use-sync-workers / --reset-environ as the per-request isolation mechanisms (async --max-requests is worker hygiene, not isolation) * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(lfx): warn on --timeout with single worker; drop dead limit-concurrency env - Make --timeout an int|None sentinel (matching --max-requests) so the single-worker path can warn that it is ignored instead of silently honoring it; None resolves to DEFAULT_TIMEOUT (120) for gunicorn. - Remove the unused _SERVE_LIMIT_CONCURRENCY_ENV constant. - Dedup the FakeGunicornApp test stubs behind _make_fake_gunicorn_app. * update dep docs * simplify cmmments * skip gunicorn threads in ghost finder * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * fix(lfx): address review comments on serve worker flags - normalize reset_environ/sync_workers OptionInfo sentinels for direct serve_command() callers (truthy sentinels silently enabled the flags) - _exported_env now restores overwritten env vars instead of deleting them - clarify MAX_REQUESTS help: strict isolation needs --reset-environ, or --use-sync-workers WITH --max-requests 1 (sync worker alone is not isolation) - correct Windows fallback comment in pyproject - assert LFX_SERVE_RESET_ENVIRON cleanup; reset preloaded module state in test - add synchronous error-contract test for /run * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * fix(lfx): close reset-environ auth race and warn on unisolated multi-worker - guarded_execute restores os.environ by diff instead of clear()+update(): clear() empties key-by-key and could race a threadpool-run verify_api_key into a 401 - snapshot the API key at app startup (app.state) so auth never reads live os.environ per request; lazy-cache fallback preserves post-creation config (tests) - warn when --workers > 1 has no per-request isolation (no --reset-environ and not --use-sync-workers with --max-requests 1) - wrap the gunicorn import with a friendly error, matching the a2wsgi check - xfail(strict=False) for the timing-dependent async-recycle leak test - --workers help: gunicorn on Unix, uvicorn on Windows - tests: diff-restore add/change/delete + no-clear lock, cached-key auth, isolation warning * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * fix: prevent LangfuseResourceManager deepcopy failure in Agent/Tool Calling Agent (#13966) * fix: prevent LangfuseResourceManager deepcopy failure in Agent components Adds __deepcopy__ to _RootRunReparentingCallbackHandler so that deepcopy(component) calls in component_tool.py no longer trigger LangfuseResourceManager.__new__() with no credential kwargs. Root cause: the #13429 fix (c4d27818a) introduced _RootRunReparentingCallbackHandler as a CallbackHandler subclass. CallbackHandler internally holds a LangfuseResourceManager whose __new__ now requires explicit public_key/secret_key/base_url in newer langfuse builds. Python's deepcopy calls __new__ with no args during copy construction, raising: LangfuseResourceManager.new() missing required keyword arguments The handler carries no per-invocation mutable state, so returning self from __deepcopy__ is safe. The same root cause and workaround are already documented in flow_loader.py (agentic path) for issue #13429. Adds a focused regression test asserting deepcopy(handler) is handler and does not raise. Fixes #13965 * fix(ruff): replace unused lambda args a/kw with _ to fix ARG005 * fix(ci): publish bundle deps before nightly main (#13982) * fix(ci): publish bundle deps before nightly main * fix(ci): wait for bundle pypi propagation --------- Co-authored-by: Eric Hare <ericrhare@gmail.com> * feat(mcp): persist MCP servers in a database table (#13976) * feat(mcp): persist MCP servers in a database table MCP servers were stored in a per-user JSON file guarded by an in-process lock, so concurrent edits lose updates and the list diverges across workers/replicas. Store them in a new mcp_server table (one row per server, unique on (user_id, name)); get_server_list/get_server/update_server become per-row reads/upserts and the in-process lock is removed, so MCP config is safe at any worker/replica count. Secret values (env/headers) are encrypted at rest. Existing _mcp_servers_*.json files are imported by an idempotent startup backfill and a `langflow migrate-mcp` CLI; the file is kept for rollback. REST shape and function signatures are unchanged. Fixes #13970 * fix(mcp): clear hashed cache-key variants when a server changes The shared tool cache is keyed {server_name}:{hash of headers+timeout} (MCPComponent._mcp_servers_cache_key), so clearing only the bare server name left servers with custom headers/timeouts serving stale config after an update. Clear the bare name and all {server_name}: variants. * fix(mcp): re-apply create/merge rules after IntegrityError refetch The IntegrityError fallback in update_server reused the pre-race config, so a concurrent same-name create (check_existing) could overwrite the winning row and a concurrent PATCH (merge_existing) skipped the merge. Now re-apply the rules against the refetched row: check_existing raises 'already exists', merge_existing merges, otherwise overwrite. Adds a concurrency test. * fix(mcp): order get_server_list by created_at to preserve insertion order get_server_list had no ORDER BY, so with several rows the server list order was undefined. The legacy file (a dict) preserved insertion order and the UI relies on it (the starter project must stay first). Order by created_at to restore stable insertion order. * fix(mcp): re-point migration onto current base head to resolve alembic multi-head release-1.11.0 gained migration c3e7a1b9d2f4 after this branch opened. Our migration also chained off 4f0d2c9a8b7e, producing two alembic heads so 'alembic upgrade head' failed and the DB never initialized (cascading to all e2e/integration/docker jobs). Re-point down_revision from 4f0d2c9a8b7e to c3e7a1b9d2f4 so the chain is linear with a single head. --------- Co-authored-by: Saad ur Rehman <saad.urrehman@cleura.com> * docs: external authentication, RBAC, SSO (#13866) * docs: add rate limiting and signup env vars * docs: configure global vars in k8s secrets * docs: per-process login rate limiting * docs: initial rbac and sso content * docs: move jwt page to auth and add redirect * docs: add keycloak example * docs: authorization plugin interface * fix(docker): give runtime user a writable npm cache so stdio MCP servers start (#13992) * fix(docker): give runtime user a writable npm cache so stdio MCP servers start The ubi10 runtime base sets HOME=/opt/app-root/src, and the main image runs `npm install -g` as root, which seeds a root-owned npm cache at /opt/app-root/src/.npm. The runtime user (uid 1000) spawns stdio MCP servers via `npx`, which then fails with `EACCES` on ~/.npm/_cacache, so every stdio MCP server registers but never lists any tools (toolsCount stays null in GET /api/v2/mcp/servers?action_count=true). Pin npm's cache to a uid-1000-owned directory via NPM_CONFIG_CACHE=/app/.npm (immune to the base image's HOME) and hand ownership of the default HOME cache to the runtime user as a fallback. Applied to all three runtime images (main, with_extras, base) that ship Node so npx has a writable cache. * fix(docker): avoid incompatible npm latest * fix: thin single focus border on global-variable input (#13995) fix input style on GV input * ci: add non-blocking flaky test report job with 30-day retention (#13996) * ci: add non-blocking flaky test report job with 30-day retention * fix: tolerate partial blob downloads in flaky-report job One of the ~62 shard blob artifacts failed its download after 5 retries (transient artifact storage error), which failed the whole download step and skipped the merge. For a metrics-only job, merging the shards that did download beats producing nothing: mark the download step continue-on-error and no-op the merge cleanly when no blobs landed. * fix(a11y): improve API keys tab order (#13953) * fix(frontend): repair flow list card a11y * fix(frontend): clear flows page a11y debt * test(frontend): update header count a11y names * test(e2e): open flow cards via action button * fix(a11y): address flow card review * chore: update secrets baseline * fix(a11y): improve settings table scans * refactor(a11y): isolate table scan fixes * fix(a11y): improve API keys tab order * test(frontend): fix shard click targets * fix(a11y): link API key expiry label * fix(a11y): resolve AG Grid accessibility bugs in table component - Fix hidden row control restoration bug by tracking original tabindex values - Fix pagination button self-latching bug by using only class as input signal - Add comprehensive unit tests for both fixes (10 tests, all passing) - Export patchGridAccessibility function and AgGridAccessibilityLabels type for testing - Improve api-keys.a11y.spec.ts to be less brittle with dynamic checks Addresses feedback from keval718 on PR #13953 * fix(a11y): rework API keys table keyboard nav and clear IBM violations Replace the AG Grid DOM-patching hack (MutationObserver + global keydown/ focusin listeners + role rewriting + multi-timeout retries) with a minimal, event-driven patch applied via the grid's own events. Keyboard/tab order: - Tab from the last cell reaches the next control in one press (was three via two dead <body> stops) using AG Grid's tabToNextCell hook. - Disabled pagination buttons are no longer tab stops, and a container-scoped focusin redirect keeps AG Grid from programmatically focusing them; inert/ disabled attributes are avoided because they break the tab guards and trap reverse (Shift+Tab) entry into the grid (WCAG 2.1.2). - API key name cell opens by keyboard (Enter/Space). IBM Equal Access: 8 -> 0 violations on /settings/api-keys (and the shared /settings/global-variables table): - tab guards given role=button + label (element_tabbable_role_valid, 4.1.2) - empty rowgroups demoted to role=presentation (aria_child_valid, 1.3.1) - first body row made tabbable via roving tabindex (aria_child_tabbable) Also: ProviderListItem and the text-cell modal trigger become real buttons, and Shortcuts settings cells open by keyboard. Tests: Playwright single-Tab exit + reverse-tab re-entry regression guards, tab-order test updated to the roving-tabindex model, and Jest unit tests for the tab-guard / rowgroup / roving-row / disabled-paging patches. * fix(frontend): center update toast buttons vertically (#14004) * docs: lfx user docs and bundle extension changes (#13775) * docs: add LFX user documentation section Creates a new Lfx/ folder in the docs with three pages covering the LFX executor from a user perspective: - lfx-overview.mdx: what LFX is, when to use it, and a full command reference table linking to the DevOps SDK docs - lfx-install.mdx: install from PyPI, with bundles, from source, or via uvx — plus bundle install guidance for standalone lfx users - lfx-run.mdx: lfx serve and lfx run with full option tables, stdin/inline JSON, Python script usage, and component category allowlist/blocklist controls Also adds an LFX sidebar category in the "Develop & Deploy" section and a tip in flow-devops-sdk.mdx noting that standalone lfx installs do not include bundle package components. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: move Flow DevOps SDK from API Reference to LFX sidebar * docs: add terminal sidebar icon for LFX section * docs: move Flow DevOps SDK page into Lfx folder * docs: move extensions to lfx section * docs: clean up lfx overview * docs: extension manifest update and release notes * docs: revert release notes * docs: add back nextplaid bundle to release notes * docs: move mcp and compatibility, add prewarm * docs: fix relative link * docs: partials for lfx-bundles * docs: clarify bundle differences * docs: remove lfx prewarm * docs: fix broken lfx user docs link * docs: lfx bundle installation * docs: update graduated bundles * docs: torch opt-in changes * docs: fix partial link * docs: bundles some small fixes * docs: allowlist and blocklist for lfx * docs: separate run and serve pages * docs: final extensions check * docs: lfx MCP page instructions --------- Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a11y): make /assets/files accessible (#13987) * fix(a11y): make /assets/files accessible (LE-1744) Bring the Files page to WCAG 2.1 A/AA, verified with IBM Equal Access plus keyboard/focus tests. - Name the icon-only actions column header, hidden visually (4.1.2) - Open the row actions menu by keyboard from the grid cell (2.1.1) - Label the upload/bulk-delete buttons so they are named on mobile (4.1.2) - Restore a keyboard-only :focus-visible ring on borderless grid cells (2.4.7) - Merge the bulk-delete dialog trigger via asChild to remove the duplicate tab stop (2.4.3) - Move focus into the cell editor when renaming (2.4.3) - Make the upload "try again" retry a real button (2.1.1) Add a dedicated Playwright a11y suite (files.a11y.spec.ts) covering the full state matrix and keyboard/focus behaviour, move the IBM baseline folder under tests/a11y, and baseline the known Radix menu-portal landmark item. * feat(a11y): add frontend accessibility check skill documentation * fix(a11y): return focus to upload button after file picker cancel (LE-1744) Keyboard-activating Upload Files then dismissing the native picker with Esc dropped focus to <body> (WCAG 2.4.3). Only blur on mouse clicks (to keep the #13178 tooltip fix); restore focus to the trigger on keyboard activation. Adds two behavioural a11y tests. * feat(playground): content blocks frontend renderer for v2 workflows (#13391) * feat: native v2 workflows endpoint with pluggable stream protocols Rebased onto release-1.10.0. The base independently rebuilt the v2 workflows backend (RBAC, body globals, share-aware fetch); keep our forward design and conform its auth to that work: 1. Auth: keep get_current_user_for_workflow (session-or-API-key authN that does not hold a DB connection during the inline run, avoiding the SQLite lock contention api_key_security would cause) and enforce the base's RBAC on top: ensure_flow_permission(EXECUTE) before run, (READ) before status reconstruct, with widen_for_shares fetch. 2. Port the base's request-body globals onto the v2 WorkflowRunRequest. The X-LANGFLOW-GLOBAL-VAR-* headers stay supported (the Responses API passes globals that way); body globals win on conflict. Converters echo the effective globals via effective_globals. 3. Public endpoint keeps the v1 build_public_tmp posture (access_type==PUBLIC, run-as-owner); RBAC applies to the authenticated endpoint only. 4. Preserve the base's post-build KB-cache invalidation in the AG-UI build path. The endpoint, AG-UI bridge, pluggable stream adapters, public endpoint, and re-attach are unchanged. * feat(api): add output_text and session_id to v2 workflow response The synchronous /api/v2/workflows response keyed every result under its component id, so reading the answer meant knowing an id you can't predict. Surface two additive fields: - output_text: the flow's single text answer (ChatOutput/TextOutput). None when the flow has zero or multiple text outputs, so callers read outputs rather than the shortcut guessing which channel is the answer. - session_id: echoes the resolved session so chat/memory callers can continue the same thread (v1 /run returned this; v2 had dropped it). outputs is unchanged, so this is non-breaking. * test(api/v2): cover output_text and session_id on the v2 workflow response Pin the sync-response shortcuts on the v2 workflows endpoint: - output_text surfaces the lone ChatOutput/TextOutput text and stays None for non-output message nodes, data-only flows, and multi-text flows - session_id echoes the resolved session; the error response exposes neither - each outputs entry exposes only {type, status, content, metadata}, with the component id carried by the dict key Also drop the component_id kwarg the converter passed to ComponentOutput, which has no such field and silently dropped it. * feat(api/v2): structured output with resolution reason on v2 response Replace the flat output_text shortcut with an `output` object carrying the resolved text answer plus a `reason` that explains why it resolved that way (single/multiple/none/non_string/failed), so a null answer is always diagnosable instead of silently None. `reason` follows the LLM-domain finish_reason/stop_reason convention, distinct from the lifecycle status. Also add `display_name` to each ComponentOutput (the stable component id stays the dict key) and a computed `has_errors` flag derived from errors. * feat(api/v2): add request-side output selection (output_ids) Let a sync caller name the output(s) they want via output_ids so output.text resolves deterministically (reason=single) on multi-output flows instead of going null. Selection is steer-only: it picks the answer among the named outputs without filtering the outputs map. Invalid ids are rejected with 422 before the flow runs (and before any job row is created), so a typo costs no compute. Resolution considers selected outputs that actually fired, so branching flows resolve to whichever candidate ran. * feat(api/v2): emit per-output events on the langflow stream Give v2-workflows sync and the langflow stream protocol one parser. The stream now emits a normalized "output" event per terminal output carrying an OutputEvent (the ComponentOutput shape sync returns in outputs[id], plus component_id). A shared build_component_output() backs both the sync converter and the adapter, and the build loop ships authoritative vertex metadata as an additive output_meta key on end_vertex (existing consumers read build_data and ignore it). This is access-pattern parity (one parser, same fields, same terminal set), not byte-identical content: the stream reuses the v1 build path whose display serialization differs from sync's run_graph output. * feat: make content_blocks the source of truth for Message content Migrate Message.text from a Pydantic field to a @computed_field over content_blocks, and unify ContentBlock into the discriminated ContentType union so a Message's payload is one uniform shape. Schema changes: - Add 7 new content types (Image, Audio, Video, File, Reasoning, Usage, Citation) with validators for media sources, non-negative tokens, and ordered citation indices - Promote 'contents: list[ContentType]' to BaseContent so any node can nest (multimodal tool outputs, multi-step reasoning, grouped errors) - Fold ContentBlock into BaseContent and into the ContentType union with tag 'group'; content_blocks is now 'list[ContentType]' everywhere - Fix Data.__setattr__ to route through property descriptors via MRO walk Setter / serialization: - text setter appends a single TextContent at the end of content_blocks, preserving non-text blocks in chronological order (tool calls first, final text last) - model_post_init preserves explicit None in data['text'] when no TextContent exists in content_blocks, so callers can still distinguish 'text was never set' from 'text was set to empty string' from_lc_message: - Handle AIMessage tool_calls and usage_metadata regardless of whether content is a string or a list (tool-calling agents commonly emit content='' alongside tool_calls) - Tolerate explicit source=None in multimodal image payloads MessageResponse.from_message / MessageTable.from_message: - Accept any of (data['text'] set, text_stream pending, content_blocks non-empty) as 'content present', so tool-call-only and media-only messages persist rather than getting rejected as missing required fields Tests cover all new content types, the unified ContentType union, the text/content_blocks contract, the setter's chronological append, and the required-fields gate. * feat: stable id on content blocks + plumb LangChain tool_call_id Adds an optional 'id: str | None' field to BaseContent for stable identity across re-emissions of the same logical block. Producers that have a natural id (LangChain tool_call_id, external API id, a UUID stamped before the first emission) set it; consumers use it for dedup and cross-frame correlation. Without an id, consumers fall back to position-derived dedup, which assumes content_blocks is append-only within a message lifetime. Plumbs LangChain's 'tool_call_id' through 'Message.from_lc_message' into 'ToolContent.id'. The same logical tool call across start, args streaming, and result lifecycle now carries the same id, so a re-fired add_message dedups to one ToolContent instead of producing duplicates. Tests cover id default/round-trip/inheritance across every concrete content type, plus tool_call_id stability across repeated conversion, multiple tool calls each keeping their own id, and tool_calls alongside string content. * fix(schema): MessageResponse parses microsecond timestamps and ContentBlock partial updates preserve unset fields Two schema regressions surfaced in QA across the content-blocks chain: 1. MessageResponse.timestamp was typed as a bare datetime, but Message.timestamp default is a string with microsecond precision and a UTC timezone label ('%Y-%m-%d %H:%M:%S.%f %Z') that Pydantic's default datetime parser rejects. Any freshly built Message routed through MessageResponse.from_message raised ValidationError. Reuse the shared str_to_timestamp_validator so MessageResponse accepts every format Message itself recognises. 2. ContentBlock.__init__ marked every field as model_fields_set, not just the discriminator. The override defeated exclude_unset for the group content type: a patch like ContentBlock(title='new') dumped every defaulted field and, when merged onto an existing block by aupdate_messages, overwrote fields the caller never touched. Mark only 'type' (the discriminator) so partial updates carry the variant tag without clobbering the rest. Adds regression tests in test_message_content_blocks.py: from_message round-trips Message.timestamp without crashing, and ContentBlock exclude_unset stays narrow to the explicit fields plus the discriminator. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * feat(playground): content blocks frontend renderer for v2 workflows * feat(frontend): add v2 (beta) tab to the API Access modal Add a v1 / v2 (beta) version toggle above the language tabs. The v2 tab emits Python, JavaScript, and cURL snippets for POST /api/v2/workflows. The two examples are framed by outcome, not by API jargon: "Get the full result" (the default single JSON response) and "Stream the result as it runs", each with a one-line plain-language description. The streaming snippets consume the default langflow protocol (switch on the event field; handle add_message, token, and end) rather than forcing the agui protocol. The API key is read from an env var, and a short response peek shows the shape a caller gets back. * feat(frontend): v2 examples read the answer from output.text * feat(agent): interleaved text + tool_use rendering and tabbed tool-output visualizer * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * fix(api/v2): buffer parallel messages in the AG-UI translator instead of dropping them Parallel components stream tokens for different message ids interleaved. The translator tracked a single open message: the first foreign token closed the open message and tombstoned its id, so every later event for it was dropped and its remaining text never reached the client. Tokens for a message that cannot take the wire now buffer until the open message genuinely ends (its add_message finalizer), then flush in arrival order; complete messages landing mid-stream buffer the same way instead of interleaving a second START. end/error drain all buffers before the terminal event. The wire still carries at most one open text message, so the stream stays AG-UI-conformant. * [autofix.ci] apply automated fixes * fix(api/v2): gate AG-UI message finalization on non-partial state and purge removed buffers A partial add_message re-fire (the agent path emits these at tool start/end for a message it is still streaming) was treated as the finalizer: it closed and tombstoned the id, so the post-tool answer was dropped. Only a non-partial add_message finalizes now; state defaults to complete, so payloads without properties are unchanged. remove_message now purges a buffered message and tombstones its id, so text the backend retracted is not flushed to the client later. * fix(playground): scheme-guard content block URLs against unsafe schemes Lift safeUrl into a shared chatComponents/url.ts and route the file content block's download anchor through it, degrading unsafe-scheme URLs to a non-clickable label. Gate the image/audio/video src through the same helper so a data:text/html or attacker-chosen src is dropped rather than rendered. Matches the existing citation sanitization in SourcesStrip. Adds file-branch tests for an http URL and a javascript: URL. * refactor(playground): extract content-block layout + media renderers, drop favicon egress - Extract resolveContentBlockLayout from the duplicated shape-detection block in bot-message and chat-message into chat-messages/utils, with a direct unit test (legacy group, interleaved flat, text-only, divergent text, edit mode). - Split image/audio/video/file renderers out of ContentDisplay into MediaContentDisplay so the dispatcher stops growing per content type. - Replace the Google favicon fetch in SourcesStrip with a local Globe icon so cited source domains aren't leaked to a third party. - Trim WHAT comments to WHY, add typed test factories. * feat: make content_blocks the source of truth for Message content Migrate Message.text from a Pydantic field to a @computed_field over content_blocks, and unify ContentBlock into the discriminated ContentType union so a Message's payload is one uniform shape. Schema changes: - Add 7 new content types (Image, Audio, Video, File, Reasoning, Usage, Citation) with validators for media sources, non-negative tokens, and ordered citation indices - Promote 'contents: list[ContentType]' to BaseContent so any node can nest (multimodal tool outputs, multi-step reasoning, grouped errors) - Fold ContentBlock into BaseContent and into the ContentType union with tag 'group'; content_blocks is now 'list[ContentType]' everywhere - Fix Data.__setattr__ to route through property descriptors via MRO walk Setter / serialization: - text setter appends a single TextContent at the end of content_blocks, preserving non-text blocks in chronological order (tool calls first, final text last) - model_post_init preserves explicit None in data['text'] when no TextContent exists in content_blocks, so callers can still distinguish 'text was never set' from 'text was set to empty string' from_lc_message: - Handle AIMessage tool_calls and usage_metadata regardless of whether content is a string or a list (tool-calling agents commonly emit content='' alongside tool_calls) - Tolerate explicit source=None in multimodal image payloads MessageResponse.from_message / MessageTable.from_message: - Accept any of (data['text'] set, text_stream pending, content_blocks non-empty) as 'content present', so tool-call-only and media-only messages persist rather than getting rejected as missing required fields Tests cover all new content types, the unified ContentType union, the text/content_blocks contract, the setter's chronological append, and the required-fields gate. (cherry picked from commit 3b92500349420e2651a08587aa4e4292314ef8bf) * feat: stable id on content blocks + plumb LangChain tool_call_id Adds an optional 'id: str | None' field to BaseContent for stable identity across re-emissions of the same logical block. Producers that have a natural id (LangChain tool_call_id, external API id, a UUID stamped before the first emission) set it; consumers use it for dedup and cross-frame correlation. Without an id, consumers fall back to position-derived dedup, which assumes content_blocks is append-only within a message lifetime. Plumbs LangChain's 'tool_call_id' through 'Message.from_lc_message' into 'ToolContent.id'. The same logical tool call across start, args streaming, and result lifecycle now carries the same id, so a re-fired add_message dedups to one ToolContent instead of producing duplicates. Tests cover id default/round-trip/inheritance across every concrete content type, plus tool_call_id stability across repeated conversion, multiple tool calls each keeping their own id, and tool_calls alongside string content. (cherry picked from commit a3e8b40811dd72eb1650b299dccb20be7cd0c8cf) * fix(schema): MessageResponse parses microsecond timestamps and ContentBlock partial updates preserve unset fields Two schema regressions surfaced in QA across the content-blocks chain: 1. MessageResponse.timestamp was typed as a bare datetime, but Message.timestamp default is a string with microsecond precision and a UTC timezone label ('%Y-%m-%d %H:%M:%S.%f %Z') that Pydantic's default datetime parser rejects. Any freshly built Message routed through MessageResponse.from_message raised ValidationError. Reuse the shared str_to_timestamp_validator so MessageResponse accepts every format Message itself recognises. 2. ContentBlock.__init__ marked every field as model_fields_set, not just the discriminator. The override defeated exclude_unset for the group content type: a patch like ContentBlock(title='new') dumped every defaulted field and, when merged onto an existing block by aupdate_messages, overwrote fields the caller never touched. Mark only 'type' (the discriminator) so partial updates carry the variant tag without clobbering the rest. Adds regression tests in test_message_content_blocks.py: from_message round-trips Message.timestamp without crashing, and ContentBlock exclude_unset stays narrow to the explicit fields plus the discriminator. (cherry picked from commit 6f6639374faf52bfd7d0041a3fa3aec7a1c26a8a) * fix(schema): address content_blocks review feedback - sync langflow-base ContentBlock.__init__ with the lfx copy (model_fields_set parity) so exclude_unset no longer clobbers type; add cross-module regression test - route MessageResponse content_blocks discriminator-first so stored flat blocks with contents=[] validate instead of raising - move Message SecretStr coercion into model_post_init and drop the dead validate_text before-validator - drop the no-op _fold_text_into_content_blocks validator - log a shape-only debug line when from_lc_message drops an undecodable image - type MessageResponse.content_blocks as list[ContentType] | None (cherry picked from commit 6c7cec8a529c38d2f9eb7a35f94277611c8696cc) * fix(agents): stop duplicating the final answer in content_blocks With content_blocks as the source of truth, Message.text is a computed field whose setter appends a trailing top-level TextContent. handle_on_chain_end also appended the same answer into the Agent Steps group, so the final answer rendered twice (assert 2 == 1 in test_multiple_events). The streaming path already relies on the setter alone; make the non-streaming path match. * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * feat(schema): project new content_blocks back to the v1 wire shape The in-memory Message and the v2 (AG-UI) path use the new content_blocks union (groups tagged "group", every node carries id/contents, the agent answer is a trailing top-level TextContent). The v1 API keeps emitting the pre-1.11.0 shape via a pure legacy_render projection that runs only at the v1 boundaries: the v1 read/response models, the memories endpoint, the v1 build SSE stream, the webhook events SSE stream, and the /run response and stream. The build, webhook, and /run projections recurse so the Data mirror (data.data.content_blocks) is projected alongside the top-level copy. v2 serializes the live Message and keeps the new shape. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes * fix(api): keep simple_run_flow returning RunResponse, project v1 at the HTTP boundary simple_run_flow is a shared helper, so wrapping its return in a JSONResponse to apply the v1 content_blocks projection broke internal callers that call .model_dump() on the result (the streaming run_flow_generator and get_build_results). Return the RunResponse object from the helper and apply the projection at the non-stream HTTP boundary in _run_flow_internal instead. The streaming path already projects the end event via _project_run_event. * [autofix.ci] apply automated fixes * fix(frontend): route media content URLs through safeUrl The media content type rendered <img src={url}> with the raw url, unlike the sibling image/audio/video/file cases that guard via safeUrl. A javascript:/data: scheme from untrusted tool output reached the img src. Apply the same safeUrl guard and skip rendering when it returns null. * fix(frontend): detect untyped legacy groups in content-block layout resolveContentBlockLayout detected groups with type === "group", missing the legacy / v1-projected "Agent Steps" group that is persisted without a type field (just title + contents). Those untyped groups failed hasGroup and were miscounted as flat non-text items, wrongly enabling ordering mode so the duplicate top-level text rendered above the tools and the bubble body was suppressed. Use the shared isGroupedBlock predicate, matching the rest of the render pipeline. Also document the latent tool-less-group gap in ContentBlockDisplay (a no-tool answer projects to a text-only group; the drop is benign today but the gate keys off toolItems, not groupedBlocks). * fix(frontend): render a group's displayable non-tool content ContentBlockDisplay gated entirely on a group's tool_use leaves, so a group whose contents had no tool_use (reasoning / citation / media, …) rendered nothing. Collect a group's displayable non-tool leaves and render them through the same loose renderer as top-level flat leaves, while keeping text and usage out so the legacy v1 Input/Output scaffolding stays hidden (the bubble body already paints the answer). Adds collectGroupLooseLeaves and unit tests. * test(frontend): deep probes for ContentBlockDisplay group rendering Renders the real ContentBlockDisplay (real ContentDisplay / ToolCallCard / SourcesStrip / accordion; only ESM infra is mocked) and asserts the DOM for each content_blocks shape: a tool-less group's citation and media now render, an untyped legacy group renders its non-tool content, a tool-bearing group renders both the tool and its extra leaf, the legacy Input/Output text stays hidden, text-only and usage-only groups render nothing, and the flat shape is unchanged. Confirmed RED on the pre-fix component (4/8 fail) and GREEN after. * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * chore: re-trigger CI after [skip ci] bake commit * fix: regenerate component_index.json after agent.py content_blocks change The merged index still carried AgentComponent's pre-#13390 code, so the runtime hash allow-list (built from the index) didn't match the live agent.py. That failed the custom-component admin-only known-template carve-out (403 instead of 200) and drifted Update Component Index. Rebuilt via 'make build_component_index'; sha256 now 070077b2. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * feat(api): durable background execution service (store + default backend) (#13507) * feat: native v2 workflows endpoint with pluggable stream protocols Rebased onto release-1.10.0. The base independently rebuilt the v2 workflows backend (RBAC, body globals, share-aware fetch); keep our forward design and conform its auth to that work: 1. Auth: keep get_current_user_for_workflow (session-or-API-key authN that does not hold a DB connection during the inline run, avoiding the SQLite lock contention api_key_security would cause) and enforce the base's RBAC on top: ensure_flow_permission(EXECUTE) before run, (READ) before status reconstruct, with widen_for_shares fetch. 2. Port the base's request-body globals onto the v2 WorkflowRunRequest. The X-LANGFLOW-GLOBAL-VAR-* headers stay supported (the Responses API passes globals that way); body globals win on conflict. Converters echo the effective globals via effective_globals. 3. Public endpoint keeps the v1 build_public_tmp posture (access_type==PUBLIC, run-as-owner); RBAC applies to the authenticated endpoint only. 4. Preserve the base's post-build KB-cache invalidation in the AG-UI build path. The endpoint, AG-UI bridge, pluggable stream adapters, public endpoint, and re-attach are unchanged. * feat(api): add output_text and session_id to v2 workflow response The synchronous /api/v2/workflows response keyed every result under its component id, so reading the answer meant knowing an id you can't predict. Surface two additive fields: - output_text: the flow's single text answer (ChatOutput/TextOutput). None when the flow has zero or multiple text outputs, so callers read outputs rather than the shortcut guessing which channel is the answer. - session_id: echoes the resolved session so chat/memory callers can continue the same thread (v1 /run returned this; v2 had dropped it). outputs is unchanged, so this is non-breaking. * test(api/v2): cover output_text and session_id on the v2 workflow response Pin the sync-response shortcuts on the v2 workflows endpoint: - output_text surfaces the lone ChatOutput/TextOutput text and stays None for non-output message nodes, data-only flows, and multi-text flows - session_id echoes the resolved session; the error response exposes neither - each outputs entry exposes only {type, status, content, metadata}, with the component id carried by the dict key Also drop the component_id kwarg the converter passed to ComponentOutput, which has no such field and silently dropped it. * feat(api/v2): structured output with resolution reason on v2 response Replace the flat output_text shortcut with an `output` object carrying the resolved text answer plus a `reason` that explains why it resolved that way (single/multiple/none/non_string/failed), so a null answer is always diagnosable instead of silently None. `reason` follows the LLM-domain finish_reason/stop_reason convention, distinct from the lifecycle status. Also add `display_name` to each ComponentOutput (the stable component id stays the dict key) and a computed `has_errors` flag derived from errors. * feat(api/v2): add request-side output selection (output_ids) Let a sync caller name the output(s) they want via output_ids so output.text resolves deterministically (reason=single) on multi-output flows instead of going null. Selection is steer-only: it picks the answer among the named outputs without filtering the outputs map. Invalid ids are rejected with 422 before the flow runs (and before any job row is created), so a typo costs no compute. Resolution considers selected outputs that actually fired, so branching flows resolve to whichever candidate ran. * feat(api/v2): emit per-output events on the langflow stream Give v2-workflows sync and the langflow stream protocol one parser. The stream now emits a normalized "output" event per terminal output carrying an OutputEvent (the ComponentOutput shape sync returns in outputs[id], plus component_id). A shared build_component_output() backs both the sync converter and the adapter, and the build loop ships authoritative vertex metadata as an additive output_meta key on end_vertex (existing consumers read build_data and ignore it). This is access-pattern parity (one parser, same fields, same terminal set), not byte-identical content: the stream reuses the v1 build path whose display serialization differs from sync's run_graph output. * feat(api/v2): durable background execution service (store + default backend) Turns v2 mode:background into a durable, in-API background execution service behind a BackgroundExecutionService facade. Adds the store layer (result/error columns, job_events durable milestone log, execution_signals control, heartbeat/lease, 3 migrations), the default backend (bounded executor, runner, in-memory live bus, liveness-aware single-flight orphan sweep), the v2 endpoint rewiring, and the real-instance test harness. Needs no new infra; works on the SQLite single-process install. The redis-scaled worker backend is stacked on top in a follow-up PR. * test(background-execution): rename hard_proof marker to real_services The hard_proof marker name was a vibe word that said nothing about what the tests need. Rename it to real_services everywhere: the pytest marker registration, the *_hard_proof.py test files, the Makefile target (real_services_tests), the -m selector in migration-validation.yml, and the CI job. real_services says what these tests require: real Postgres + Redis + worker subprocesses. (integration was already taken for the external-API suite under tests/integration.) * fix(api/v2): enforce no-code-execution gate on public workflow endpoint The v2 public endpoint only ran validate_flow_for_current_settings and skipped validate_public_flow_no_code_execution, which the v1 build_public_tmp path applies. A public flow containing a Python interpreter/REPL (or the legacy Python Code Structured tool, Smart Transform lambda) was therefore an unauthenticated server-side code-execution primitive (report H1-3754930). Mirror v1: import the validator and call it right after the public-access gate. PublicFlowValidationError subclasses CustomComponentValidationError, so the existing handler already sanitizes it to a 400 'This flow cannot be executed.' without leaking the blocked component class names. Add a non-mocking test that builds a public flow with a real PythonREPLComponent and asserts the sanitized 400 (verified RED: returns 200 without the gate). LE-1389 * fix(api/v2): reconstruct background workflow status from job-keyed vertex builds A completed background job's GET status 500'd with 'No vertex builds found for job_id'. The background build path differed from the sync path twice: 1. generate_flow_events minted a fresh run_id instead of using job_id, so vertex builds were keyed by an id the status query never uses. Thread run_id through _stream_event_frames -> generate_flow_events and pass job_id from the background buffer so graph.run_id == job_id (the sync path already does graph.set_run_id(job_id)). 2. The SSE build loop (build_vertices) only persisted builds when log_builds was set and never passed job_id. Tie log_builds to job-tracked runs (run_id present) and pass job_id=graph.run_id on the persist call. Job-tracked runs also persist streaming terminal vertices so reconstruction is complete; the live build path (run_id is None) keeps its original behavior, so the v1 build path is unchanged. Test: a real background run polled to completion, then GET status asserts a reconstructed 200 (verified RED: 500 'No vertex builds found' before the fix). Covers the non-streaming flow. v1 build path unchanged (35 build tests pass); AG-UI suite 46 pass. LE-1389 * fix(api/v2): merge workflow AG-UI cancellation hardening LE-1389 * fix(api/v2): signal cross-worker workflow stops LE-1389 * fix(api/v2): report unconfirmed workflow stops LE-1389 * fix(api/v2): keep background workflows out of polling watchdog LE-1389 * fix(api/v2): buffer parallel messages in the AG-UI translator instead of dropping them Parallel components stream tokens for different message ids interleaved. The translator tracked a single open message: the first foreign token closed the open message and tombstoned its id, so every later event for it was dropped and its remaining text never reached the client. Tokens for a message that cannot take the wire now buffer until the open message genuinely ends (its add_message finalizer), then flush in arrival order; complete messages landing mid-stream buffer the same way instead of interleaving a second START. end/error drain all buffers before the terminal event. The wire still carries at most one open text message, so the stream stays AG-UI-conformant. * fix(api/v2): gate AG-UI message finalization on non-partial state and purge removed buffers A partial add_message re-fire (the agent path emits these at tool start/end for a message it is still streaming) was treated as the finalizer: it closed and tombstoned the id, so the post-tool answer was dropped. Only a non-partial add_message finalizes now; state defaults to complete, so payloads without properties are unchanged. remove_message now purges a buffered message and tombstones its id, so text the backend retracted is not flushed to the client later. * Fix AG-UI workflow lifecycle edges * [autofix.ci] apply automated fixes * fix(frontend): enable downlevelIteration for jest Set/Map iteration ts-jest compiles with target es5; without downlevelIteration, [...set] and for...of over a Set/Map emit ES5 that yields nothing. That silently broke the AG-UI bridge tests: runningNodeIds spread, markRunningNodesFailed, and restoreOriginalBuildStatuses all iterated empty. Production (Vite/SWC, modern target) was never affected; only the ts-jest harness was. Fixes the 3 failing jest tests on this branch with no other suite changes (4994/4994 pass). * fix(api/v2): surface inactivated branch vertices over AG-UI A branch component (If-Else, Conditional Router) reports its not-taken vertices in build_data.inactivated_vertices, but the AG-UI translator only emitted the branch node's own success/error status and dropped that list. The canvas seeds every planned node as pending from vertices_sorted; skipped vertices then get no build_start/end_vertex, so they stayed stuck on pending instead of rendering as inactive (the v1 build path marked them INACTIVE). The translator now appends an inactive STATE_DELTA op per inactivated vertex, and the frontend bridge maps the new inactive status to BuildStatus.INACTIVE and tears its edges down like a completed node. Fixes the If-Else regression in general-bugs-reset-flow-run.spec.ts. * fix(api/v2): dedupe repeated inactive node deltas in AG-UI stream build.py keeps reporting a conditionally-excluded vertex in inactivated_vertices on every subsequent end_vertex (the excluded set persists until the ConditionalRouter clears it), so the translator was putting the same inactive STATE_DELTA on the wire once per remaining vertex. Track emitted inactive nodes and skip re-emitting; drop a node from the set when it actually runs again (build_start/end_vertex) so a loop re-activation can still re-emit inactive later. * fix(api/v2): no duplicate WORKFLOW job row on durable background runs A v2 background run created TWO JobType.WORKFLOW rows for one flow execution: the durable row (submit()'s job_id, owned by JobRunner) plus an orphan keyed by the run_id generate_flow_events mints, because the build pipeline's track_job_status defaults True and the durable frame source never passed False. The flow ran once (double bookkeeping), but every background run left a phantom WORKFLOW row + job_events and double-fired the memory-base hook, skewing metrics. Thread track_job_status through _stream_event_frames; pass False only from the background frame source (the durable runner already owns the row + fires the hook with the durable job_id). Stream/public paths keep default True. Also gate build.py's memory-base hook fire behind track_job_status so background doesn't double-fire. Adds a regression test (RED before fix: found 2 rows). * test(api/v2): update stale workflow-stop tests for the durable design These 3 tests targeted the removed queue-service stop helper (_cancel_workflow_queue_job / get_queue_service), inherited via the agui->bg-default merge and failing with AttributeError across the stack: - test_stop_workflow_success: adapted to the durable stop path (revoke_task -> stop_job -> update_job_status(CANCELLED)). - test_stop_workflow_allowed_for_legacy_job_with_no_user_id (IDOR): adapted to the durable mechanism; still asserts the ownership check does not block a legacy user_id=None row. - test_stop_workflow_returns_503_when_queue_cancel_cannot_be_confirmed: dropped — the durable stop writes a best-effort STOP signal and always finalizes CANCELLED; the queue-service 'cannot confirm -> 503' path no longer exists. test_workflow.py now passes 24/24. * fix(api/v2): move FrameSourceFactory alias under TYPE_CHECKING As a module-level runtime value, FrameSourceFactory = Callable[..., Any] is a GenericAlias that passes isinstance(obj, type) but makes issubclass(obj, Service) raise on Python 3.10/3.14. The service factory scans this module for Service subclasses (services/factory.py:90), so the runtime alias crashed service initialization on those interpreters with 'issubclass() arg 1 must be a class' -> 'Could not initialize services', erroring out dozens of unrelated tests at setup (3.13 was unaffected, which is why local runs passed). The alias is only referenced in a lazy annotation (from __future__ import annotations), so moving it + the Callable import under TYPE_CHECKING removes it from the runtime namespace with no behavior change. Verified: alias absent from runtime module namespace, factory scan finds BackgroundExecutionService cleanly, durable service tests 26/26 pass. * test(lfx): register background-execution Settings fields in the field-count gate The durable background-execution work added six Settings fields (background_max_concurrency, background_job_timeout, background_lease_ttl_s, background_heartbeat_interval_s, background_watchdog_interval_s, test_redis_url) without updating EXPECTED_FIELDS, so test_field_count_unchanged failed 152 != 146. These are intentional bg-exec config; add them to the gate. * fix(api/v2): restore "end" side-channel event in AG-UI workflow stream The durable background-execution rewrite of workflow.py reverted `side_channel_events` to its pre-"end" form, dropping the "end" event from the AG-UI side-channel. That event carries `build_duration` to the playground chat-view, and the message metadata badge only renders when `hasDuration || hasTokens`. With build_duration gone the badge vanished, failing the token-usage and shareable-playground "Finished In" regression tests. Re-add "end" so the streaming playground path delivers it again. * fix(api/v2): apply request tweaks on the streaming and background paths The v2 workflows endpoint applied `tweaks` only on mode=sync. The stream and background paths build the graph via the v1 build-vertex loop (`generate_flow_events`), which never received the tweaks, so they were silently dropped. The confusing symptom: a model passed via tweaks surfaced as "A model selection is required", and any per-component override was ignored on non-sync runs. Thread `parsed.tweaks` into `generate_flow_events` and apply them to the built graph via `vertex.update_raw_params`. We do not use the lfx `process_tweaks_on_graph` helper because it only sets `vertex.params`, which does not persist to runtime (the same bug `lfx.base.tools.run_flow._process_tweaks_on_graph` works around). No-tweaks runs are unchanged (guarded by `if tweaks`). Adds a streaming regression test that overrides ChatInput via tweaks and asserts the value drives the run. * fix(api/v2): return background run output from completed status A completed background run's GET status returned a bare COMPLETED with an empty `outputs` and a null `output`. The COMPLETED branch reconstructs from `vertex_builds` keyed by job_id, which the durable path does not write, so reconstruction raised ValueError and fell through to an empty response; the result was only retrievable via a /events re-attach. The runner now captures the terminal `output` events (the langflow adapter's normalized ComponentOutput payloads) into `Job.result`, and the status COMPLETED branch rebuilds the `outputs` map and resolved `output` from them via `workflow_response_from_output_events`, matching the sync response. agui-protocol runs emit no `output` events, so their status stays result-less (the result remains on the /events log). * fix(api/v2): address review findings on the v2 workflows endpoint - recover session_id for completed background jobs from the persisted terminal message instead of always returning null, so GET status can continue the same chat/memory thread - replay a user-cancel as a CUSTOM cancel marker + RUN_FINISHED (agui) and a `cancelled` terminal (langflow) instead of RUN_ERROR, so a re-attaching client no longer reads a deliberate stop as a failure - cancel the evicted still-running buffer writer when the background-run registry is full, so it stops appending into a run no reader can find - derive per-component status from the error artifact / valid flag instead of hardcoding COMPLETED, and stop the langflow adapter dropping `valid` - throttle the unauthenticated public endpoint per IP and bound its input_value/session_id length - document the sync-only scope of request-body globals - document that live event re-attach is intentionally owner-only * test(lfx): register public_flow_rate_limit_per_minute in settings composition * refactor(v2 workflows): split workflow.py and address review blockers Splits the ~1.5k-line workflow.py into focused modules and folds in the execution-timeout and error-sanitization fixes from Cristhianzl's review of #13307. - B1: workflow.py now holds only the four route handlers. Validation guards move to workflow_validation, the sync/stream run loop to workflow_execution, and the durable background machinery to workflow_background (layered, acyclic). - I1: add workflow_execution_timeout (default 300) and apply a single wall-clock ceiling across sync, stream, background, and public via _stream_event_frames. A timeout becomes a sanitized terminal error and marks a background job failed. - I3: the route error handlers no longer echo raw exception text. They return a generic, code-tagged message and log the full exception server-side. - R1: remove the "commented out / future scope" comments that sat over live dataframe-extraction code in converters.py. - R4: drop the worker-routing internals from the reattach 409 message. Tests cover the timeout terminal-error path and the error-body sanitization, and the settings field-count guard is updated for the new setting. * refactor(lfx): extract v2 workflow contract layer into lfx.workflow Moves the protocol-agnostic pieces of the v2 workflows API out of the langflow backend into lfx so both the backend and `lfx serve` can share one contract. First step toward giving lfx (the production runtime) the v2 workflows API. - Move api/v2/adapters/, agui_translator.py, and converters.py to lfx/workflow/. They depend only on lfx.schema.workflow and ag_ui (already an lfx dep), so lfx carries the contract with zero langflow imports. - Decouple the one langflow reference: converters typed run_response against langflow.api.v1.schemas.RunResponse (TYPE_CHECKING only). Replaced with a local RunResponseLike Protocol (outputs + session_id), the only attributes used. - Repoint the six backend v2 workflow modules to import from lfx.workflow. - Move the five protocol-agnostic contract tests into src/lfx/tests/unit/workflow/ (run in the lfx-only env). test_output_event_parity and test_workflow_agui stay in langflow (they need langflow.api.build) with repointed imports. Coverage unchanged: 201 contract tests pass in the lfx-only env, 191 backend v2 tests pass; 392 total, same as before the move. * fix(background-execution): prevent worker deadlock on stop() under Python 3.10 The bounded executor's worker awaited the in-flight job task with a bare `await task`. On Python 3.10, when stop() cancels a worker while its job task is finishing, the awaiter's wakeup is lost and the event loop idles forever in select(), deadlocking stop(). Await via a done-callback Event (the same mechanism stop()'s own asyncio.gather already uses), which delivers the wakeup reliably; task.result() preserves the cancellation and exception semantics of the bare await. * fix(background-execution): keep ephemeral frames on reattach, pin psycopg in real-service tests The real-service CI job broke on `ModuleNotFoundError: No module named 'asyncpg'`. asyncpg was never declared anywhere in this repo; it arrived transitively through `cuga`, which release-1.11.0 dropped when it moved the root dep to `lfx-bundles[all-no-torch]` (#13886). Normalize the harness URL to `postgresql+psycopg` instead: it is what `--extra postgresql` installs and what DatabaseService already selects for async Postgres, so the test now exercises the production driver rather than a stowaway. Also fix a real reattach bug. The runner publishes ephemeral token frames tagged with the last durable seq (they have no job_events row), while reattach's tail skipped anything with `seq <= highest`. After replay left `highest` at that same seq, every token delta was dropped until the next durable milestone advanced it, so a reconnect mid-stream saw no tokens. Mark frames durable/ephemeral and dedupe only the durable ones, which are the only frames a replay can return. Log instead of silently suppressing a failed stop_job signal, and drop a comment block duplicated verbatim in InProcessExecutor.stop(). * fix(migrations): merge alembic heads (mcp_server + execution_signals) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * docs: AGUI protocol, workflows quickstart, update examples (#13911) * docs: curl code snippet contains placeholder * docs: update workflows example to match new schema * docs: include ag-ui in workflow api reference * docs: workflows api agui streaming * docs: workflows api quickstart and ag-ui example * docs: fix broken build links * docs: agui peer review * feat(frontend): render the flow share seam in the editor Share dropdown (#14009) The CustomFlowShareAction customization seam only rendered in the flow list's card menu, so overlays that implement user/team sharing had no entry point inside the flow editor. Render the same seam at the top of the editor's Share dropdown; the OSS stub renders nothing, so the OSS menu is unchanged. Adds an optional menuContext prop to the seam so overlays can label the editor placement differently from the card menu. * fix: fail loud on undecryptable KB embedding key with recovery path (#13806) * fix: fail loud on undecryptable KB embedding key with recovery path - Add KBKeyDecryptError exception for SECRET_KEY rotation scenarios - Implement require_api_key flag in load_kb_metadata for critical vs non-critical paths - Retrieval: raise when component doesn't supply key, allow component key bypass - Ingestion: raise when no component key, warn when component key used as fallback - Add comprehensive test coverage for decrypt failure scenarios - Consolidate _kb_paths imports in knowledge.py * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * chore: trigger CI * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Eric Hare <ericrhare@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * feat(models): add Azure AI Foundry to unified model provider setup (#13912) * feat(models): add Azure AI Foundry to unified model provider setup Expose Azure AI…
Some additional changes for 1.11:
Summary by CodeRabbit