# How to Control Message Delivery with Steering vs Sequential Queueing in Copilot SDK

> Master message delivery in Copilot SDK. Learn to control sequential queueing with enqueue() and immediate steering with steerMessage() for optimized interaction.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**Use `session.rpc.messages.enqueue()` for FIFO sequential processing and `session.rpc.steering.steerMessage()` for immediate injection into the current turn, contingent on the `remote` session mode being set to `"on"`.**

Controlling when and how user messages reach the agent is critical for building responsive Copilot SDK applications. The github/copilot-sdk provides two distinct delivery mechanisms—**sequential queueing** and **steering**—that determine whether a message waits in line or interrupts the active generation. Understanding these modes allows you to implement everything from batched command processing to real-time mid-turn corrections.

## Understanding Sequential Queueing (FIFO)

Sequential queueing is the default delivery mode where messages are appended to a first-in, first-out (FIFO) queue. The runtime processes each message only after the current turn completes, ensuring strict ordering for conversational flows and batched commands.

According to the SDK source code in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts), sequential queueing uses the `EnqueueMessageParams` interface with `mode: "enqueue"` (lines ~1800). When you call the enqueue method, the SDK constructs a `QueueEnqueueMessageResult` and appends the payload to the `PendingItems.items` array.

```typescript
// Sequential queueing – the default FIFO behavior
await session.rpc.messages.enqueue({
  content: "Please list the files in the workspace.",
  // mode defaults to "enqueue"
});

```

The message now resides in `session.rpc.queue.pendingItems().items` and will execute only after preceding queued items finish. This mode is appropriate for standard conversational turns where maintaining message order matters.

## Understanding Steering (Immediate Injection)

Steering bypasses the queue entirely by injecting a message into the **live turn** currently being processed. Instead of waiting for the current generation to finish, the runtime interjects the payload immediately via the `steeringMessages` lane.

This mechanism relies on the `SteerMessageParams` interface defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts) (lines ~20700-20730). When you invoke `steerMessage`, the SDK places the content into `PendingItems.steeringMessages`, allowing the model to react without a full turn boundary.

```typescript
// Immediate steering – interject into the current turn
await session.rpc.steering.steerMessage({
  addressableItemId: undefined, // Optional: ID of queued item to replace
  content: "Actually, focus on `src/utils` only.",
});

```

Use steering when you need urgent corrections, rapid clarifications, or UI-driven "undo" operations that must affect the generation in progress. Note that steering only functions when the session's `remoteSteerable` flag is enabled.

## Configuring Remote Steering Permissions

The SDK can disable steering entirely via the `SessionMode.remote` setting, found in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) (lines 2583-2584). When set to `"off"`, any `steerMessage` call becomes a no-op: it returns success but the `steeringMessages` array remains empty, as validated in [`nodejs/test/e2e/rpc_server_remote_control.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_server_remote_control.e2e.test.ts).

```typescript
// Disable remote steering (steering becomes no-op)
await session.rpc.session.setRemoteSteerable({
  remote: "off", // disables both export and steering
});

// Re-enable steering
await session.rpc.session.setRemoteSteerable({
  remote: "on", // enables export + steering
});

```

The `remote` setting accepts three values:
- `"on"` – Full steering and export capabilities enabled
- `"export"` – Export-only mode (steering disabled)
- `"off"` – All remote control disabled (steering calls ignored)

## How the Runtime Processes Messages

Internally, the SDK maintains a `PendingItems` snapshot that distinguishes between queued and steered messages. As defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts) (lines ~12231-12233), the interface separates these into distinct lanes:

```typescript
interface PendingItems {
  items: QueuedMessage[];          // Normal FIFO queue (sequential)
  steeringMessages: string[];     // Immediate-steering lane
  // …other metadata
}

```

When `remoteSteerable` is `true`, the runtime checks the `steeringMessages` array during active generation and interjects those payloads immediately. If `remoteSteerable` is `false`, the array stays empty despite successful API calls, and messages follow only the sequential path.

## Practical Implementation Example

Combine these APIs to build a "quick edit" feature that interrupts the current assistant response:

```typescript
async function quickEdit(session: Session, newPrompt: string) {
  // Ensure steering is enabled
  await session.rpc.session.setRemoteSteerable({ remote: "on" });

  // Inject the correction immediately
  await session.rpc.steering.steerMessage({
    content: newPrompt,
  });

  // Verify the pending snapshot
  const pending = await session.rpc.queue.pendingItems();
  console.log("Queue length:", pending.items.length);
  console.log("Steering lane:", pending.steeringMessages);
}

```

This pattern first confirms that steering is permitted, then inserts the message directly into the active turn without waiting for queue processing.

## Summary

- **Sequential queueing** is the default FIFO mode accessed via `session.rpc.messages.enqueue()`, suitable for ordered conversational flows and batched commands.
- **Steering** provides immediate injection via `session.rpc.steering.steerMessage()`, placing messages in the `steeringMessages` lane for mid-turn interruption.
- **Remote permissions** control steering availability through `session.rpc.session.setRemoteSteerable()`; when set to `"off"`, steering calls succeed but have no effect.
- **Internal structure** in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts) defines `PendingItems` with separate `items` and `steeringMessages` arrays to track both delivery paths.

## Frequently Asked Questions

### What happens if I call steerMessage when remote steering is disabled?

The call returns a success response, but the message is ignored. The SDK treats it as a no-op, leaving the `steeringMessages` array empty in the `PendingItems` snapshot. This behavior is verified in the test suite [`nodejs/test/e2e/rpc_server_remote_control.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_server_remote_control.e2e.test.ts).

### Can I use both sequential queueing and steering in the same session?

Yes. You can enqueue messages to the FIFO queue while simultaneously steering messages into the current active turn. The two mechanisms operate on separate lanes within the `PendingItems` structure, allowing mixed delivery strategies within a single session.

### How do I check if steering is currently enabled?

Query the current session mode using `session.rpc.session.getRemoteSteerable()`. Alternatively, inspect the `SessionMode.remote` property in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) to determine whether the value is `"on"`, `"export"`, or `"off"` before attempting to steer.

### What is the difference between addressableItemId and regular steering?

The `addressableItemId` parameter in `SteerMessageParams` allows you to target a specific queued item for replacement or modification. When omitted or set to `undefined`, the steering message inserts as a new interjection into the current turn without replacing a specific queued message.