protocol

package
v1.13.1 Latest Latest
Warning

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

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

Documentation

Overview

Package protocol implements the admin-scoped `agent_config.*` Protocol methods the Console agent-config control plane consumes:

  • agent_config.get — read an agent's active config revision.
  • agent_config.set_revision — write a new immutable revision.
  • agent_config.list_revisions — the agent's revision chain.
  • agent_config.diff — server-side compare of two revisions.
  • agent_config.rollback — repoint the active pointer.
  • agent_config.skills.{list,upsert,delete} — skills control (the first consumer of the registry primitive; see skills.go).

Every write is admin-scoped (the verified `auth.ScopeAdmin` claim, enforced at the wire handler — the agent-config authorization model) and identity-mandatory. A config edit applies to the agent's NEXT run (next-turn projection — never mid-flight, per the concurrent-reuse contract).

The seam (CLAUDE.md §4.4)

The Service depends on the narrow `agentcfg.Registry` interface (the StateStore-backed concrete satisfies it) plus an optional `skills.SkillStore` for the skills consumer; tests inject fakes. The Service owns wire validation + the wire↔domain mapping; the registry owns persistence + the revision events; the SkillStore owns the skill rows + the skill events.

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

Every method takes the wire request's IdentityScope. An incomplete triple fails closed with ErrIdentityRequired. The handler overlays the verified identity onto the request body, so a caller cannot target another identity's config through this surface.

Concurrent reuse

A constructed *Service is immutable after NewService and safe to share across N goroutines: it holds only the registry / skill-store references + a clock + logger. Per-call state lives in arguments and locals.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAuthRequired — the attacher signals the MCP server needs
	// authorization. The service parks on the unified pause/resume primitive.
	// Concrete attachers wrap this so the service reaches it via errors.Is.
	ErrAuthRequired = errors.New("agentcfg/protocol: mcp connection requires authorization")
	// ErrConnectionAttachUnavailable — add_mcp_connection was called but no
	// ConnectionAttacher is wired on this runtime (→ 501).
	ErrConnectionAttachUnavailable = errors.New("agentcfg/protocol: mcp connection attach not wired on this runtime")
	// ErrStdioNotAllowed — a stdio add named a command absent from the
	// fail-closed allowlist (→ 403). Adding a stdio server is the most
	// privileged action; the allowlist is the §7 RCE gate.
	ErrStdioNotAllowed = errors.New("agentcfg/protocol: stdio mcp connection command is not allowlisted")
	// ErrInvalidConnection — the connection descriptor failed validation
	// (empty name, unknown transport, missing/incoherent command/URL) (→ 400).
	ErrInvalidConnection = errors.New("agentcfg/protocol: invalid mcp connection descriptor")
	// ErrCoordinatorUnavailable — an auth-required attach fired but no
	// pause/resume Coordinator is wired (→ 500). Fail loud rather than drop
	// the auth requirement silently (CLAUDE.md §13).
	ErrCoordinatorUnavailable = errors.New("agentcfg/protocol: pause/resume coordinator not wired for the mcp oauth path")
)

Add-connection sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

View Source
var (
	// ErrConnectionNotFound — remove_mcp_connection named a connection that is
	// not in the agent's active revision (never runtime-added, or already
	// removed). No revision recorded, no event emitted (→ 404).
	ErrConnectionNotFound = errors.New("agentcfg/protocol: mcp connection not found in the agent's active revision")
	// ErrBootDeclaredConnection — remove_mcp_connection named a server that is
	// declared in the boot yaml (`tools.mcp_servers`). Distinct from
	// ErrConnectionNotFound: the verb governs revisioned state only; a
	// boot-declared server is edited in yaml + restart, not removed through
	// the control plane. No revision recorded, no event emitted (→ 400).
	ErrBootDeclaredConnection = errors.New("agentcfg/protocol: connection is boot-declared (yaml) — edit tools.mcp_servers and restart; the control plane governs runtime-added connections only")
)

Remove-connection sentinel errors. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.

View Source
var (
	// ErrIdentityRequired — the request carried an incomplete identity
	// triple or an empty agent id. Fails closed.
	ErrIdentityRequired = errors.New("agentcfg/protocol: identity scope incomplete")
	// ErrMisconfigured — NewService was called with a nil registry.
	ErrMisconfigured = errors.New("agentcfg/protocol: NewService missing a mandatory dependency")
	// ErrSkillsUnavailable — a skills method was called but no SkillStore
	// was wired into the Service.
	ErrSkillsUnavailable = errors.New("agentcfg/protocol: skills control not wired on this runtime")
	// ErrUnknownModel — a set_llm_params (or a set_revision carrying an
	// LLMParams.Model) named a model with no configured ModelProfile. The
	// per-agent model is validated at set time (parity with the tenant
	// model-swap), so an invalid model can never be persisted — fail loud,
	// never a silent fallback (CLAUDE.md §13).
	ErrUnknownModel = errors.New("agentcfg/protocol: unknown model (no configured ModelProfile)")
	// ErrInvalidLLMParams — a set_llm_params (or a set_revision carrying an
	// LLMParams section) supplied an out-of-range sampling value (temperature
	// outside [0,2], non-positive max-tokens, or an unknown reasoning-effort).
	// Validated at set time so an invalid value never reaches a run (parity
	// with runs.set_overrides), fail loud (CLAUDE.md §13).
	ErrInvalidLLMParams = errors.New("agentcfg/protocol: invalid LLM parameters")
	// ErrInvalidHooks — a set_revision carrying a hooks section supplied an
	// invalid run-completion hook (a negative timeout_ms). Validated at set
	// time so an invalid value is rejected loud BEFORE any registry write —
	// parity with the yaml validator's negative-timeout rejection; the
	// normalizer's negative→0 coercion is defence-in-depth behind this gate,
	// never the primary posture (CLAUDE.md §13).
	ErrInvalidHooks = errors.New("agentcfg/protocol: invalid hooks section")
	// ErrInvalidToolExposureLoading — a set_tool_exposure (or a set_revision
	// carrying a ToolExposure section) supplied a server_loading_modes /
	// tool_loading_modes entry with an empty key or a value outside
	// "always"|"deferred". Validated at set time BEFORE any registry write
	// so an invalid override can never be persisted — no revision, no
	// event — fail loud, never a silent drop (CLAUDE.md §13).
	ErrInvalidToolExposureLoading = errors.New("agentcfg/protocol: invalid tool-loading-mode override")
	// ErrInvalidNaming — a set_revision carrying a naming section supplied an
	// invalid session auto-naming policy: a negative after_turns / repeat_every
	// / max_repetitions, a max_title_len outside [8,200], a repeat_every > 0
	// with max_repetitions < 1 (no unlimited value exists), or an unknown
	// model. Validated at set time BEFORE any registry write so an invalid
	// policy can never be persisted — fail loud, never a silent clamp
	// (CLAUDE.md §13). The model is validated via the same validateModel path
	// as set_llm_params.
	ErrInvalidNaming = errors.New("agentcfg/protocol: invalid naming section")
)

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

View Source
var ErrSessionOverlayUnavailable = errors.New("agentcfg/protocol: session safe-subset control not wired on this runtime")

ErrSessionOverlayUnavailable — a session-safe method was called but no session-overlay store was wired into the Service.

Functions

This section is empty.

Types

type AttachRequest

type AttachRequest struct {
	// Identity is the verified caller triple (the attach runs under it; the
	// driver stamps it on transport-side events).
	Identity identity.Identity
	// Name is the unique MCP source id.
	Name string
	// Transport is "stdio" or "http".
	Transport agentcfg.MCPTransport
	// Command is the stdio argv (argv[0] is the binary; no shell).
	Command []string
	// URL is the http(s) endpoint.
	URL string
	// Headers are operator-supplied auth headers used ONLY for the live
	// attach. SECRET — never persisted (CLAUDE.md §7).
	Headers map[string]string
	// OAuthProvider is the non-secret provider NAME to bind for per-identity
	// southbound bearer injection (empty leaves the connection on its static
	// Headers). The attacher resolves it against the declared registry.
	OAuthProvider string
	// MetaAnnotations is the non-secret operator `_meta` annotation set the
	// attacher carries onto the live connection's per-call `_meta`.
	MetaAnnotations map[string]string
}

AttachRequest is the input to a ConnectionAttacher. It carries the non-secret descriptor PLUS the optional operator-supplied auth headers used ONLY for the live transport — the attacher never persists them.

type Clock

type Clock func() time.Time

Clock is the time source the Service stamps response timestamps from.

type ConnectionAttacher

type ConnectionAttacher interface {
	Attach(ctx context.Context, req AttachRequest) error
}

ConnectionAttacher drives the real MCP attach lifecycle for a runtime add: dial → `initialize` handshake → discover → register, fail-loud per step. It is the seam that keeps the concrete MCP driver out of this package (the §4.4 boundary): the cmd/harbor + devstack boundary injects a concrete that builds a `config.MCPServerConfig` and calls the MCP driver's `Attach`.

Attach returns:

  • nil on a successful attach (online — the server's tools are registered on the live catalog);
  • an error wrapping ErrAuthRequired when the server needs authorization (the service parks on the unified pause/resume primitive);
  • any other error when an attach step failed (the service records the `failed` lifecycle, never a half-attached server).

type ConnectionState

type ConnectionState string

ConnectionState is the explicit attach lifecycle state surfaced on the response + the lifecycle events. The set is closed.

const (
	// ConnectionStatePending — the attach has begun (dial → handshake →
	// discover → register). The transient initial state.
	ConnectionStatePending ConnectionState = "pending"
	// ConnectionStateOnline — the attach completed; the server's tools are
	// registered on the live catalog.
	ConnectionStateOnline ConnectionState = "online"
	// ConnectionStateFailed — an attach step failed; no revision recorded,
	// no half-attached server registered.
	ConnectionStateFailed ConnectionState = "failed"
	// ConnectionStateAuthRequired — the attach parked on the unified
	// pause/resume primitive awaiting authorization. NOTE: the resume
	// continuation that re-drives the attach to online is not yet
	// implemented — resume currently only releases the pause (tracked in
	// issue #375). The descriptor revision is recorded; the server comes
	// online on a subsequent (authorized) add.
	ConnectionStateAuthRequired ConnectionState = "auth_required"
)

The canonical attach lifecycle states.

type Option

type Option func(*Service)

Option configures NewService.

func WithBootDeclaredMCPServers added in v1.11.0

func WithBootDeclaredMCPServers(names []string) Option

WithBootDeclaredMCPServers records the set of MCP server names declared in the boot yaml (`tools.mcp_servers[].name`). `remove_mcp_connection` rejects a name in this set with ErrBootDeclaredConnection (distinct from the unknown-name error) — a boot-declared server is not revisioned state and is edited in yaml + restart. An empty / nil list leaves every unknown name a plain not-found. Injected at the cmd/harbor + devstack boundary from the loaded config so the verb (and the run-start reconcile, which never detaches a boot server) share one authoritative set.

func WithBus

func WithBus(b events.EventBus) Option

WithBus wires the EventBus the tool-exposure consumer publishes the `mcp.connection.paused` / `.resumed` overlay events through. A nil bus leaves those events unpublished (the revision is still recorded — the generic `agent.config.revised` still fires from the registry).

func WithClock

func WithClock(c Clock) Option

WithClock injects the time source. Defaults to time.Now.

func WithConnectionAttacher

func WithConnectionAttacher(a ConnectionAttacher) Option

WithConnectionAttacher wires the concrete that drives the real MCP attach lifecycle for `agent_config.add_mcp_connection`. A nil attacher leaves the method returning ErrConnectionAttachUnavailable (→ 501 at the wire edge). The concrete (which imports the MCP driver) is injected at the cmd/harbor + devstack boundary; this package depends only on the interface.

func WithCoordinator

func WithCoordinator(c pauseresume.Coordinator) Option

WithCoordinator wires the unified pause/resume primitive an auth-required MCP attach parks on. A nil coordinator leaves an auth-required attach failing loud with ErrCoordinatorUnavailable (never a silent drop).

func WithLogger

func WithLogger(l *slog.Logger) Option

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

func WithSessionOverlay

func WithSessionOverlay(st sessionoverlay.Store) Option

WithSessionOverlay wires the session-scoped safe-subset overlay store so the NON-admin `agent_config.session.*` verbs are live. A nil store leaves those methods returning ErrSessionOverlayUnavailable (→ 501 at the wire edge). The store is keyed by the caller's real (tenant, user, session) triple, so a session's overlay is invisible to another session.

func WithSkillStore

func WithSkillStore(st skills.SkillStore) Option

WithSkillStore wires the SkillStore so the `agent_config.skills.*` methods are live. A nil store leaves the skills methods returning ErrSkillsUnavailable (→ 501 at the wire edge).

func WithStdioAllowlist

func WithStdioAllowlist(commands []string) Option

WithStdioAllowlist sets the fail-closed allowlist of permitted stdio commands (matched on argv[0]) for `agent_config.add_mcp_connection`. A stdio add whose command[0] is absent is rejected with ErrStdioNotAllowed. An empty / nil allowlist rejects EVERY stdio add (the secure default); http adds are unaffected. Adding a stdio server runs an operator-supplied command (an RCE surface) — this gate is the §7 fail-closed boundary.

func WithValidModels

func WithValidModels(models []string) Option

WithValidModels sets the set of model names with a configured ModelProfile. `set_llm_params` (and a `set_revision` carrying an LLMParams.Model) reject a model outside this set with ErrUnknownModel — parity with the tenant model-swap, validated at SET time so an invalid model can never be persisted (never a silent run-start fallback).

An empty / nil set means model-pinning is UNAVAILABLE: every non-empty model set fails loud (the binary always wires at least the bound default model, so an empty set signals a misconfiguration, not "accept anything"). This matches the governance tenant-override policy's fail-loud-on-empty stance; it deliberately DIFFERS from runs.set_overrides (the one-shot session swap), which accepts any model when no set is configured.

type Service

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

Service implements the admin-scoped agent-config methods.

func NewService

func NewService(registry agentcfg.Registry, opts ...Option) (*Service, error)

NewService builds the agent-config Service over a Registry. registry 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) AddMCPConnection

AddMCPConnection adds a NEW MCP server connection. See the file doc for the full lifecycle, secret-hygiene, and stdio-gate contract.

func (*Service) Diff

Diff returns the server-side compare of two existing revisions.

func (*Service) Get

Get reads the agent's active config revision.

func (*Service) ListRevisions

ListRevisions returns the agent's revision chain, newest-first.

func (*Service) RemoveMCPConnection added in v1.11.0

RemoveMCPConnection removes a runtime-added MCP connection by name. See the file doc for the full contract (revision + residue prune, the two distinct loud errors, the projection-boundary teardown, token retention).

func (*Service) Rollback

Rollback repoints the active pointer to an existing revision.

func (*Service) SessionSetSourceDisables

SessionSetSourceDisables records the session's narrow-only disable set (servers + tools). There is intentionally no enable field: the set names what the session wants OFF, and the run-start projection unions it into the admin exclusion set — so a session edit can only narrow the admin-allowed exposure, never widen it.

func (*Service) SessionSetUserPrompt

SessionSetUserPrompt sets ONLY the session's user prompt layer. The session-writable shape carries no base field, so this can never alter the operator base — base-unwritable-by-session is structural.

func (*Service) SessionSkillsDelete

SessionSkillsDelete deletes a personal skill under the caller's real triple and removes its name from the session overlay.

func (*Service) SessionSkillsList

SessionSkillsList lists the session's skills (metadata only) under the caller's real triple. The SkillStore is already session-scoped, so the list is naturally isolated to the caller's session.

func (*Service) SessionSkillsUpsert

SessionSkillsUpsert upserts an EPHEMERAL personal skill under the caller's real triple and records its name in the session overlay (so the run-start projection adds it back above the admin membership filter). The skill scope is FORCED to session — a session personal skill never promotes to the agent/tenant scope.

func (*Service) SetLLMParams

SetLLMParams records a new config revision pinning the supplied per-agent LLM-parameter section (model / temperature / max-tokens / reasoning-effort) as a desired-state replace of the LLM-params section. A set Model is validated against the configured ModelProfiles (an unknown model is rejected with ErrUnknownModel). The prompt-layer + skills + tool-exposure + connection + hooks sections of the active revision are carried forward unchanged.

func (*Service) SetPromptLayers

SetPromptLayers records a new config revision pinning the supplied layered system prompt (operator base and/or user layer) as a desired-state replace of the prompt-layer section. The skills + tool-exposure + connections + llm-params + hooks sections of the active revision are carried forward unchanged.

func (*Service) SetRevision

SetRevision writes a new immutable revision and advances the active pointer.

func (*Service) SetToolExposure

SetToolExposure records a new config revision pinning the supplied MCP-exposure desired state (paused servers + disabled tools) and emits an `mcp.connection.paused` / `.resumed` event for each server whose pause state changed relative to the prior active revision. The skills + prompt-layer + connections + llm-params + hooks sections of the active revision are carried forward unchanged — every sibling section survives a tool-exposure edit.

func (*Service) SkillsDelete

SkillsDelete deletes a skill from the SkillStore and records the membership change as a config revision.

func (*Service) SkillsList

SkillsList returns the agent's skills (metadata only) from the SkillStore under the caller's identity.

func (*Service) SkillsUpsert

SkillsUpsert upserts a skill into the SkillStore and records the membership change as a config revision. A pack-overwrite refusal surfaces as the typed `skills.ErrPackOverwriteRefused` (mapped to CodeInvalidRequest at the wire edge) — never a silent overwrite.

func (*Service) UserDiff added in v1.6.0

UserDiff returns the server-side compare of two existing revisions of the caller's own variant.

func (*Service) UserGet added in v1.6.0

UserGet reads the caller's own durable config variant active revision.

func (*Service) UserListRevisions added in v1.6.0

UserListRevisions returns the caller's own variant revision chain, newest-first.

func (*Service) UserRollback added in v1.6.0

UserRollback repoints the caller's own variant active pointer to an existing revision WITHOUT mutating any revision. Serialised per-owner.

func (*Service) UserSetRevision added in v1.6.0

UserSetRevision writes a new immutable revision of the caller's durable variant from the bounded safe-subset payload and advances the active pointer. Serialised per-owner (scope, tenant, real-user, agent) so distinct users never contend.

Jump to

Keyboard shortcuts

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