steering

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package steering delivers a user's mid-turn chat message INTO the turn that is already running, instead of making it wait for that turn to finish.

It is deliberately not a control signal. A steering message is an ordinary user chat message — it is persisted to history, it enters the model's context, and the model answers it. The only thing that distinguishes it is WHEN it is delivered: at the next input-assembly boundary of a turn already in flight, rather than as the opening message of a turn of its own. Overloading the control channel (cancel/approve/deny) would have relabelled user content as machinery; a message that the model reads and replies to is not a control.

Steering never interrupts work. Tool calls already issued run to completion and their results are delivered alongside the steering message; a user who wants the current work abandoned has the existing cancel control for that.

This package is a dependency-free leaf (peer of turncancel) so both the transport/runtime side, which parks messages, and the turn side, which drains them, can use it without an import cycle.

Index

Constants

View Source
const (
	OpenTag  = "[user_steering]"
	CloseTag = "[/user_steering]"
)

OpenTag and CloseTag delimit the steering body in the delivered message. The block must be the WHOLE text of the message: a model that sees ordinary user text arriving mid-turn reads it as a new task and abandons what it was doing, which is the opposite of steering.

View Source
const MetaKey = "steering"

MetaKey marks a chat message as a steering send: the sender is declaring that this message is an interjection into whatever turn is already running on the thread, not the start of a new one.

The declaration is explicit rather than inferred from "a turn happens to be active", because the sender is also the party that decides not to open a task for it. A message that merely arrives during a busy period — a scheduled turn, a subagent completion notice, a send from another surface — is still its own turn and must not be swallowed into an unrelated one.

Variables

This section is empty.

Functions

func Deliver

Deliver renders a parked message as the user-role message the turn appends to its history.

The text is wrapped whole in the steering tags, and any non-text parts (images, files) follow it unchanged — an attachment cannot carry the block without breaking the "the block is the entire text" rule that gives the message its steering identity. A message whose text is empty still gets an empty block rather than being delivered bare, so an image-only interjection is still recognizable as one.

func IsRequested

func IsRequested(message protocol.ChatMessage) bool

IsRequested reports whether the sender marked this message as a steering send.

func Key

func Key(workspaceID, threadID string) string

Key is the steering lane identity: one in-flight turn per (workspace, thread). It MUST agree with the runtime dispatcher's key — a message parked under one spelling and drained under another is silently never delivered — so the dispatcher derives its key from this function rather than repeating the scheme. The workspace is length-prefixed so two opaque (workspace, thread) pairs cannot concatenate into the same lane.

func Mark

Mark flags a message as a steering send. Returns the message unchanged when it already carries the marker.

func Unmark

func Unmark(message protocol.ChatMessage) protocol.ChatMessage

Unmark returns a copy that is no longer classified as steering. It is used when a delivery with an explicit safe fallback must become its own turn.

Types

type Inbox

type Inbox struct {
	// contains filtered or unexported fields
}

Inbox holds messages parked for turns that are currently running. The zero value is not usable; call New. A nil *Inbox is safe for every method, so a host that has not wired steering behaves exactly as before.

func New

func New() *Inbox

func (*Inbox) Begin

func (i *Inbox) Begin(key string) func() []protocol.ChatMessage

Begin marks a lane as running and returns the close func for that turn. The close func returns any messages that were parked but never drained — the caller MUST re-submit them as an ordinary turn.

That handoff is the whole reason this type exists. Without it there is a race with no safe side: a message can be parked microseconds before the turn reaches its last drain point, and would then sit in the inbox with nobody left to deliver it. Returning the remainder converts a lost message into a slightly late one.

A nil Inbox or empty key yields a close func that returns nothing.

func (*Inbox) Drain

func (i *Inbox) Drain(key string) []protocol.ChatMessage

Drain removes and returns the messages parked for key since the last drain.

func (*Inbox) Park

func (i *Inbox) Park(key string, message protocol.ChatMessage) bool

Park queues a steering message for the turn running on key, reporting whether it was accepted. False means no turn is running and the caller must dispatch the message as an ordinary turn.

The active check and the append happen under one lock precisely so that "accepted" is a promise: once Park returns true the message is in a queue that either a drain or the close handoff will deliver.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL