Skip to content

RFC: make the public framework route prefix configurable (proposal only, no code) - #4482

Open
multiplehats wants to merge 1 commit into
BuilderIO:mainfrom
multiplehats:rfc/framework-route-prefix
Open

RFC: make the public framework route prefix configurable (proposal only, no code)#4482
multiplehats wants to merge 1 commit into
BuilderIO:mainfrom
multiplehats:rfc/framework-route-prefix

Conversation

@multiplehats

@multiplehats multiplehats commented Sep 8, 2026

Copy link
Copy Markdown

This pull request is a proposal. It contains no code. It asks the maintainers for a decision before I write an implementation.

The commit is empty. It changes no files. The proposal is in this description.

1. The problem

Agent-Native serves framework routes under /_agent-native. This keeps framework routes separate from application routes under /api/*. PR #132 added this namespace. The separation is correct. It must stay the default.

Some deployments must serve these endpoints under a different namespace. A gateway or a reverse proxy selects that namespace.

The framework cannot do this today. The prefix /_agent-native is a fixed value in the browser client, the authentication URLs, the server mounts, and the deployment output. A rewrite rule in a proxy changes the incoming request only. It does not change the URLs that the framework generates.

2. Evidence from a production application

This application serves framework requests under /_api. It does this with version-pinned pnpm patch files. The cost is high:

  • The patch for @agent-native/core@0.176.5 is 9438 lines. The patch for @agent-native/toolkit@0.19.3 is 274 lines.
  • I must write the patch again after each Core release. Core releases are frequent.
  • The prefix occurs in many places. These are the browser client, the authentication code, the OAuth callback validation, the CSRF classification, the server handlers, the background self-dispatch, and the generated output of the deployment adapters. A rewrite at one boundary is not sufficient.
  • I added a check that runs during pnpm build. The build fails if the package version is not reviewed, or if an old prefix remains. This check is necessary. A missed occurrence causes a broken OAuth callback in production. A test does not find this error.
  • The route /_agent-native/auth/reset is a separate route. A simple text replacement does not find it.

I do not ask you to merge this patch. The patch shows that applications need this option. It also shows that the current workaround is unsafe.

3. An important detail: two different prefixes

A prefix setting applies to the local deployment only. The framework must not apply it to the URL of a different installation.

Core contains four requests to other installations. A global text replacement breaks these requests:

File Endpoint
mcp-client/hub-client /_agent-native/mcp/hub/servers
mcp-client/remote-store /_agent-native/mcp (discovery fallback)
mcp/org-directory /_agent-native/org/apps
org/handlers /_agent-native/org/a2a-secret/receive

My patch tool stores these four lines as exact text. The tool stops if any of these lines changes upstream.

Therefore this feature is not one constant. It is two concepts that use one text value today:

  1. The namespace that this deployment serves.
  2. The namespace that this deployment calls on a different installation.

A remote destination keeps its own declared endpoint. A destination that declares no endpoint keeps the current default.

4. Proposed configuration

import { defineAgentNativeConfig } from "@agent-native/core/config";

export default defineAgentNativeConfig({
  runtime: {
    frameworkRoutePrefix: "/_agent-native", // this is the current default value
  },
});

If you do not set this field, the behavior does not change. The environment variable follows the existing descriptor convention: AGENT_NATIVE_CONFIG_RUNTIME_FRAMEWORK_ROUTE_PREFIX.

Resolution time. The framework resolves the value one time. It resolves it at development startup, or at deployment build. It writes the value into the client output and the server output together. A change to the value needs a restart or a new build. This prevents a condition where the browser and the server use different values.

Validation. The value is one absolute path segment. It contains ASCII letters, digits, _, and -. It contains a minimum of one letter or digit. The framework rejects these values:

  • An empty value, and the value /
  • A trailing slash, a query, or a fragment
  • An escape sequence or a dot segment
  • A reserved namespace, such as /api, /mcp, or /.well-known

A simple format keeps URL matching and security checks unambiguous. You can add nested namespaces later as a separate change.

Base path. The prefix and the application base path combine. An application at /mail with the prefix /_platform serves /mail/_platform/actions/.... The framework applies the base path one time only.

Collisions. At startup or at build, the framework compares the prefix with the existing application routes and the workspace mounts. If a collision occurs, the diagnostic message names both owners.

5. Routing contract

Keep /_agent-native as the internal namespace. Route registration and route discovery do not change. Translate between the public namespace and the internal namespace at the request boundary.

Add one shared path module. The module must run in the browser and on the server. The module owns these functions:

  • The internal prefix, and the validated public prefix
  • Segment-aware matching. The prefix /_platform must not match the path /_platform-extra.
  • Exact-root requests
  • Public URL construction, with the application base path applied one time

Incoming requests

Map the public path to the internal path before route selection, before authentication classification, and before the CSRF check.

The mapping must keep the query, the method, the headers, and the streaming body. It must also keep the original public URL. Origin checks and OAuth validation need the original public URL. The mapping must be idempotent.

Outgoing URLs

Build every outgoing URL with the shared path module. Build it at the point of construction. Do not rewrite response bodies.

These surfaces construct URLs:

  • Client requests, event streams, uploads, navigation, and toolkit calls
  • Sign-in documents, reset documents, session endpoints, magic links, and authentication redirects
  • Google OAuth, identity SSO, integration OAuth, MCP metadata, and callback validation
  • Agent-run continuation, scheduled jobs, health probes, and background dispatch that calls the same deployment
  • Generated function routing, redirects, adapter manifests, and URLs from the CLI

A warning about the adapters

framework-request-handler centralizes plugin mount matching. It is the natural request boundary.

However, two other components handle the base path themselves: the Vite development gateway, and the generated worker. In addition, the generated action routes do not use the plugin mount shim. Therefore a successful Vite build does not prove that the deployment adapters work. Each adapter needs its own test.

Do not rewrite HTML, JavaScript, or JSON payloads.

6. Compatibility

If a deployment does not set the field, these items do not change: the URLs, the helper results, the authentication behavior, and the deployment output.

A custom prefix changes URLs that are registered in external systems. These systems include:

  • OAuth redirect registrations
  • Webhook subscriptions
  • MCP connection URLs
  • External monitors
  • Magic links and reset links that users received before the change
  • Client bundles in a browser cache

A change to this value must not rewrite a stored third-party destination.

Migration aliases are your decision. If you support an alias, then the alias must obey these rules:

  • The alias applies to framework routes only.
  • The alias enforces the same authentication and CSRF rules as the primary path.
  • The alias must not hide an application route.
  • The framework must not redirect a POST request with a body from one namespace to the other.

If you do not support an alias, then two conditions apply. The internal namespace must not remain reachable from outside. Self-dispatch must use the shared path module.

7. Acceptance criteria

Area Checks
Configuration Default value; file and environment precedence; invalid types and paths; unknown keys; collisions
Path handling Root and nested requests; queries; similar prefixes; base-path composition; idempotence; external URLs unchanged
Clients Actions, upload, SSE, chat, and toolkit calls use the selected path
Authentication Session and sign-in flows; reset and magic links; generated documents; callback construction and callback validation
Security Protected requests stay protected; an unsafe cross-origin request fails; a malformed or encoded path cannot bypass classification
Server dispatch Health probes, scheduled jobs, continuation, and background handoff reach the configured routes
Deployment Development and each adapter serve action routes and discovered file routes under the prefix, including base-path mounts
Compatibility Default fixtures do not change; remote installations keep their own endpoints

Additional requirements:

  • Test endpoints that have a file extension, such as .json and image files. These must reach the framework handlers. They must not reach the static-file middleware.
  • A configured framework route must never fall through to the SSR shell.
  • The existing no-store header rules must follow the configured namespace.
  • Add a minimum of one integration fixture. The fixture must build and serve under a custom prefix. It must run an authenticated action and an event stream through the real request boundary. Unit tests of the URL builder do not prove that routing works.

8. Entry points

I reviewed these files in packages/core/src/:

config.ts · client/api-path.ts · server/framework-request-handler.ts · server/core-routes-plugin.ts · server/auth.ts · server/better-auth-instance.ts · server/google-oauth.ts · client/auth/AuthPage.tsx · server/csrf.ts · server/action-routes.ts · vite/client.ts · deploy/build.ts · deploy/route-discovery.ts · deploy/workspace-deploy.ts · deploy/netlify-static-headers.ts

9. Decisions requested

  1. Do you want to support a configurable public framework namespace? If yes, is runtime.frameworkRoutePrefix the correct location for the setting?
  2. Must the internal registration stay canonical, with translation at the request boundary? Or must every registration use the configured prefix?
  3. Must the first version include a migration alias? Or is a coordinated cutover acceptable?

I have a working implementation today. It uses the patches described in section 2. It includes the peer-endpoint exclusions from section 3 and the build-time check. Give me a decision on items 1 to 3, and I will open the implementation pull request in the form that you select.

Note: the commit is empty on purpose. It changes no files. This puts the proposal in the pull request list, because the issue tracker receives a high volume of automated reports. The empty commit starts a CI run. The tree is identical to main. Tell me if you prefer proposals in a different location, and I will move it.

Proposal only. No file changes; the proposal is in the pull request body.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes — looks good ✅

Review Details

Code Review Summary

PR #4482 is an RFC-only proposal carried by an empty commit: the diff contains 0 changed files and 0 changed lines. There is therefore no implementation, configuration, documentation, or UI code to validate, and no diff location where an actionable review comment could be placed.

The proposal's design questions—separating local serving prefixes from remote federation destinations, validating the prefix, composing it with an app base path, and mapping requests at the boundary—are maintainer decisions for a future implementation rather than reviewable changes in this PR. Both independent review passes found no reportable issues in the empty diff.

Risk assessment: low, because this PR proposes no code or behavior change.

🧪 Browser testing: Skipped — PR only modifies the PR description/empty commit, with no UI impact.

@multiplehats multiplehats changed the title RFC: configurable public framework route prefix (no code — maintainer decision requested) RFC: make the public framework route prefix configurable (proposal only, no code) Sep 8, 2026
@steve8708

Copy link
Copy Markdown
Contributor

thanks @multiplehats, I'm game for a PR that makes this configurable 👍

@multiplehats

Copy link
Copy Markdown
Author

thanks @multiplehats, I'm game for a PR that makes this configurable 👍

Excellent, thank you. I'll get the "team" on it ✌️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants