Skip to content

feat: hook system and plugin support design spec#63

Draft
araujof wants to merge 1 commit intopraxis-proxy:mainfrom
araujof:feat/hook_system
Draft

feat: hook system and plugin support design spec#63
araujof wants to merge 1 commit intopraxis-proxy:mainfrom
araujof:feat/hook_system

Conversation

@araujof
Copy link
Copy Markdown

@araujof araujof commented Apr 20, 2026

Description

A proposal for a second extension surface in Praxis, complementing the existing HttpFilter / TcpFilter surface with typed, lifecycle-anchored hooks backed by the CPEX runtime embedded in-process.

Specification

Closes: #30

What it adds

Hooks sit at well-defined points across the server lifecycle: startup, TLS handshake, TCP accept and session, and the HTTP request and response path. Each has a typed payload and a declared interaction class (observe, mutate, or policy), so plugin authors know what they can touch and the dispatcher knows how to compose results.
Plugins run as native shared libraries or WASM modules; Python is excluded because the latency budget does not accommodate it. A plugin call is an in-process function call, not an IPC hop, and a deployment with no plugins configured pays nothing beyond a single atomic check per call site.

The central structural property is tighten-only composition. Policy hooks can strengthen any of Praxis' built-in security boundaries but never weaken them, and this is enforced by the dispatcher rather than left to plugin discipline.

What it defers

Protocol-semantic concerns, MCP tool invocations, A2A tasks, LLM inputs and outputs, are named and reserved but intentionally unregistered. They probably belong in future protocol-native services that parse bodies once during routing, not in an HTTP filter that would have to buffer speculatively. Hot reload, Python, and sidecar hosts for synchronous paths are also out of scope for v1.

@shaneutt this is a design question we should discuss before committing to protocol-semantic hooks implementation specification. We documented the cost and performance disadvantages of content-based filters for this in the specification linked to this PR.

Operational surface

Configuration is YAML-driven, with per-hook timeouts, error policies, and priorities. Plugins that span multiple hooks, for example deciding at request-time and auditing at session-end, get a typed request-scoped state channel rather than having to smuggle state through headers. Body-handling hooks have cumulative budgets at the chunk, request, and plugin level, and a circuit breaker auto-disables plugins that repeatedly misbehave. Metrics and tracing cover invocations, rejections, short-circuits, and the time the plugin chain contributes to each request.

Rollout

The work is staged so each phase is independently shippable.

cc: @terylt @imolloy

Signed-off-by: Ian Molloy <molloyim@us.ibm.com>
Signed-off-by: Teryl Taylor <terylt@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Co-authored-by: Ian Molloy <molloyim@us.ibm.com>
Co-authored-by: Teryl Taylor <terylt@ibm.com>
Co-authored-by: Frederico Araujo <frederico.araujo@ibm.com>
@shaneutt shaneutt self-assigned this Apr 20, 2026
@shaneutt shaneutt moved this to Review in AI Gateway Apr 20, 2026
@shaneutt shaneutt added this to the v0.5.0 milestone Apr 20, 2026
@araujof
Copy link
Copy Markdown
Author

araujof commented Apr 29, 2026

@shaneutt Thanks for 0f0f656. It addresses the listener-wide collapse by removing the precedence rung that let one filter promote the whole listener to all-or-nothing buffered mode, and per-request set_request_body_mode / set_response_body_mode on the context means the cost is opt-in per request instead of baked into capabilities at startup.

Net: the spec's §4.1 Cost 1 is resolved at the architectural level. I'll update the plugins doc to reference per-request set_*_body_mode as the mechanism plugins use, drop the Buffer references, and reword the streaming-collapse argument to reflect "per-request opt-in" rather than "per-listener promotion."

One small thing: the BodyMode::StreamBuffer { .. } | BodyMode::Stream | _ => {} arms in request_body_filter.rs / response_body_filter.rs plus #[non_exhaustive] on the enum mean future variants silently fall through as Stream. Explicit arms would catch that at compile time.

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

Labels

None yet

Projects

Status: Review

Development

Successfully merging this pull request may close these issues.

Context Forge

2 participants