Repository navigation
OSPP_PR1:Add request-scoped context support for the Agent Gateway plugin - #7133
Conversation
Aias00
left a comment
There was a problem hiding this comment.
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:
-
plugin.id = '67'is already taken. All six init scripts insert
('67', 'agentGateway', ... 'Ai', 198 ...), but master already hasINSERT 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). -
resourceids1942847622591684620/…621/…622are already used. Master hasINSERT 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
resourcerows → duplicate primary key. Re-allocate, and please re-verify thepermissionids1942847622591684630–639against 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
trafficTypeis hard-restricted to"LLM".AgentGatewayRuleHandleParserreturnsinvalid("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.- 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. 503is the wrong status for a configuration error.reject()setsSERVICE_UNAVAILABLE, which tells clients and upstream proxies to retry — but a bad rule handle will never start working. Use500(server-side misconfiguration) or400, and reserve 503 for real unavailability. TheAGENT_GATEWAY_CONFIG_INVALIDcode prefix is a nice touch.selector.getContinued() == FALSE→ 503.continued=falseconventionally means "stop the chain after this plugin", not "misconfigured". Failing the request here is surprising. Consider just proceeding, or document the constraint clearly.- The
AtomicBoolean subscribedguard is risky.WebFlux subscribes once, but this makes the returnedif (!subscribed.compareAndSet(false, true)) { return Mono.error(new IllegalStateException("...cannot be subscribed twice")); }
Mononon-idempotent, so anyretry,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
resolveHandlere-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.restorePreviousContextrelies onMap#remove(k, v)/Map#replace(k, old, new).DefaultServerWebExchangebacks attributes with aConcurrentHashMapso it works today, but those default methods throwUnsupportedOperationExceptionon otherMapimplementations — worth a comment stating the assumption.AgentTrafficContexthas notoString(); adding one (requestId + trafficType + selector/rule) makes correlation in logs much easier.- 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
AgentTrafficContextis 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.contextWriteis applied tochain.execute(exchange), so downstream plugins see the context — correct Reactor usage, and the test asserts exchange context == reactor context.doFinallyrestores/removes the previous attribute, including on cancel — good cleanup hygiene.AgentGatewayPluginDataHandlercleans the handle cache onremoveRule,removeSelector(prefix scan) andremovePlugin— 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.
| 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); |
There was a problem hiding this comment.
🔴 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.
| 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'); |
There was a problem hiding this comment.
🔴 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); |
There was a problem hiding this comment.
🟠 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")); |
There was a problem hiding this comment.
🟠 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.
|
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. |
|
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
8dc2373 to
18b2f88
Compare
Aias00
left a comment
There was a problem hiding this comment.
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.
…pgrade-migrations # Conflicts: # db/upgrade/2.7.1-upgrade-2.7.2-mysql.sql
|
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. |
|
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
left a comment
There was a problem hiding this comment.
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.
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-gatewayplugin 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.