Skip to content

OSPP_PR1:Add request-scoped context support for the Agent Gateway plugin - #7133

Merged
Aias00 merged 10 commits into
apache:masterfrom
BobSong-dev:feat/agent-gateway-request-context
Oct 1, 2026
Merged

Aias00 merged 10 commits into
apache:masterfrom
BobSong-dev:feat/agent-gateway-request-context

Conversation

@BobSong-dev

@BobSong-dev BobSong-dev commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Background

Add request-scoped context support through a new Agent Gateway plugin without changing the existing AI Proxy or MCP implementation. The plugin provides an immutable request context that can be propagated through the Reactor chain and optionally returned as a response request ID.

Changes

  • Add the shenyu-plugin-agent-gateway plugin and Spring Boot starter;

  • Register plugin metadata, all six fresh schemas and all five 2.7.1-to-2.7.2 upgrade migrations;

  • Validate rule handles in Admin; ignore unknown fields with a warning at runtime for forward compatibility. Use HTTP 500 for configuration errors, remove the non-idempotent subscription guard, and pass selectors with continued=false through. Preserve Reactor context propagation, request isolation, response correlation and cache cleanup;

  • Keep the existing AI Proxy and MCP plugins reusable and independently configurable.

Verification

After merging current master (without rewriting published history):

  • Maven wrapper plugin/starter tests: 27 passed, zero failures/errors/skips;

  • Admin validation, rule service and config import tests on the earlier fix: 36 passed, zero failures/errors/skips;

  • Checkstyle passed in both test reactors; Apache RAT passed for Admin, plugin and starter;

  • Migration/schema consistency checker: 24 Agent Gateway seed rows match for each of MySQL, OceanBase, OpenGauss, Oracle and PostgreSQL; all 8 checker tests passed;

  • Plugin row ID is 68; Agent Gateway handles/resources/permissions/namespace relation use the dedicated 1960000000000001xxx range.

GitHub CI verified on head 6b15f5d: 37 successful checks and 3 intentionally skipped workflow jobs; no pending, failed or canceled checks. This includes pr_build, Docker integration/e2e tests, all four storage e2e jobs and the Agent Gateway migration check. Local SQL checks validate seed consistency; only the database engines exercised by CI have runtime verification.

Latest-master local verification: 27 plugin/starter tests passed, and 34 targeted Admin mapper/rule-service tests passed, zero failures/errors/skips. Checkstyle, Admin/plugin/starter RAT, all five 24-row seed parity checks, and the 8 migration-checker tests passed.

Scope

  • Full MCP JSON-RPC tool workflow is not included;

  • Native RISC-V hardware has not been tested;

  • Other database dialects have not been executed;

  • The upcoming mentor version has not yet been merged.

@dengliming
dengliming requested a balanced review from Copilot September 19, 2026 14:14

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BobSong-dev
BobSong-dev marked this pull request as draft September 20, 2026 01:25

@Aias00 Aias00 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.

Review (PMC Aias00) — OSPP_PR1: Add request-scoped context support for the Agent Gateway plugin

I am leaving this as a comment, not a formal approve/request-changes, because the PR is still a draft and the description says the mentor version has not been merged yet. But there are two hard blockers below that will break a fresh install, plus a few design points worth settling before this leaves draft.


🔴 Blocking — rebase required, the SQL IDs now collide with master

The branch was cut around 2026-09-19; master has since landed the sensitiveWord plugin (2026-09-21), which consumed exactly the ID range this PR allocates. Verified against current master:

  1. plugin.id = '67' is already taken. All six init scripts insert
    ('67', 'agentGateway', ... 'Ai', 198 ...), but master already has

    INSERT INTO `plugin` VALUES ('67', 'sensitiveWord', null, 'Ai', 197, ...);

    → duplicate primary key, schema init fails on a fresh install. Please move to the next free id (68) in all six files (db/init/mysql|ob|og|oracle|pg + shenyu-admin/.../h2/schema.sql).

  2. resource ids 1942847622591684620 / …621 / …622 are already used. Master has

    INSERT INTO `plugin_handle` VALUES ('1942847622591684620', '67', 'failClosed', ...);
    INSERT INTO `plugin_handle` VALUES ('1942847622591684621', '67', 'words', ...);
    INSERT INTO `plugin_handle` VALUES ('1942847622591684622', '67', 'maxBodySize', ...);

    This PR reuses the same three values for its resource rows → duplicate primary key. Re-allocate, and please re-verify the permission ids 1942847622591684630–639 against master as well while you are there.

Good news: plugin code 198 itself is free and slots correctly immediately before AI_PROXY(199, ...), so the enum addition is fine as-is.


🟠 Should fix before leaving draft

  1. trafficType is hard-restricted to "LLM". AgentGatewayRuleHandleParser returns invalid("trafficType must be LLM") for anything else, and an invalid handle becomes a 503 on live traffic. The PR claims MCP coexistence and only lists "full MCP JSON-RPC tool workflow" as out of scope — but as written the plugin cannot even be configured for MCP later without another code change. Either open the enum now (LLM, MCP, …) or state plainly in the README that PR1 supports LLM only.
  2. Unknown fields reject live traffic. Any key outside {trafficType, responseRequestId} → invalid handle → 503. Strict validation is right at config-save time, but failing requests because a stored handle carries an extra key is fragile (admin schema evolution, hand-edited JSON, copy-paste from docs). Suggest logging a warning and ignoring unknown fields on the request path, keeping the strict check in admin.
  3. 503 is the wrong status for a configuration error. reject() sets SERVICE_UNAVAILABLE, which tells clients and upstream proxies to retry — but a bad rule handle will never start working. Use 500 (server-side misconfiguration) or 400, and reserve 503 for real unavailability. The AGENT_GATEWAY_CONFIG_INVALID code prefix is a nice touch.
  4. selector.getContinued() == FALSE → 503. continued=false conventionally means "stop the chain after this plugin", not "misconfigured". Failing the request here is surprising. Consider just proceeding, or document the constraint clearly.
  5. The AtomicBoolean subscribed guard is risky.
    if (!subscribed.compareAndSet(false, true)) {
        return Mono.error(new IllegalStateException("...cannot be subscribed twice"));
    }
    WebFlux subscribes once, but this makes the returned Mono non-idempotent, so any retry, repeat, cache() or re-subscribing composition turns into a hard error. If double subscription is not a real scenario, drop the guard; if it is, handle it without failing the request.

🟡 Non-blocking

  1. resolveHandle re-parses JSON on every request when the handle is invalid (invalid results are never cached). Consider caching the invalid handle too, keyed by the raw string.
  2. restorePreviousContext relies on Map#remove(k, v) / Map#replace(k, old, new). DefaultServerWebExchange backs attributes with a ConcurrentHashMap so it works today, but those default methods throw UnsupportedOperationException on other Map implementations — worth a comment stating the assumption.
  3. AgentTrafficContext has no toString(); adding one (requestId + trafficType + selector/rule) makes correlation in logs much easier.
  4. The Verification section says GitHub CI has not been run. Apache requires a green CI run before merge — please make sure the full matrix passes (and rerun after the rebase).

✅ What looks good

  • AgentTrafficContext is genuinely immutable, created per subscription, and never stored in a global map or thread local — real request isolation, and the test asserting 12 distinct request ids proves it.
  • contextWrite is applied to chain.execute(exchange), so downstream plugins see the context — correct Reactor usage, and the test asserts exchange context == reactor context.
  • doFinally restores/removes the previous attribute, including on cancel — good cleanup hygiene.
  • AgentGatewayPluginDataHandler cleans the handle cache on removeRule, removeSelector (prefix scan) and removePlugin — no leak on config removal.
  • Response correlation via beforeCommit + X-Shenyu-Agent-Request-Id, and deliberately not trusting a client-supplied id.
  • 26 tests across plugin / parser / data handler / starter config, covering isolation, restore, cancellation, rejection and the response-header opt-in.

Solid foundation — fix the ID collisions and the status-code semantics and this is in good shape.

Comment thread db/init/mysql/schema.sql Outdated
INSERT INTO `plugin` VALUES ('53', 'aiRequestTransformer', NULL, 'Ai', 65, 0, '2023-12-20 18:02:53', '2023-12-20 18:02:53', null);

INSERT INTO `plugin` VALUES ('61', 'mcpServer', null, 'MCP', 180, 0, '2023-12-20 18:02:53', '2023-12-20 18:02:53', null);
INSERT INTO `plugin` VALUES ('67', 'agentGateway', null, 'Ai', 198, 0, '2026-09-19 00:00:00', '2026-09-19 00:00:00', null);

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.

🔴 Blocking — primary key collision with master.

plugin.id = '67' was assigned to sensitiveWord on master (2026-09-21):

INSERT INTO `plugin` VALUES ('67', 'sensitiveWord', null, 'Ai', 197, 0, '2026-09-21 00:00:00', ...);

Inserting ('67', 'agentGateway', ...) here fails with a duplicate primary key, so a fresh install cannot initialise the schema.

Please rebase and move to the next free id (68) — and apply the same change to all six init scripts (db/init/mysql, ob, og, oracle, pg, and shenyu-admin/src/main/resources/sql-script/h2/schema.sql).

The plugin code 198 itself is free and correctly ordered just before AI_PROXY(199), so only the row id needs to change.

Comment thread db/init/mysql/schema.sql Outdated
INSERT INTO `resource` VALUES ('1844026199075534868', '1844026199075534860', 'SHENYU.BUTTON.PLUGIN.RULE.DELETE', '', '', '', 2, 0, '', 1, 0, 'plugin:mcpServerRule:delete', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');
INSERT INTO `resource` VALUES ('1844026199075534869', '1844026199075534860', 'SHENYU.BUTTON.PLUGIN.SYNCHRONIZE', '', '', '', 2, 0, '', 1, 0, 'plugin:mcpServer:modify', 1, '2022-05-25 18:02:58', '2022-05-25 18:02:58');

INSERT INTO `resource` VALUES ('1942847622591684620', '1346775491550474240', 'agentGateway', 'agentGateway', '/plug/agentGateway', 'agentGateway', 1, 0, 'pic-center', 0, 0, '', 1, '2026-09-19 00:00:00', '2026-09-19 00:00:00');

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.

🔴 Blocking — these resource ids are already used on master.

1942847622591684620, …621 and …622 are already taken by plugin_handle rows belonging to sensitiveWord (plugin 67):

INSERT INTO `plugin_handle` VALUES ('1942847622591684620', '67', 'failClosed', ...);
INSERT INTO `plugin_handle` VALUES ('1942847622591684621', '67', 'words', ...);
INSERT INTO `plugin_handle` VALUES ('1942847622591684622', '67', 'maxBodySize', ...);

Please re-allocate this id block, and double-check the paired permission ids 1942847622591684630–639 while you are at it.

}

private Mono<Void> reject(final ServerWebExchange exchange, final String reason) {
exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);

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.

🟠 503 SERVICE_UNAVAILABLE is the wrong status for a configuration error.

503 tells clients and upstream proxies that the failure is transient and worth retrying, but a malformed rule handle will never start working — so this invites pointless retry storms. Please use 500 INTERNAL_SERVER_ERROR (server-side misconfiguration) or 400, and keep 503 only for genuine unavailability.

final AtomicBoolean subscribed = new AtomicBoolean();
return Mono.defer(() -> {
if (!subscribed.compareAndSet(false, true)) {
return Mono.error(new IllegalStateException("agent gateway execution cannot be subscribed twice"));

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.

🟠 This guard makes the returned Mono non-idempotent: any second subscription (e.g. a retry, repeat, or cache() composed downstream) turns into an IllegalStateException and fails the request.

WebFlux subscribes once, so if double subscription is not a scenario you actually hit, I would drop the guard. If it is a real scenario, handling it should not mean failing the request — consider logging and reusing the already-built context instead.

@BobSong-dev

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion. We will add strict validation before saving or importing configurations in Admin for the agentGateway rule.handle. Only trafficType and responseRequestId will be allowed, with validation for required fields and value types. At runtime, unknown fields in legacy configurations will still be logged and ignored to preserve compatibility and avoid affecting existing requests.

@BobSong-dev

Copy link
Copy Markdown
Contributor Author

I’ve added strict validation in Admin for agentGateway rule handles. Rule creation, updates, and configuration imports now reject unknown fields and invalid values before they are saved. At runtime, unknown fields in existing configurations are still logged and ignored to avoid disrupting requests. The Admin tests pass locally (31 tests), and the change is included in commit 8dc2373.

# Conflicts:
#	shenyu-admin/src/main/resources/sql-script/h2/schema.sql
# Conflicts:
#	shenyu-plugin/shenyu-plugin-agent-gateway/README.md
# Conflicts:
#	shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/AgentGatewayPlugin.java
@BobSong-dev
BobSong-dev force-pushed the feat/agent-gateway-request-context branch from 8dc2373 to 18b2f88 Compare September 24, 2026 01:01
@BobSong-dev
BobSong-dev marked this pull request as ready for review September 28, 2026 04:25

@Aias00 Aias00 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.

The runtime context/plugin implementation and targeted tests look sound, but fresh-install schemas are the only place that seed agentGateway. None of the mysql/ob/og/oracle/pg 2.7.1-to-2.7.2 upgrade scripts add the plugin, handles, resources, permissions, or namespace relation. Existing upgraded installations therefore cannot expose/configure the plugin. Please add dialect-appropriate upgrade migrations and a migration/schema consistency check.

@BobSong-dev

Copy link
Copy Markdown
Contributor Author

Thank you for the careful review, @Aias00. I missed the upgrade path in the previous changes, and I appreciate you pointing this out.

I have added the Agent Gateway seed data to all five 2.7.1-to-2.7.2 upgrade scripts (MySQL, OceanBase, openGauss, Oracle, and PostgreSQL), using each dialect's existing syntax. Each migration now includes the plugin, both rule-handle fields, resources, permissions, and the default namespace relation, matching the corresponding fresh-install schema.

I also added a schema/migration consistency check and wired it into CI independently of the Maven change filter, so SQL-only changes are checked as well. The check covers all 24 seed rows per dialect, and its regression tests detect missing or duplicate rows and changed values.

Local verification: all five dialect comparisons and all eight checker tests passed. The root Apache RAT check passed before the upstream merge; the consistency checks and their tests were rerun successfully after merging the latest master. I preserved both the upstream MySQL indexes and the new migration data when resolving the append conflict. I have not yet executed the full upgrade against live instances of all five databases, so these results are static consistency checks rather than database integration verification.

The fix is in 87c874f, with the upstream merge in b4b55b4. Thank you again for your guidance. Please let me know if you would prefer any changes to the migration or check structure.

@Aias00

Aias00 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Re-check: the runtime context/plugin implementation and tests look sound, but the point I raised stands — the fresh-install schemas appear to be the only place that seed the required data, and the upgrade path looks unseeded. Please confirm the upgrade scripts seed it too, or address the gap.

@Aias00 Aias00 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.

Re-reviewed after the new commits, and my previous concern is fully addressed: the upgrade path now seeds the Agent Gateway data too. All five dialects' 2.7.1-upgrade-2.7.2-*.sql insert the plugin row, plugin handles, resources and permissions, and on top of that you added check-agent-gateway-migrations.py (plus its test) to assert seed parity between the fresh-install schemas and the upgrade scripts across dialects — that is a better guarantee than what I asked for.

The plugin itself (context, rule handle + parser + validator, configuration, plugin data handler) has substantial test coverage, and the migration check is wired into CI. Approving.

@Aias00
Aias00 merged commit fb0393d into apache:master Oct 1, 2026
40 checks passed
@Aias00 Aias00 added this to the 2.7.2 milestone Nov 13, 2026
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.

3 participants