# Extension Protocol v1 in DeepSeek-Reasonix: How Sidecars Intercept Runtime Events

> Explore Extension Protocol v1 in DeepSeek-Reasonix to learn how sidecars intercept runtime events. Understand this stable, language-agnostic contract for out-of-process interaction.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: deep-dive
- Published: 2026-08-07

---

**Extension Protocol v1 is a stable, language-agnostic JSON-RPC 2.0 contract that enables out-of-process sidecars to intercept, modify, and respond to runtime events in the DeepSeek-Reasonix engine without linking against the host binary.**

DeepSeek-Reasonix separates its core engine logic from extensible user code through a strict process boundary. This architecture leverages **Extension Protocol v1**, a wire contract that defines how sidecars communicate with the host over NDJSON streams. By implementing this protocol, developers can hook into critical runtime events—such as input processing and permission decisions—while maintaining the engine's deterministic execution and security guarantees.

## What Is Extension Protocol v1?

**Extension Protocol v1** is the frozen, major-version-locked interface between the Reasonix host and out-of-process extensions. Defined in [`docs/EXTENSION_PROTOCOL.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/EXTENSION_PROTOCOL.md) and enforced by the machine-readable schema in [`internal/extension/protocol/schema.generated.json`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/extension/protocol/schema.generated.json), the protocol specifies how sidecars subscribe to events, return decisions, and publish UI elements.

The contract uses **JSON-RPC 2.0 over NDJSON** (Newline-Delimited JSON) via stdin/stdout, with each line representing a complete JSON object. Frames are strictly capped at **8 MiB**; exceeding this limit triggers a `frame_too_large` error and connection termination. All capabilities must be declared in the sidecar's manifest during the initialization handshake—any undeclared capability results in a `capability_not_declared` failure.

## Architecture of the Host-Sidecar Model

The system comprises three primary components that communicate through the protocol's strict lifecycle:

- **Host (Reasonix Engine)**: The main process managing sessions, caching, and permissions. It spawns sidecars as plain subprocesses (no shell) and supplies a manifest declaring which events the sidecar may subscribe to, replace, or provide UI for.
- **Sidecar**: A standalone binary compiled by the plugin author that implements the Extension Protocol. It communicates via stdin/stdout using NDJSON framing.
- **Transport**: NDJSON over stdio with an 8 MiB frame limit, ensuring bounded memory usage during communication.

The **lifecycle** follows a deterministic sequence:
1. Host launches sidecar and sends `extension/initialize`.
2. Sidecar replies with its declaration (subscriptions, capabilities).
3. Host validates against the manifest and sends `extension/initialized`.
4. Runtime proceeds with event interception.
5. On shutdown, host sends `extension/shutdown`; if the process does not exit, it is killed.

The host enforces resource constraints: only one sidecar per runtime generation may be started, with up to four sidecars bootable in parallel under a shared **30-second startup budget**.

## How Sidecars Intercept Runtime Events

Sidecars intercept events by registering for **hook points** (e.g., `input.receive`, `permission.decision`) during initialization. The host executes interceptors sequentially in deterministic priority order: manifest-defined priority → plugin ID → registration order.

### The Interception Lifecycle

When a subscribed event occurs, the host emits `extension/intercept` to the sidecar. This method is **blocking**—the host awaits the sidecar's decision before continuing. In contrast, `extension/event` is fire-and-forget for telemetry or logging.

The interception flow operates as follows:
1. **Declare Subscription**: In the `Initialize` call, the sidecar returns a list of hook points (e.g., `["input.receive"]`).
2. **Host Sends Event**: When processing user input, the host emits `extension/intercept` with the method name and payload.
3. **Sidecar Responds**: The interceptor returns a decision type (`continue`, `block`, `replace`, `allow`, or `deny`).
4. **Host Continues**: The host proceeds with the original or modified payload.

If the sidecar crashes or times out, the pending RPC is cancelled, the operation fails explicitly, and the sidecar is only restarted during an idle-time reload.

### Decision Types and Payload Replacement

Sidecars influence runtime behavior through specific decision values:

- **`continue`**: Pass the event through unchanged.
- **`block`**: Halt the operation.
- **`replace`**: Supply a new payload that the host re-validates against the DTO schema before proceeding.
- **`allow` / `deny`**: Specific to permission decisions. Returning `allow` from a full-trust sidecar can override a host deny and is audited.

## Implementing a Sidecar

Developers implement sidecars using the Go SDK located in `sdk/go/`, though any language capable of JSON-RPC over stdio can conform to the protocol.

### Minimal Starter Sidecar

The repository provides a minimal example in [`sdk/go/examples/starterextension/main.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/sdk/go/examples/starterextension/main.go) that intercepts `input.receive` to rewrite user input:

```go
package main

import (
	"context"
	"encoding/json"
	"os"
	"strings"

	extension "github.com/esengine/DeepSeek-Reasonix/sdk/go"
)

const inputPrefix = "starter: "

type starter struct{}

func (starter) Initialize(context.Context, extension.InitializeParams) (*extension.InitializeResult, error) {
	return &extension.InitializeResult{
		Subscriptions: []string{"input.receive"},
	}, nil
}

func interceptInput(_ context.Context, _ string, payload json.RawMessage) (*extension.InterceptResult, error) {
	var in struct{ Text string `json:"text"` }
	if err := json.Unmarshal(payload, &in); err != nil || !strings.HasPrefix(in.Text, inputPrefix) {
		return extension.Continue(), nil
	}
	return extension.Replace(map[string]string{
		"text": strings.TrimPrefix(in.Text, inputPrefix) + " [rewritten by starter-extension]",
	})
}

func main() {
	err := extension.Serve(context.Background(), starter{}, extension.Options{
		Name:    "starter-extension",
		Version: "0.1.0",
		Interceptors: map[string]extension.InterceptorFunc{
			"input.receive": interceptInput,
		},
	})
	if err != nil {
		os.Exit(1)
	}
}

```

To test this sidecar:

```bash
go build -o bin/starter-extension.exe .
reasonix plugin install "$(pwd -P)" --link --replace --yes

```

When a user types `starter: explain protocols`, the model receives the rewritten text with the suffix appended.

### Intercepting Permission Decisions

Sidecars can override host security decisions by subscribing to `permission.decision`. The following skeleton demonstrates forcing an `allow` decision, which overrides host denials:

```go
package main

import (
	"context"
	"encoding/json"
	"os"

	extension "github.com/esengine/DeepSeek-Reasonix/sdk/go"
)

type allowAll struct{}

func (allowAll) Initialize(context.Context, extension.InitializeParams) (*extension.InitializeResult, error) {
	return &extension.InitializeResult{
		Subscriptions: []string{"permission.decision"},
	}, nil
}

func interceptPermission(_ context.Context, _ string, payload json.RawMessage) (*extension.InterceptResult, error) {
	var req struct {
		Decision string `json:"decision"`
	}
	if json.Unmarshal(payload, &req) != nil {
		return extension.Continue(), nil
	}
	return extension.Allow(), nil
}

func main() {
	err := extension.Serve(context.Background(), allowAll{}, extension.Options{
		Name:    "allow-all",
		Version: "0.1.0",
		Interceptors: map[string]extension.InterceptorFunc{
			"permission.decision": interceptPermission,
		},
	})
	if err != nil {
		os.Exit(1)
	}
}

```

Because sidecars are **full-trust**, this capability bypasses sandbox restrictions and is strictly audited in host logs.

### Publishing Structured UI

Sidecars communicate with front-ends (desktop, web, or TUI) via structured UI methods, not HTML or JavaScript. To publish a status card:

```go
func publishStatus(ctx context.Context) error {
	card := map[string]interface{}{
		"title":   "Hello from Sidecar",
		"content": "✅ Extension is running",
	}
	_, err := extension.Call(ctx, "host/ui/publish", map[string]interface{}{
		"kind":   "status",
		"payload": card,
	})
	return err
}

```

UI updates include plugin and runtime generation IDs to ensure stale updates are discarded by the front-end.

## Security and Trust Model

Sidecars operate as **full-trust** processes outside the Reasonix sandbox. They inherit the host environment and can read/write files unless blocked by host-level sandboxing. 

Authorization occurs solely at installation time: only plugins installed via the official flow (`reasonix plugin install …`) can start a sidecar. The host validates every capability declaration against the plugin manifest; undeclared capabilities cause immediate handshake failure. This design ensures that while sidecars possess broad system access, their capabilities are explicitly scoped and user-consented during the installation phase.

## Summary

- **Extension Protocol v1** provides a stable, version-locked JSON-RPC 2.0 interface for out-of-process extensions in DeepSeek-Reasonix.
- Sidecars communicate via **NDJSON over stdio** with an 8 MiB frame limit and must declare all capabilities in their manifest.
- **Event interception** uses blocking `extension/intercept` calls that support `continue`, `block`, `replace`, and permission-specific `allow`/`deny` decisions.
- The host enforces **deterministic ordering** of interceptors and validates all payload replacements against DTO schemas.
- Sidecars run as **full-trust** processes with file system access, requiring installation through the official plugin flow for security.
- Developers can implement sidecars in any language, with reference implementations available in `sdk/go/` and protocol specifications in [`docs/EXTENSION_PROTOCOL.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/EXTENSION_PROTOCOL.md).

## Frequently Asked Questions

### What transport protocol does Extension Protocol v1 use?

Extension Protocol v1 uses **JSON-RPC 2.0 over NDJSON** (Newline-Delimited JSON) transported via stdin/stdout. Each line represents a single JSON-RPC message, and frames are strictly limited to 8 MiB to prevent memory exhaustion. This design ensures language-agnostic compatibility while maintaining strict message boundaries.

### How does a sidecar modify or replace a runtime payload?

When intercepting an event like `input.receive`, the sidecar returns a `replace` decision containing the new payload. According to the protocol implementation in [`internal/extension/sidecar/doc.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/extension/sidecar/doc.go), the host validates this replacement against the DTO schema before substituting it into the processing pipeline. If validation fails, the host rejects the replacement and aborts the operation.

### What security privileges do sidecars have in DeepSeek-Reasonix?

Sidecars operate as **full-trust** processes that run outside the Reasonix sandbox. They inherit the host's environment variables and file system access unless restricted by external sandboxing. Because they can override permission decisions and access sensitive resources, sidecars can only be installed through the official `reasonix plugin install` flow, which serves as the sole authorization checkpoint.

### How many sidecars can run simultaneously?

The host supports **up to four sidecars** booting in parallel under a shared 30-second startup budget. However, only **one sidecar per runtime generation** may be active at a time. If a sidecar crashes or times out during a blocking interception, the operation fails explicitly, and the sidecar is only restarted during an idle-time reload to prevent disruption of active sessions.