How DeepSeek-Reasonix Implements Extension Protocol v2 for Runtime Event Interception
DeepSeek-Reasonix implements Extension Protocol v2 as a stdio-based RPC contract between the host runtime and an out-of-process sidecar, using method constants like MethodExtensionIntercept and DTOs such as InterceptParams and InterceptResult to route events through registered interceptor functions.
The esengine/DeepSeek-Reasonix repository provides a robust mechanism for extending AI reasoning workflows through Extension Protocol v2. This protocol enables secure, language-agnostic runtime event interception by spawning sidecar processes that communicate with the host over stdio. By leveraging generated DTOs and a transport-neutral RPC wire format, the system allows extensions to observe, modify, or block critical runtime events without compiling into the core binary.
Architecture of Extension Protocol v2
Transport and Connection Management
The protocol relies on internal/extension/rpcwire/conn.go to manage transport-neutral connections between the host and sidecar. According to the DeepSeek-Reasonix source code, this layer abstracts the stdio pipe into a framed RPC connection capable of handling request-response cycles asynchronously.
Sidecar Process Model
Extensions run as separate OS processes spawned by the host via sdk/go/sdk.go. The host initializes these sidecars using a versioned handshake defined in the protocol specification, ensuring both sides agree on supported method names and DTO schemas before processing events.
Handshake and Interceptor Registration
Protocol Initialization
Before intercepting events, the host performs a handshake using MethodExtensionInitialize (defined in sdk/go/types_generated.go). This method establishes the protocol version and capability negotiation between the Reasonix host and the extension sidecar.
Configuring the Interceptor Map
The host builds an interceptor registry in internal/extension/uihub/hub.go using an Options struct containing an Interceptors map. Keys are event names (e.g., tool.before, session.start) or the wildcard "*" for catch-all handlers. Each entry maps to an InterceptorFunc that receives the event string and raw JSON payload.
Runtime Event Interception Implementation
Host-Side Event Triggering
When the Reasonix runtime reaches a hook point, the host constructs an InterceptParams DTO containing the Event enum and payload. The hub implementation in internal/extension/uihub/hub.go then dispatches a JSON-RPC request using the method constant MethodExtensionIntercept.
Request Structure and Wire Format
The generated code in sdk/go/types_generated.go defines the wire format precisely. The InterceptParams struct includes the event type and payload, while InterceptResult contains the Decision enum and optional replacement data. These DTOs are generated by internal/extension/protocolgen/sdkgo.go to ensure type safety across language boundaries.
Sidecar Request Processing
Upon receiving the request, the sidecar client in internal/extension/sidecar/client.go unmarshals the parameters and routes them to the appropriate handler. The lookup follows a priority order: exact event name match, wildcard "*", then default behavior. The handler returns an InterceptResult serialized back to the host.
Decision Application
The host evaluates the Decision field from the sidecar response. As implemented in internal/extension/uihub/hub.go, valid decisions include:
- continue: Forward the original payload unchanged.
- replace: Substitute the payload with the
Replacementfield from the result. - block: Abort the current operation immediately.
- allow/deny: Grant or revoke permission for protected operations.
Error Handling and Timeouts
The protocol defines specific error constants in sdk/go/types_generated.go, including ErrUnknownMethod and ErrInterceptTimeout. The host enforces per-intercept deadlines; if the sidecar fails to respond within the timeout window, the host defaults to a conservative fallback decision to prevent pipeline stalls.
Implementation Example
The following example demonstrates registering interceptors and handling decisions in a Go-based extension:
// Initialize host with interceptors
host, err := NewHost(Options{
Interceptors: map[string]InterceptorFunc{
"tool.before": func(ctx context.Context, event string, payload json.RawMessage) (*InterceptResult, error) {
// Inspect tool invocation
return &InterceptResult{Decision: DecisionContinue}, nil
},
"*": func(ctx context.Context, event string, payload json.RawMessage) (*InterceptResult, error) {
// Catch-all logging or policy enforcement
return &InterceptResult{Decision: DecisionAllow}, nil
},
},
})
// Host-side dispatch (simplified from internal/extension/uihub/hub.go)
params := InterceptParams{
Event: InterceptEvent("tool.before"),
Payload: json.RawMessage(`{"tool":"search","query":"data"}`),
}
// Send via rpcwire
resp, err := host.Request(MethodExtensionIntercept, params)
// Apply decision based on InterceptResult
Summary
- Extension Protocol v2 uses stdio-based RPC via
internal/extension/rpcwire/conn.goto communicate with out-of-process sidecars. - The host registers interceptors in
internal/extension/uihub/hub.gousing anOptionsmap keyed by event names or the"*"wildcard. - Runtime events trigger
MethodExtensionInterceptrequests withInterceptParamsDTOs defined insdk/go/types_generated.go. - Sidecars process requests in
internal/extension/sidecar/client.goand returnInterceptResultcontaining decisions: continue, block, replace, allow, or deny. - Timeout handling and error constants like
ErrInterceptTimeoutensure robust production operation.
Frequently Asked Questions
What transport mechanism does Extension Protocol v2 use?
Extension Protocol v2 communicates over stdio pipes managed by internal/extension/rpcwire/conn.go. This transport-neutral design allows the same protocol to run over alternative transports if needed, though stdio is the default for sidecar isolation.
How does the host route events to the correct interceptor?
The host maintains an interceptor map in internal/extension/uihub/hub.go. When an event occurs, it looks up handlers by exact event name first, falls back to the wildcard "*" handler if no specific match exists, and finally applies default behavior if neither is registered.
What happens if a sidecar interceptor times out?
If the sidecar does not return an InterceptResult within the configured deadline, the host raises ErrInterceptTimeout (defined in sdk/go/types_generated.go) and applies a conservative fallback decision—typically continue or block depending on the event type—to prevent blocking the Reasonix runtime.
Can extensions modify event payloads or only block them?
Extensions can modify payloads by returning DecisionReplace in the InterceptResult struct. The host then substitutes the original payload with the Replacement field contents before continuing execution, enabling transformation of tool arguments or session data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →