Skip to content

chore(deps-dev): Update ruff requirement from <0.16,>=0.15 to >=0.15,<0.17 in /apps/api in the python group - #20

Closed
dependabot[bot] wants to merge 124 commits into
mainfrom
dependabot/pip/apps/api/python-c784f51798
Closed

dependabot[bot] wants to merge 124 commits into
mainfrom
dependabot/pip/apps/api/python-c784f51798

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 26, 2026

Copy link
Copy Markdown
Contributor

Updates the requirements on ruff to permit the latest version.
Updates ruff to 0.16.0

Release notes

Sourced from ruff's releases.

0.16.0

Release Notes

Released on 2026-07-23.

Check out the blog post for a migration guide and overview of the changes!

Breaking changes

  • Ruff now enables a much larger set of rules by default (413, up from 59). See the blog post for more details and the new Default Rules page for a full listing of the enabled rules.

  • Ruff can now format Python code blocks in Markdown files and will do this by default. See the documentation for more details.

  • Ruff now supports ruff: ignore comments at the ends of lines, like noqa comments, or on the line preceding a diagnostic. For example, these both suppress an unused-import (F401) diagnostic:

    import math  # ruff: ignore[F401]
    ruff: ignore[F401]
    import os

  • Fixes are now shown in check and format --check output:

    ❯ ruff format --check .
    unformatted: File would be reformatted
     --> try.md:1:1
      |
    1 | ```python
      - import   math
    2 + import math
    3 | ```
      |
    1 file would be reformatted

    This example also shows off the Markdown formatting.

  • format --check now supports the same output formats as the linter, including the github and gitlab outputs for rendering annotations in CI:

    ❯ ruff format --check --output-format github .
    ::error title=ruff (unformatted),file=try.md,line=2,col=8,endLine=2,endColumn=10::try.md:2:8: unformatted: File would be reformatted

    See the CLI help or documentation for the full list of supported formats.

  • The filename, location, end_location, fix.edits[].location, and fix.edits[].end_location fields in the JSON output format may now be null rather than defaulting to the empty string and row 1, column 1, respectively.

... (truncated)

Changelog

Sourced from ruff's changelog.

0.16.0

Released on 2026-07-23.

Check out the blog post for a migration guide and overview of the changes!

Breaking changes

  • Ruff now enables a much larger set of rules by default (413, up from 59). See the blog post for more details and the new Default Rules page for a full listing of the enabled rules.

  • Ruff can now format Python code blocks in Markdown files and will do this by default. See the documentation for more details.

  • Ruff now supports ruff: ignore comments at the ends of lines, like noqa comments, or on the line preceding a diagnostic. For example, these both suppress an unused-import (F401) diagnostic:

    import math  # ruff: ignore[F401]
    ruff: ignore[F401]
    import os

  • Fixes are now shown in check and format --check output:

    ❯ ruff format --check .
    unformatted: File would be reformatted
     --> try.md:1:1
      |
    1 | ```python
      - import   math
    2 + import math
    3 | ```
      |
    1 file would be reformatted

    This example also shows off the Markdown formatting.

  • format --check now supports the same output formats as the linter, including the github and gitlab outputs for rendering annotations in CI:

    ❯ ruff format --check --output-format github .
    ::error title=ruff (unformatted),file=try.md,line=2,col=8,endLine=2,endColumn=10::try.md:2:8: unformatted: File would be reformatted

... (truncated)

Commits
  • a2635fd Bump 0.16.0 (#27136)
  • 3433449 [ty] Reuse full call diagnostics for implicit setter calls (#27115)
  • 2240070 Reflect ruff: ignore and --add-ignore stabilization in documentation (#27...
  • 17ef711 Stabilize --add-ignore (#27125)
  • ef912bb Add newly stabilized rules to defaults (#27055)
  • b30f040 Stabilize new default rules (#27035)
  • bcd70c5 Exclude Markdown files from format-dev runs (#27052)
  • 87e51e2 Fix format --check spans for syntax errors (#27045)
  • afe2723 [flake8-gettext] Stabilize qualified-name and built-in binding resolution (...
  • a9702d8 [flake8-bandit] Stabilize string literal binding resolution (S310) (#26944)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions

nihalashetty and others added 30 commits June 16, 2026 02:29
Initial commit: Forge — self-hosted platform for building AI agents & workflows

FastAPI engine (compiler/registry/middleware, tools, auth, knowledge, MCP)
+ Next.js console, shared JSON schemas, docs, and local-first infra.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
…entity, and a production-hardening pass

Bundles two bodies of previously-uncommitted work that share core files
(main.py, services/runs.py, routers/runs.py, security.py); the "feature-only"
state was never committed, so a faithful per-file split isn't possible — hence
one commit.

Features (already in the working tree before the hardening pass):
- Generative-UI components: author HTML/CSS/props widgets exposed to agents as
  widget-tools; sandboxed iframe renderer; component frame on the run stream.
- Embeddable chat widget: /embed page, publishable-key public router, floating
  launcher, frame-ancestors CSP.
- End-user identity: server-minted session tokens, prompt awareness, REST tool
  entitlement gate.
- GFM markdown structured responses.

Hardening pass (2026-06-18) — full detail in docs/HARDENING-2026-06-18.md:
- Security S1-S11: embed runs scoped by project; embed quota + per-IP limits;
  anonymous thread-takeover closed; body-identity role gate; code tool off by
  default + prod guard; fail-closed validate_production; SQL DSN SSRF guard;
  guarded OAuth token fetches; OAuth callback XSS escape; sanitized embed error
  frames; JWT kid/rotation + jti revocation + shorter TTL.
- Correctness F1-F12: disconnect-safe run finalize; atomic quota admission;
  stale-run reaper; tracked/bounded webhook tasks; failure traces persisted;
  email/Teams thread continuity; per-thread run serialization; channel HITL
  handoff; app_event poller dedupe race; router/fanout dead-end logging; bounded
  + isolated + quota-gated evals; MCP cache TTL/invalidation/close; misc lows.
- Production P1-P5 + ops: arq/Redis worker offload + per-tenant concurrency caps;
  Postgres checkpointer; single-leader scheduler; Dockerfiles + docker-compose +
  GitHub Actions CI + .env.example; idempotent Alembic 0002 migration; Redis-backed
  rate-limit/idempotency; /readyz + /livez + Prometheus /metrics; TrustedHost +
  trusted-proxy XFF; OTLP BatchSpanProcessor.

Verified: 177 backend tests pass; ruff clean; web build clean; migration chain
runs and is idempotent; adversarial diff review's one finding fixed + tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat+harden: generative-UI components, embeddable widget, end-user id…
…pped backfill

Three latent bugs in the agent Q&A/FAQ path could silently turn a working
knowledge lookup into "I don't have that info":

- resolve_embedder silently fell back to FakeEmbedder (dim 256) for a real
  openai: model when the key was missing or the client failed to construct,
  flipping the dim-keyed Chroma collection with no signal. Now warns once
  per model in both fallback paths.
- _ensure_qa_indexed skipped backfill on `count_where >= len(rows)`, which hid
  a missing/stale pair whenever the counts happened to match. Now indexes only
  the rows actually missing (id-diff via new ChromaStore.ids_where).
- top_qa awaited the backfill outside its try/except (a failed re-index aborted
  the whole lookup) and create_qa swallowed its upsert error. Backfill moved
  inside the guard (degrades to no-match); both failures now logged.

Tests: 26 passed (test_qa_vectorstore, test_knowledge, test_hybrid_search,
test_fixes_2026_06_11).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add MIT LICENSE; set license to MIT in pyproject.toml and web/package.json
- Rewrite root README: professional structure, feature tour with media (video + screenshots), quickstart, Docker, docs links
- Remove internal/dated working docs from docs/ (audit, fixes, hardening, plans)
- Remove internal _design_reference/ (research, design docs, chat log, UI prototype, screenshots)
- Fix dangling references to removed design docs (ROADMAP, pyproject comment)
- Normalize em-dashes to hyphens repo-wide

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strip all emojis from headings, highlight bullets, feature list, and footer;
fix table-of-contents anchors and the PRs-welcome badge link to match the
plain-text headings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s direction

- Restructure ROADMAP into Shipped / In progress / Planned (drop the internal dev-log)
- Reflect now-shipped features (auth, full node catalog, channels, triggers, evals, generative UI, pgvector + Redis prod, fastembed)
- Add forward-looking direction led by Connectors (one-click OAuth sign-in)
- Update README roadmap description to match

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Updated video source link in README.md.
…ntext

The production-shaped compose stack could not build/run as-is. Move the
api/worker build context to the repo root and fix the driver/path
assumptions that only held in a source checkout.

- api/worker build context -> repo root (dockerfile: apps/api/Dockerfile)
  so the image can COPY packages/schemas, which lives above apps/api;
  schemas baked at /app/packages/schemas.
- config.py: fall back REPO_ROOT -> API_ROOT when parents[3] is absent
  (the container flattens the tree to /app/forge); identical in a checkout.
- migrations/env.py: map async drivers to psycopg v3 (+psycopg), not
  psycopg2 -- the `postgres` extra ships psycopg[binary], so the old
  +psycopg2 mapping raised ModuleNotFoundError at `alembic upgrade head`.
- worker.py: enforce the same validate_production guard the API runs
  (arq skips the FastAPI lifespan), and guard _shutdown against a _stack
  that startup never set.
- web: pass FORGE_API_URL as a build ARG (standalone Next freezes the
  rewrite at build time); default 127.0.0.1:8000 keeps host `pnpm dev`,
  compose overrides to http://api:8000. Kept out of NEXT_PUBLIC_*.
- Dockerfile: stub-package + editable install + BuildKit pip cache mount
  so a pure source edit doesn't re-hit PyPI.
- .dockerignore: fold apps/api/.dockerignore into the root one (context is
  now the repo root, so only the root ignore is read); keeps .data
  (master.key/secrets) and tests out of the image.
- compose: gate worker/web on api `service_healthy` (/readyz) so nothing
  hits the DB/API before `alembic upgrade head`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/app/.data (the Fernet master.key and the Chroma vector store) lived only
in the container filesystem, so every `up --build` regenerated the key --
orphaning every secret already encrypted in Postgres -- and reset the
vector store. The worker also had its own ephemeral copy, so it could not
decrypt secrets the api encrypted.

- compose: mount a shared `forge-data` named volume at /app/.data on both
  api and worker, so the key/vectors persist across rebuilds and both
  services use the SAME master.key.
- Dockerfile: pre-create /app/.data owned by forge so the empty named
  volume inherits forge ownership on first mount (else it mounts
  root-owned and the non-root process can't write the key).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s + audit)

Two independent ways a directly-connected client could forge its IP:
1. The api ran uvicorn with `--forwarded-allow-ips='*'`, so uvicorn rewrote
   the socket peer from X-Forwarded-For for ANY client -- defeating
   deps.client_ip()'s own trusted-proxy check (it reads the rewritten peer).
2. The audit middleware's _client_ip() trusted X-Forwarded-For
   unconditionally, so audit-log IPs were spoofable regardless of (1).

- Drop uvicorn --proxy-headers/--forwarded-allow-ips from the CMD; the app
  derives the client IP itself. Behind a real proxy, set FORGE_TRUSTED_PROXIES
  to the proxy IP(s).
- Consolidate both resolvers into forge.util.clientip.resolve_client_ip so
  they apply the same trust rule and can't drift (that drift is what let the
  audit path diverge from the rate-limit path).
- Believe X-Forwarded-For only when the socket peer is in FORGE_TRUSTED_PROXIES
  (default empty => use the real socket peer).
- Add tests/test_clientip.py asserting the anti-spoof property.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The worker reuses the api image, whose HEALTHCHECK curls :8000/readyz. The
worker runs arq (no HTTP server), so the probe always fails and the container
sits "unhealthy" -- a false signal that also hangs any `worker: service_healthy`
gate and every `--scale worker=N` replica. Disable it; container run state
already reflects arq liveness (it exits if arq dies), and nothing gates on it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…allow-private

Enable on-behalf-of tool calls: a trusted backend passes per-request secrets
(e.g. a per-user session cookie / CSRF token) into a run via the X-Forge-Context
header; tools inject them into outbound calls via {{ctx.*}}, and they are never
persisted, never placed in the LLM prompt, and never an LLM-visible arg.

Per-run context (ephemeral, in-memory):
- CompileContext.run_context, threaded through RunService.stream/run_to_completion/
  resume -> build_compile_context. Carried on the EXECUTION request (stream/resume)
  via the X-Forge-Context JSON header (deps.run_context), not on create -> never stored.
- REST tools template {{ctx.*}} in url, path, query, header, cookie, structured body
  fields, and the free-form body_template (now wired: {{input.*}} + {{ctx.*}}).
  Config-declared headers are server-authoritative (an LLM header field cannot override
  them); a {{ctx.*}} that resolves to a missing key is dropped, not sent literally.
  GraphQL tools merge the same context for the auth resolver.
- Web tool editor: per-field `in` (query/header/path/body/cookie) + `default` +
  model-visible toggle; headers/body-template help note {{ctx.*}}.

Server-to-server integration primitives:
- FORGE_SERVICE_API_TOKEN: a static bearer that authenticates a trusted backend as a
  least-privilege (editor) service identity (constant-time compare in get_current_user);
  non-expiring, revoked by rotation. Only a gate when FORGE_AUTH_REQUIRED=true.
- FORGE_EGRESS_ALLOW_PRIVATE_HOSTS (+ per-project egress.allow_private_hosts): named
  hosts bypass the SSRF private/loopback block while block_private stays on globally
  (default-deny, explicit-allow), so localhost/internal targets are reachable in dev
  without disabling the guard or blocking production boot.

Tests: apps/api/tests/test_run_context.py (16), test_service_token_egress.py (10);
full backend suite green (208).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ATE_HOSTS to the stack

The container only sees env listed in the compose `environment:` block, so the new
service-token and egress-allow-private settings need explicit passthrough (with
${...} substitution from the repo-root .env) to take effect in the Docker stack.
Added to `api` (both) and `worker` (egress, for worker-executed tool runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
config's env_file pointed at apps/api/.env while docker-compose and the docs use the
repo-root .env, so local (.venv) runs and the Docker stack read DIFFERENT files - the
"many .env paths" confusion. Point env_file at REPO_ROOT/.env so both run modes are
configured in one place. In the flattened container image REPO_ROOT == /app (no .env
copied there) and env still comes from the compose environment block; pydantic skips
the missing file. Real env vars still take precedence over the file. Full suite green (208).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ice-auth

Feat/per run context and service auth
The async engine pooled aiosqlite connections; under pytest-asyncio's per-test event
loops a pooled connection could be torn down in a loop other than the one that opened it,
causing intermittent CI errors during the _ensure_tables fixture setup ("Task was
destroyed but it is pending" / "object NoneType can't be used in 'await'" from
AsyncAdaptedQueuePool). Use NullPool for SQLite (dev/test) so each connection is closed
immediately and nothing lingers across loops. Postgres (prod) keeps the default pooled
behaviour - no production perf change. Full suite green across repeated runs (5/5, 208 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(db): use NullPool for SQLite to stop cross-event-loop teardown flake
The api container failed to boot when FORGE_EGRESS_ALLOW_PRIVATE_HOSTS resolved to a
stray "[" from the ${VAR:-[]} compose default: pydantic-settings ran json.loads('[') ->
JSONDecodeError -> SettingsError, killing startup.

Mark the list[str] settings NoDecode and normalize them via _as_str_list: accept a JSON
array, a comma-separated string, or blank ("" -> []), and tolerate an unquoted/mangled
bracketed value. Applied to all list env fields (egress allow/deny/allow-private,
cors_origins, trusted_proxies/hosts, jwt_secret_previous) so none can crash boot on
env/compose quoting. Also drop the bracket from the compose default (${...:-}) so it
emits an empty string, not "[]".

Tests: apps/api/tests/test_config_env_lists.py; full suite green (215).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(config): parse list env settings leniently (JSON, CSV, or blank)
The Connect screen only documented the MCP endpoint - there was nowhere to get the
server-to-server run API details. Add a panel that lets you pick the workflow and the
Forge API base URL, then shows ready-to-copy Project ID, Workflow ID, and the fully
built create / stream / resume endpoints plus a curl example (Authorization: Bearer
FORGE_SERVICE_API_TOKEN + the X-Forge-Context per-user header). Retitle the screen
"Connect" with MCP as a second section. Web typechecks clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primary fix (verified): a streaming caller could capture the composite
LangGraph id ({tenant}:{uuid}) from the SSE `run` frame and echo it back as
thread_id. It never matched Thread.id, so every turn started a fresh thread
and the agent forgot the conversation.

- runs.py: the `run` SSE frame now emits the caller-facing DB thread_id
  (same handle as the create response / `ready` frame), not lg_thread_id;
  create_run reuses a thread on EITHER handle (Thread.id or lg_thread_id).
- project_run.py: HITL resume resolves either handle too.
- tests: streaming thread-id consistency regression + create_run tolerance
  test; conftest resets sse_starlette's loop-bound singleton so more than one
  SSE test can run per process.

Also lands this branch's pending work: single POST /v1/projects/{id}/run
endpoint, per-run X-Forge-Context, egress TLS verify + gated tls_skip_verify,
audit/quota updates, and the Connect panel UI. Backend suite: 225 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nihalashetty and others added 22 commits July 16, 2026 22:07
…of truth

The console's model pickers (chat / embedding / reranker) and the built-in
pricing rates now derive from a single canonical list (forge/model_catalog.py,
served at GET /v1/models). The frontend hardcodes no model lists — it fetches
them via useModels / useEmbeddingModels / useRerankerModels — so a dropdown can
only offer models the backend can actually run (and, for chat, price) and the
two can't drift into silent $0 cost tracking. test_model_catalog enforces the
invariants (every offered model priced, cheap defaults selectable, no dupes).

Also folded in here because they share the touched files: the agent tool-count
badge now counts DISTINCT tool names (matching what the model receives), and the
middleware-stack row is a single compact truncating header.
resolve_tool_ids already de-dupes by id, but the final tool list mixes sources
(tools + knowledge + MCP + components) and tool names are not unique per project,
so two entries can still collide by name — which providers reject (OpenAI errors
on a duplicate function name). De-dupe by name (keep the first, warn on drops)
for both the agent and its subagents.
…compat

The classifier resent the entire message history each call (progressively slower
and pricier as a thread grows) and passed the prompt as a lone SystemMessage —
which Gemini routes to system_instruction and then rejects for empty contents,
and which Anthropic rejects for lacking a leading user turn. Fold a bounded slice
of recent context into a single self-contained HumanMessage instead.

UI: the classifier's model field now reads "Auto (cheapest model)" instead of
"Project default", matching what actually runs when it is left blank.
A LangGraph bubble-up (interrupt() for human approval, or a Command reroute) is
RAISED to suspend/redirect the graph — control flow, not a failure — but it
reached the callback error hooks and rendered a run paused for approval as a
crash. Recognize GraphBubbleUp and close the span cleanly, tagged `interrupted`,
across the llm / tool / chain / retriever error hooks.
One user message can produce several Trace rows under the same run_id: a pause
writes an `interrupted` trace, then the resume writes a `done` trace (both carry
the same user_message, since run.input is unchanged). Count turns by distinct
run_id in the conversation summary, and group segments by run_id in the Traces
transcript, so a paused message reads as one turn that paused — not two. The
expanded turn lazy-loads spans for every segment and labels them (Started /
Paused for approval / Resumed).
langchain-openai renamed OpenAIModerationMiddleware's toggles apply_to_* ->
check_* (and added check_tool_results / exit_behavior / model / violation_message).
Enabling the middleware used to crash at compile with "unexpected keyword argument
'apply_to_input'". Translate from our schema's apply_to_* names to the library's
current kwargs and pass the new options through from Advanced JSON.
Local architecture-alignment notes, not part of the product.
The .select class already draws its own chevron via a background-image, so the overlaid Icon rendered the arrow twice. Remove the overlay on the auth provider-kind and tool method/encoding/error/auth selects.
…erver

Native MCP clients (Claude Desktop, Cursor, VS Code) now connect directly over the
Streamable-HTTP transport — no `mcp-remote` proxy bridge. The endpoint serves both
transports from one auth + tool-resolution core, chosen per request:

  - a POST that accepts text/event-stream (or any GET/DELETE) -> Streamable HTTP,
    backed by the official `mcp` SDK's StreamableHTTPServerTransport in STATELESS
    mode (a fresh transport per request), which matches Forge's model: every request
    is authenticated on its own and the tool surface is resolved per project + per
    acting identity. The reply is application/json or an SSE stream per the Accept.
  - a plain-JSON POST -> the original request/response JSON-RPC path, preserved for
    simple HTTP clients and internal callers.

Both paths share _resolve / _list_items / _dispatch, so behavior and tracing are
identical whichever transport a client uses. The transport's own DNS-rebinding guard
is disabled (Forge already enforces trusted-hosts at the app layer), and the SDK
import is guarded so the router still loads without the `mcp` extra (the streamable
branch then returns 501).

test_mcp_streamable.py drives the endpoint with the real `mcp` SDK client end-to-end
(initialize / list / call), plus SSE content-negotiation, the legacy-JSON fallback,
and per-project auth on the streaming path.
One admin-configurable screen (Settings > Guardrails & Egress) that enforces an
I/O policy on every agent by default, with per-agent middleware left for
exceptions. Compiles to two enforcement points that already existed:

- config.default_middleware -> prepended to every agent's stack: PII redaction
  (built-in types + custom regex patterns) and a case-insensitive blocked-terms
  guardrail. Managed entries tagged _managed so hand-authored middleware is kept.
- config.egress -> EgressPolicy (SSRF guard): block-private + allow/deny domain
  lists; project can only tighten, never loosen.

No backend enforcement changes and no new deps; content guardrails are local
regex (no added latency). Adds test_default_middleware.py locking the
"injected into every agent, ahead of the agent's own middleware" guarantee.
…use LLM connections

Chat turns took 15–40s. Root cause: the Docker/glibc resolver stalls ~4–5s on the IPv6
(AAAA) lookup for hosts with no IPv6 address (api.openai.com, …), and glibc waits for it
before falling back to the A record — so the FIRST (cold) outbound call of every idle
connection paid a multi-second penalty. On a multi-hop run (classifier + agent + tool +
agent) that dominated latency, and it recurred whenever a connection went idle. Gemini was
unaffected (it publishes IPv6).

Authoritative fix: RES_OPTIONS=no-aaaa in the process env for the api and worker services
(docker-compose), so glibc never issues the AAAA query. Works under uvloop — whose C-level
resolver bypasses a Python socket.getaddrinfo patch — and for every client (LLM providers,
REST tools, Postgres, Redis). Overridable via .env for an IPv6-only egress network. Needs
glibc >= 2.36 (image has 2.41).

- forge/util/netfix.py: best-effort fallback for non-compose / non-uvloop launches (sets
  RES_OPTIONS if unset + an IPv4-first socket.getaddrinfo wrapper), gated by the new
  prefer_ipv4_egress setting; installed in the API lifespan and worker startup.
- forge/util/http.py + engine/models.py: reuse ONE keep-alive httpx connection pool for
  OpenAI model calls across runs (the per-run graph compile otherwise re-handshakes on the
  first call of every run), gated by the new llm_http_keepalive setting. No token/cost impact.

Measured (Test project main_workflow): cold OpenAI call 5.0–6.4s -> ~0.9–2.6s; warm ~1s; a
full "Hi" turn on an OpenAI agent ~5–6s -> ~2.5s (on par with Gemini); general query ~12s ->
~5s.
Bumps the actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 6 to 7
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Adds a connector-safe self-service surface so each end user supplies their OWN downstream token for a per-user auth provider (no shared secret, no token passthrough); tools then act as that user, keyed server-side by the caller's user id.

- new /v1/projects/{id}/connections router (list/get/set/clear my own connection), gated at any real logged-in user and returning minimal fields so a least-privileged connector needs no auth-provider admin access
- resolver support for per-user bearer/api_key providers
- self-serve UI on the Auth and Connect screens, connector token page, and the api client + auth_provider schema wiring
…gents

Each list screen gets Export (select-all picker -> downloadable single-type JSON bundle) and Import (upload -> re-create in the current project). Import goes through the normal create path (fresh UUID, same validation, version snapshot) so an imported entity is indistinguishable from a hand-built one.

- PortabilityService serializes every authored field (strips runtime junk like a tool's _last_test; never exports secret values, only secret:// refs)
- auto-rename on name collision (never overwrites; required for the component unique-name constraint)
- intra-bundle id remap so references (workflow subworkflow_id, agent config.tools/components) follow the new ids
- a tool's auth_provider_id is kept only if that provider exists in the target project, else cleared with a warning
- 2 routes per type + reusable web ImportExport control; wrong-type bundles rejected with a clear 422
- tests: service round-trips, remap, auto-rename, auth resolution, scoping + in-process HTTP integration
…tions-eefdb6dedd

chore(deps): Bump actions/setup-node from 6 to 7 in the actions group
…resh screenshots

Bring all user-facing docs up to date with features shipped Jul 16-20 and the console reskin:
- README: tool sets, MCP over Streamable-HTTP/SSE with PAT/OAuth 2.1, guardrails & egress, import/export, connector role, version history, expanded docs table.
- MANUAL: grouped sidebar nav (Build/Deploy/Observe), corrected Settings sections, Connect (MCP) rewrite, new Import & export section, Guardrails & Egress explainer.
- ROADMAP: shipped items moved up; PII redaction dropped from planned (ships via guardrails).
- CHANGELOG: new Unreleased entries.
- TECH_STACK: ruff pin >=0.15, add mypy + Vitest, croniter range.
- apps/api/README: fix prod vector swap to pgvector; refresh router/service inventory.
- Refresh Components/Embeddings screenshots to the current UI.
- Remove internal docs/reports notes.
…ebugger, run-API & governance shots

- Replace Agents/Tools/Components/Knowledge/Traces screenshots with current-UI (shadcn+indigo) captures on generic demo data.
- Add Tools_dashboard (tool sets), Knowledge search debugger (chunk map), Integrations (run API) and Settings (governance) images.
- New README sections: 'Deploy anywhere - one run API, MCP & channels' and 'Guardrails, budgets & governance'.
- Note: forge-demo.mp4 (128MB) intentionally not tracked; exceeds GitHub's 100MB limit — host externally and reference by URL.
Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version.

Updates `ruff` to 0.16.0
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.15.0...0.16.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.16.0
  dependency-type: direct:development
  dependency-group: python
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file python Pull requests that update python code labels Jul 26, 2026
@dependabot @github

dependabot Bot commented on behalf of github Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

This pull request was built based on a group rule. Closing it will not ignore any of these versions in future pull requests.

To ignore these dependencies, configure ignore rules in dependabot.yml

@dependabot
dependabot Bot deleted the dependabot/pip/apps/api/python-c784f51798 branch July 27, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant