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: 12 Imported by: 0

Documentation

Overview

Package protocol implements the eight `agents.*` Protocol methods the Console Agents page consumes:

  • agents.list — paginated, faceted agent catalog projection.
  • agents.get — one agent's full registration-identity projection (identity / hosting / status / health / AgentConfig).
  • agents.tools — the agent's tool bindings + per-binding OAuth.
  • agents.memory — the agent's memory strategy + TTL + scope.
  • agents.governance — per-identity-tier ceilings + spend + limits.
  • agents.skills — the agent's attached skills.
  • agents.permissions — the agent's permission model (V1: implicit).
  • agents.metrics — the registry-wide rollup the page hero shows.

The seam (CLAUDE.md §4.4)

The Service depends on the `Projector` interface, not on a concrete Agent Registry. The V1 production implementation is `RegistryProjector` (registry_projector.go) — a thin read-only projection over a `registry.AgentRegistry`. A future projector that joins richer operational metadata (live task counts, real governance accumulators) slots in behind the same interface without reshaping the Service.

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. The registry filters by the (tenant, user, session) tuple, NEVER by `agent_id` — `agent_id` is a registration identity, not a WHERE-clause isolation key (CLAUDE.md §6 clarifying note).

No `agents.*` control method

The five agent-control verbs the Agents page exposes (Pause / Drain / Restart / Force-Stop / Deregister) are NOT `agents.*` methods — they are the EXISTING shipped `registry.*` control verbs, gated on the elevated control-scope claim. Harbor mints NO control method; the page invokes the shipped registry control surface. All eight `agents.*` methods here are READ-ONLY projections (CLAUDE.md §13).

Concurrent reuse

A constructed *Service is immutable after NewService and safe to share across N concurrent goroutines: it holds only the Projector reference + a logger; every method's per-call state lives in the call's arguments and locals, never on the Service.

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("registry/protocol: identity scope incomplete")
	// ErrAgentNotFound — the requested agent_id is not registered in
	// the registry visible to the caller's identity scope.
	ErrAgentNotFound = errors.New("registry/protocol: agent not found")
	// ErrInvalidRequest — the request was structurally invalid (an
	// empty agent id, an out-of-range page size).
	ErrInvalidRequest = errors.New("registry/protocol: invalid request")
	// ErrMisconfigured — NewService was called with a nil Projector.
	ErrMisconfigured = errors.New("registry/protocol: NewService missing a mandatory dependency")
	// ErrControlScopeRequired — a fleet-control verb (Pause / Drain /
	// Restart / ForceStop / Deregister) was called without the elevated
	// `auth.ScopeAdmin` control claim. Fails closed.
	ErrControlScopeRequired = errors.New("registry/protocol: fleet-control requires the elevated control-scope claim")
	// ErrControlUnsupported — a fleet-control verb was called on a Service
	// built without a Controller (WithController). The dev/server wiring
	// always injects one; this guards a misconfiguration rather than
	// nil-panicking (CLAUDE.md §5).
	ErrControlUnsupported = errors.New("registry/protocol: fleet-control not wired on this Service")
	// ErrScopeMismatch — an admin-widened `agents.list` fleet enumeration
	// (a non-empty Filter.TenantIDs) was issued without the verified
	// `auth.ScopeAdmin` claim. Fails closed — never a silent narrowing to
	// own scope (CLAUDE.md §6 rule 5 / §13).
	ErrScopeMismatch = errors.New("registry/protocol: fleet enumeration requires the admin scope claim")
)

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

This section is empty.

Types

type AgentsAdminQueryPayload added in v1.11.0

type AgentsAdminQueryPayload struct {
	events.SafeSealed
	// Actor is the verified admin identity at the Protocol edge — the
	// (tenant, user, session) triple the JWT carried.
	Actor identity.Identity
	// Method is the Protocol method that carried the fleet enumeration
	// (`agents.list`).
	Method string
	// TenantCount is the number of distinct tenants the fleet enumeration
	// named.
	TenantCount int
}

AgentsAdminQueryPayload is the typed SafePayload published on the canonical `audit.admin_scope_used` event when an operator runs an admin-widened `agents.list` fleet enumeration (a query whose Filter.TenantIDs names one or more tenants) under the verified `auth.ScopeAdmin` claim.

SafePayload by construction: every field is a bounded identity component, a Protocol method name, or a small count — no caller-supplied bytes reach the bus. The Agents wire surface rejects malformed requests at the Protocol edge before the emit.

The payload is distinct from the sessions / tasks / tools / events / auth admin-scope payloads: all ride the same canonical `audit.admin_scope_used` event type, but each emit source declares its own typed payload. A subscriber type-switches on the payload.

type ConfigSource

type ConfigSource interface {
	// Config returns the agent's AgentConfig projection.
	Config(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentConfig, error)
	// Tools returns the agent's tool bindings.
	Tools(ctx context.Context, id identity.Identity, agentID string) ([]prototypes.AgentToolBinding, error)
	// Memory returns the agent's memory binding.
	Memory(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentMemoryBinding, error)
	// Governance returns the agent's governance posture.
	Governance(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentGovernance, error)
	// Skills returns the agent's attached skills.
	Skills(ctx context.Context, id identity.Identity, agentID string) ([]prototypes.AgentSkillBinding, error)
}

ConfigSource is the optional join seam the RegistryProjector uses to resolve an agent's configuration-derived projections — the AgentConfig, tool bindings, memory binding, governance posture, and attached skills. The Agent Registry stores only the version_hash of an agent's AgentConfig, not the config itself; the configuration-derived tabs of the Console Agents page therefore need a join over the subsystems that DO own that data (the tool catalog, the memory configs, the governance accumulators, the skills catalog).

ConfigSource is OPTIONAL on the projector. When it is nil, the configuration-derived methods return an HONEST empty projection — an empty tool-binding list, a zero-value memory binding, etc. — which the Console renders as a real "nothing configured for this agent" empty state. This is NOT a stubbed success (CLAUDE.md §13): the methods still validate identity and the agent's existence; they simply report that no configuration join is wired. Production wiring (`harbor dev`) supplies a ConfigSource so the tabs carry live data.

Every method takes the verified identity tuple so the implementation scopes its reads — NEVER by agent_id.

type Controller added in v1.3.0

type Controller interface {
	Pause(ctx context.Context, agentID, reason string) error
	Drain(ctx context.Context, agentID, reason string) error
	Restart(ctx context.Context, agentID, reason string) error
	ForceStop(ctx context.Context, agentID, reason string) error
	Deregister(ctx context.Context, agentID string) error
}

Controller is the mutate seam the five fleet-control verbs depend on. The V1 production implementation is the in-process `*registry.Registry`, whose control methods self-enforce `registry.HasControlScope(ctx)` and emit the `agent.*` events. Every method takes the verified identity (folded into ctx by the handler) plus the control-scope claim (attached by the Service before the call) agent_id is the target handle, never an isolation key.

type DefaultAgentDescriptor added in v1.16.0

type DefaultAgentDescriptor struct {
	// ID is the well-known boot agent id — the same id the serving
	// assembly threads elsewhere (e.g. the MCP Apps accessor's AgentID).
	// An empty ID means "no default agent to synthesize".
	ID string
	// DisplayName is the operator-facing name for the row.
	DisplayName string
	// PlannerType is the boot agent's planner, when the assembly knows it
	// cheaply; empty is an honest "not joined" projection (no ConfigSource
	// join runs for the synthetic row).
	PlannerType string
	// Model is the boot agent's model, when cheaply known; empty is an
	// honest "not joined" projection.
	Model string
	// BootedAt is when this runtime's serving assembly came up. It backs
	// the row's RegisteredAt / UpdatedAt as the closest honest timestamp;
	// the synthetic agent was never "registered", so there is no
	// registration time to report.
	BootedAt time.Time
}

DefaultAgentDescriptor describes the runtime's synthetic default agent — the boot-configured agent every process serves through but which is never written to the Agent Registry's StateStore, so the registry-scoped projections never produce a row for it. When one is wired (WithDefaultAgent), the projector synthesizes a first-class, IsDefault-marked catalog row for it so a fleet Agents catalog sees "one agent, not enumerable this way" instead of an indistinguishable empty page (the absence-must-be-representable class).

The descriptor is immutable after NewRegistryProjector; it holds only value-typed fields, carrying no per-run state, so the projector stays a concurrency-safe compiled artifact.

type Option

type Option func(*Service)

Option configures NewService.

func WithBus added in v1.11.0

func WithBus(b events.EventBus) Option

WithBus wires the canonical events.EventBus the Service publishes the `audit.admin_scope_used` event onto when an admin-widened `agents.list` fleet enumeration succeeds. A nil bus is treated as "WithBus not supplied" — the fleet path still works, but the audit observation is logged at Info instead of published (the admin action is NEVER fully silent — CLAUDE.md §13).

func WithController added in v1.3.0

func WithController(c Controller) Option

WithController injects the fleet-control mutate seam Without it, the five control verbs fail closed with ErrControlUnsupported. The production wiring passes the in-process `*registry.Registry`.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger the Service logs projector failures to. A nil logger routes to slog.Default().

func WithRedactor added in v1.11.0

func WithRedactor(r audit.Redactor) Option

WithRedactor wires the audit.Redactor the Service runs the `audit.admin_scope_used` payload through before publishing. A nil redactor is treated as "WithRedactor not supplied".

type Projector

type Projector interface {
	// ListAgents returns every agent visible to id, in agent_id order.
	// The Service applies the facet filter + pagination on top.
	ListAgents(ctx context.Context, id identity.Identity) ([]prototypes.Agent, error)
	// ListTenantAgents returns every agent across ALL sessions of the
	// named tenants — the admin-widened FLEET read. It is invoked by
	// Service.List ONLY after the admin-scope gate passes; each row carries
	// full per-(tenant, user, session) identity attribution. This lands the
	// aggregating fleet projector behind the unchanged Projector interface.
	ListTenantAgents(ctx context.Context, tenantIDs []string) ([]prototypes.Agent, error)
	// GetAgent returns the full projection for agentID, or
	// ErrAgentNotFound.
	GetAgent(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentGetResponse, error)
	// AgentTools returns the agent's tool bindings, or ErrAgentNotFound.
	AgentTools(ctx context.Context, id identity.Identity, agentID string) ([]prototypes.AgentToolBinding, error)
	// AgentMemory returns the agent's memory binding, or
	// ErrAgentNotFound.
	AgentMemory(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentMemoryBinding, error)
	// AgentGovernance returns the agent's governance posture, or
	// ErrAgentNotFound.
	AgentGovernance(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentGovernance, error)
	// AgentSkills returns the agent's attached skills, or
	// ErrAgentNotFound.
	AgentSkills(ctx context.Context, id identity.Identity, agentID string) ([]prototypes.AgentSkillBinding, error)
	// AgentPermissions returns the agent's permission model, or
	// ErrAgentNotFound.
	AgentPermissions(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentPermissions, error)
	// Metrics returns the registry-wide rollup for id's scope.
	Metrics(ctx context.Context, id identity.Identity) (prototypes.AgentMetrics, error)
}

Projector is the read seam the Service depends on. The V1 production implementation is RegistryProjector. Every method takes the verified identity triple so the implementation scopes its reads by the (tenant, user, session) tuple — NEVER by agent_id.

type ProjectorOption

type ProjectorOption func(*RegistryProjector)

ProjectorOption configures NewRegistryProjector.

func WithConfigSource

func WithConfigSource(src ConfigSource) ProjectorOption

WithConfigSource wires the optional ConfigSource join. A nil source is treated as "WithConfigSource not supplied".

func WithDefaultAgent added in v1.16.0

func WithDefaultAgent(d DefaultAgentDescriptor) ProjectorOption

WithDefaultAgent wires the synthetic default-agent row. A descriptor with an empty ID is treated as "WithDefaultAgent not supplied" — the projector's behavior is then byte-identical to an unwired projector, matching the ConfigSource nil-is-honest pattern this type already uses. When wired, the descriptor's row is emitted by ListAgents / ListTenantAgents / GetAgent / Metrics UNLESS a real registration under the same well-known id is present, in which case the real record wins and no synthetic row is emitted (one row per id).

type RegistryProjector

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

RegistryProjector is the V1 production Projector — a read-only projection over a registry.AgentRegistry, optionally joined to a ConfigSource for the configuration-derived tabs. It is a concurrency-safe compiled artifact: immutable after NewRegistryProjector, holding only interface references and a value-typed default-agent descriptor.

func NewRegistryProjector

func NewRegistryProjector(reg registry.AgentRegistry, opts ...ProjectorOption) (*RegistryProjector, error)

NewRegistryProjector builds the V1 production Projector. reg is mandatory — a nil fails loud with ErrMisconfigured (CLAUDE.md §5).

func (*RegistryProjector) AgentGovernance

func (p *RegistryProjector) AgentGovernance(ctx context.Context, id identity.Identity, agentID string) (prototypes.AgentGovernance, error)

AgentGovernance implements Projector.AgentGovernance.

func (*RegistryProjector) AgentMemory

AgentMemory implements Projector.AgentMemory.

func (*RegistryProjector) AgentPermissions

func (p *RegistryProjector) AgentPermissions(ctx context.Context, _ identity.Identity, agentID string) (prototypes.AgentPermissions, error)

AgentPermissions implements Projector.AgentPermissions. V1 default is the implicit permission model (page-agents.md §10): every authenticated user in the tenant can invoke the agent. The method still validates the agent exists; an explicit ACL surface is post-V1.

func (*RegistryProjector) AgentSkills

AgentSkills implements Projector.AgentSkills.

func (*RegistryProjector) AgentTools

AgentTools implements Projector.AgentTools.

func (*RegistryProjector) GetAgent

GetAgent implements Projector.GetAgent. A get on the well-known default-agent id resolves the synthetic projection when no real registration shadows it — so a Console drill-down from the list row composes (no ErrAgentNotFound). A real registration under the id wins (the registry.Get succeeds and its record is projected).

func (*RegistryProjector) ListAgents

ListAgents implements Projector.ListAgents. When a default agent is wired and no real registration shares its well-known id, it appends the synthetic IsDefault row under the caller's own verified scope.

func (*RegistryProjector) ListTenantAgents added in v1.11.0

func (p *RegistryProjector) ListTenantAgents(ctx context.Context, tenantIDs []string) ([]prototypes.Agent, error)

ListTenantAgents implements Projector.ListTenantAgents — the admin-widened FLEET read. It enumerates every registered agent across ALL sessions of the named tenants through the Agent Registry's explicit tenant-scoped enumeration seam (registry.AgentRegistry.ListTenant) and projects each onto the flat Agent wire row, carrying full per-(tenant, user, session) identity attribution.

Scope + gate

This is the widened counterpart to ListAgents. It is invoked by Service.List ONLY after the cross-tenant admin-scope gate passes; the RegistryProjector honours whatever tenant set the Service hands it. The registry's ListTenant reads the whole per-runtime agent set for a tenant (all users, all sessions) via the StateStore maintenance-scan surface — cross-RUNTIME federation stays the coordinator's job over per-runtime reads (the same division as sessions / events).

Identity under which config joins run

Each record is projected under its OWN registration identity (never the caller's): the ConfigSource joins that hydrate planner/model/tool counts are scoped to the agent's own (tenant, user, session), matching the "act on each record under its own identity" contract the maintenance scan carries. agent_id is never an isolation key.

func (*RegistryProjector) Metrics

Metrics implements Projector.Metrics — the registry-wide rollup. The V1 rollup counts Active Agents from the registry's identity-scoped List; Running Tasks / Total Cost / Total Tokens come from the ConfigSource's governance join when one is wired, else zero (an honest "not joined" rollup, not a faked number).

type Service

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

Service implements the thirteen `agents.*` Protocol methods (the eight read projections + the five fleet-control verbs). It is a concurrency-safe compiled artifact — immutable after NewService.

func NewService

func NewService(projector Projector, opts ...Option) (*Service, error)

NewService builds the Agents Protocol service over a Projector. The projector 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) Deregister added in v1.3.0

Deregister implements the `agents.deregister` fleet-control verb. Irreversible; the reason is not carried to the registry (registry.Deregister takes no reason).

func (*Service) Drain added in v1.3.0

Drain implements the `agents.drain` fleet-control verb.

func (*Service) ForceStop added in v1.3.0

ForceStop implements the `agents.force_stop` fleet-control verb.

func (*Service) Get

Get implements the `agents.get` method — the full projection of one agent.

func (*Service) Governance

Governance implements the `agents.governance` method.

func (*Service) List

List implements the `agents.list` method. It validates identity, resolves the agent set from the Projector — the caller's own identity-scoped set, or (when the request names tenants for fleet widening AND the verified admin claim is present) the admin-widened set across the named tenants — applies the facet filter + free-text search + pagination, computes the filtered-view aggregates, and emits an `audit.admin_scope_used` event on a successful fleet enumeration.

adminScoped is the verified-JWT scope decision the wire handler computes from `auth.HasScope(ctx, auth.ScopeAdmin)`. It is consulted ONLY when the request names tenants for fleet widening (Filter.TenantIDs); a non-widened request never requires it and stays byte-compatible with the identity-scoped read.

func (*Service) Memory

Memory implements the `agents.memory` method.

func (*Service) Metrics

Metrics implements the `agents.metrics` method — the registry-wide rollup over the caller's identity scope.

func (*Service) Pause added in v1.3.0

Pause implements the `agents.pause` fleet-control verb.

func (*Service) Permissions

Permissions implements the `agents.permissions` method.

func (*Service) Restart added in v1.3.0

Restart implements the `agents.restart` fleet-control verb.

func (*Service) Skills

Skills implements the `agents.skills` method.

func (*Service) Tools

Tools implements the `agents.tools` method.

Jump to

Keyboard shortcuts

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