protocol

package
v1.21.0 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package protocol implements the `runs.set_overrides` Protocol method the Console Playground page consumes.

`runs.set_overrides` records the reasoning-effort / temperature / max-tokens / system-prompt override an operator applies to the NEXT message in a session. The override is:

  • Session-scoped — keyed by the full identity triple `(tenant, user, session)`. A second session never sees the first's pending override (CLAUDE.md §6 — multi-isolation).
  • One-shot — `Consume` removes and returns the pending override. The override applies to exactly the next `user_message` / `start` and is gone afterwards. It does NOT apply retroactively to past messages; a session that records an override then never sends a message simply drops it (documented behaviour — the phase plan's "next-message semantics" risk).

The seam (CLAUDE.md §4.4)

The Service depends on the in-process `Store` — a mutex-guarded map keyed by the identity triple. The Store is the V1 production implementation; it is deliberately a single concrete (the override slot is ephemeral per-runtime state, not a persistence-shaped subsystem with plausible alternate backends — there is no SQLite / Postgres override store, so §4.4's interface-plus-driver ceremony would be optional-capability smell). When a future durable / remote override store becomes a real requirement, it slots in behind a promoted interface; today the concrete is correct.

Identity is mandatory (CLAUDE.md §6 rule 9)

Every method takes the wire request's `IdentityScope`. An incomplete triple fails closed with `ErrIdentityRequired` — there is no identity-downgrading knob. The Service NEVER reads identity from a package-level global; the triple flows in via the request.

Cross-session gating

`runs.set_overrides` carries both an `IdentityScope` (the verified JWT identity) and `RunOverrides.SessionID` (the named target). The Service rejects a request whose `SessionID` names a session other than the verified `Identity.Session` with `ErrCrossSessionScope` — an operator cannot record an override for a session outside its own verified scope. Admin impersonation is out of scope for this method at V1 (the Playground records overrides for the operator's own session); the impersonation triplet on `IdentityScope` is honoured by the `user_message` / `start` consumer, not by override recording.

Concurrent reuse

A constructed *Service is immutable after NewService and safe to share across N concurrent goroutines: it holds only the Store reference + an optional bus + redactor + logger + clock. Every method's per-call state lives in the call's arguments and locals, never on the Service. The Store guards its map with a sync.Mutex — the only mutable state, and it is documented "internally synchronised" per the carve-out.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrIdentityRequired — the request carried an incomplete identity
	// triple. RFC §5.5 / CLAUDE.md §6 rule 9 — fails closed.
	ErrIdentityRequired = errors.New("runs/protocol: identity scope incomplete")
	// ErrCrossSessionScope — the request's RunOverrides.SessionID named
	// a session outside the caller's verified Identity.Session.
	ErrCrossSessionScope = errors.New("runs/protocol: override targets a session outside the caller's verified scope")
	// ErrInvalidRequest — the override payload was structurally invalid
	// (an out-of-range temperature, a non-positive max-tokens, an
	// unknown reasoning-effort value).
	ErrInvalidRequest = errors.New("runs/protocol: invalid request")
	// ErrMisconfigured — NewService was called with a nil Store.
	ErrMisconfigured = errors.New("runs/protocol: NewService missing a mandatory dependency")
)

Sentinel errors the Service returns. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

Functions

func ComposeLLMOverrides added in v1.5.0

func ComposeLLMOverrides(session *PendingOverride, agent, tenant *planner.LLMOverrides) *planner.LLMOverrides

ComposeLLMOverrides merges the three per-run LLM-override layers into the planner's override bundle in precedence order **session › per-agent › tenant-wide baseline** (the run loop reads it at run start). Per field, a non-nil session value wins, then the per-agent (agent-config) value, then the tenant-wide baseline fills the rest. The session's SystemPromptOverride (full system-prompt REPLACE) is session-only; the tenant's ExtraInstructions (additive) is tenant-only; the per-agent layer carries sampling parameters only (model / temperature / max-tokens / reasoning-effort) — never prompt text. Returns nil when no layer set anything. config defaults are applied last by the planner's applyLLMOverrides (an unset field leaves the request untouched).

This is the ONE production composition; cmd/harbor's run loop, the devstack twin, and the integration test all call it (no re-implemented copy — CLAUDE.md §17.4).

Types

type Clock

type Clock func() time.Time

Clock is the time source the Service stamps RecordedAt / AppliedAt from. Injected so tests pin a deterministic instant (CLAUDE.md §11 — time-sensitive tests use a controllable clock).

type Option

type Option func(*Service)

Option configures NewService.

func WithBus

func WithBus(b events.EventBus) Option

WithBus wires the canonical events.EventBus the Service publishes the `runs.overrides_set` audit event onto. A nil bus is treated as "WithBus not supplied" — the override recording is then logged at Info instead of published (the action is NEVER fully silent — CLAUDE.md §13).

func WithClock

func WithClock(c Clock) Option

WithClock injects the time source. Defaults to time.Now. Tests pin a deterministic instant.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger. A nil logger routes to slog.Default().

func WithRedactor

func WithRedactor(r audit.Redactor) Option

WithRedactor wires the audit.Redactor. The `runs.overrides_set` payload is a SafePayload by construction (it carries no caller-supplied bytes — only identity + boolean flags), so the bus bypasses the redactor for it; the redactor is held for parity with the other Console-page services and for any future non-safe emit.

func WithValidModels added in v1.5.0

func WithValidModels(models []string) Option

WithValidModels wires the set of model names that have a configured `ModelProfile`. When supplied (non-empty), a `Model` override naming a model outside the set is rejected at set time with ErrInvalidRequest (fail loud, mirroring the tenant-default layer). When unsupplied, the Service does not validate the model name — an unknown model surfaces at the LLM safety edge instead.

type PendingOverride

type PendingOverride struct {
	// ReasoningEffort, when non-nil, is the validated reasoning-effort
	// hint for the next message.
	ReasoningEffort *string
	// Temperature, when non-nil, is the validated sampling temperature.
	Temperature *float64
	// MaxTokens, when non-nil, is the validated per-message token ceiling.
	MaxTokens *int
	// SystemPromptOverride, when non-nil, replaces the agent's system
	// prompt for the next message only.
	SystemPromptOverride *string
	// Model, when non-nil, is the validated model the next message's run
	// requests (the session-level model swap).
	Model *string
	// RecordedAt is the runtime instant the override entered the slot.
	RecordedAt time.Time
}

PendingOverride is the recorded, validated override held in a session's one-shot slot. It is the internal projection of a types.RunOverrides — the same pointer-optional fields, plus the recording instant.

A PendingOverride is immutable once stored; Consume returns it by value and removes the slot.

type Service

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

Service implements `runs.set_overrides`. It validates the override payload, enforces identity, records the override into the Store, and emits the `runs.overrides_set` audit event.

The Service is a compiled artifact: immutable after NewService; every method's per-call state lives in arguments + locals.

func NewService

func NewService(store *Store, opts ...Option) (*Service, error)

NewService builds the `runs.set_overrides` Service over an override Store. store is mandatory — a nil fails loud with ErrMisconfigured rather than building a Service that would nil-panic on the first request (CLAUDE.md §5).

The returned *Service is immutable after construction and safe for concurrent use by N goroutines.

func (*Service) SetOverrides

SetOverrides records the next-message override carried by req. It validates identity, cross-session scope, and the override payload, writes the validated override into the Store, emits the `runs.overrides_set` audit event, and returns the recording instant.

The override applies to the NEXT message in the session — it is not retroactive. SetOverrides does not touch any past message.

type Store

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

Store is the in-process, identity-scoped pending-override slot map. It is a compiled artifact: constructed once via NewStore, shared across N goroutines, with its single mutable field — the slot map — guarded by an internally-synchronised sync.Mutex.

The map is keyed by the identity triple so a session's pending override is invisible to every other `(tenant, user, session)` — multi-isolation is enforced by the key, not by a post-fetch filter.

func NewStore

func NewStore() *Store

NewStore builds an empty override Store. The returned *Store is safe for concurrent use by N goroutines.

func (*Store) Consume

func (s *Store) Consume(id identity.Identity) (PendingOverride, bool)

Consume removes and returns the pending override for id. The second return is false when the identity triple has no pending override — the common case (most messages carry no override). Consume is the one-shot read: the slot is empty after a Consume until the next Set.

This is the seam the `user_message` / `start` consumer calls at the start of the next message in a session.

func (*Store) Peek

func (s *Store) Peek(id identity.Identity) (PendingOverride, bool)

Peek returns the pending override for id WITHOUT removing it. Used by tests and read-side projections that must not consume the slot.

func (*Store) Set

func (s *Store) Set(id identity.Identity, po PendingOverride)

Set records po into the slot for id, replacing any prior pending override for that identity triple. An operator that records two overrides before sending a message keeps only the second — the slot is last-write-wins, the documented behaviour.

Jump to

Keyboard shortcuts

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