MIDDLEWARE ORDER · THE STOPPING GATE
Middleware ordering: inspect the stopping gate
Four hooks guard a refund tool: tracing, a cache, an amount normaliser and the refund policy. Reorder them and see which one stops the chain first.
Your experiment
Six refund requests pass through a before-tool chain: a response cache keyed by order and amount, the refund policy (own orders only, at most 1,000), a normaliser that turns “1.500,00” into 1500, and tracing. In the opening order the cache runs first and tracing last. Reorder them, then move the policy from a plugin to an agent callback.
Play replays the computed run over sixty inspection seconds; Next event steps, Restart rewinds, and Inspection time scrubs. Any input change recomputes the run and rewinds it without autoplay.
Event trace
Computed data
Final metrics
Composition rules as described in the linked article on ADK Java: plugins run in registration order before agent callbacks, and the first hook that returns a value ends the chain. The cache key omitting the user and the policy misreading “1.500,00” as 1.5 are deliberate fixture bugs. No model or payment service is called.
One hook point, four hooks
Before issue_refund runs, a chain of hooks is evaluated. In ADK Java plugins run in registration order, then agent callbacks in list order, and the first hook that returns a value ends the chain: nothing after it runs, including the tool. Hooks that return empty let the chain continue. Response cache returns a stored result for the same order and amount. Amount normaliser rewrites “1.500,00” to 1500 in place. Tracing records a span. Refund policy registered as decides whether the policy is a plugin or an agent callback.
The opening run: cache first, tracing last
The evaluation order is cache, policy, normaliser, tracing. R2 asks for “1.500,00”: the policy reads it as 1.5 and approves, then the normaliser turns it into 1500, which is refunded. R3 is user B asking about user A’s order O1: the cache answers with R1’s result before the policy runs, serving another user’s refund. R3, R4 and R5 end before tracing, so three decisions have no span. 1,615 is refunded, including the 1,500 that should have been denied.
Reordering the chain
Set Response cache to Registered after the policy: R3 is now denied as not the owner, and R5, the same user repeating R1, still gets the cached result instead of a second refund. Set Amount normaliser to Before the policy: R2 is checked as 1500 and denied. Set Tracing to Registered first: every request has a span, including the denied and cached ones. With all three changes the chain is safe and refunds 115. Turn the cache off and R5 refunds 40 a second time.
Failure boundary: plugin or agent callback
With the safe order, set Refund policy registered as to “An agent callback”. Plugins run before agent callbacks, so every plugin, the cache included, now runs before the policy, whatever position you chose for it: R3 is served from the cache again. Registration order is the policy, so it must be decided in one place and tested: tracing first, normalisers before checks, gates before anything that can answer on their behalf.
Reading the run and its limits
Play replays the six requests, where each chain stopped and whether it was traced over sixty inspection seconds; the table lists each result and verdict. Next event, Restart, Speed and Inspection time control the replay. The two fixture bugs, a cache key without the user and a policy that misparses a localized amount, are deliberate; real chains have their own. A recording test that asserts which hooks ran, in what order, catches most of them.