Documentation
¶
Index ¶
- Constants
- func MarshalCommand(cmd Command) ([]byte, error)
- func ValidateCommand(cmd Command) error
- type ApproveToolCall
- type CancelDelegateRequest
- type CancelQueuedInput
- type ChangeLoopInference
- type Command
- type CommandDecodeError
- type CommandEncodeError
- type CommandField
- type CommandLimitError
- type CommandName
- type CommandValidationError
- type Compact
- type DelegateCancelResult
- type DelegateDeliveryPhase
- type DenyToolCall
- type GateRoute
- type Header
- type Interrupt
- type InvalidCommandError
- type LoopChangeResult
- type LoopTerminatedError
- type LoopToolsResult
- type ProcessNotification
- type ProcessNotificationResult
- type ProvideUserInput
- type ReplaceLoopExternalTools
- type Rule
- type SetLoopMode
- type Shutdown
- type SubagentResult
- type UnbufferedAckError
- type UnknownCommandTypeError
- type UserInput
Constants ¶
const ( CommandCancelDelegateRequest CommandName = "CancelDelegateRequest" CancelDelegateRequestAck CommandField = "Ack" )
const ( CommandInterrupt CommandName = "Interrupt" InterruptAck CommandField = "Ack" )
const ( CommandSetLoopMode CommandName = "SetLoopMode" CommandChangeLoopInference CommandName = "ChangeLoopInference" SetLoopModeAck CommandField = "Ack" ChangeLoopInferenceAck CommandField = "Ack" )
const ( CommandReplaceLoopExternalTools CommandName = "ReplaceLoopExternalTools" ReplaceLoopExternalToolsAck CommandField = "Ack" ReplaceLoopExternalToolsSource CommandField = "Source" )
const ( CommandProcessNotification CommandName = "ProcessNotification" FieldNotification CommandField = "Notification" )
CommandProcessNotification/FieldNotification name this command for CommandValidationError, alongside the shared vocabulary in validate.go.
const ( CommandShutdown CommandName = "Shutdown" ShutdownAck CommandField = "Ack" )
const ( CommandUserInput CommandName = "UserInput" CommandSubagentResult CommandName = "SubagentResult" CommandCancelQueuedInput CommandName = "CancelQueuedInput" CommandApproveToolCall CommandName = "ApproveToolCall" CommandDenyToolCall CommandName = "DenyToolCall" CommandProvideUserInput CommandName = "ProvideUserInput" CommandCompact CommandName = "Compact" CommandUnknown CommandName = "Command" FieldCommandID CommandField = "CommandID" FieldSessionID CommandField = "SessionID" FieldLoopID CommandField = "LoopID" FieldTargetCommandID CommandField = "TargetCommandID" FieldTargetLoopID CommandField = "TargetLoopID" FieldBackgroundHandBack CommandField = "BackgroundHandBack" FieldDelegateDeliveryPhase CommandField = "DelegateDeliveryPhase" FieldToolExecutionID CommandField = "ToolExecutionID" FieldAgency CommandField = "Agency" FieldAction CommandField = "Action" )
Command/field names for CommandValidationError. The CommandName/CommandField types are reused from command.go (the existing InvalidCommandError vocabulary).
Variables ¶
This section is empty.
Functions ¶
func MarshalCommand ¶
MarshalCommand encodes a Command into the durable intent-log wire envelope: a JSON object carrying a "type" discriminator (== the CommandName naming source, the package's single source of truth), a "v" schema version, the embedded Header fields, and the type-specific payload. It fails closed on a type outside the sealed union (UnknownCommandTypeError). The transient ack channels (Interrupt.Ack/Shutdown.Ack, tagged json:"-") never serialize. The content blocks (UserInput.Blocks/SubagentResult.Blocks) are a sealed-interface slice with no general codec, so they are delegated to content.MarshalBlocks; every other field round-trips through encoding/json.
func ValidateCommand ¶
ValidateCommand checks cmd against the ID fill matrix and returns a typed *CommandValidationError on the first violation, nil when cmd satisfies every invariant. CommandID is required on every command; per-type addressing rules then apply (UserInput's machine delegate target/phase marker, SubagentResult/ CancelQueuedInput coordinates, and the gate-reply GateRoute). Interrupt and Shutdown are session-wide control commands with no addressing today, so they are not validated here (their Ack-channel contract is checked by their own Validate). Fail-secure: a command type outside the addressed set passes only the universal CommandID check.
Types ¶
type ApproveToolCall ¶
type ApproveToolCall struct {
Header
// GateRoute locates the loop (the session dispatches by LoopID) and names the
// pending gate (ToolExecutionID), which the actor matches against.
GateRoute
// Action is gate.ApprovalApprove or gate.ApprovalApproveAlwaysWorkspace.
// Any other value — including gate.ApprovalDeny, which travels on
// DenyToolCall — fails ValidateCommand closed, so a malformed or legacy
// record can never decode into an approval.
Action gate.ApprovalAction `json:"action"`
}
ApproveToolCall approves the pending tool call identified by ToolExecutionID with exactly one of the two approve actions. It is a fire-and-route control command: the actor routes it by GateToolExecutionID to the permission gate blocked on that call, so there is no Ack (the gate's unblocking and the subsequent ToolCallStarted event are the observable effect, not a reply on this command).
The command carries no scope, no grant tokens, and no persistence payload: gate.ApprovalApprove approves once and writes nothing, while gate.ApprovalApproveAlwaysWorkspace additionally instructs the evaluator to atomically persist the displayed reusable rule candidates before execution. Fresh execution-bound grants are minted by the evaluator AFTER the decision and travel only in the prepared execution contract, never on this wire.
func (ApproveToolCall) GateToolExecutionID ¶
func (c ApproveToolCall) GateToolExecutionID() uuid.UUID
GateToolExecutionID returns the tool-call id this command targets, so the actor can route it to the matching pending gate.
type CancelDelegateRequest ¶
type CancelDelegateRequest struct {
Header
identity.Coordinates
TargetCommandID uuid.UUID `json:"target_command_id,omitzero"`
Ack chan<- DelegateCancelResult `json:"-"`
}
CancelDelegateRequest atomically cancels one managed request on one loop.
func (CancelDelegateRequest) Validate ¶
func (c CancelDelegateRequest) Validate() error
type CancelQueuedInput ¶
type CancelQueuedInput struct {
Header
identity.Coordinates
TargetCommandID uuid.UUID `json:"target_command_id,omitzero"`
}
CancelQueuedInput retracts a still-queued submit (a UserInput/SubagentResult that received an InputQueued disposition). It is routed to the loop like the gate commands; the loop resolves it against its OWN inbox (race-free, since the actor is the sole owner of the queue), so there is no session-side TOCTOU.
Coordinates selects the target loop; TargetCommandID names the queued submit to remove (it is the submit command's Header.CommandID, returned in InputQueued.Cause.CommandID).
It is fire-and-forget — there is no Ack. Its outcome is observable as events, not a point-to-point reply: when the input was still queued the loop publishes the Enduring event.InputCancelled{CancelClientRetracted} keyed by TargetCommandID. When it had already started or folded into a turn — or was never queued — the retract is a pure no-op: the issuer infers "already committed / unknown" from the event.TurnStarted / event.TurnFoldedInto it already saw for that command.
type ChangeLoopInference ¶
type ChangeLoopInference struct {
Header
Model model.Model `json:"model,omitzero"`
Effort model.Effort `json:"effort,omitzero"`
SetModel bool `json:"set_model,omitzero"`
SetEffort bool `json:"set_effort,omitzero"`
Ack chan<- LoopChangeResult `json:"-"` // live reply channel; no JSON representation
}
ChangeLoopInference changes only the secret-free model descriptor and/or the inference effort. Like SetLoopMode it is a CONTROL command on a live reply channel — its durable record is the event.LoopInferenceChanged the actor emits — so it is not in the wire codec. SetModel/SetEffort select which of Model/Effort the batch changes; the whole batch is validated atomically by the actor before anything is applied. The change takes effect at the NEXT turn boundary; a running turn keeps the model/effort it started under. Ack is required and must be non-nil and buffered(1).
func (ChangeLoopInference) Validate ¶
func (c ChangeLoopInference) Validate() error
Validate checks the reply-channel contract: Ack must be present AND buffered (cap >= 1), like SetLoopMode. The model/effort VALUES are validated by the actor against the loop definition (atomically), not here.
type Command ¶
type Command interface {
CommandHeader() Header
// contains filtered or unexported methods
}
Command is a sealed interface for all loop commands. Only types in this package can implement it.
func UnmarshalCommand ¶
UnmarshalCommand decodes a durable intent-log wire envelope back into a concrete Command. It fails closed on the untrusted restore boundary: input over the byte cap → CommandLimitError; malformed envelope → CommandDecodeError; an unknown or missing "type" tag → UnknownCommandTypeError; a malformed payload for a known type → CommandDecodeError. A successfully decoded command is validated against the ID fill matrix (ValidateCommand), so a structurally-valid but semantically- invalid record is rejected rather than resurrected. The transient ack channels are never on the wire, so a restored Interrupt/Shutdown carries a nil Ack.
type CommandDecodeError ¶
type CommandDecodeError struct {
Type CommandName
Cause error
}
CommandDecodeError wraps a failure to unmarshal serialized command bytes (malformed JSON, wrong field types) once a known "type" tag has been read, or the initial envelope probe failure (nil/garbage/non-object), in which case Type is empty.
func (*CommandDecodeError) Error ¶
func (e *CommandDecodeError) Error() string
func (*CommandDecodeError) Unwrap ¶
func (e *CommandDecodeError) Unwrap() error
type CommandEncodeError ¶
type CommandEncodeError struct {
Type CommandName
Cause error
}
CommandEncodeError wraps a failure to marshal a command's payload (a json.Marshal failure, or a delegated content-block codec failure on the marshal path).
func (*CommandEncodeError) Error ¶
func (e *CommandEncodeError) Error() string
func (*CommandEncodeError) Unwrap ¶
func (e *CommandEncodeError) Unwrap() error
type CommandField ¶
type CommandField string
const ReplaceLoopExternalToolsTools CommandField = "Tools"
ReplaceLoopExternalToolsTools names the Tools/Identities alignment violation.
type CommandLimitError ¶
CommandLimitError is returned when serialized input exceeds the envelope byte cap at the untrusted decode boundary.
func (*CommandLimitError) Error ¶
func (e *CommandLimitError) Error() string
type CommandName ¶
type CommandName string
type CommandValidationError ¶
type CommandValidationError struct {
Command CommandName
Field CommandField
Rule Rule
}
CommandValidationError reports that a command violates the ID fill matrix: Field names the offending identity/addressing field and Rule says why (required). It is a typed package-API error so a journal/test can errors.As it to inspect the exact violation rather than parse a string. It is distinct from InvalidCommandError (which guards required-channel contracts like Interrupt.Ack) so the two failure modes never alias at a call site.
func (*CommandValidationError) Error ¶
func (e *CommandValidationError) Error() string
type Compact ¶
type Compact struct {
Header
identity.Coordinates
}
Compact requests conversation compaction for one exact loop. Agency records whether the trusted dispatcher admitted a manual or automatic request.
type DelegateCancelResult ¶
type DelegateCancelResult uint8
DelegateCancelResult is the actor-authoritative outcome of a targeted request cancellation. It is transient control-plane state and is never serialized.
const ( DelegateCancelNoop DelegateCancelResult = iota DelegateCancelQueued DelegateCancelActive )
type DelegateDeliveryPhase ¶
type DelegateDeliveryPhase string
DelegateDeliveryPhase is the durable phase marker on a machine delegate UserInput. The command record remains the source of truth for the request payload and request id; this marker lets restore distinguish an accepted intent from the one fallback admission that was already journaled. The zero value is intentionally unset for ordinary interactive input and legacy records.
const ( // DelegateDeliveryPhaseIntent means the exact command record was accepted // durably but has not yet been admitted to the actor path. DelegateDeliveryPhaseIntent DelegateDeliveryPhase = "intent" // DelegateDeliveryPhaseFallbackQueued means the exact command record was // durably admitted once as the normal fallback; restore may re-admit that // same command id if no opening/cancellation event exists. DelegateDeliveryPhaseFallbackQueued DelegateDeliveryPhase = "fallback_queued" )
func (DelegateDeliveryPhase) Valid ¶
func (p DelegateDeliveryPhase) Valid() bool
Valid reports whether p is one of the non-zero durable delegate phases.
type DenyToolCall ¶
type DenyToolCall struct {
Header
// GateRoute locates the loop (the session dispatches by LoopID) and names the
// pending gate (ToolExecutionID), which the actor matches against.
GateRoute
}
DenyToolCall denies a pending tool call identified by ToolExecutionID. Like ApproveToolCall it is a fire-and-route control command with no Ack: the actor routes it by GateToolExecutionID to the permission gate, which fails the call closed (fail-secure). Denial carries no scope — nothing is ever persisted on a deny.
func (DenyToolCall) GateToolExecutionID ¶
func (c DenyToolCall) GateToolExecutionID() uuid.UUID
GateToolExecutionID returns the tool-call id this command targets, so the actor can route it to the matching pending gate.
type GateRoute ¶
type GateRoute struct {
identity.Coordinates
GateID uuid.UUID `json:"gate_id,omitzero"`
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
}
GateRoute is the routing key for a gate reply. identity.Coordinates locates the target loop — the session dispatches by GateRoute.LoopID — and ToolExecutionID names the pending gate the loop matches against (its pendingGates key). It is embedded in the gate commands so a reply carries both the dispatch target (the loop) and the match key (the tool call), replacing the former route-to-primary.
A zero coordinate means "unspecified at this granularity": a GateRoute with only LoopID set addresses a loop without naming a turn. The loop resolves the gate by ToolExecutionID, so the coordinates are the addressing envelope, not the match key.
type Header ¶
type Header struct {
CommandID uuid.UUID `json:"command_id,omitzero"` // fresh per command instance, stamped by the sender before send
Cause identity.Cause `json:"cause,omitzero"` // the direct cause of this command; zero = root (user-initiated)
Agency identity.Agency `json:"agency,omitzero"` // who issued this command; AgencyMachine (zero) by default
// CreatedAt is when this command was created, stamped at the session dispatch
// boundary from the injected clock (mirrors event.Header.CreatedAt: minted at
// creation, not delivery). It is the journal's creation timestamp for the
// intent-log record and round-trips through the command codec as-is. Zero means
// the sender supplied none (omitzero drops it), so an in-process command that is
// never persisted is unaffected.
CreatedAt time.Time `json:"created_at,omitzero"`
}
Header is the correlation/idempotency metadata embedded in every command. The sender stamps the fields before sending the command (the session does this via newCommandID); zero-valued fields mean the sender supplied none.
func (Header) CommandHeader ¶
CommandHeader is promoted onto every command that embeds Header.
type Interrupt ¶
type Interrupt struct {
Header
Ack chan<- bool `json:"-"` // live reply channel; no JSON representation
}
Interrupt cancels the running turn. Ack receives true if a turn was cancelled, false if idle or the session is already shutting down. Ack is required and must be non-nil.
type InvalidCommandError ¶
type InvalidCommandError struct {
Command CommandName
Field CommandField
}
InvalidCommandError is returned when an internal caller violates a command contract.
func (*InvalidCommandError) Error ¶
func (e *InvalidCommandError) Error() string
type LoopChangeResult ¶
LoopChangeResult is the loop actor's synchronous reply to a SetLoopMode or ChangeLoopInference command. Err is the typed failure (nil on success); on success Mode/Model/Effort report the EFFECTIVE mode, secret-free model descriptor, and effort the actor committed — the values the NEXT turn will start under. The session controller updates its live Handle view from these committed values so Handle.Mode()/Model() reflect the current selection. It rides a live channel and is never serialized (the ack channel is json:"-").
type LoopTerminatedError ¶
type LoopTerminatedError struct{ Cause error }
LoopTerminatedError is sent on Shutdown.Ack when the loop's root context was cancelled before the actor finished cleanup.
func (*LoopTerminatedError) Error ¶
func (e *LoopTerminatedError) Error() string
func (*LoopTerminatedError) Unwrap ¶
func (e *LoopTerminatedError) Unwrap() error
type LoopToolsResult ¶
LoopToolsResult is the loop actor's synchronous reply to ReplaceLoopExternalTools. Err is the typed failure (nil on success); on success Generation echoes the installed generation and Installed reports how many external tools that source now contributes to the next turn. It rides a live channel and is never serialized.
type ProcessNotification ¶
type ProcessNotification struct {
Header
Notification tool.ProcessCompletionNotification `json:"notification"`
// Result is the transient, optional live disposition channel: nil for a
// restore-reconstructed redelivery (fire-and-forget — restore seeds the
// loop directly rather than replaying this command live) or for a decoded
// wire record (json:"-": it never rides the durable payload), and a
// caller-owned buffered channel for a live NotifyProcessCompletion call
// awaiting the loop's disposition.
Result chan<- ProcessNotificationResult `json:"-"`
}
ProcessNotification is the sealed, durable wire command that carries Task 4's metadata-only tool.ProcessCompletionNotification DTO to its owning loop. It is deliberately as narrow as the DTO it wraps: it adds only the generic command envelope (Header) and a transient live disposition channel — never a command, output, stdin, host path, OS PID, or any other free-form field. Restore reconstructs it directly from the durable intent log (round-tripping through the sealed codec below) to detect and re-enqueue undelivered notifications.
Header.CommandID and Notification.CommandID carry the SAME stable, pre-persisted id Tools allocates before a completion can be published. Header.CommandID is the generic envelope field the journal's idempotency index and the intent-log CommandRecord key on (mirroring every other sealed command); Notification.CommandID is Task 4's own DTO field. ValidateCommand rejects any disagreement fail-secure rather than silently preferring one.
type ProcessNotificationResult ¶
type ProcessNotificationResult uint8
ProcessNotificationResult is the transient, non-serialized outcome of dispatching a ProcessNotification to its owning loop (Task 24C). It answers "what happened to THIS delivery attempt", never a durable record:
- ProcessNotificationAccepted: the durable append genuinely persisted a NEW frame (or no-persistence/headless mode has no durable concept at all) and the loop took ownership of the notification.
- ProcessNotificationDuplicate: the append's idempotency id already named an IDENTICAL durable record (a same-CommandID retry) — no second frame was written; the loop may still have been (re)delivered the value, which is harmless because a completion notification is metadata-only.
- ProcessNotificationCollision: the append's idempotency id already named a durable record with a DIFFERENT persisted payload — a genuine id reuse bug or forged retry. Never dispatched to the loop.
- ProcessNotificationStopped: delivery could not complete right now — the owning loop has exited, its bounded live notification set is full, or the addressed loop cannot accept process notifications at all (a foreign engine). The durable command (once appended) remains authoritative: the caller may retry dispatch later with the SAME CommandID.
const ( ProcessNotificationAccepted ProcessNotificationResult = iota + 1 ProcessNotificationDuplicate ProcessNotificationCollision ProcessNotificationStopped )
type ProvideUserInput ¶
type ProvideUserInput struct {
Header
// GateRoute locates the loop (the session dispatches by LoopID) and names the
// pending gate (ToolExecutionID), which the actor matches against.
GateRoute
Answer string `json:"answer,omitempty"`
}
ProvideUserInput supplies the user's Answer to a pending AskUser request identified by ToolExecutionID. Like the approve/deny pair it is a fire-and-route control command with no Ack: the actor routes it by GateToolExecutionID to the user-input gate blocked on that call, which delivers Answer to the waiting tool.
func (ProvideUserInput) GateToolExecutionID ¶
func (c ProvideUserInput) GateToolExecutionID() uuid.UUID
GateToolExecutionID returns the tool-call id this command targets, so the actor can route it to the matching pending gate.
type ReplaceLoopExternalTools ¶
type ReplaceLoopExternalTools struct {
Header
Source string `json:"source,omitzero"`
Generation string `json:"generation,omitzero"`
Tools []tool.InvokableTool `json:"-"` // live values; no JSON representation
Identities []event.ExternalToolIdentity `json:"-"` // durable projection of Tools
Ack chan<- LoopToolsResult `json:"-"` // live reply channel
}
ReplaceLoopExternalTools atomically REPLACES one source's external tool slot on a loop. Like SetLoopMode it is a CONTROL command carried on a live reply channel, not a journaled wire command: its durable, replayable record is the event.LoopExternalToolsetChanged the actor emits, so it is deliberately absent from the intent-log codec. The change takes effect at the NEXT turn boundary; a running turn keeps the toolset it started under.
Tools are ALREADY BUILT. Building is the session's job, not the actor's: the session owns the loop's tool.Bindings (the actor only ever receives a BoundDefinition), and an external factory may perform I/O — building on the actor goroutine would stall the loop and, with it, the very idle detection this feature depends on. Identities is the matching durable identity projection, computed from the built tools by the same caller so the emitted record cannot drift from what is installed; it is index-aligned with Tools.
An empty Tools is legal: it clears the source's slot. Ack is required and must be non-nil and buffered(1).
func (ReplaceLoopExternalTools) Validate ¶
func (c ReplaceLoopExternalTools) Validate() error
Validate checks the actor-protecting structural contract: Ack must be present AND buffered (cap >= 1) — the actor replies with a single non-blocking direct send, so an unbuffered Ack would wedge it — and Tools must be index-aligned with Identities, since the actor installs Tools while the journal records Identities and a mismatch would make the durable record a lie. Source is required here because it names the slot the actor would otherwise replace ambiguously. The tool VALUES and the name-collision rules are validated by the caller and the actor, not here.
type Rule ¶
type Rule string
Rule is the human-readable invariant a CommandValidationError records, so the caller learns WHY a field is wrong, not just which.
type SetLoopMode ¶
type SetLoopMode struct {
Header
Mode string `json:"mode,omitzero"`
Ack chan<- LoopChangeResult `json:"-"` // live reply channel; no JSON representation
}
SetLoopMode selects one predeclared loop mode. It is a CONTROL command carried on a live reply channel (Ack), not a journaled wire command: its durable, replayable record is the event.LoopModeChanged the actor emits, which restore folds — so it is deliberately absent from the intent-log codec. The actor validates the mode name against the loop's bound definition, emits the enduring event, and applies the change at the NEXT turn boundary; the running turn keeps the mode it started under. An empty Mode names the base mode. Ack is required and must be non-nil and buffered(1).
func (SetLoopMode) Validate ¶
func (c SetLoopMode) Validate() error
Validate checks the reply-channel contract: Ack must be present AND buffered (cap >= 1). The actor delivers the reply with a single non-blocking direct send, so an unbuffered Ack would wedge the actor; both violations are typed so a caller can errors.As them.
type Shutdown ¶
type Shutdown struct {
Header
Ack chan<- error `json:"-"` // live reply channel; no JSON representation
}
Shutdown cancels the running turn (if any), delivers its terminal event, and exits the actor. Ack receives nil after clean exit, or *LoopTerminatedError if the loop's root context was cancelled before cleanup completed. Ack is required and must be non-nil.
type SubagentResult ¶
type SubagentResult struct {
Header // command.Header; Cause.LoopID = CHILD loop; Agency = AgencyMachine
identity.Coordinates // addresses the PARENT loop (delivery target)
Blocks []content.Block `json:"blocks,omitempty"`
}
SubagentResult delivers a finished subagent's output to its parent loop (the hand-back). It shares UserInput's submit semantics — the parent loop's events go to the session fan-in.
It carries TWO loop ids with distinct jobs:
- The embedded identity.Coordinates addresses the PARENT loop — the delivery target. The session dispatches the command to loops[Coordinates.LoopID].
- Header.Cause.LoopID is the CHILD loop that produced the result. When the parent folds the result into a turn, the loop stamps this Cause.LoopID onto any start/queue/fold/return event the submit causes, which releases the parent's quiescence wake token on the publish path.
Header.Agency stays AgencyMachine (the zero default): a hand-back is machine-originated, never user.
A SubagentResult is NEVER rejected, so its wake token is ALWAYS released by a published Enduring event (TurnStarted/TurnFoldedInto, or InputCancelled if the loop ends before it commits) — there is no off-publish-path reconciliation anymore.
type UnbufferedAckError ¶
type UnbufferedAckError struct {
Command CommandName
Field CommandField
}
UnbufferedAckError reports that a loop-change command's live reply channel is present but unbuffered (cap < 1). The loop actor replies with a single non-blocking direct send, so an unbuffered Ack would wedge it; the contract requires a buffered(1) channel. It is distinct from InvalidCommandError (a MISSING channel) so the two failure modes never alias.
func (*UnbufferedAckError) Error ¶
func (e *UnbufferedAckError) Error() string
type UnknownCommandTypeError ¶
type UnknownCommandTypeError struct{ Type CommandName }
UnknownCommandTypeError is returned by UnmarshalCommand when the envelope's "type" tag names no concrete command (including the empty/missing tag), or by MarshalCommand when a foreign concrete type is handed in (one not in classifyCommand's sealed union). The restore path is an untrusted boundary; callers fail secure on this error rather than guess a concrete command to reconstruct.
func (*UnknownCommandTypeError) Error ¶
func (e *UnknownCommandTypeError) Error() string
type UserInput ¶
type UserInput struct {
Header
Blocks []content.Block `json:"blocks,omitempty"`
// NoFold requests a DISTINCT non-folding turn: the input still queues behind a
// running turn, but it NEVER folds into that turn at a tool-continuation boundary —
// it starts its OWN turn (Cause.CommandID = this command's id) when the running turn
// finishes. It is the delegation follow-up path, where each request is an independent
// question/answer correlated by command id; the interactive submit path leaves it
// false so ordinary input keeps its fold-into-turn semantics.
NoFold bool `json:"no_fold,omitzero"`
// TargetLoopID durably carries the dispatch target for machine NoFold or phased
// delegate requests because storage replay cannot recover CommandRecord's
// transport-only loop.
TargetLoopID uuid.UUID `json:"target_loop_id,omitzero"`
// BackgroundHandBack durably marks the narrow managed-delegation request shape that
// requires automatic background parent hand-back after the child terminal commits.
// Legacy no-fold hand-backs remain valid; a foldable hand-back must carry a valid
// non-zero DelegateDeliveryPhase alongside its durable target identity. Foreground
// delegate requests and ordinary user input leave it false.
BackgroundHandBack bool `json:"background_hand_back,omitzero"`
// DelegateDeliveryPhase is the durable phase marker for machine MessageAgent
// delivery. Intent and fallback_queued are journaled together with this exact
// command record, so the actor payload and fallback phase cannot diverge.
DelegateDeliveryPhase DelegateDeliveryPhase `json:"delegate_delivery_phase,omitzero"`
// Accepted is the transient durable-acceptance ack used only by managed delegate
// sends. It is never serialized; prepared starts use LoopStarted.InitialRequestID.
Accepted chan error `json:"-"`
}
UserInput is interactive input. The loop decides its outcome; the caller never assumes a turn was created. Submit commands DO NOT carry a context (no Ctx field): a queued input can start much later, fold, be cancelled, or be returned, so the loop derives the turn context from its own loopCtx only when a turn actually starts. A UserInput may queue behind a running turn (it later folds into a tool-continuation request or starts a later turn).
The loop announces the outcome by PUBLISHING a typed Reply event onto the normal session fan-in (event.TurnStarted / event.InputQueued / event.TurnRejected, each carrying Cause.CommandID == this command's id), NOT a point-to-point reply: every submit observes its outcome on the session event fan-in.